chromium/chrome/browser/resources/chromeos/accessibility/common/cursors/recovery_strategy.ts

// Copyright 2017 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

/**
 * @fileoverview Defines various strategies for recovering automation nodes.
 */

type AutomationNode = chrome.automation.AutomationNode;
import RoleType = chrome.automation.RoleType;

export abstract class RecoveryStrategy {
  private node_: AutomationNode;

  constructor(node: AutomationNode) {
    this.node_ = node;
  }

  get node(): AutomationNode {
    if (this.requiresRecovery()) {
      this.node_ = this.recover() || this.node_;
    }

    return this.node_;
  }

  requiresRecovery(): boolean {
    return !this.node_ || !this.node_.role;
  }

  protected abstract recover(): AutomationNode;

  equalsWithoutRecovery(rhs: RecoveryStrategy): boolean {
    return this.node_ === rhs.node_;
  }
}


/**
 * A recovery strategy that uses the node's ancestors.
 */
export class AncestryRecoveryStrategy extends RecoveryStrategy {
  protected ancestry_: AutomationNode[] = [];

  constructor(node: AutomationNode) {
    super(node);

    let nodeWalker: AutomationNode|undefined = node;
    while (nodeWalker) {
      this.ancestry_.push(nodeWalker);
      nodeWalker = nodeWalker.parent;
      if (nodeWalker && nodeWalker.role === RoleType.WINDOW) {
        break;
      }
    }
  }

  override recover(): AutomationNode {
    return this.ancestry_[this.getFirstValidNodeIndex_()];
  }

  protected getFirstValidNodeIndex_(): number {
    for (let i = 0; i < this.ancestry_.length; i++) {
      const firstValidNode = this.ancestry_[i];
      if (firstValidNode != null && firstValidNode.role !== undefined &&
          firstValidNode.root !== undefined) {
        return i;
      }
    }
    return 0;
  }
}


/**
 * A recovery strategy that uses the node's tree path.
 */
export class TreePathRecoveryStrategy extends AncestryRecoveryStrategy {
  private recoveryChildIndex_: number[] = [];

  constructor(node: AutomationNode) {
    super(node);

    let nodeWalker: AutomationNode|undefined = node;
    while (nodeWalker) {
      // TODO(b/314203187): Not null asserted, check these to make sure this
      // is correct.
      this.recoveryChildIndex_.push(nodeWalker.indexInParent!);
      nodeWalker = nodeWalker.parent;
      if (nodeWalker && nodeWalker.role === RoleType.WINDOW) {
        break;
      }
    }
  }

  override recover(): AutomationNode {
    const index = this.getFirstValidNodeIndex_();
    if (index === 0) {
      return this.ancestry_[index];
    }

    // Otherwise, attempt to recover.
    let node = this.ancestry_[index];
    for (let j = index - 1; j >= 0; j--) {
      const childIndex = this.recoveryChildIndex_[j];
      const children = node.children;
      if (!children[childIndex]) {
        return node;
      }
      node = children[childIndex];
    }
    return node;
  }
}