blob: 1c34e48e5dc9bd8813a35331b72c887cd941bb7e [file] [log] [blame]
// Copyright 2022 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
const AutomationNode = chrome.automation.AutomationNode;
const AutomationEvent = chrome.automation.AutomationEvent;
const EventType = chrome.automation.EventType;
import {AutomationPredicate} from '../../common/automation_predicate.js';
import {AsyncUtil} from '../../common/async_util.js';
import {EventHandler} from '../../common/event_handler.js';
export class FocusHandler {
constructor() {
/** @private {boolean} */
this.active_ = false;
/**
* The currently focused editable node.
* @private {?AutomationNode}
*/
this.editableNode_ = null;
/** @private {?number} */
this.deactivateTimeoutId_ = null;
/** @private {?EventHandler} */
this.eventHandler_ = null;
/** @private {?function(): void} */
this.onActiveChangedForTesting_ = null;
/** @private {?function(): void} */
this.onEditableNodeChangedForTesting_ = null;
/** @private {?function(): void} */
this.onFocusChangedForTesting_ = null;
}
/**
* Starts listening to focus events and sets a timeout to deactivate after
* a certain amount of inactivity.
* @return {!Promise}
*/
async refresh() {
if (this.deactivateTimeoutId_ !== null) {
clearTimeout(this.deactivateTimeoutId_);
}
this.deactivateTimeoutId_ = setTimeout(
() => this.deactivate_(), FocusHandler.DEACTIVATE_TIMEOUT_MS_);
await this.activate_();
}
/**
* Gets the current focus and starts listening for focus events.
* @private
* @return {!Promise}
*/
async activate_() {
if (this.active_) {
return;
}
const desktop = await AsyncUtil.getDesktop();
const focus = await AsyncUtil.getFocus();
if (focus && AutomationPredicate.editText(focus)) {
this.setEditableNode_(focus);
}
if (!this.eventHandler_) {
this.eventHandler_ = new EventHandler(
[], EventType.FOCUS, event => this.onFocusChanged_(event));
}
this.eventHandler_.setNodes(desktop);
this.eventHandler_.start();
this.setActive_(true);
}
/** @private */
deactivate_() {
this.eventHandler_.stop();
this.eventHandler_ = null;
this.setActive_(false);
this.setEditableNode_(null);
}
/**
* Saves the focused node if it's an editable.
* @param {!AutomationEvent} event
* @private
*/
onFocusChanged_(event) {
const node = event.target;
if (!node || !AutomationPredicate.editText(node)) {
this.setEditableNode_(null);
return;
}
this.setEditableNode_(node);
if (this.onFocusChangedForTesting_) {
this.onFocusChangedForTesting_();
}
}
/** @return {?AutomationNode} */
getEditableNode() {
return this.editableNode_;
}
/**
* @param {boolean} value
* @private
*/
setActive_(value) {
this.active_ = value;
if (this.onActiveChangedForTesting_) {
this.onActiveChangedForTesting_();
}
}
/**
* @param {AutomationNode} node
* @private
*/
setEditableNode_(node) {
this.editableNode_ = node;
if (this.onEditableNodeChangedForTesting_) {
this.onEditableNodeChangedForTesting_();
}
}
/** @return {boolean} */
isReadyForTesting() {
return this.active_ && this.editableNode_ !== null;
}
}
/**
* @const {number}
* @private
*/
FocusHandler.DEACTIVATE_TIMEOUT_MS_ = 45 * 1000;