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.
6 * @fileoverview Implements the setFocus function.
9 goog
.provide('cvox.Focuser');
11 goog
.require('cvox.ChromeVoxEventSuspender');
12 goog
.require('cvox.DomUtil');
16 * Sets the browser focus to the targetNode or its closest ancestor that is
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.
25 cvox
.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();
29 if (sel
.rangeCount
> 0) {
30 range
= sel
.getRangeAt(0);
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();
38 // Video elements should always be focusable.
39 if (targetNode
&& (targetNode
.constructor == HTMLVideoElement
)) {
40 if (!cvox
.DomUtil
.isFocusable(targetNode
)) {
41 targetNode
.setAttribute('tabIndex', 0);
45 if (opt_focusDescendants
&& !cvox
.DomUtil
.isFocusable(targetNode
)) {
46 var focusableDescendant
= cvox
.DomUtil
.findFocusableDescendant(targetNode
);
47 if (focusableDescendant
) {
48 targetNode
= focusableDescendant
;
51 // Search up the parent chain until a focusable node is found.
52 while (targetNode
&& !cvox
.DomUtil
.isFocusable(targetNode
)) {
53 targetNode
= targetNode
.parentNode
;
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
68 if (cvox
.ChromeVoxEventSuspender
.areEventsSuspended()) {
69 if (cvox
.Focuser
.shouldEnterSuspendEvents_(targetNode
)) {
70 cvox
.ChromeVoxEventSuspender
.enterSuspendEvents();
72 window
.setTimeout(function() {
74 cvox
.ChromeVoxEventSuspender
.exitSuspendEvents();
78 window
.setTimeout(function() {
83 } else if (document
.activeElement
&&
84 document
.activeElement
.tagName
!= 'BODY') {
85 document
.activeElement
.blur();
88 // Restore the selection, unless the focused item is a text box.
89 if (cvox
.DomUtil
.isInputTypeText(targetNode
)) {
92 sel
.removeAllRanges();
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.
103 * @param {Node} targetNode The node that is being focused.
104 * @return {boolean} True if enterSuspendEvents should be called.
106 cvox
.Focuser
.shouldEnterSuspendEvents_ = function(targetNode
){
107 if (targetNode
.constructor && targetNode
.constructor == HTMLVideoElement
) {
110 if (targetNode
.hasAttribute
) {
111 switch (targetNode
.getAttribute('type')) {