chromium/chrome/browser/resources/chromeos/accessibility/switch_access/nodes/back_button_node.ts

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

import {EventHandler} from '/common/event_handler.js';
import {RepeatedEventHandler} from '/common/repeated_event_handler.js';
import {TestImportManager} from '/common/testing/test_import_manager.js';

import {ActionManager} from '../action_manager.js';
import {FocusRingManager} from '../focus_ring_manager.js';
import {MenuManager} from '../menu_manager.js';
import {Navigator} from '../navigator.js';
import {SwitchAccess} from '../switch_access.js';
import {ActionResponse} from '../switch_access_constants.js';

import {SAChildNode, SARootNode} from './switch_access_node.js';

type AutomationNode = chrome.automation.AutomationNode;
const EventType = chrome.automation.EventType;
import MenuAction = chrome.accessibilityPrivate.SwitchAccessMenuAction;
type Rect = chrome.automation.Rect;
import RoleType = chrome.automation.RoleType;

/**
 * This class handles the behavior of the back button.
 */
export class BackButtonNode extends SAChildNode {
  /** The group that the back button is shown for. */
  private group_: SARootNode;
  private locationChangedHandler_?: RepeatedEventHandler;

  private static automationNode_: AutomationNode;
  private static clickHandler_: EventHandler;
  static locationForTesting?: Rect;

  constructor(group: SARootNode) {
    super();
    this.group_ = group;
  }

  // ================= Getters and setters =================

  override get actions(): MenuAction[] {
    return [MenuAction.SELECT];
  }

  override get automationNode(): AutomationNode {
    return BackButtonNode.automationNode_;
  }

  override get group(): SARootNode {
    return this.group_;
  }

  override get location(): Rect | undefined {
    if (BackButtonNode.locationForTesting) {
      return BackButtonNode.locationForTesting;
    }
    if (this.automationNode) {
      return this.automationNode.location;
    }
    return undefined;
  }

  override get role(): RoleType {
    return RoleType.BUTTON;
  }

  // ================= General methods =================

  override asRootNode(): SARootNode | undefined {
    return undefined;
  }

  override equals(other: SAChildNode): boolean {
    return other instanceof BackButtonNode;
  }

  override isEquivalentTo(
      node: SAChildNode | SARootNode | AutomationNode | null | undefined)
      : boolean {
    return node instanceof BackButtonNode || this.automationNode === node;
  }

  override isGroup(): boolean {
    return false;
  }

  override isValidAndVisible(): boolean {
    return this.group_.isValidGroup();
  }

  override onFocus(): void {
    super.onFocus();
    chrome.accessibilityPrivate.updateSwitchAccessBubble(
        chrome.accessibilityPrivate.SwitchAccessBubble.BACK_BUTTON,
        true /* show */, this.group_.location);
    BackButtonNode.findAutomationNode_();

    this.locationChangedHandler_ = new RepeatedEventHandler(
        this.group_.automationNode,
        chrome.automation.EventType.LOCATION_CHANGED,
        () => FocusRingManager.setFocusedNode(this),
        {exactMatch: true, allAncestors: true});
  }

  override onUnfocus(): void {
    super.onUnfocus();
    chrome.accessibilityPrivate.updateSwitchAccessBubble(
        chrome.accessibilityPrivate.SwitchAccessBubble.BACK_BUTTON,
        false /* show */);

    if (this.locationChangedHandler_) {
      this.locationChangedHandler_.stop();
    }
  }

  override performAction(action: MenuAction): ActionResponse {
    if (action === MenuAction.SELECT && this.automationNode) {
      BackButtonNode.onClick_();
      return ActionResponse.CLOSE_MENU;
    }
    return ActionResponse.NO_ACTION_TAKEN;
  }

  override ignoreWhenComputingUnionOfBoundingBoxes(): boolean {
    return true;
  }

  // ================= Debug methods =================

  override debugString(
      wholeTree: boolean, prefix = '', currentNode = null): string {
    if (!this.automationNode) {
      return 'BackButtonNode';
    }
    return super.debugString(wholeTree, prefix, currentNode);
  }

  // ================= Static methods =================

  /** Looks for the back button automation node. */
  private static findAutomationNode_(): void {
    if (BackButtonNode.automationNode_ && BackButtonNode.automationNode_.role) {
      return;
    }
    SwitchAccess.findNodeMatching(
        {
          role: RoleType.BUTTON,
          attributes: {className: 'SwitchAccessBackButtonView'},
        },
        BackButtonNode.saveAutomationNode_);
  }

  /**
   * This function defines the behavior that should be taken when the back
   * button is pressed.
   */
  private static onClick_(): void {
    if (MenuManager.isMenuOpen()) {
      ActionManager.exitCurrentMenu();
    } else {
      Navigator.byItem.exitGroupUnconditionally();
    }
  }

  /** Saves the back button automation node. */
  private static saveAutomationNode_(automationNode: AutomationNode): void {
    BackButtonNode.automationNode_ = automationNode;

    if (BackButtonNode.clickHandler_) {
      BackButtonNode.clickHandler_.setNodes(automationNode);
    } else {
      BackButtonNode.clickHandler_ = new EventHandler(
          automationNode, EventType.CLICKED,
          BackButtonNode.onClick_);
    }
  }
}

TestImportManager.exportForTesting(BackButtonNode);