Index: chrome/browser/resources/options/options_page.js |
diff --git a/chrome/browser/resources/options/options_page.js b/chrome/browser/resources/options/options_page.js |
index 715de30257100d1aecf729b0d29372c53c47718b..429ee08f4e3936fa7643ab313728f5b2768df9bd 100644 |
--- a/chrome/browser/resources/options/options_page.js |
+++ b/chrome/browser/resources/options/options_page.js |
@@ -129,11 +129,20 @@ cr.define('options', function() { |
* @private |
*/ |
OptionsPage.isOverlayVisible_ = function() { |
+ return this.getVisibleOverlay_() != null; |
+ }; |
+ |
+ /** |
+ * Returns the currently visible overlay, or null if no page is visible. |
+ * @return {OptionPage} The visible overlay. |
+ */ |
+ OptionsPage.getVisibleOverlay_ = function() { |
for (var name in this.registeredOverlayPages) { |
- if (this.registeredOverlayPages[name].visible) |
- return true; |
+ var page = this.registeredOverlayPages[name]; |
+ if (page.visible) |
+ return page; |
} |
- return false; |
+ return null; |
}; |
/** |
@@ -159,7 +168,7 @@ cr.define('options', function() { |
topPage = page; |
} |
return topPage; |
- } |
+ }; |
/** |
* Closes the topmost open subpage, if any. |
@@ -349,6 +358,9 @@ cr.define('options', function() { |
// Install handler for key presses. |
document.addEventListener('keydown', this.keyDownEventHandler_.bind(this)); |
+ |
+ document.addEventListener('focus', this.manageFocusChange_.bind(this), |
+ true); |
}; |
/** |
@@ -373,6 +385,30 @@ cr.define('options', function() { |
}; |
/** |
+ * Called when focus changes; ensures that focus doesn't move outside |
+ * the topmost subpage/overlay. |
+ * @param {Event} e The focus change event. |
+ * @private |
+ */ |
+ OptionsPage.manageFocusChange_ = function(e) { |
+ var focusableItemsRoot; |
+ // If an overlay is visible, that defines the tab loop. |
+ var topPage = this.getVisibleOverlay_(); |
+ if (topPage) |
+ focusableItemsRoot = topPage.pageDiv; |
+ // If a subpage is visible, use its parent as the tab loop constraint. |
+ // (The parent is used because it contains the close button.) |
+ if (!topPage) { |
+ var topPage = this.getTopmostVisiblePage(); |
+ if (topPage && topPage.nestingLevel > 0) |
+ focusableItemsRoot = topPage.pageDiv.parentNode; |
+ } |
+ |
+ if (focusableItemsRoot && !focusableItemsRoot.contains(e.target)) |
+ topPage.focusFirstElement(); |
+ }; |
+ |
+ /** |
* A function to handle mouse events (mousedown or click) on the html body by |
* closing subpages and/or stopping event propagation. |
* @return {Event} a mousedown or click event. |
@@ -542,6 +578,17 @@ cr.define('options', function() { |
}, |
/** |
+ * Focuses the first control on the page. |
+ */ |
+ focusFirstElement: function() { |
+ // Sets focus on the first interactive element in the page. |
+ var focusElement = |
+ this.pageDiv.querySelector('button, input, list, select'); |
+ if (focusElement) |
+ focusElement.focus(); |
+ }, |
+ |
+ /** |
* The nesting level of this page. |
* @type {number} The nesting level of this page (0 for top-level page) |
*/ |