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')) {