Merge Chromium + Blink git repositories
[chromium-blink-merge.git] / chrome / browser / resources / chromeos / chromevox / common / focuser.js
blobf0a29d87c4364cb220b56dad88343bb17474c6f8
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.
5 /**
6  * @fileoverview Implements the setFocus function.
7  */
9 goog.provide('cvox.Focuser');
11 goog.require('cvox.ChromeVoxEventSuspender');
12 goog.require('cvox.DomUtil');
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  */
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();
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   }
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   }
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   }
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   }
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   }
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.
103  * @param {Node} targetNode The node that is being focused.
104  * @return {boolean} True if enterSuspendEvents should be called.
105  */
106 cvox.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;