summaryrefslogtreecommitdiffstats
path: root/chrome/browser/resources/chromeos/chromevox/common/focuser.js
blob: f0a29d87c4364cb220b56dad88343bb17474c6f8 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
// Copyright 2014 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

/**
 * @fileoverview Implements the setFocus function.
 */

goog.provide('cvox.Focuser');

goog.require('cvox.ChromeVoxEventSuspender');
goog.require('cvox.DomUtil');


/**
 * Sets the browser focus to the targetNode or its closest ancestor that is
 * focusable.
 *
 * @param {Node} targetNode The node to move the browser focus to.
 * @param {boolean=} opt_focusDescendants Whether or not we check descendants
 * of the target node to see if they are focusable. If true, sets focus on the
 * first focusable descendant. If false, only sets focus on the targetNode or
 * its closest ancestor. Default is false.
 */
cvox.Focuser.setFocus = function(targetNode, opt_focusDescendants) {
  // Save the selection because Chrome will lose it if there's a focus or blur.
  var sel = window.getSelection();
  var range;
  if (sel.rangeCount > 0) {
    range = sel.getRangeAt(0);
  }
  // Blur the currently-focused element if the target node is not a descendant.
  if (document.activeElement &&
      !cvox.DomUtil.isDescendantOfNode(targetNode, document.activeElement)) {
    document.activeElement.blur();
  }

  // Video elements should always be focusable.
  if (targetNode && (targetNode.constructor == HTMLVideoElement)) {
    if (!cvox.DomUtil.isFocusable(targetNode)) {
      targetNode.setAttribute('tabIndex', 0);
    }
  }

  if (opt_focusDescendants && !cvox.DomUtil.isFocusable(targetNode)) {
    var focusableDescendant = cvox.DomUtil.findFocusableDescendant(targetNode);
    if (focusableDescendant) {
      targetNode = focusableDescendant;
    }
  } else {
    // Search up the parent chain until a focusable node is found.
    while (targetNode && !cvox.DomUtil.isFocusable(targetNode)) {
      targetNode = targetNode.parentNode;
    }
  }

  // If we found something focusable, focus it - otherwise, blur it.
  if (cvox.DomUtil.isFocusable(targetNode)) {
    // Don't let the instance of ChromeVox in the parent focus iframe children
    // - instead, let the instance of ChromeVox in the iframe focus itself to
    // avoid getting trapped in iframes that have no ChromeVox in them.
    // This self focusing is performed by calling window.focus() in
    // cvox.NavigationManager.prototype.addInterframeListener_
    if (targetNode.tagName != 'IFRAME') {
      // setTimeout must be used because there's a bug (in Chrome, I think)
      // with .focus() which causes the page to be redrawn incorrectly if
      // not in setTimeout.
      if (cvox.ChromeVoxEventSuspender.areEventsSuspended()) {
        if (cvox.Focuser.shouldEnterSuspendEvents_(targetNode)) {
          cvox.ChromeVoxEventSuspender.enterSuspendEvents();
        }
        window.setTimeout(function() {
          targetNode.focus();
          cvox.ChromeVoxEventSuspender.exitSuspendEvents();
        }, 0);
      }
      else {
        window.setTimeout(function() {
            targetNode.focus();
        }, 0);
      }
    }
  } else if (document.activeElement &&
             document.activeElement.tagName != 'BODY') {
    document.activeElement.blur();
  }

  // Restore the selection, unless the focused item is a text box.
  if (cvox.DomUtil.isInputTypeText(targetNode)) {
    targetNode.select();
  } else if (range) {
    sel.removeAllRanges();
    sel.addRange(range);
  }
};

/**
 * Rules for whether or not enterSuspendEvents should be called.
 * In general, we should not enterSuspendEvents if the targetNode will get some
 * special handlers attached when a focus event is received for it; otherwise,
 * the special handlers will not get attached.
 *
 * @param {Node} targetNode The node that is being focused.
 * @return {boolean} True if enterSuspendEvents should be called.
 */
cvox.Focuser.shouldEnterSuspendEvents_ = function(targetNode){
  if (targetNode.constructor && targetNode.constructor == HTMLVideoElement) {
    return false;
  }
  if (targetNode.hasAttribute) {
    switch (targetNode.getAttribute('type')) {
      case 'time':
      case 'date':
      case 'week':
      case 'month':
        return false;
    }
  }
  return true;
};