blob: 79c98298f1528cebe283d43a32f6915515233364 [file] [log] [blame]
// Copyright 2017 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
cr.define('bookmarks', function() {
/**
* Manages focus restoration for modal dialogs. After the final dialog in a
* stack is closed, restores focus to the element which was focused when the
* first dialog was opened.
* @constructor
*/
function DialogFocusManager() {
/** @private {HTMLElement} */
this.previousFocusElement_ = null;
/** @private {Set<HTMLDialogElement>} */
this.dialogs_ = new Set();
}
DialogFocusManager.prototype = {
/**
* @param {HTMLDialogElement} dialog
* @param {function()=} showFn
*/
showDialog: function(dialog, showFn) {
if (!showFn) {
showFn = function() {
dialog.showModal();
};
}
// Update the focus if there are no open dialogs or if this is the only
// dialog and it's getting reshown.
if (!this.dialogs_.size ||
(this.dialogs_.has(dialog) && this.dialogs_.size == 1)) {
this.updatePreviousFocus_();
}
if (!this.dialogs_.has(dialog)) {
dialog.addEventListener('close', this.getCloseListener_(dialog));
this.dialogs_.add(dialog);
}
showFn();
},
/**
* @return {boolean} True if the document currently has an open dialog.
*/
hasOpenDialog: function() {
return this.dialogs_.size > 0;
},
/**
* Clears the stored focus element, so that focus does not restore when all
* dialogs are closed.
*/
clearFocus: function() {
this.previousFocusElement_ = null;
},
/** @private */
updatePreviousFocus_: function() {
this.previousFocusElement_ = this.getFocusedElement_();
},
/**
* @return {HTMLElement}
* @private
*/
getFocusedElement_: function() {
let focus = document.activeElement;
while (focus.root && focus.root.activeElement)
focus = focus.root.activeElement;
return focus;
},
/**
* @param {HTMLDialogElement} dialog
* @return {function(Event)}
* @private
*/
getCloseListener_: function(dialog) {
const closeListener = (e) => {
// If the dialog is open, then it got reshown immediately and we
// shouldn't clear it until it is closed again.
if (dialog.open)
return;
assert(this.dialogs_.delete(dialog));
// Focus the originally focused element if there are no more dialogs.
if (!this.hasOpenDialog() && this.previousFocusElement_)
this.previousFocusElement_.focus();
dialog.removeEventListener('close', closeListener);
};
return closeListener;
},
};
cr.addSingletonGetter(DialogFocusManager);
return {
DialogFocusManager: DialogFocusManager,
};
});