• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1// Copyright 2014 The Chromium Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5/**
6 * @fileoverview Implements the setFocus function.
7 */
8
9goog.provide('cvox.Focuser');
10
11goog.require('cvox.ChromeVoxEventSuspender');
12goog.require('cvox.DomUtil');
13
14
15/**
16 * Sets the browser focus to the targetNode or its closest ancestor that is
17 * focusable.
18 *
19 * @param {Node} targetNode The node to move the browser focus to.
20 * @param {boolean=} opt_focusDescendants Whether or not we check descendants
21 * of the target node to see if they are focusable. If true, sets focus on the
22 * first focusable descendant. If false, only sets focus on the targetNode or
23 * its closest ancestor. Default is false.
24 */
25cvox.Focuser.setFocus = function(targetNode, opt_focusDescendants) {
26  // Save the selection because Chrome will lose it if there's a focus or blur.
27  var sel = window.getSelection();
28  var range;
29  if (sel.rangeCount > 0) {
30    range = sel.getRangeAt(0);
31  }
32  // Blur the currently-focused element if the target node is not a descendant.
33  if (document.activeElement &&
34      !cvox.DomUtil.isDescendantOfNode(targetNode, document.activeElement)) {
35    document.activeElement.blur();
36  }
37
38  // Video elements should always be focusable.
39  if (targetNode && (targetNode.constructor == HTMLVideoElement)) {
40    if (!cvox.DomUtil.isFocusable(targetNode)) {
41      targetNode.setAttribute('tabIndex', 0);
42    }
43  }
44
45  if (opt_focusDescendants && !cvox.DomUtil.isFocusable(targetNode)) {
46    var focusableDescendant = cvox.DomUtil.findFocusableDescendant(targetNode);
47    if (focusableDescendant) {
48      targetNode = focusableDescendant;
49    }
50  } else {
51    // Search up the parent chain until a focusable node is found.
52    while (targetNode && !cvox.DomUtil.isFocusable(targetNode)) {
53      targetNode = targetNode.parentNode;
54    }
55  }
56
57  // If we found something focusable, focus it - otherwise, blur it.
58  if (cvox.DomUtil.isFocusable(targetNode)) {
59    // Don't let the instance of ChromeVox in the parent focus iframe children
60    // - instead, let the instance of ChromeVox in the iframe focus itself to
61    // avoid getting trapped in iframes that have no ChromeVox in them.
62    // This self focusing is performed by calling window.focus() in
63    // cvox.NavigationManager.prototype.addInterframeListener_
64    if (targetNode.tagName != 'IFRAME') {
65      // setTimeout must be used because there's a bug (in Chrome, I think)
66      // with .focus() which causes the page to be redrawn incorrectly if
67      // not in setTimeout.
68      if (cvox.ChromeVoxEventSuspender.areEventsSuspended()) {
69        if (cvox.Focuser.shouldEnterSuspendEvents_(targetNode)) {
70          cvox.ChromeVoxEventSuspender.enterSuspendEvents();
71        }
72        window.setTimeout(function() {
73          targetNode.focus();
74          cvox.ChromeVoxEventSuspender.exitSuspendEvents();
75        }, 0);
76      }
77      else {
78        window.setTimeout(function() {
79            targetNode.focus();
80        }, 0);
81      }
82    }
83  } else if (document.activeElement &&
84             document.activeElement.tagName != 'BODY') {
85    document.activeElement.blur();
86  }
87
88  // Restore the selection, unless the focused item is a text box.
89  if (cvox.DomUtil.isInputTypeText(targetNode)) {
90    targetNode.select();
91  } else if (range) {
92    sel.removeAllRanges();
93    sel.addRange(range);
94  }
95};
96
97/**
98 * Rules for whether or not enterSuspendEvents should be called.
99 * In general, we should not enterSuspendEvents if the targetNode will get some
100 * special handlers attached when a focus event is received for it; otherwise,
101 * the special handlers will not get attached.
102 *
103 * @param {Node} targetNode The node that is being focused.
104 * @return {boolean} True if enterSuspendEvents should be called.
105 */
106cvox.Focuser.shouldEnterSuspendEvents_ = function(targetNode){
107  if (targetNode.constructor && targetNode.constructor == HTMLVideoElement) {
108    return false;
109  }
110  if (targetNode.hasAttribute) {
111    switch (targetNode.getAttribute('type')) {
112      case 'time':
113      case 'date':
114      case 'week':
115      case 'month':
116        return false;
117    }
118  }
119  return true;
120};
121