295 static void updateFocusCandidateInSameContainer(const FocusCandidate& candidate, FocusCandidate& closest)
296 {
297 if (closest.isNull()) {
298 closest = candidate;
299 return;
300 }
301
302 if (candidate.alignment == closest.alignment) {
303 if (candidate.distance < closest.distance)
304 closest = candidate;
305 return;
306 }
307
308 if (candidate.alignment > closest.alignment)
309 closest = candidate;
310 }
311
312 static void updateFocusCandidateIfCloser(Node* focusedNode, const FocusCandidate& candidate, FocusCandidate& closest)
313 {
314 // First, check the common case: neither candidate nor closest are
315 // inside scrollable content, then no need to care about enclosingScrollableBox
316 // heuristics or parent{Distance,Alignment}, but only distance and alignment.
317 if (!candidate.inScrollableContainer() && !closest.inScrollableContainer()) {
318 updateFocusCandidateInSameContainer(candidate, closest);
319 return;
320 }
321
322 bool sameContainer = candidate.document() == closest.document() && candidate.enclosingScrollableBox == closest.enclosingScrollableBox;
323
324 // Second, if candidate and closest are in the same "container" (i.e. {i}frame or any
325 // scrollable block element), we can handle them as common case.
326 if (sameContainer) {
327 updateFocusCandidateInSameContainer(candidate, closest);
328 return;
329 }
330
331 // Last, we are considering moving to a candidate located in a different enclosing
332 // scrollable box than closest.
333 bool isInInnerDocument = !isInRootDocument(focusedNode);
334
335 bool sameContainerAsCandidate = isInInnerDocument ? focusedNode->document() == candidate.document() :
336 focusedNode->isDescendantOf(candidate.enclosingScrollableBox);
337
338 bool sameContainerAsClosest = isInInnerDocument ? focusedNode->document() == closest.document() :
339 focusedNode->isDescendantOf(closest.enclosingScrollableBox);
340
341 // sameContainerAsCandidate and sameContainerAsClosest are mutually exclusive.
342 ASSERT(!(sameContainerAsCandidate && sameContainerAsClosest));
343
344 if (sameContainerAsCandidate) {
345 closest = candidate;
346 return;
347 }
348
349 if (sameContainerAsClosest) {
350 // Nothing to be done.
351 return;
352 }
353
354 // NOTE: !sameContainerAsCandidate && !sameContainerAsClosest
355 // If distance is shorter, and we are talking about scrollable container,
356 // lets compare parent distance and alignment before anything.
357 if (candidate.distance < closest.distance) {
358 if (candidate.alignment >= closest.parentAlignment
359 || candidate.parentAlignment == closest.parentAlignment) {
360 closest = candidate;
361 return;
362 }
363
364 } else if (candidate.parentDistance < closest.distance) {
365 if (candidate.parentAlignment >= closest.alignment) {
366 closest = candidate;
367 return;
368 }
369 }
370 }
371
372 void FocusController::findFocusableNodeInDirection(Node* outer, Node* focusedNode,
373 FocusDirection direction, KeyboardEvent* event,
374 FocusCandidate& closest, const FocusCandidate& candidateParent)
375 {
376 ASSERT(outer);
377 ASSERT(candidateParent.isNull()
378 || candidateParent.node->hasTagName(frameTag)
379 || candidateParent.node->hasTagName(iframeTag)
380 || isScrollableContainerNode(candidateParent.node));
381
382 // Walk all the child nodes and update closest if we find a nearer node.
383 Node* node = outer;
384 while (node) {
385
386 // Inner documents case.
387 if (node->isFrameOwnerElement()) {
388 deepFindFocusableNodeInDirection(node, focusedNode, direction, event, closest);
389
390 // Scrollable block elements (e.g. <div>, etc) case.
391 } else if (isScrollableContainerNode(node)) {
392 deepFindFocusableNodeInDirection(node, focusedNode, direction, event, closest);
393 node = node->traverseNextSibling();
394 continue;
395
396 } else if (node != focusedNode && node->isKeyboardFocusable(event)) {
397 FocusCandidate candidate(node);
398
399 // There are two ways to identify we are in a recursive call from deepFindFocusableNodeInDirection
400 // (i.e. processing an element in an iframe, frame or a scrollable block element):
401
402 // 1) If candidateParent is not null, and it holds the distance and alignment data of the
403 // parent container element itself;
404 // 2) Parent of outer is <frame> or <iframe>;
405 // 3) Parent is any other scrollable block element.
406 if (!candidateParent.isNull()) {
407 candidate.parentAlignment = candidateParent.alignment;
408 candidate.parentDistance = candidateParent.distance;
409 candidate.enclosingScrollableBox = candidateParent.node;
410
411 } else if (!isInRootDocument(outer)) {
412 if (Document* document = static_cast<Document*>(outer->parentNode()))
413 candidate.enclosingScrollableBox = static_cast<Node*>(document->ownerElement());
414
415 } else if (isScrollableContainerNode(outer->parentNode()))
416 candidate.enclosingScrollableBox = outer->parentNode();
417
418 // Get distance and alignment from current candidate.
419 distanceDataForNode(direction, focusedNode, candidate);
420
421 // Bail out if distance is maximum.
422 if (candidate.distance == maxDistance()) {
423 node = node->traverseNextNode(outer->parentNode());
424 continue;
425 }
426
427 updateFocusCandidateIfCloser(focusedNode, candidate, closest);
428 }
429
430 node = node->traverseNextNode(outer->parentNode());
431 }
432 }
433
434 void FocusController::deepFindFocusableNodeInDirection(Node* container, Node* focusedNode,
435 FocusDirection direction, KeyboardEvent* event,
436 FocusCandidate& closest)
437 {
438 ASSERT(container->hasTagName(frameTag)
439 || container->hasTagName(iframeTag)
440 || isScrollableContainerNode(container));
441
442 // Track if focusedNode is a descendant of the current container node being processed.
443 bool descendantOfContainer = false;
444 Node* firstChild = 0;
445
446 // Iframe or Frame.
447 if (container->hasTagName(frameTag) || container->hasTagName(iframeTag)) {
448
449 HTMLFrameOwnerElement* owner = static_cast<HTMLFrameOwnerElement*>(container);
450 if (!owner->contentFrame())
451 return;
452
453 Document* innerDocument = owner->contentFrame()->document();
454 if (!innerDocument)
455 return;
456
457 descendantOfContainer = isNodeDeepDescendantOfDocument(focusedNode, innerDocument);
458 firstChild = innerDocument->firstChild();
459
460 // Scrollable block elements (e.g. <div>, etc)
461 } else if (isScrollableContainerNode(container)) {
462
463 firstChild = container->firstChild();
464 descendantOfContainer = focusedNode->isDescendantOf(container);
465 }
466
467 if (descendantOfContainer) {
468 findFocusableNodeInDirection(firstChild, focusedNode, direction, event, closest);
469 return;
470 }
471
472 // Check if the current container element itself is a good candidate
473 // to move focus to. If it is, then we traverse its inner nodes.
474 FocusCandidate candidateParent = FocusCandidate(container);
475 distanceDataForNode(direction, focusedNode, candidateParent);
476
477 // Bail out if distance is maximum.
478 if (candidateParent.distance == maxDistance())
479 return;
480
481 // FIXME: Consider alignment?
482 if (candidateParent.distance < closest.distance)
483 findFocusableNodeInDirection(firstChild, focusedNode, direction, event, closest, candidateParent);
484 }
485