Commit f44e304d authored by Dominic Battré's avatar Dominic Battré Committed by Commit Bot

Revert "[SpatNav] Navigation of stacked focusables"

This reverts commit 8964034e.

Reason for revert: see crbug.com/961620

Original change's description:
> [SpatNav] Navigation of stacked focusables
> 
> Background:
>  SpatNav compares activeElement's bounding rect
>  with all other focusables' bounding rects.
> 
> Example (snav-fragmented-link.html):
>  A multi-line ("fragmented") link has focus.
> 
>   xxxxxxLINK L
>   INKLINKxx #A
> 
>  Here LINK's bounding box's area is bigger than the
>  the combined area of its two fragments. The box,
>  returned by NodeRectInRootFrame(), even overlaps
>  another focusable, #A. We can say that LINK's
>  bounding box "contains" #A.
> 
> Problem:
>  Spatial navigation searches _from_ the focused
>  element's bounding box (but not inside of it).
>  Here LINK's rect completely contains A's rect so
>  #A will never be considered as a focus candidate.
> 
> Solution:
>  Give priority to focusables inside the current focus rect.
> 
>  This also solves the generic case, crbug.com/798102,
>  where one focusable lays "under" another one, i.e
>  where a focus rect "contains" (or is positioned
>  behind) another focusable. I test this case in
>  snav-focus-rect-contains-links.html.
> 
>  Previously, such containers where unreachable because
>  of 86d40f3d's (anno 2010):
> 
>  "... if there are 2 overlapping focusable nodes, we do
>  a hit test and only the node on top can get focus."
> 
>  Now, when two focusables overlap, SpatNav first goes
>  go the outer box. From there, focus can go to the
>  "on-top", or "inner" (thinking in 2D) focusables.
> 
> Related CLs:
>  Search scrollers from their edges: crrev.com/589502.
>  This CL uses the same approach as crrev.com/589502;
>  when a "container" is focused, we first search inside of
>  it, from one of its edges.
> 
> Distance calculation fixup:
>  OppositeEdge()'s returned slice will now be "just outside"
>  the box it is slicing. This change helps us avoid if-logic
>  to cover cases when focusables sit at the very top of their
>  container.
> 
> Bonus cleanup:
>  AreElementsOnSameLine() can now be removed. It was
>  introduced back in 2011 at 3568f132 to support
>  [some] line-broken links but it didn't fix 956900.
> 
> Bug: 798102, 956900
> Change-Id: I00aaba88a7846b36275f9913034145679576e410
> Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/1587559
> Commit-Queue: Hugo Holgersson <hholgersson@fb.com>
> Reviewed-by: David Bokan <bokan@chromium.org>
> Cr-Commit-Position: refs/heads/master@{#658484}

TBR=bokan@chromium.org,fs@opera.com,junho0924.seo@lge.com,hholgersson@fb.com

Change-Id: If908a9fe49b9a653018ae7f9fdbf9120b130cc6d
No-Presubmit: true
No-Tree-Checks: true
No-Try: true
Bug: 798102, 956900, 961620
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/1605410Reviewed-by: default avatarDominic Battré <battre@chromium.org>
Commit-Queue: Dominic Battré <battre@chromium.org>
Cr-Commit-Position: refs/heads/master@{#658503}
parent bf7258cc
...@@ -47,14 +47,6 @@ ...@@ -47,14 +47,6 @@
namespace blink { namespace blink {
// A small integer that easily fits into a double with a good margin for
// arithmetic. In particular, we don't want to use
// std::numeric_limits<double>::lowest() because, if subtracted, it becomes
// NaN which will make all following arithmetic NaN too (an unusable number).
constexpr double kMinDistance = std::numeric_limits<int>::lowest();
constexpr int kFudgeFactor = 2;
static void DeflateIfOverlapped(LayoutRect&, LayoutRect&); static void DeflateIfOverlapped(LayoutRect&, LayoutRect&);
FocusCandidate::FocusCandidate(Node* node, SpatialNavigationDirection direction) FocusCandidate::FocusCandidate(Node* node, SpatialNavigationDirection direction)
...@@ -254,7 +246,7 @@ static void DeflateIfOverlapped(LayoutRect& a, LayoutRect& b) { ...@@ -254,7 +246,7 @@ static void DeflateIfOverlapped(LayoutRect& a, LayoutRect& b) {
if (!a.Intersects(b) || a.Contains(b) || b.Contains(a)) if (!a.Intersects(b) || a.Contains(b) || b.Contains(a))
return; return;
LayoutUnit deflate_factor = LayoutUnit(-kFudgeFactor); LayoutUnit deflate_factor = LayoutUnit(-FudgeFactor());
// Avoid negative width or height values. // Avoid negative width or height values.
if ((a.Width() + 2 * deflate_factor > 0) && if ((a.Width() + 2 * deflate_factor > 0) &&
...@@ -489,42 +481,55 @@ void EntryAndExitPointsForDirection(SpatialNavigationDirection direction, ...@@ -489,42 +481,55 @@ void EntryAndExitPointsForDirection(SpatialNavigationDirection direction,
} }
} }
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;
}
double ComputeDistanceDataForNode(SpatialNavigationDirection direction, double ComputeDistanceDataForNode(SpatialNavigationDirection direction,
const FocusCandidate& current_interest, const FocusCandidate& current_interest,
const FocusCandidate& candidate) { const FocusCandidate& candidate) {
double distance = 0.0; if (!IsRectInDirection(direction, current_interest.rect_in_root_frame,
double overlap = 0.0; candidate.rect_in_root_frame))
LayoutRect node_rect = candidate.rect_in_root_frame; return MaxDistance();
LayoutRect current_rect = current_interest.rect_in_root_frame;
if (node_rect.Contains(current_rect)) { if (AreElementsOnSameLine(current_interest, candidate)) {
// When leaving an "insider", don't focus its underlaying container box. if ((direction == SpatialNavigationDirection::kUp &&
// Go directly to the outside world. This avoids focus from being trapped current_interest.rect_in_root_frame.Y() >
// inside a container. candidate.rect_in_root_frame.Y()) ||
return kMaxDistance; (direction == SpatialNavigationDirection::kDown &&
candidate.rect_in_root_frame.Y() >
current_interest.rect_in_root_frame.Y())) {
return 0.0;
}
} }
if (current_rect.Contains(node_rect)) { LayoutRect node_rect = candidate.rect_in_root_frame;
// We give priority to "insiders", candidates that are completely inside the LayoutRect current_rect = current_interest.rect_in_root_frame;
// current focus rect, by giving them a negative, < 0, distance number.
distance = kMinDistance;
// For insiders we cannot meassure the distance from the outer box. Instead,
// we meassure distance _from_ the focused container's rect's "opposite
// edge" in the navigated direction, just like we do when we look for
// candidates inside a focused scroll container.
current_rect = OppositeEdge(direction, current_rect);
// This candidate fully overlaps the current focus rect so we can omit the
// overlap term of the equation. An "insider" will always win against an
// "outsider".
} else if (!IsRectInDirection(direction, current_rect, node_rect)) {
return kMaxDistance;
} else {
DeflateIfOverlapped(current_rect, node_rect); DeflateIfOverlapped(current_rect, node_rect);
LayoutRect intersection_rect = Intersection(current_rect, node_rect);
overlap =
(intersection_rect.Width() * intersection_rect.Height()).ToDouble();
}
LayoutPoint exit_point; LayoutPoint exit_point;
LayoutPoint entry_point; LayoutPoint entry_point;
...@@ -533,9 +538,6 @@ double ComputeDistanceDataForNode(SpatialNavigationDirection direction, ...@@ -533,9 +538,6 @@ double ComputeDistanceDataForNode(SpatialNavigationDirection direction,
LayoutUnit x_axis = (exit_point.X() - entry_point.X()).Abs(); LayoutUnit x_axis = (exit_point.X() - entry_point.X()).Abs();
LayoutUnit y_axis = (exit_point.Y() - entry_point.Y()).Abs(); LayoutUnit y_axis = (exit_point.Y() - entry_point.Y()).Abs();
double euclidian_distance =
sqrt((x_axis * x_axis + y_axis * y_axis).ToDouble());
distance += euclidian_distance;
LayoutUnit navigation_axis_distance; LayoutUnit navigation_axis_distance;
LayoutUnit weighted_orthogonal_axis_distance; LayoutUnit weighted_orthogonal_axis_distance;
...@@ -570,17 +572,21 @@ double ComputeDistanceDataForNode(SpatialNavigationDirection direction, ...@@ -570,17 +572,21 @@ double ComputeDistanceDataForNode(SpatialNavigationDirection direction,
break; break;
default: default:
NOTREACHED(); NOTREACHED();
return kMaxDistance; return MaxDistance();
} }
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 // Distance calculation is based on http://www.w3.org/TR/WICD/#focus-handling
return distance + navigation_axis_distance + return sqrt(euclidian_distance_pow2) + navigation_axis_distance +
weighted_orthogonal_axis_distance - sqrt(overlap); weighted_orthogonal_axis_distance - sqrt(overlap);
} }
// Returns a thin rectangle that represents one of |box|'s edges. // Returns a thin rectangle that represents one of box's sides.
// 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, LayoutRect OppositeEdge(SpatialNavigationDirection side,
const LayoutRect& box, const LayoutRect& box,
LayoutUnit thickness) { LayoutUnit thickness) {
...@@ -589,20 +595,16 @@ LayoutRect OppositeEdge(SpatialNavigationDirection side, ...@@ -589,20 +595,16 @@ LayoutRect OppositeEdge(SpatialNavigationDirection side,
case SpatialNavigationDirection::kLeft: case SpatialNavigationDirection::kLeft:
thin_rect.SetX(thin_rect.MaxX() - thickness); thin_rect.SetX(thin_rect.MaxX() - thickness);
thin_rect.SetWidth(thickness); thin_rect.SetWidth(thickness);
thin_rect.Move(1, 0);
break; break;
case SpatialNavigationDirection::kRight: case SpatialNavigationDirection::kRight:
thin_rect.SetWidth(thickness); thin_rect.SetWidth(thickness);
thin_rect.Move(-1, 0);
break; break;
case SpatialNavigationDirection::kDown: case SpatialNavigationDirection::kDown:
thin_rect.SetHeight(thickness); thin_rect.SetHeight(thickness);
thin_rect.Move(0, -1);
break; break;
case SpatialNavigationDirection::kUp: case SpatialNavigationDirection::kUp:
thin_rect.SetY(thin_rect.MaxY() - thickness); thin_rect.SetY(thin_rect.MaxY() - thickness);
thin_rect.SetHeight(thickness); thin_rect.SetHeight(thickness);
thin_rect.Move(0, 1);
break; break;
default: default:
NOTREACHED(); NOTREACHED();
......
...@@ -36,7 +36,13 @@ class HTMLFrameOwnerElement; ...@@ -36,7 +36,13 @@ class HTMLFrameOwnerElement;
enum class SpatialNavigationDirection { kNone, kUp, kRight, kDown, kLeft }; enum class SpatialNavigationDirection { kNone, kUp, kRight, kDown, kLeft };
constexpr double kMaxDistance = std::numeric_limits<double>::max(); inline double MaxDistance() {
return std::numeric_limits<double>::max();
}
inline int FudgeFactor() {
return 2;
}
CORE_EXPORT bool IsSpatialNavigationEnabled(const LocalFrame*); CORE_EXPORT bool IsSpatialNavigationEnabled(const LocalFrame*);
...@@ -73,6 +79,8 @@ CORE_EXPORT bool IsScrollableAreaOrDocument(const Node*); ...@@ -73,6 +79,8 @@ CORE_EXPORT bool IsScrollableAreaOrDocument(const Node*);
CORE_EXPORT Node* ScrollableAreaOrDocumentOf(Node*); CORE_EXPORT Node* ScrollableAreaOrDocumentOf(Node*);
bool CanScrollInDirection(const Node* container, SpatialNavigationDirection); bool CanScrollInDirection(const Node* container, SpatialNavigationDirection);
bool CanScrollInDirection(const LocalFrame*, SpatialNavigationDirection); bool CanScrollInDirection(const LocalFrame*, SpatialNavigationDirection);
bool AreElementsOnSameLine(const FocusCandidate& first_candidate,
const FocusCandidate& second_candidate);
double ComputeDistanceDataForNode(SpatialNavigationDirection, double ComputeDistanceDataForNode(SpatialNavigationDirection,
const FocusCandidate& current_interest, const FocusCandidate& current_interest,
......
...@@ -91,7 +91,7 @@ static void ConsiderForBestCandidate(SpatialNavigationDirection direction, ...@@ -91,7 +91,7 @@ static void ConsiderForBestCandidate(SpatialNavigationDirection direction,
double distance = double distance =
ComputeDistanceDataForNode(direction, current_interest, candidate); ComputeDistanceDataForNode(direction, current_interest, candidate);
if (distance == kMaxDistance) if (distance == MaxDistance())
return; return;
if (best_candidate->IsNull()) { if (best_candidate->IsNull()) {
...@@ -100,6 +100,37 @@ static void ConsiderForBestCandidate(SpatialNavigationDirection direction, ...@@ -100,6 +100,37 @@ static void ConsiderForBestCandidate(SpatialNavigationDirection direction,
return; return;
} }
LayoutRect intersection_rect = Intersection(
candidate.rect_in_root_frame, best_candidate->rect_in_root_frame);
if (!intersection_rect.IsEmpty() &&
!AreElementsOnSameLine(*best_candidate, 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 (!IsA<LocalFrame>(
candidate.visible_node->GetDocument().GetPage()->MainFrame()))
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->ContainsIncludingHostElements(
*result.InnerNode())) {
*best_candidate = candidate;
*best_distance = distance;
return;
}
if (best_candidate->visible_node->ContainsIncludingHostElements(
*result.InnerNode()))
return;
}
if (distance < *best_distance) { if (distance < *best_distance) {
*best_candidate = candidate; *best_candidate = candidate;
*best_distance = distance; *best_distance = distance;
...@@ -275,7 +306,7 @@ FocusCandidate SpatialNavigationController::FindNextCandidateInContainer( ...@@ -275,7 +306,7 @@ FocusCandidate SpatialNavigationController::FindNextCandidateInContainer(
current_interest.visible_node = interest_child_in_container; current_interest.visible_node = interest_child_in_container;
FocusCandidate best_candidate; FocusCandidate best_candidate;
double best_distance = kMaxDistance; double best_distance = MaxDistance();
for (; element; for (; element;
element = element =
IsScrollableAreaOrDocument(element) IsScrollableAreaOrDocument(element)
......
...@@ -26,28 +26,26 @@ class SpatialNavigationTest : public RenderingTest { ...@@ -26,28 +26,26 @@ class SpatialNavigationTest : public RenderingTest {
LayoutRect TopOfVisualViewport() { LayoutRect TopOfVisualViewport() {
LayoutRect visual_viewport = RootViewport(&GetFrame()); LayoutRect visual_viewport = RootViewport(&GetFrame());
visual_viewport.SetY(visual_viewport.Y() - 1);
visual_viewport.SetHeight(LayoutUnit(0)); visual_viewport.SetHeight(LayoutUnit(0));
return visual_viewport; return visual_viewport;
} }
LayoutRect BottomOfVisualViewport() { LayoutRect BottomOfVisualViewport() {
LayoutRect visual_viewport = RootViewport(&GetFrame()); LayoutRect visual_viewport = RootViewport(&GetFrame());
visual_viewport.SetY(visual_viewport.MaxY() + 1); visual_viewport.SetY(visual_viewport.MaxY());
visual_viewport.SetHeight(LayoutUnit(0)); visual_viewport.SetHeight(LayoutUnit(0));
return visual_viewport; return visual_viewport;
} }
LayoutRect LeftSideOfVisualViewport() { LayoutRect LeftSideOfVisualViewport() {
LayoutRect visual_viewport = RootViewport(&GetFrame()); LayoutRect visual_viewport = RootViewport(&GetFrame());
visual_viewport.SetX(visual_viewport.X() - 1);
visual_viewport.SetWidth(LayoutUnit(0)); visual_viewport.SetWidth(LayoutUnit(0));
return visual_viewport; return visual_viewport;
} }
LayoutRect RightSideOfVisualViewport() { LayoutRect RightSideOfVisualViewport() {
LayoutRect visual_viewport = RootViewport(&GetFrame()); LayoutRect visual_viewport = RootViewport(&GetFrame());
visual_viewport.SetX(visual_viewport.MaxX() + 1); visual_viewport.SetX(visual_viewport.MaxX());
visual_viewport.SetWidth(LayoutUnit(0)); visual_viewport.SetWidth(LayoutUnit(0));
return visual_viewport; return visual_viewport;
} }
...@@ -247,7 +245,7 @@ TEST_F(SpatialNavigationTest, StartAtVisibleFocusedIframe) { ...@@ -247,7 +245,7 @@ TEST_F(SpatialNavigationTest, StartAtVisibleFocusedIframe) {
} }
TEST_F(SpatialNavigationTest, StartAtTopWhenGoingDownwardsWithoutFocus) { TEST_F(SpatialNavigationTest, StartAtTopWhenGoingDownwardsWithoutFocus) {
EXPECT_EQ(LayoutRect(0, -1, 111, 0), EXPECT_EQ(LayoutRect(0, 0, 111, 0),
SearchOrigin({0, 0, 111, 222}, nullptr, SearchOrigin({0, 0, 111, 222}, nullptr,
SpatialNavigationDirection::kDown)); SpatialNavigationDirection::kDown));
...@@ -258,7 +256,7 @@ TEST_F(SpatialNavigationTest, StartAtTopWhenGoingDownwardsWithoutFocus) { ...@@ -258,7 +256,7 @@ TEST_F(SpatialNavigationTest, StartAtTopWhenGoingDownwardsWithoutFocus) {
TEST_F(SpatialNavigationTest, StartAtBottomWhenGoingUpwardsWithoutFocus) { TEST_F(SpatialNavigationTest, StartAtBottomWhenGoingUpwardsWithoutFocus) {
EXPECT_EQ( EXPECT_EQ(
LayoutRect(0, 222 + 1, 111, 0), LayoutRect(0, 222, 111, 0),
SearchOrigin({0, 0, 111, 222}, nullptr, SpatialNavigationDirection::kUp)); SearchOrigin({0, 0, 111, 222}, nullptr, SpatialNavigationDirection::kUp));
EXPECT_EQ(SearchOrigin(RootViewport(&GetFrame()), nullptr, EXPECT_EQ(SearchOrigin(RootViewport(&GetFrame()), nullptr,
...@@ -267,7 +265,7 @@ TEST_F(SpatialNavigationTest, StartAtBottomWhenGoingUpwardsWithoutFocus) { ...@@ -267,7 +265,7 @@ TEST_F(SpatialNavigationTest, StartAtBottomWhenGoingUpwardsWithoutFocus) {
} }
TEST_F(SpatialNavigationTest, StartAtLeftSideWhenGoingEastWithoutFocus) { TEST_F(SpatialNavigationTest, StartAtLeftSideWhenGoingEastWithoutFocus) {
EXPECT_EQ(LayoutRect(-1, 0, 0, 222), EXPECT_EQ(LayoutRect(0, 0, 0, 222),
SearchOrigin({0, 0, 111, 222}, nullptr, SearchOrigin({0, 0, 111, 222}, nullptr,
SpatialNavigationDirection::kRight)); SpatialNavigationDirection::kRight));
...@@ -277,7 +275,7 @@ TEST_F(SpatialNavigationTest, StartAtLeftSideWhenGoingEastWithoutFocus) { ...@@ -277,7 +275,7 @@ TEST_F(SpatialNavigationTest, StartAtLeftSideWhenGoingEastWithoutFocus) {
} }
TEST_F(SpatialNavigationTest, StartAtRightSideWhenGoingWestWithoutFocus) { TEST_F(SpatialNavigationTest, StartAtRightSideWhenGoingWestWithoutFocus) {
EXPECT_EQ(LayoutRect(111 + 1, 0, 0, 222), EXPECT_EQ(LayoutRect(111, 0, 0, 222),
SearchOrigin({0, 0, 111, 222}, nullptr, SearchOrigin({0, 0, 111, 222}, nullptr,
SpatialNavigationDirection::kLeft)); SpatialNavigationDirection::kLeft));
...@@ -332,15 +330,14 @@ TEST_F(SpatialNavigationTest, StartAtContainersEdge) { ...@@ -332,15 +330,14 @@ TEST_F(SpatialNavigationTest, StartAtContainersEdge) {
// Go down. // Go down.
LayoutRect container_top_edge = container_box; LayoutRect container_top_edge = container_box;
container_top_edge.SetHeight(LayoutUnit(0)); container_top_edge.SetHeight(LayoutUnit(0));
container_top_edge.SetY(container_top_edge.Y() - 1);
EXPECT_EQ(SearchOrigin(RootViewport(&GetFrame()), b, EXPECT_EQ(SearchOrigin(RootViewport(&GetFrame()), b,
SpatialNavigationDirection::kDown), SpatialNavigationDirection::kDown),
container_top_edge); container_top_edge);
// Go up. // Go up.
LayoutRect container_bottom_edge = container_box; LayoutRect container_bottom_edge = container_box;
container_bottom_edge.SetY(container_bottom_edge.MaxX());
container_bottom_edge.SetHeight(LayoutUnit(0)); container_bottom_edge.SetHeight(LayoutUnit(0));
container_bottom_edge.SetY(container_bottom_edge.MaxX() + 1);
EXPECT_EQ(SearchOrigin(RootViewport(&GetFrame()), b, EXPECT_EQ(SearchOrigin(RootViewport(&GetFrame()), b,
SpatialNavigationDirection::kUp), SpatialNavigationDirection::kUp),
container_bottom_edge); container_bottom_edge);
...@@ -348,14 +345,13 @@ TEST_F(SpatialNavigationTest, StartAtContainersEdge) { ...@@ -348,14 +345,13 @@ TEST_F(SpatialNavigationTest, StartAtContainersEdge) {
// Go right. // Go right.
LayoutRect container_leftmost_edge = container_box; LayoutRect container_leftmost_edge = container_box;
container_leftmost_edge.SetWidth(LayoutUnit(0)); container_leftmost_edge.SetWidth(LayoutUnit(0));
container_leftmost_edge.SetX(container_leftmost_edge.X() - 1);
EXPECT_EQ(SearchOrigin(RootViewport(&GetFrame()), b, EXPECT_EQ(SearchOrigin(RootViewport(&GetFrame()), b,
SpatialNavigationDirection::kRight), SpatialNavigationDirection::kRight),
container_leftmost_edge); container_leftmost_edge);
// Go left. // Go left.
LayoutRect container_rightmost_edge = container_box; LayoutRect container_rightmost_edge = container_box;
container_rightmost_edge.SetX(container_bottom_edge.MaxX() + 1); container_rightmost_edge.SetX(container_bottom_edge.MaxX());
container_rightmost_edge.SetWidth(LayoutUnit(0)); container_rightmost_edge.SetWidth(LayoutUnit(0));
EXPECT_EQ(SearchOrigin(RootViewport(&GetFrame()), b, EXPECT_EQ(SearchOrigin(RootViewport(&GetFrame()), b,
SpatialNavigationDirection::kLeft), SpatialNavigationDirection::kLeft),
...@@ -600,7 +596,7 @@ TEST_F(SpatialNavigationTest, BottomOfPinchedViewport) { ...@@ -600,7 +596,7 @@ TEST_F(SpatialNavigationTest, BottomOfPinchedViewport) {
EXPECT_EQ(origin.Height(), 0); EXPECT_EQ(origin.Height(), 0);
EXPECT_EQ(origin.Width(), GetFrame().View()->Width()); EXPECT_EQ(origin.Width(), GetFrame().View()->Width());
EXPECT_EQ(origin.X(), 0); EXPECT_EQ(origin.X(), 0);
EXPECT_EQ(origin.Y(), GetFrame().View()->Height() + 1); EXPECT_EQ(origin.Y(), GetFrame().View()->Height());
EXPECT_EQ(origin, BottomOfVisualViewport()); EXPECT_EQ(origin, BottomOfVisualViewport());
// Now, test SearchOrigin with a pinched viewport. // Now, test SearchOrigin with a pinched viewport.
...@@ -612,7 +608,7 @@ TEST_F(SpatialNavigationTest, BottomOfPinchedViewport) { ...@@ -612,7 +608,7 @@ TEST_F(SpatialNavigationTest, BottomOfPinchedViewport) {
EXPECT_EQ(origin.Height(), 0); EXPECT_EQ(origin.Height(), 0);
EXPECT_LT(origin.Width(), GetFrame().View()->Width()); EXPECT_LT(origin.Width(), GetFrame().View()->Width());
EXPECT_GT(origin.X(), 0); EXPECT_GT(origin.X(), 0);
EXPECT_LT(origin.Y(), GetFrame().View()->Height() + 1); EXPECT_LT(origin.Y(), GetFrame().View()->Height());
EXPECT_EQ(origin, BottomOfVisualViewport()); EXPECT_EQ(origin, BottomOfVisualViewport());
} }
...@@ -622,7 +618,7 @@ TEST_F(SpatialNavigationTest, TopOfPinchedViewport) { ...@@ -622,7 +618,7 @@ TEST_F(SpatialNavigationTest, TopOfPinchedViewport) {
EXPECT_EQ(origin.Height(), 0); EXPECT_EQ(origin.Height(), 0);
EXPECT_EQ(origin.Width(), GetFrame().View()->Width()); EXPECT_EQ(origin.Width(), GetFrame().View()->Width());
EXPECT_EQ(origin.X(), 0); EXPECT_EQ(origin.X(), 0);
EXPECT_EQ(origin.Y(), -1); EXPECT_EQ(origin.Y(), 0);
EXPECT_EQ(origin, TopOfVisualViewport()); EXPECT_EQ(origin, TopOfVisualViewport());
// Now, test SearchOrigin with a pinched viewport. // Now, test SearchOrigin with a pinched viewport.
...@@ -634,7 +630,7 @@ TEST_F(SpatialNavigationTest, TopOfPinchedViewport) { ...@@ -634,7 +630,7 @@ TEST_F(SpatialNavigationTest, TopOfPinchedViewport) {
EXPECT_EQ(origin.Height(), 0); EXPECT_EQ(origin.Height(), 0);
EXPECT_LT(origin.Width(), GetFrame().View()->Width()); EXPECT_LT(origin.Width(), GetFrame().View()->Width());
EXPECT_GT(origin.X(), 0); EXPECT_GT(origin.X(), 0);
EXPECT_GT(origin.Y(), -1); EXPECT_GT(origin.Y(), 0);
EXPECT_EQ(origin, TopOfVisualViewport()); EXPECT_EQ(origin, TopOfVisualViewport());
} }
......
<!doctype html>
<a id="outerA" href="www" style="margin-left: 150px">outerA</a>
<div id="containerA" style="width: 300px; height: 300px; background: pink; position: relative; margin: 20px" tabindex="0">
<div id="containerB" style="width: 270px; height: 270px; background: yellow; position: relative; top: 15px; left: 15px" tabindex="0">
<!-- The focusables' DOM order is not the same as their layout order.
SpatNav should prioritize layout order. -->
<a id="innerB" href="www" style="position: absolute; top: 150px; left: 20px">innerB</a>
<a id="innerC" href="www" style="position: absolute; top: 150px; left: 170px">innerC</a>
<a id="innerD" href="www" style="position: absolute; top: 230px; left: 130px">innerD</a>
<a id="innerA" href="www" style="position: absolute; top: 20px; left: 130px">innerA</a>
</div>
</div>
<a id="outerB" href="www" style="margin-left: 150px">outerB</a>
<script src="../../resources/testharness.js"></script>
<script src="../../resources/testharnessreport.js"></script>
<script src="resources/snav-testharness.js"></script>
<script>
var resultMap = [
["Down", "outerA"],
["Down", "containerA"],
["Down", "containerB"],
["Down", "innerA"],
["Down", "innerC"],
["Down", "innerD"],
["Down", "outerB"],
["Up", "containerA"],
["Up", "containerB"],
["Up", "innerD"],
["Up", "innerC"],
["Up", "innerA"],
["Up", "outerA"],
];
snav.assertFocusMoves(resultMap);
</script>
<p><em>Manual test instruction: Ensure that all links are reachable. Especially, when the
yellow "container" is focused, ensure that its "inner" focusables can be reached.</em></p>
<p>Once the bottommost inner node gets focus, we exit the complete stack of
containers at once (moving to #outerB from #innerD). Note that we don't go back to
one of the containers because that would create focus traps.</p>
<p>Also, when holding one of the four directional keys, ensure that the page is fully scrolled
in that direction. Especially, ensure that focus does not get stuck inside the container.</p>
\ No newline at end of file
<!doctype html>
<style>
body {margin: 0;}
#top-of-root {background: green; width: 100%; height: 20px;}
</style>
<div id="top-of-root" class="top" tabindex="1"></div>
<script src="../../resources/testharness.js"></script>
<script src="../../resources/testharnessreport.js"></script>
<script src="resources/snav-testharness.js"></script>
<script>
snav.assertFocusMoves([["Down", "top-of-root"]]);
</script>
<p><em>Manual test instruction: Ensure the div is reachable even though it sits at the very top of the document.</em></p>
\ No newline at end of file
<!doctype html>
<style>
#scroller {background: yellow; width: 130px; height: 80px; overflow: scroll;}
#insider-at-top {background: purple; width: 130px; height: 20px; margin-bottom: 300px}
</style>
<div id="scroller">
<div id="insider-at-top" class="top" tabindex="1">at top of scroller</div>
</div>
<script src="../../resources/testharness.js"></script>
<script src="../../resources/testharnessreport.js"></script>
<script src="resources/snav-testharness.js"></script>
<script>
var resultMap = [
["Down", "scroller"],
["Down", "insider-at-top"]
];
snav.assertFocusMoves(resultMap);
</script>
<p><em>Manual test instruction: Ensure the insider is reachable even though it sits at the very top of the scroller.</em></p>
<!doctype html>
<style>
body {font-size: 15px;}
</style>
<p>Here are two links that (due to their parent divs' limited width) split over two lines:</p>
<a id="start" href="www">Random start link</a>.
<div style="width: 230px; background: yellow; position: relative; margin-left: 20px;">
<a id="fragmentedA" href="www">This is a long bla bla bla fragmented link.</a>
Hey <a id="now_reachableA" href="www">previously</a> <a id="now_reachableB" href="www">unreachable</a>.
<div style="position: absolute; right: -100px; top: 0px; width: 100px">
<a id="anotherA" href="www">AnotherA random link</a>
</div>
<div style="position: absolute; left: -15px; top: 0px; width: 100px">
<a id="B" href="www">B</a>
</div>
</div>
<br>
<div style="width: 230px; background: yellow; position: relative; margin-left: 20px;">
This is a <a id="short" href="www">short</a> bla bla <a id="fragmentedB" href="www">fragmented liiiiiink.</a>
Hey now <a id="now_reachableC" href="www">reachable</a>.
<div style="position: absolute; right: -100px; top: 0px; width: 100px">
<a id="anotherC" href="www">AnotherC random link</a>
</div>
<div style="position: absolute; left: -15px; top: 0px; width: 100px">
<a id="D" href="www">D</a>
</div>
</div>
<a id="end" href="www">Random end link</a>.
<p>The yellow background highlights a fragmented link's bounding box</em>.<br>
This bounding box covers another link. Here we test that SpatNav can reach that link.</p>
<script src="../../resources/testharness.js"></script>
<script src="../../resources/testharnessreport.js"></script>
<script src="resources/snav-testharness.js"></script>
<script>
var resultMap = [
["Down", "start"],
["Down", "fragmentedA"],
["Right", "now_reachableA"], // We searched *inside* #fragmentedA's box.
["Right", "now_reachableB"],
["Right", "anotherA"],
["Down", "anotherC"], // #anotherA and #anotherC are perfectly aligned.
["Up", "anotherA"],
["Left", "fragmentedA"],
["Left", "now_reachableB"], // We searched *inside* #fragmentedA's box.
["Left", "now_reachableA"],
["Left", "B"], // We don't go to the enclosing #fragmentedA. If we did, holding
// Left would cause a loop inside #fragmentedA's bounding box.
["Right", "fragmentedA"],
["Down", "now_reachableA"], // We searched *inside* #fragmentedA's box.
["Down", "short"],
["Right", "anotherC"], // We don't go to #fragmentedB because Right from #fragmentedB goes back to #short.
// This avoids an endless loop between #fragmentedB and #short if Right is held.
["Left", "fragmentedB"],
["Right", "short"], // We searched *inside* #fragmentedB's box.
["Down", "end"], // This avoids an endless loop between #fragmentedB and #short if Down is held.
["Up", "fragmentedB"],
["Down", "short"], // We searched *inside* #fragmentedB's box.
["Right", "anotherC"],
["Left", "fragmentedB"],
["Left", "now_reachableC"], // We searched *inside* #fragmentedB's box.
["Down", "end"]
];
snav.assertFocusMoves(resultMap);
</script>
<p><em>Manual test instruction: Ensure that all links are reachable.</em></p>
<p>Also, when holding one of the four directional keys, ensure that the page is fully
scrolled in that direction. Especially, ensure that focus does not get stuck inside the
fragmented link's bounding box.</p>
\ No newline at end of file
...@@ -32,7 +32,7 @@ ...@@ -32,7 +32,7 @@
} }
</style> </style>
<div tabindex="0" id="outer"> <div tabindex="0">
Outer Target Outer Target
<div id="clip" tabindex="0"> <div id="clip" tabindex="0">
Clip Clip
...@@ -41,30 +41,16 @@ ...@@ -41,30 +41,16 @@
</div> </div>
<script> <script>
const outer = document.getElementById("outer");
const clip = document.getElementById("clip");
const target = document.getElementById("target"); const target = document.getElementById("target");
// This test checks that the spatial navigation overlap testing works // This test checks that the spatial navigation overlap testing works
// correctly in the presence of clipping. The spatial navigation algorithm // correctly in the presence of clipping. The spatial navigation algorithm
// attempts to prioritize inner elements that are fully contained by another // attempts to prioritize the inner element when one valid target is fully
// valid target. // contained by another valid target. This test ensures the inner is
// // considered fully contained even if it has clipped overflow that would
// The spatial navigation algorithm focuses "containers" from the outside and // spill outside the outer target.
// in. This test ensures that also clipped "insiders" are reachable.
test(() => { test(() => {
assert_true(!!window.internals); assert_true(!!window.internals);
snav.triggerMove('Down');
assert_equals(window.internals.interestedElement,
outer,
"Expected interest to move to |outer| element.");
snav.triggerMove('Down');
assert_equals(window.internals.interestedElement,
clip,
"Expected interest to move to |clip| element.");
snav.triggerMove('Down'); snav.triggerMove('Down');
assert_equals(window.internals.interestedElement, assert_equals(window.internals.interestedElement,
target, target,
......
...@@ -19,30 +19,22 @@ ...@@ -19,30 +19,22 @@
} }
</style> </style>
<div tabindex="0" id="container"> <div tabindex="0">
<input id="input" type="text" value="second"></input> <input id="input" type="text" value="second"></input>
</div> </div>
<a href="www" id="outsider">Outsider</a>
<script> <script>
const container = document.getElementById("container");
const input = document.getElementById("input"); const input = document.getElementById("input");
const outsider = document.getElementById("outsider");
// Spatial navigation attempts to determine when one valid focusable fully // Spatial navigation attempts to determine when one valid target fully
// contains another. In this case, the contained, "inner" target should be // contains another. In this case, the contained "inner" target should be
// selected when we start from the container div. // selected. This is done by performing a hit test.
// //
// This test ensures the "is contained"-test works correctly on an <input>. // This test ensures the hit test works correctly on an input box. This
// This requires special attention because the focusable node is in the // requires special attention because the hit element will be in the input's
// the <input>'s Shadow DOM. // shadow dom.
test(() => { test(() => {
assert_true(!!window.internals); assert_true(!!window.internals);
snav.triggerMove('Down');
assert_equals(window.internals.interestedElement,
container,
"Expected interest to move to the surrounding div.");
snav.triggerMove('Down'); snav.triggerMove('Down');
assert_equals(window.internals.interestedElement, assert_equals(window.internals.interestedElement,
input, input,
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment