// Copyright 2020 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
type AutomationNode = chrome.automation.AutomationNode;
type AutomationEvent = chrome.automation.AutomationEvent;
import EventType = chrome.automation.EventType;
interface RepeatedEventHandlerOptions {
/**
* True if events should only be handled if the provided node is the target.
*/
exactMatch?: boolean;
/**
* True if events for children of |node| should be handled before they reach
* the target node; false to be handled after the target node.
*/
capture?: boolean;
/**
* True if a listener should be added to all ancestors of the provided nodes.
*/
allAncestors?: boolean;
}
/**
* This class assists with processing repeated events in nontrivial ways by
* allowing only the most recent event to be processed.
*/
export class RepeatedEventHandler {
private eventStack_: AutomationEvent[] = [];
private nodes_: AutomationNode[];
private type_: EventType;
private callback_: (event: AutomationEvent) => void;
private exactMatch_: boolean;
private capture_: boolean;
private listening_ = false;
private handler_: (event: AutomationEvent) => void;
constructor(
nodes: AutomationNode|AutomationNode[], type: EventType,
callback: (event: AutomationEvent) => void,
options: RepeatedEventHandlerOptions = {}) {
this.nodes_ = nodes instanceof Array ? nodes : [nodes];
if (options.allAncestors) {
nodes = this.nodes_; // Make sure nodes is an array.
this.nodes_ = [];
for (let node of nodes) {
while (node) {
this.nodes_.push(node);
// TODO(b/314203187): Not null asserted, check these to make sure they
// are correct.
node = node.parent!;
}
}
}
this.type_ = type;
this.callback_ = callback;
this.exactMatch_ = options.exactMatch || false;
this.capture_ = options.capture || false;
this.handler_ = event => this.onEvent_(event);
this.start();
}
/** Starts listening or handling events. */
start(): void {
if (this.listening_) {
return;
}
this.listening_ = true;
for (const node of this.nodes_) {
node.addEventListener(this.type_, this.handler_, this.capture_);
}
}
/** Stops listening or handling future events. */
stop(): void {
if (!this.listening_) {
return;
}
this.listening_ = false;
for (const node of this.nodes_) {
node.removeEventListener(this.type_, this.handler_, this.capture_);
}
}
private onEvent_(event: AutomationEvent): void {
this.eventStack_.push(event);
setTimeout(() => this.handleEvent_(), 0);
}
private handleEvent_(): void {
if (!this.listening_ || this.eventStack_.length === 0) {
return;
}
// TODO(b/314203187): Not null asserted, check these to make sure they are
// correct.
const event = this.eventStack_.pop()!;
if (this.exactMatch_ && !this.nodes_.includes(event!.target)) {
return;
}
this.eventStack_ = [];
this.callback_(event);
}
}