| // 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 AutomationEvent = chrome.automation.AutomationEvent; |
| type AutomationNode = chrome.automation.AutomationNode; |
| import EventType = chrome.automation.EventType; |
| |
| interface EventHandlerOptions { |
| /** |
| * Whether to ignore events where the target is not the provided node. |
| */ |
| exactMatch?: boolean; |
| |
| /** |
| * True if the event should be processed before it has reached the target |
| * node, false if it should be processed after. |
| */ |
| capture?: boolean; |
| |
| /** |
| * True if the event listeners should automatically be removed when the |
| * callback is called once. |
| */ |
| listenOnce?: boolean; |
| |
| /** |
| * A predicate for what events will be processed. |
| */ |
| predicate?: (event: AutomationEvent) => boolean; |
| } |
| |
| /** |
| * This class wraps AutomationNode event listeners, adding some convenience |
| * functions. |
| */ |
| export class EventHandler { |
| private nodes_: AutomationNode[]; |
| private types_: EventType[]; |
| private callback_: ((event: AutomationEvent) => void)|null; |
| private capture_: boolean; |
| private exactMatch_: boolean; |
| private listenOnce_: boolean; |
| private listening_ = false; |
| private predicate_: (event: AutomationEvent) => boolean; |
| private handler_: (event: AutomationEvent) => void; |
| |
| constructor( |
| nodes: AutomationNode|AutomationNode[], types: EventType|EventType[], |
| callback: (event: AutomationEvent) => void, |
| options: EventHandlerOptions = {}) { |
| this.nodes_ = nodes instanceof Array ? nodes : [nodes]; |
| this.types_ = types instanceof Array ? types : [types]; |
| this.callback_ = callback; |
| this.capture_ = options.capture || false; |
| this.exactMatch_ = options.exactMatch || false; |
| this.listenOnce_ = options.listenOnce || false; |
| |
| /** |
| * Default is a function that always returns true. |
| */ |
| this.predicate_ = options.predicate || (_e => true); |
| |
| this.handler_ = event => this.handleEvent_(event); |
| } |
| |
| /** Starts listening to events. */ |
| start(): void { |
| if (this.listening_) { |
| return; |
| } |
| |
| for (const node of this.nodes_) { |
| for (const type of this.types_) { |
| node.addEventListener(type, this.handler_, this.capture_); |
| } |
| } |
| this.listening_ = true; |
| } |
| |
| /** Stops listening or handling future events. */ |
| stop(): void { |
| for (const node of this.nodes_) { |
| for (const type of this.types_) { |
| node.removeEventListener(type, this.handler_, this.capture_); |
| } |
| } |
| this.listening_ = false; |
| } |
| |
| /** |
| * @return Whether this EventHandler is currently listening for events. |
| */ |
| listening(): boolean { |
| return this.listening_; |
| } |
| |
| setCallback(callback: ((event: AutomationEvent) => void)|null): void { |
| this.callback_ = callback; |
| } |
| |
| /** |
| * Changes what nodes are being listened to. Removes listeners from existing |
| * nodes before adding listeners on new nodes. |
| */ |
| setNodes(nodes: AutomationNode|AutomationNode[]): void { |
| const wasListening = this.listening_; |
| // TODO(b/318557827): Shouldn't this be: if (wasListening) this.stop()? |
| this.stop(); |
| this.nodes_ = nodes instanceof Array ? nodes : [nodes]; |
| if (wasListening) { |
| this.start(); |
| } |
| } |
| |
| /** |
| * Adds another node to the set of nodes being listened to. |
| */ |
| addNode(node: AutomationNode): void { |
| this.nodes_.push(node); |
| |
| if (this.listening_) { |
| for (const type of this.types_) { |
| node.addEventListener(type, this.handler_, this.capture_); |
| } |
| } |
| } |
| |
| /** |
| * Removes a specific node from the set of nodes being listened to. |
| */ |
| removeNode(node: AutomationNode): void { |
| this.nodes_ = this.nodes_.filter(n => n !== node); |
| |
| if (this.listening_) { |
| for (const type of this.types_) { |
| node.removeEventListener(type, this.handler_, this.capture_); |
| } |
| } |
| } |
| |
| private handleEvent_(event: AutomationEvent): void { |
| if (this.exactMatch_ && !this.nodes_.includes(event.target)) { |
| return; |
| } |
| |
| if (!this.predicate_(event)) { |
| return; |
| } |
| |
| if (this.listenOnce_) { |
| this.stop(); |
| } |
| |
| if (this.callback_) { |
| this.callback_(event); |
| } |
| } |
| } |