blob: fe88a2bf468150199734d96c4781577f98a62a55 [file] [log] [blame]
// Copyright 2018 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.
/**
* @fileoverview Listens for a find keyboard shortcut (i.e. Ctrl/Cmd+f)
* and keeps track of an stack of potential listeners. Only the listener at the
* top of the stack will be notified that a find shortcut has been invoked.
*/
const FindShortcutManager = (() => {
/**
* Stack of listeners. Only the top listener will handle the shortcut.
* @type {!Array}
*/
const listeners = [];
/**
* Tracks if any modal context is open in settings. This assumes only one
* modal can be open at a time. The modals that are being tracked include
* cr-dialog and cr-drawer.
* @type {boolean}
*/
let modalContextOpen = false;
const shortcut =
new cr.ui.KeyboardShortcutList(cr.isMac ? 'meta|f' : 'ctrl|f');
window.addEventListener('keydown', e => {
if (e.defaultPrevented || listeners.length == 0 ||
!shortcut.matchesEvent(e)) {
return;
}
const focusIndex =
listeners.findIndex(listener => listener.searchInputHasFocus());
// If no listener has focus or the first (outer-most) listener has focus,
// try the last (inner-most) listener.
// If a listener has a search input with focus, the next listener that
// should be called is the right before it in |listeners| such that the
// goes from inner-most to outer-most.
const index = focusIndex <= 0 ? listeners.length - 1 : focusIndex - 1;
if (listeners[index].handleFindShortcut(modalContextOpen)) {
e.preventDefault();
}
});
window.addEventListener('cr-dialog-open', () => {
modalContextOpen = true;
});
window.addEventListener('cr-drawer-opened', () => {
modalContextOpen = true;
});
window.addEventListener('close', e => {
if (['CR-DIALOG', 'CR-DRAWER'].includes(e.composedPath()[0].nodeName)) {
modalContextOpen = false;
}
});
return Object.freeze({listeners: listeners});
})();
/**
* Used to determine how to handle find shortcut invocations.
* @polymerBehavior
*/
const FindShortcutBehavior = {
/**
* @type {boolean}
* @protected
*/
findShortcutListenOnAttach: true,
attached: function() {
if (this.findShortcutListenOnAttach) {
this.becomeActiveFindShortcutListener();
}
},
detached: function() {
if (this.findShortcutListenOnAttach) {
this.removeSelfAsFindShortcutListener();
}
},
becomeActiveFindShortcutListener: function() {
const listeners = FindShortcutManager.listeners;
assert(!listeners.includes(this), 'Already listening for find shortcuts.');
listeners.push(this);
},
/**
* If handled, return true.
* @param {boolean} modalContextOpen
* @return {boolean}
*/
handleFindShortcut: function(modalContextOpen) {
assertNotReached();
},
removeSelfAsFindShortcutListener: function() {
const listeners = FindShortcutManager.listeners;
const index = listeners.indexOf(this);
assert(listeners.includes(this), 'Find shortcut listener not found.');
listeners.splice(index, 1);
},
/** @return {boolean} */
searchInputHasFocus: function() {
assertNotReached();
},
};