OLD | NEW |
1 // Copyright (c) 2012 The Chromium Authors. All rights reserved. | 1 // Copyright (c) 2012 The Chromium Authors. All rights reserved. |
2 // Use of this source code is governed by a BSD-style license that can be | 2 // Use of this source code is governed by a BSD-style license that can be |
3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
4 | 4 |
5 cr.define('cr.ui', function() { | 5 cr.define('cr.ui', function() { |
6 /** | 6 /** |
7 * Constructor for FocusManager singleton. Checks focus of elements to ensure | 7 * Constructor for FocusManager singleton. Checks focus of elements to ensure |
8 * that elements in "background" pages (i.e., those in a dialog that is not | 8 * that elements in "background" pages (i.e., those in a dialog that is not |
9 * the topmost overlay) do not receive focus. | 9 * the topmost overlay) do not receive focus. |
10 * @constructor | 10 * @constructor |
11 */ | 11 */ |
12 function FocusManager() { | 12 function FocusManager() { |
13 } | 13 } |
14 | 14 |
15 FocusManager.prototype = { | 15 FocusManager.prototype = { |
16 /** | 16 /** |
17 * Whether focus is being transferred backward or forward through the DOM. | 17 * Whether focus is being transferred backward or forward through the DOM. |
18 * @type {boolean} | 18 * @type {boolean} |
19 * @private | 19 * @private |
20 */ | 20 */ |
21 focusDirBackwards_: false, | 21 focusDirBackwards_: false, |
22 | 22 |
23 /** | 23 /** |
24 * Determines whether the |child| is a descendant of |parent| in the page's | 24 * Determines whether the |child| is a descendant of |parent| in the page's |
25 * DOM. | 25 * DOM. |
26 * @param {Element} parent The parent element to test. | 26 * @param {Node} parent The parent element to test. |
27 * @param {Element} child The child element to test. | 27 * @param {Node} child The child element to test. |
28 * @return {boolean} True if |child| is a descendant of |parent|. | 28 * @return {boolean} True if |child| is a descendant of |parent|. |
29 * @private | 29 * @private |
30 */ | 30 */ |
31 isDescendantOf_: function(parent, child) { | 31 isDescendantOf_: function(parent, child) { |
32 return parent && !(parent === child) && parent.contains(child); | 32 return !!parent && !(parent === child) && parent.contains(child); |
33 }, | 33 }, |
34 | 34 |
35 /** | 35 /** |
36 * Returns the parent element containing all elements which should be | 36 * Returns the parent element containing all elements which should be |
37 * allowed to receive focus. | 37 * allowed to receive focus. |
38 * @return {Element} The element containing focusable elements. | 38 * @return {Element} The element containing focusable elements. |
39 */ | 39 */ |
40 getFocusParent: function() { | 40 getFocusParent: function() { |
41 return document.body; | 41 return document.body; |
42 }, | 42 }, |
43 | 43 |
44 /** | 44 /** |
45 * Returns the elements on the page capable of receiving focus. | 45 * Returns the elements on the page capable of receiving focus. |
46 * @return {Array.Element} The focusable elements. | 46 * @return {Array.<Element>} The focusable elements. |
47 */ | 47 */ |
48 getFocusableElements_: function() { | 48 getFocusableElements_: function() { |
49 var focusableDiv = this.getFocusParent(); | 49 var focusableDiv = this.getFocusParent(); |
50 | 50 |
51 // Create a TreeWalker object to traverse the DOM from |focusableDiv|. | 51 // Create a TreeWalker object to traverse the DOM from |focusableDiv|. |
52 var treeWalker = document.createTreeWalker( | 52 var treeWalker = document.createTreeWalker( |
53 focusableDiv, | 53 focusableDiv, |
54 NodeFilter.SHOW_ELEMENT, | 54 NodeFilter.SHOW_ELEMENT, |
55 { acceptNode: function(node) { | 55 /** @type {NodeFilter} */ |
| 56 ({ |
| 57 acceptNode: function(node) { |
56 var style = window.getComputedStyle(node); | 58 var style = window.getComputedStyle(node); |
57 // Reject all hidden nodes. FILTER_REJECT also rejects these | 59 // Reject all hidden nodes. FILTER_REJECT also rejects these |
58 // nodes' children, so non-hidden elements that are descendants of | 60 // nodes' children, so non-hidden elements that are descendants of |
59 // hidden <div>s will correctly be rejected. | 61 // hidden <div>s will correctly be rejected. |
60 if (node.hidden || style.display == 'none' || | 62 if (node.hidden || style.display == 'none' || |
61 style.visibility == 'hidden') { | 63 style.visibility == 'hidden') { |
62 return NodeFilter.FILTER_REJECT; | 64 return NodeFilter.FILTER_REJECT; |
63 } | 65 } |
64 | 66 |
65 // Skip nodes that cannot receive focus. FILTER_SKIP does not | 67 // Skip nodes that cannot receive focus. FILTER_SKIP does not |
66 // cause this node's children also to be skipped. | 68 // cause this node's children also to be skipped. |
67 if (node.disabled || node.tabIndex < 0) | 69 if (node.disabled || node.tabIndex < 0) |
68 return NodeFilter.FILTER_SKIP; | 70 return NodeFilter.FILTER_SKIP; |
69 | 71 |
70 // Accept nodes that are non-hidden and focusable. | 72 // Accept nodes that are non-hidden and focusable. |
71 return NodeFilter.FILTER_ACCEPT; | 73 return NodeFilter.FILTER_ACCEPT; |
72 } | 74 } |
73 }, | 75 }), |
74 false); | 76 false); |
75 | 77 |
76 var focusable = []; | 78 var focusable = []; |
77 while (treeWalker.nextNode()) | 79 while (treeWalker.nextNode()) |
78 focusable.push(treeWalker.currentNode); | 80 focusable.push(treeWalker.currentNode); |
79 | 81 |
80 return focusable; | 82 return focusable; |
81 }, | 83 }, |
82 | 84 |
83 /** | 85 /** |
84 * Dispatches an 'elementFocused' event to notify an element that it has | 86 * Dispatches an 'elementFocused' event to notify an element that it has |
85 * received focus. When focus wraps around within the a page, only the | 87 * received focus. When focus wraps around within the a page, only the |
86 * element that has focus after the wrapping receives an 'elementFocused' | 88 * element that has focus after the wrapping receives an 'elementFocused' |
87 * event. This differs from the native 'focus' event which is received by | 89 * event. This differs from the native 'focus' event which is received by |
88 * an element outside the page first, followed by a 'focus' on an element | 90 * an element outside the page first, followed by a 'focus' on an element |
89 * within the page after the FocusManager has intervened. | 91 * within the page after the FocusManager has intervened. |
90 * @param {Element} element The element that has received focus. | 92 * @param {EventTarget} element The element that has received focus. |
91 * @private | 93 * @private |
92 */ | 94 */ |
93 dispatchFocusEvent_: function(element) { | 95 dispatchFocusEvent_: function(element) { |
94 cr.dispatchSimpleEvent(element, 'elementFocused', true, false); | 96 cr.dispatchSimpleEvent(element, 'elementFocused', true, false); |
95 }, | 97 }, |
96 | 98 |
97 /** | 99 /** |
98 * Attempts to focus the appropriate element in the current dialog. | 100 * Attempts to focus the appropriate element in the current dialog. |
99 * @private | 101 * @private |
100 */ | 102 */ |
(...skipping 47 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
148 }, | 150 }, |
149 | 151 |
150 /** | 152 /** |
151 * Handler for focus events on the page. | 153 * Handler for focus events on the page. |
152 * @param {Event} event The focus event. | 154 * @param {Event} event The focus event. |
153 * @private | 155 * @private |
154 */ | 156 */ |
155 onDocumentFocus_: function(event) { | 157 onDocumentFocus_: function(event) { |
156 // If the element being focused is a descendant of the currently visible | 158 // If the element being focused is a descendant of the currently visible |
157 // page, focus is valid. | 159 // page, focus is valid. |
158 if (this.isDescendantOf_(this.getFocusParent(), event.target)) { | 160 var targetNode = /** @type {Node} */(event.target); |
| 161 if (this.isDescendantOf_(this.getFocusParent(), targetNode)) { |
159 this.dispatchFocusEvent_(event.target); | 162 this.dispatchFocusEvent_(event.target); |
160 return; | 163 return; |
161 } | 164 } |
162 | 165 |
163 // Focus event handlers for descendant elements might dispatch another | 166 // Focus event handlers for descendant elements might dispatch another |
164 // focus event. | 167 // focus event. |
165 event.stopPropagation(); | 168 event.stopPropagation(); |
166 | 169 |
167 // The target of the focus event is not in the topmost visible page and | 170 // The target of the focus event is not in the topmost visible page and |
168 // should not be focused. | 171 // should not be focused. |
(...skipping 58 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
227 window.focus(); | 230 window.focus(); |
228 event.preventDefault(); | 231 event.preventDefault(); |
229 } | 232 } |
230 }, false); | 233 }, false); |
231 }; | 234 }; |
232 | 235 |
233 return { | 236 return { |
234 FocusManager: FocusManager, | 237 FocusManager: FocusManager, |
235 }; | 238 }; |
236 }); | 239 }); |
OLD | NEW |