WebCore/ChangeLog

 12010-11-19 Yael Aharon <yael.aharon@nokia.com>, Chang Shu <chang.shu@nokia.com>
 2
 3 Reviewed by NOBODY (OOPS!).
 4
 5 Spatial Navigation: issues with the node selection algorithm.
 6 https://bugs.webkit.org/show_bug.cgi?id=49382
 7
 8 Modify the Spatial Navigation algorithm, to better handle initial focus and
 9 navigation between frames.
 10 The new algorithm takes the rect of the focused node as the startingRect,
 11 instead of the node itself. That allows us to construct a virtual rect if
 12 there is no focused node, or if it is off the screen.
 13 The virtual rect is the edge of the container in the direction of the navigation.
 14
 15 With this patch, scrollable containers and frames will scroll regardless of weather
 16 they have focusable content. Users will be able to use arrow keys to view all the
 17 content of such a container. The only exception is if the container has style overflow:hidden.
 18 We will not scroll in that case.
 19
 20 With this patch, we handle z-index and positioning so that if there are 2 overlapping focusable nodes,
 21 we do a hit test and only the node on top can get focus.
 22
 23 hasOffScreenRect() was modified so that it can check if a node will be off-screen even after we scrolled
 24 its parent container. We do not add the scrolling conditions for containers that have overflow:hidden
 25 and cannot scroll.
 26
 27 calculateScrollbarModesForLayout is used to decide if a frame can scroll or not. We cannot rely on
 28 the exsistance of the scrollbar, because it could be removed via the API, while the frame is still
 29 allowed to scroll.
 30
 31 * page/FocusController.cpp:
 32 (WebCore::updateFocusCandidateIfNeeded):
 33 (WebCore::FocusController::findFocusCandidateInContainer):
 34 (WebCore::FocusController::navigateInContainer):
 35 (WebCore::FocusController::advanceFocusDirectionally):
 36 * page/FocusController.h:
 37 * page/FrameView.h:
 38 * page/SpatialNavigation.cpp:
 39 (WebCore::FocusCandidate::FocusCandidate):
 40 (WebCore::distanceDataForNode):
 41 (WebCore::alignmentForRects):
 42 (WebCore::areRectsMoreThanFullScreenApart):
 43 (WebCore::isRectInDirection):
 44 (WebCore::hasOffscreenRect):
 45 (WebCore::scrollInDirection):
 46 (WebCore::isScrollableContainerNode):
 47 (WebCore::scrollableEnclosingBoxOrParentFrameForNodeInDirection):
 48 (WebCore::canScrollInDirection):
 49 (WebCore::rectToAbsoluteCoordinates):
 50 (WebCore::nodeRectInAbsoluteCoordinates):
 51 (WebCore::frameRectInAbsoluteCoordinates):
 52 (WebCore::entryAndExitPointsForDirection):
 53 (WebCore::canBeScrolledIntoView):
 54 (WebCore::virtualStartingRectForDirection):
 55 * page/SpatialNavigation.h:
 56
1572010-11-19 James Simonsen <simonjam@chromium.org>
258
359 Reviewed by Darin Fisher.
72415

WebCore/page/FocusController.cpp

4040#include "Frame.h"
4141#include "FrameTree.h"
4242#include "FrameView.h"
 43#include "HitTestResult.h"
4344#include "HTMLFrameOwnerElement.h"
4445#include "HTMLNames.h"
4546#include "KeyboardEvent.h"
4647#include "Page.h"
4748#include "Range.h"
 49#include "RenderLayer.h"
4850#include "RenderObject.h"
4951#include "RenderWidget.h"
5052#include "SelectionController.h"

@@namespace WebCore {
5759using namespace HTMLNames;
5860using namespace std;
5961
 62static void updateFocusCandidateIfNeeded(FocusDirection direction, const IntRect& startingRect, FocusCandidate& candidate, FocusCandidate& closest);
6063static inline void dispatchEventsOnWindowAndFocusedNode(Document* document, bool focused)
6164{
6265 // If we have a focused node we should dispatch blur on it before we blur the window.

@@bool FocusController::advanceFocusInDocu
289292 return true;
290293}
291294
292 bool FocusController::advanceFocusDirectionally(FocusDirection direction, KeyboardEvent* event)
293 {
294  Frame* frame = focusedOrMainFrame();
295  ASSERT(frame);
296  Document* focusedDocument = frame->document();
297  if (!focusedDocument)
298  return false;
299 
300  Node* focusedNode = focusedDocument->focusedNode();
301  if (!focusedNode) {
302  // Just move to the first focusable node.
303  FocusDirection tabDirection = (direction == FocusDirectionUp || direction == FocusDirectionLeft) ?
304  FocusDirectionBackward : FocusDirectionForward;
305  // 'initialFocus' is set to true so the chrome is not focused.
306  return advanceFocusInDocumentOrder(tabDirection, event, true);
307  }
308 
309  // Move up in the chain of nested frames.
310  frame = frame->tree()->top();
311 
312  FocusCandidate focusCandidate;
313  findFocusableNodeInDirection(frame->document()->firstChild(), focusedNode, direction, event, focusCandidate);
314 
315  Node* node = focusCandidate.node;
316  if (!node || !node->isElementNode()) {
317  // FIXME: May need a way to focus a document here.
318  Frame* frame = focusedOrMainFrame();
319  scrollInDirection(frame, direction);
320  return false;
321  }
322 
323  // In order to avoid crazy jump between links that are either far away from each other,
324  // or just not currently visible, lets do a scroll in the given direction and bail out
325  // if |node| element is not in the viewport.
326  if (hasOffscreenRect(node)) {
327  Frame* frame = node->document()->view()->frame();
328  scrollInDirection(frame, direction, focusCandidate);
329  return true;
330  }
331 
332  Document* newDocument = node->document();
333 
334  if (newDocument != focusedDocument) {
335  // Focus is going away from the originally focused document, so clear the focused node.
336  focusedDocument->setFocusedNode(0);
337  }
338 
339  if (newDocument)
340  setFocusedFrame(newDocument->frame());
341 
342  Element* element = static_cast<Element*>(node);
343  ASSERT(element);
344 
345  scrollIntoView(element);
346  element->focus(false);
347  return true;
348 }
349 
350295static void updateFocusCandidateInSameContainer(const FocusCandidate& candidate, FocusCandidate& closest)
351296{
352297 if (closest.isNull()) {

@@void FocusController::setActive(bool act
658603 dispatchEventsOnWindowAndFocusedNode(m_focusedFrame->document(), active);
659604}
660605
 606void updateFocusCandidateIfNeeded(FocusDirection direction, const IntRect& startingRect, FocusCandidate& candidate, FocusCandidate& closest)
 607{
 608 if (!candidate.node->isElementNode() ||!candidate.node->renderer())
 609 return;
 610
 611 // Ignore empty frames
 612 if (candidate.node->isFrameOwnerElement() && !static_cast<HTMLFrameOwnerElement*>(candidate.node)->contentFrame())
 613 return;
 614
 615 // Ignore off screen child nodes of containers that do not scroll (overflow:hidden)
 616 if (hasOffscreenRect(candidate.node) && !canBeScrolledIntoView(direction, candidate))
 617 return;
 618
 619 FocusCandidate current;
 620 current.rect = startingRect;
 621 distanceDataForNode(direction, current, candidate);
 622 if (candidate.distance == maxDistance())
 623 return;
 624
 625 if (hasOffscreenRect(candidate.node, direction) && candidate.alignment < Full)
 626 return;
 627
 628 if (closest.isNull()) {
 629 closest = candidate;
 630 return;
 631 }
 632
 633 IntRect intersectionRect = intersection(nodeRectInAbsoluteCoordinates(candidate.node, true), nodeRectInAbsoluteCoordinates(closest.node, true));
 634 if (!intersectionRect.isEmpty()) {
 635 // If 2 nodes are intersecting, do hit test to find which node in on top.
 636 int x = intersectionRect.x() + intersectionRect.width() / 2;
 637 int y = intersectionRect.y() + intersectionRect.height() / 2;
 638 HitTestResult result = candidate.node->document()->page()->mainFrame()->eventHandler()->hitTestResultAtPoint(IntPoint(x, y), false, true);
 639 if (candidate.node->contains(result.innerNode())) {
 640 closest = candidate;
 641 return;
 642 }
 643 if (closest.node->contains(result.innerNode()))
 644 return;
 645 }
 646
 647 if (candidate.alignment == closest.alignment) {
 648 if (candidate.distance < closest.distance)
 649 closest = candidate;
 650 return;
 651 }
 652
 653 if (candidate.alignment > closest.alignment)
 654 closest = candidate;
 655}
 656
 657void FocusController::findFocusCandidateInContainer(Node* container, const IntRect& startingRect, FocusDirection direction, KeyboardEvent* event, FocusCandidate& closest)
 658{
 659 ASSERT(container);
 660 for (Node* node = container->firstChild(); node; node = (node->isFrameOwnerElement() || canScrollInDirection(direction, node)) ? node->traverseNextSibling(container) : node->traverseNextNode(container)) {
 661 if ((!focusedFrame() || !focusedFrame()->document() || node != focusedFrame()->document()->focusedNode()) && (node->isKeyboardFocusable(event) || node->isFrameOwnerElement() || canScrollInDirection(direction, node))) {
 662 if (!node->renderer())
 663 continue;
 664 FocusCandidate candidate(node);
 665 candidate.enclosingScrollableBox = container;
 666 updateFocusCandidateIfNeeded(direction, startingRect, candidate, closest);
 667 }
 668 }
 669}
 670
 671bool FocusController::navigateInContainer(Node* container, const IntRect& startingRect, FocusDirection direction, KeyboardEvent* event)
 672{
 673 if (!container || !container->document())
 674 return false;
 675
 676 IntRect newStartingRect = startingRect;
 677
 678 if (startingRect.isEmpty())
 679 newStartingRect = virtualStartingRectForDirection(direction, nodeRectInAbsoluteCoordinates(container));
 680
 681 // Find the closest node within current container in the direction of the navigation.
 682 FocusCandidate focusCandidate;
 683 findFocusCandidateInContainer(container, newStartingRect, direction, event, focusCandidate);
 684
 685 if (focusCandidate.isNull()) {
 686 if (canScrollInDirection(direction, container)) {
 687 // Nothing to focus, scroll if possible.
 688 scrollInDirection(container, direction);
 689 return true;
 690 }
 691 // Return false will cause a re-try, skipping this container.
 692 return false;
 693 }
 694 if (focusCandidate.node->isFrameOwnerElement()) {
 695 HTMLFrameOwnerElement* frameElement = static_cast<HTMLFrameOwnerElement*>(focusCandidate.node);
 696 // If we have an iframe without the src attribute, it will not have a contentFrame().
 697 // We should never consider such an iframe as a candidate.
 698 ASSERT(frameElement->contentFrame());
 699
 700 if (hasOffscreenRect(focusCandidate.node, direction)) {
 701 scrollInDirection(focusCandidate.node->document(), direction);
 702 return true;
 703 }
 704 // Navigate into a new frame.
 705 IntRect rect;
 706 Node* focusedNode = focusedOrMainFrame()->document()->focusedNode();
 707 if (focusedNode && !hasOffscreenRect(focusedNode))
 708 rect = nodeRectInAbsoluteCoordinates(focusedNode, true /* ignore border */);
 709 frameElement->contentFrame()->document()->updateLayoutIgnorePendingStylesheets();
 710 if (!navigateInContainer(frameElement->contentFrame()->document(), rect, direction, event)) {
 711 // The new frame had nothing interesting, need to find another candidate.
 712 return navigateInContainer(container, nodeRectInAbsoluteCoordinates(focusCandidate.node, true), direction, event);
 713 }
 714 return true;
 715 }
 716 if (canScrollInDirection(direction, focusCandidate.node)) {
 717 if (hasOffscreenRect(focusCandidate.node, direction)) {
 718 scrollInDirection(focusCandidate.node, direction);
 719 return true;
 720 }
 721 // Navigate into a new scrollable container.
 722 IntRect startingRect;
 723 Node* focusedNode = focusedOrMainFrame()->document()->focusedNode();
 724 if (focusedNode && !hasOffscreenRect(focusedNode))
 725 startingRect = nodeRectInAbsoluteCoordinates(focusedNode, true);
 726 return navigateInContainer(focusCandidate.node, startingRect, direction, event);
 727 }
 728 if (hasOffscreenRect(focusCandidate.node, direction)) {
 729 Node* container = focusCandidate.enclosingScrollableBox;
 730 scrollInDirection(container, direction);
 731 return true;
 732 }
 733
 734 // We found a new focus node, navigate to it.
 735 Element* element = static_cast<Element*>(focusCandidate.node);
 736 ASSERT(element);
 737
 738 element->focus(false);
 739 return true;
 740}
 741
 742bool FocusController::advanceFocusDirectionally(FocusDirection direction, KeyboardEvent* event)
 743{
 744 Frame* curFrame = focusedOrMainFrame();
 745 ASSERT(curFrame);
 746
 747 Document* focusedDocument = curFrame->document();
 748 if (!focusedDocument)
 749 return false;
 750
 751 Node* focusedNode = focusedDocument->focusedNode();
 752 Node* container = focusedDocument;
 753
 754 // Figure out the starting rect.
 755 IntRect startingRect;
 756 if (focusedNode && !hasOffscreenRect(focusedNode)) {
 757 container = scrollableEnclosingBoxOrParentFrameForNodeInDirection(direction, focusedNode);
 758 startingRect = nodeRectInAbsoluteCoordinates(focusedNode, true /* ignore border */);
 759 }
 760
 761 bool consumed = false;
 762 do {
 763 if (container->isDocumentNode())
 764 static_cast<Document*>(container)->updateLayoutIgnorePendingStylesheets();
 765 consumed = navigateInContainer(container, startingRect, direction, event);
 766 startingRect = nodeRectInAbsoluteCoordinates(container, true /* ignore border */);
 767 container = scrollableEnclosingBoxOrParentFrameForNodeInDirection(direction, container);
 768 } while (!consumed && container);
 769
 770 return consumed;
 771}
 772
661773} // namespace WebCore
72384

WebCore/page/FocusController.h

@@private:
6868 const FocusCandidate& parentCandidate = FocusCandidate());
6969 void deepFindFocusableNodeInDirection(Node* container, Node* focused, FocusDirection, KeyboardEvent*, FocusCandidate&);
7070
 71 bool navigateInContainer(Node* container, const IntRect& startingRect, FocusDirection, KeyboardEvent*);
 72 void findFocusCandidateInContainer(Node* container, const IntRect& startingRect, FocusDirection, KeyboardEvent*, FocusCandidate& closest);
 73
7174 Page* m_page;
7275 RefPtr<Frame> m_focusedFrame;
7376 bool m_isActive;
72384

WebCore/page/FrameView.h

@@public:
236236 bool isFrameViewScrollCorner(RenderScrollbarPart* scrollCorner) const { return m_scrollCorner == scrollCorner; }
237237 void invalidateScrollCorner();
238238
 239 void calculateScrollbarModesForLayout(ScrollbarMode& hMode, ScrollbarMode& vMode);
 240
239241 // Normal delay
240242 static void setRepaintThrottlingDeferredRepaintDelay(double p);
241243 // Negative value would mean that first few repaints happen without a delay

@@private:
265267 bool hasFixedObjects() const { return m_fixedObjectCount > 0; }
266268
267269 void applyOverflowToViewport(RenderObject*, ScrollbarMode& hMode, ScrollbarMode& vMode);
268  void calculateScrollbarModesForLayout(ScrollbarMode& hMode, ScrollbarMode& vMode);
269270
270271 void updateOverflowStatus(bool horizontalOverflow, bool verticalOverflow);
271272
72384

WebCore/page/SpatialNavigation.cpp

@@namespace WebCore {
4343
4444static long long spatialDistance(FocusDirection, const IntRect&, const IntRect&);
4545static IntRect renderRectRelativeToRootDocument(RenderObject*);
46 static RectsAlignment alignmentForRects(FocusDirection, const IntRect&, const IntRect&);
 46static RectsAlignment alignmentForRects(FocusDirection, const IntRect&, const IntRect&, const IntSize& viewSize);
4747static bool areRectsFullyAligned(FocusDirection, const IntRect&, const IntRect&);
4848static bool areRectsPartiallyAligned(FocusDirection, const IntRect&, const IntRect&);
 49static bool areRectsMoreThanFullScreenApart(FocusDirection direction, const IntRect& curRect, const IntRect& targetRect, const IntSize& viewSize);
4950static bool isRectInDirection(FocusDirection, const IntRect&, const IntRect&);
5051static void deflateIfOverlapped(IntRect&, IntRect&);
5152static bool checkNegativeCoordsForNode(Node*, const IntRect&);
 53static IntRect rectToAbsoluteCoordinates(Frame* initialFrame, const IntRect& rect);
 54static void entryAndExitPointsForDirection(FocusDirection direction, const IntRect& startingRect, const IntRect& potentialRect, IntPoint& exitPoint, IntPoint& entryPoint);
 55
 56
 57FocusCandidate::FocusCandidate(Node* n)
 58 : node(n)
 59 , enclosingScrollableBox(0)
 60 , distance(maxDistance())
 61 , parentDistance(maxDistance())
 62 , alignment(None)
 63 , parentAlignment(None)
 64 , rect(nodeRectInAbsoluteCoordinates(n, true /* ignore border */))
 65{
 66}
5267
5368bool isSpatialNavigationEnabled(const Frame* frame)
5469{

@@void distanceDataForNode(FocusDirection
102117 // The distance between two nodes is not to be considered alone when evaluating/looking
103118 // for the best focus candidate node. Alignment of rects can be also a good point to be
104119 // considered in order to make the algorithm to behavior in a more intuitive way.
105  candidate.alignment = alignmentForRects(direction, curRect, targetRect);
 120 IntSize viewSize = candidate.node->document()->page()->mainFrame()->view()->visibleContentRect().size();
 121 candidate.alignment = alignmentForRects(direction, curRect, targetRect, viewSize);
106122 candidate.distance = spatialDistance(direction, curRect, targetRect);
107123}
108124

@@static IntRect renderRectRelativeToRootD
136152 return rect;
137153}
138154
139 static RectsAlignment alignmentForRects(FocusDirection direction, const IntRect& curRect, const IntRect& targetRect)
 155static RectsAlignment alignmentForRects(FocusDirection direction, const IntRect& curRect, const IntRect& targetRect, const IntSize& viewSize)
140156{
 157 // If we found a node in full alignment, but it is too far away, ignore it.
 158 if (areRectsMoreThanFullScreenApart(direction, curRect, targetRect, viewSize))
 159 return None;
 160
141161 if (areRectsFullyAligned(direction, curRect, targetRect))
142162 return Full;
143163

@@static bool areRectsPartiallyAligned(Foc
277297 || (bEnd >= aStart && bEnd <= aEnd));
278298}
279299
 300static bool areRectsMoreThanFullScreenApart(FocusDirection direction, const IntRect& curRect, const IntRect& targetRect, const IntSize& viewSize)
 301{
 302 ASSERT(isRectInDirection(direction, curRect, targetRect));
 303
 304 switch (direction) {
 305 case FocusDirectionLeft:
 306 return curRect.x() - targetRect.right() > viewSize.width();
 307 case FocusDirectionRight:
 308 return targetRect.x() - curRect.right() > viewSize.width();
 309 case FocusDirectionUp:
 310 return curRect.y() - targetRect.bottom() > viewSize.height();
 311 case FocusDirectionDown:
 312 return targetRect.y() - curRect.bottom() > viewSize.height();
 313 default:
 314 ASSERT_NOT_REACHED();
 315 return true;
 316 }
 317}
 318
280319// Return true if rect |a| is below |b|. False otherwise.
281320static inline bool below(const IntRect& a, const IntRect& b)
282321{

@@static long long spatialDistance(FocusDi
403442
404443static bool isRectInDirection(FocusDirection direction, const IntRect& curRect, const IntRect& targetRect)
405444{
406  IntPoint center(targetRect.center());
407  int targetMiddle = isHorizontalMove(direction) ? center.x() : center.y();
408 
409445 switch (direction) {
410446 case FocusDirectionLeft:
411  return targetMiddle < curRect.x();
 447 return targetRect.right() <= curRect.x();
412448 case FocusDirectionRight:
413  return targetMiddle > curRect.right();
 449 return targetRect.x() >= curRect.right();
414450 case FocusDirectionUp:
415  return targetMiddle < curRect.y();
 451 return targetRect.bottom() <= curRect.y();
416452 case FocusDirectionDown:
417  return targetMiddle > curRect.bottom();
 453 return targetRect.y() >= curRect.bottom();
418454 default:
419455 ASSERT_NOT_REACHED();
 456 return false;
420457 }
421 
422  return false;
423458}
424459
425460// Checks if |node| is offscreen the visible area (viewport) of its container
426461// document. In case it is, one can scroll in direction or take any different
427462// desired action later on.
428 bool hasOffscreenRect(Node* node)
 463bool hasOffscreenRect(Node* node, FocusDirection direction)
429464{
430465 // Get the FrameView in which |node| is (which means the current viewport if |node|
431466 // is not in an inner document), so we can check if its content rect is visible

@@bool hasOffscreenRect(Node* node)
435470 return true;
436471
437472 IntRect containerViewportRect = frameView->visibleContentRect();
 473 // We want to select a node if it is currently off screen, but will be
 474 // exposed after we scroll. Adjust the viewport to post-scrolling position.
 475 // If the container has overflow:hidden, we cannot scroll, so we do not pass direction
 476 // and we do not adjust for scrolling.
 477 switch (direction) {
 478 case FocusDirectionLeft:
 479 containerViewportRect.setX(containerViewportRect.x() - Scrollbar::pixelsPerLineStep());
 480 containerViewportRect.setWidth(containerViewportRect.width() + Scrollbar::pixelsPerLineStep());
 481 break;
 482 case FocusDirectionRight:
 483 containerViewportRect.setWidth(containerViewportRect.width() + Scrollbar::pixelsPerLineStep());
 484 break;
 485 case FocusDirectionUp:
 486 containerViewportRect.setY(containerViewportRect.y() - Scrollbar::pixelsPerLineStep());
 487 containerViewportRect.setHeight(containerViewportRect.height() + Scrollbar::pixelsPerLineStep());
 488 break;
 489 case FocusDirectionDown:
 490 containerViewportRect.setHeight(containerViewportRect.height() + Scrollbar::pixelsPerLineStep());
 491 break;
 492 default:
 493 break;
 494 }
438495
439496 RenderObject* render = node->renderer();
440497 if (!render)

@@bool hasOffscreenRect(Node* node)
447504 return !containerViewportRect.intersects(rect);
448505}
449506
450 // In a bottom-up way, this method tries to scroll |frame| in a given direction
451 // |direction|, going up in the frame tree hierarchy in case it does not succeed.
452 bool scrollInDirection(Frame* frame, FocusDirection direction, const FocusCandidate& candidate)
 507bool scrollInDirection(Frame* frame, FocusDirection direction)
453508{
454  if (!frame)
455  return false;
 509 ASSERT(frame && canScrollInDirection(direction, frame->document()));
456510
457  ScrollDirection scrollDirection;
 511 if (frame && canScrollInDirection(direction, frame->document())) {
 512 int dx = 0;
 513 int dy = 0;
 514 switch (direction) {
 515 case FocusDirectionLeft:
 516 dx = - Scrollbar::pixelsPerLineStep();
 517 break;
 518 case FocusDirectionRight:
 519 dx = Scrollbar::pixelsPerLineStep();
 520 break;
 521 case FocusDirectionUp:
 522 dy = - Scrollbar::pixelsPerLineStep();
 523 break;
 524 case FocusDirectionDown:
 525 dy = Scrollbar::pixelsPerLineStep();
 526 break;
 527 default:
 528 ASSERT_NOT_REACHED();
 529 return false;
 530 }
458531
459  switch (direction) {
460  case FocusDirectionLeft:
461  scrollDirection = ScrollLeft;
462  break;
463  case FocusDirectionRight:
464  scrollDirection = ScrollRight;
465  break;
466  case FocusDirectionUp:
467  scrollDirection = ScrollUp;
468  break;
469  case FocusDirectionDown:
470  scrollDirection = ScrollDown;
471  break;
472  default:
473  return false;
 532 frame->view()->scrollBy(IntSize(dx, dy));
 533 return true;
474534 }
 535 return false;
 536}
475537
476  if (!candidate.isNull() && isScrollableContainerNode(candidate.enclosingScrollableBox))
477  return frame->eventHandler()->scrollRecursively(scrollDirection, ScrollByLine, candidate.enclosingScrollableBox);
 538bool scrollInDirection(Node* container, FocusDirection direction)
 539{
 540 if (container->isDocumentNode())
 541 return scrollInDirection(static_cast<Document*>(container)->frame(), direction);
478542
479  return frame->eventHandler()->scrollRecursively(scrollDirection, ScrollByLine);
 543 if (!container->renderBox())
 544 return false;
 545
 546 if (container && canScrollInDirection(direction, container)) {
 547 int dx = 0;
 548 int dy = 0;
 549 switch (direction) {
 550 case FocusDirectionLeft:
 551 dx = - min(Scrollbar::pixelsPerLineStep(), container->renderBox()->scrollLeft());
 552 break;
 553 case FocusDirectionRight:
 554 ASSERT(container->renderBox()->scrollWidth() > (container->renderBox()->scrollLeft() + container->renderBox()->clientWidth()));
 555 dx = min(Scrollbar::pixelsPerLineStep(), container->renderBox()->scrollWidth() - (container->renderBox()->scrollLeft() + container->renderBox()->clientWidth()));
 556 break;
 557 case FocusDirectionUp:
 558 dy = - min(Scrollbar::pixelsPerLineStep(), container->renderBox()->scrollTop());
 559 break;
 560 case FocusDirectionDown:
 561 ASSERT(container->renderBox()->scrollHeight() - (container->renderBox()->scrollTop() + container->renderBox()->clientHeight()));
 562 dy = min(Scrollbar::pixelsPerLineStep(), container->renderBox()->scrollHeight() - (container->renderBox()->scrollTop() + container->renderBox()->clientHeight()));
 563 break;
 564 default:
 565 ASSERT_NOT_REACHED();
 566 return false;
 567 }
 568
 569 container->renderBox()->enclosingLayer()->scrollByRecursively(dx, dy);
 570 return true;
 571 }
 572 return false;
480573}
481574
482575void scrollIntoView(Element* element)

@@static bool checkNegativeCoordsForNode(N
534627 return canBeScrolled;
535628}
536629
537 bool isScrollableContainerNode(Node* node)
 630bool isScrollableContainerNode(const Node* node)
538631{
539632 if (!node)
540633 return false;

@@bool isNodeDeepDescendantOfDocument(Node
567660 return descendant;
568661}
569662
 663Node* scrollableEnclosingBoxOrParentFrameForNodeInDirection(FocusDirection direction, Node* node)
 664{
 665 ASSERT(node);
 666 Node* parent = node;
 667 do {
 668 if (parent->isDocumentNode())
 669 parent = static_cast<Document*>(parent)->document()->frame()->ownerElement();
 670 else
 671 parent = parent->parentNode();
 672 } while (parent && !canScrollInDirection(direction, parent) && !parent->isDocumentNode());
 673
 674 return parent;
 675}
 676
 677bool canScrollInDirection(FocusDirection direction, const Node* container)
 678{
 679 ASSERT(container);
 680 if (container->isDocumentNode())
 681 return canScrollInDirection(direction, static_cast<const Document*>(container)->frame());
 682
 683 if (!isScrollableContainerNode(container))
 684 return false;
 685
 686 switch (direction) {
 687 case FocusDirectionLeft:
 688 return (container->renderer()->style()->overflowX() != OHIDDEN && container->renderBox()->scrollLeft() > 0);
 689 case FocusDirectionUp:
 690 return (container->renderer()->style()->overflowY() != OHIDDEN && container->renderBox()->scrollTop() > 0);
 691 case FocusDirectionRight:
 692 return (container->renderer()->style()->overflowX() != OHIDDEN && container->renderBox()->scrollLeft() + container->renderBox()->clientWidth() < container->renderBox()->scrollWidth());
 693 case FocusDirectionDown:
 694 return (container->renderer()->style()->overflowY() != OHIDDEN && container->renderBox()->scrollTop() + container->renderBox()->clientHeight() < container->renderBox()->scrollHeight());
 695 default:
 696 ASSERT_NOT_REACHED();
 697 return false;
 698 }
 699}
 700
 701bool canScrollInDirection(FocusDirection direction, const Frame* frame)
 702{
 703 if (!frame->view())
 704 return false;
 705 ScrollbarMode verticalMode;
 706 ScrollbarMode horizontalMode;
 707 frame->view()->calculateScrollbarModesForLayout(horizontalMode, verticalMode);
 708 if ((direction == FocusDirectionLeft || direction == FocusDirectionRight) && ScrollbarAlwaysOff == horizontalMode)
 709 return false;
 710 if ((direction == FocusDirectionUp || direction == FocusDirectionDown) && ScrollbarAlwaysOff == verticalMode)
 711 return false;
 712 IntSize size = frame->view()->contentsSize();
 713 IntSize offset = frame->view()->scrollOffset();
 714 IntRect rect = frame->view()->visibleContentRect(true);
 715
 716 switch (direction) {
 717 case FocusDirectionLeft:
 718 return offset.width() > 0;
 719 case FocusDirectionUp:
 720 return offset.height() > 0;
 721 case FocusDirectionRight:
 722 return rect.width() + offset.width() < size.width();
 723 case FocusDirectionDown:
 724 return rect.height() + offset.height() < size.height();
 725 default:
 726 ASSERT_NOT_REACHED();
 727 return false;
 728 }
 729}
 730
 731static IntRect rectToAbsoluteCoordinates(Frame* initialFrame, const IntRect& initialRect)
 732{
 733 IntRect rect = initialRect;
 734 for (Frame* frame = initialFrame; frame; frame = frame->tree()->parent()) {
 735 if (Element* element = static_cast<Element*>(frame->ownerElement())) {
 736 do {
 737 rect.move(element->offsetLeft(), element->offsetTop());
 738 } while ((element = element->offsetParent()));
 739 rect.move((-frame->view()->scrollOffset()));
 740 }
 741 }
 742 return rect;
 743}
 744
 745IntRect nodeRectInAbsoluteCoordinates(Node* node, bool ignoreBorder)
 746{
 747 ASSERT(node && node->renderer());
 748
 749 if (node->isDocumentNode())
 750 return frameRectInAbsoluteCoordinates(static_cast<Document*>(node)->frame());
 751 IntRect rect = rectToAbsoluteCoordinates(node->document()->frame(), node->getRect());
 752
 753 // For authors that use border instead of outline in their CSS, we compensate by ignoring the border when calculating
 754 // the rect of the focused element.
 755 if (ignoreBorder) {
 756 rect.move(node->renderer()->style()->borderLeftWidth(), node->renderer()->style()->borderTopWidth());
 757 rect.setWidth(rect.width() - node->renderer()->style()->borderLeftWidth() - node->renderer()->style()->borderRightWidth());
 758 rect.setHeight(rect.height() - node->renderer()->style()->borderTopWidth() - node->renderer()->style()->borderBottomWidth());
 759 }
 760 return rect;
 761}
 762
 763IntRect frameRectInAbsoluteCoordinates(Frame* frame)
 764{
 765 return rectToAbsoluteCoordinates(frame, frame->view()->visibleContentRect());
 766}
 767
 768// This method calculates the exitPoint from the startingRect and the entryPoint into the candidate rect.
 769// The line between those 2 points is the closest distance between the 2 rects.
 770void entryAndExitPointsForDirection(FocusDirection direction, const IntRect& startingRect, const IntRect& potentialRect, IntPoint& exitPoint, IntPoint& entryPoint)
 771{
 772 switch (direction) {
 773 case FocusDirectionLeft:
 774 exitPoint.setX(startingRect.x());
 775 entryPoint.setX(potentialRect.right());
 776 break;
 777 case FocusDirectionUp:
 778 exitPoint.setY(startingRect.y());
 779 entryPoint.setY(potentialRect.bottom());
 780 break;
 781 case FocusDirectionRight:
 782 exitPoint.setX(startingRect.right());
 783 entryPoint.setX(potentialRect.x());
 784 break;
 785 case FocusDirectionDown:
 786 exitPoint.setY(startingRect.bottom());
 787 entryPoint.setY(potentialRect.y());
 788 break;
 789 default:
 790 ASSERT_NOT_REACHED();
 791 }
 792
 793 switch (direction) {
 794 case FocusDirectionLeft:
 795 case FocusDirectionRight:
 796 if (below(startingRect, potentialRect)) {
 797 exitPoint.setY(startingRect.y());
 798 entryPoint.setY(potentialRect.bottom());
 799 } else if (below(potentialRect, startingRect)) {
 800 exitPoint.setY(startingRect.bottom());
 801 entryPoint.setY(potentialRect.y());
 802 } else {
 803 exitPoint.setY(max(startingRect.y(), potentialRect.y()));
 804 entryPoint.setY(exitPoint.y());
 805 }
 806 break;
 807 case FocusDirectionUp:
 808 case FocusDirectionDown:
 809 if (rightOf(startingRect, potentialRect)) {
 810 exitPoint.setX(startingRect.x());
 811 entryPoint.setX(potentialRect.right());
 812 } else if (rightOf(potentialRect, startingRect)) {
 813 exitPoint.setX(startingRect.right());
 814 entryPoint.setX(potentialRect.x());
 815 } else {
 816 exitPoint.setX(max(startingRect.x(), potentialRect.x()));
 817 entryPoint.setX(exitPoint.x());
 818 }
 819 break;
 820 default:
 821 ASSERT_NOT_REACHED();
 822 }
 823}
 824
 825void distanceDataForNode(FocusDirection direction, FocusCandidate& current, FocusCandidate& candidate)
 826{
 827 if (candidate.isNull())
 828 return;
 829 if (!candidate.node->renderer())
 830 return;
 831 IntRect nodeRect = candidate.rect;
 832 IntRect currentRect = current.rect;
 833 deflateIfOverlapped(currentRect, nodeRect);
 834
 835 if (!isRectInDirection(direction, currentRect, nodeRect))
 836 return;
 837
 838 IntPoint exitPoint;
 839 IntPoint entryPoint;
 840 int sameAxisDistance = 0;
 841 int otherAxisDistance = 0;
 842 entryAndExitPointsForDirection(direction, currentRect, nodeRect, exitPoint, entryPoint);
 843
 844 switch (direction) {
 845 case FocusDirectionLeft:
 846 sameAxisDistance = exitPoint.x() - entryPoint.x();
 847 otherAxisDistance = abs(exitPoint.y() - entryPoint.y());
 848 break;
 849 case FocusDirectionUp:
 850 sameAxisDistance = exitPoint.y() - entryPoint.y();
 851 otherAxisDistance = abs(exitPoint.x() - entryPoint.x());
 852 break;
 853 case FocusDirectionRight:
 854 sameAxisDistance = entryPoint.x() - exitPoint.x();
 855 otherAxisDistance = abs(entryPoint.y() - exitPoint.y());
 856 break;
 857 case FocusDirectionDown:
 858 sameAxisDistance = entryPoint.y() - exitPoint.y();
 859 otherAxisDistance = abs(entryPoint.x() - exitPoint.x());
 860 break;
 861 default:
 862 ASSERT_NOT_REACHED();
 863 return;
 864 }
 865
 866 int x = (entryPoint.x() - exitPoint.x()) * (entryPoint.x() - exitPoint.x());
 867 int y = (entryPoint.y() - exitPoint.y()) * (entryPoint.y() - exitPoint.y());
 868
 869 float euclidianDistance = sqrt((x + y) * 1.0f);
 870
 871 // Loosely based on http://www.w3.org/TR/WICD/#focus-handling
 872 // df = dotDist + dx + dy + 2 * (xdisplacement + ydisplacement) - sqrt(Overlap)
 873
 874 float distance = euclidianDistance + sameAxisDistance + 2 * otherAxisDistance;
 875 candidate.distance = roundf(distance);
 876 IntSize viewSize = candidate.node->document()->page()->mainFrame()->view()->visibleContentRect().size();
 877 candidate.alignment = alignmentForRects(direction, currentRect, nodeRect, viewSize);
 878}
 879
 880bool canBeScrolledIntoView(FocusDirection direction, FocusCandidate& candidate)
 881{
 882 ASSERT(candidate.node && hasOffscreenRect(candidate.node));
 883 IntRect candidateRect = candidate.rect;
 884 for (Node* parentNode = candidate.node->parent(); parentNode; parentNode = parentNode->parent()) {
 885 IntRect parentRect = nodeRectInAbsoluteCoordinates(parentNode);
 886 if (!candidateRect.intersects(parentRect)) {
 887 if (((direction == FocusDirectionLeft || direction == FocusDirectionRight) && parentNode->renderer()->style()->overflowX() == OHIDDEN)
 888 || ((direction == FocusDirectionUp || direction == FocusDirectionDown) && parentNode->renderer()->style()->overflowY() == OHIDDEN))
 889 return false;
 890 }
 891 if (parentNode == candidate.enclosingScrollableBox)
 892 return canScrollInDirection(direction, parentNode);
 893 }
 894 return true;
 895}
 896
 897// The starting rect is the rect of the focused node, in document coordinates.
 898// Compose a virtual starting rect if there is no focused node or if it is off screen.
 899// The virtual rect is the edge of the container or frame. We select which
 900// edge depending on the direction of the navigation.
 901IntRect virtualStartingRectForDirection(FocusDirection direction, const IntRect& startingRect)
 902{
 903 IntRect virtualStartingRect = startingRect;
 904 switch (direction) {
 905 case FocusDirectionLeft:
 906 virtualStartingRect.setX(virtualStartingRect.right());
 907 virtualStartingRect.setWidth(0);
 908 break;
 909 case FocusDirectionUp:
 910 virtualStartingRect.setY(virtualStartingRect.bottom());
 911 virtualStartingRect.setHeight(0);
 912 break;
 913 case FocusDirectionRight:
 914 virtualStartingRect.setWidth(0);
 915 break;
 916 case FocusDirectionDown:
 917 virtualStartingRect.setHeight(0);
 918 break;
 919 default:
 920 ASSERT_NOT_REACHED();
 921 }
 922
 923 return virtualStartingRect;
 924}
 925
 926
570927} // namespace WebCore
72384

WebCore/page/SpatialNavigation.h

2222#define SpatialNavigation_h
2323
2424#include "FocusDirection.h"
 25#include "IntRect.h"
2526#include "Node.h"
2627
2728#include <limits>

@@struct FocusCandidate {
107108 {
108109 }
109110
110  FocusCandidate(Node* n)
111  : node(n)
112  , enclosingScrollableBox(0)
113  , distance(maxDistance())
114  , parentDistance(maxDistance())
115  , alignment(None)
116  , parentAlignment(None)
117  {
118  }
119 
 111 FocusCandidate(Node* n);
120112 bool isNull() const { return !node; }
121113 bool inScrollableContainer() const { return node && enclosingScrollableBox; }
122114 Document* document() const { return node ? node->document() : 0; }

@@struct FocusCandidate {
127119 long long parentDistance;
128120 RectsAlignment alignment;
129121 RectsAlignment parentAlignment;
 122 IntRect rect;
130123};
131124
132125void distanceDataForNode(FocusDirection direction, Node* start, FocusCandidate& candidate);
133 bool scrollInDirection(Frame*, FocusDirection, const FocusCandidate& candidate = FocusCandidate());
 126bool scrollInDirection(Frame*, FocusDirection);
 127bool scrollInDirection(Node* container, FocusDirection);
134128void scrollIntoView(Element*);
135 bool hasOffscreenRect(Node*);
 129bool hasOffscreenRect(Node*, FocusDirection direction = FocusDirectionNone);
136130bool isInRootDocument(Node*);
137 bool isScrollableContainerNode(Node*);
 131bool isScrollableContainerNode(const Node*);
138132bool isNodeDeepDescendantOfDocument(Node*, Document*);
139 
 133Node* scrollableEnclosingBoxOrParentFrameForNodeInDirection(FocusDirection, Node* node);
 134bool canScrollInDirection(FocusDirection, const Node* container);
 135bool canScrollInDirection(FocusDirection, const Frame*);
 136IntRect nodeRectInAbsoluteCoordinates(Node*, bool ignoreBorder = false);
 137IntRect frameRectInAbsoluteCoordinates(Frame*);
 138void distanceDataForNode(FocusDirection, FocusCandidate& current, FocusCandidate& candidate);
 139bool canBeScrolledIntoView(FocusDirection, FocusCandidate&);
 140IntRect virtualStartingRectForDirection(FocusDirection, const IntRect& startingRect);
140141} // namspace WebCore
141142
142143#endif // SpatialNavigation_h
72384