chromium/chrome/browser/resources/chromeos/accessibility/chromevox/background/find_handler.js

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

/**
 * @fileoverview Handles output for Chrome's built-in find.
 */
import {CursorRange} from '/common/cursors/range.js';

import {ChromeVoxRange} from './chromevox_range.js';
import {Output} from './output/output.js';
import {OutputCustomEvent} from './output/output_types.js';

const MarkerType = chrome.automation.MarkerType;
const TreeChangeObserverFilter = chrome.automation.TreeChangeObserverFilter;

/**
 * Handles navigation among the results when using the built-in find behavior
 * (i.e. Ctrl-F).
 */
export class FindHandler {
  /** @private */
  constructor() {
    /**
     * The last time a find marker was received.
     * @type {!Date}
     */
    this.lastFindMarkerReceived = new Date();

    /** @private {function(chrome.automation.TreeChange)} */
    this.treeChangeObserver_ = change => this.onTextMatch_(change);

    chrome.automation.addTreeChangeObserver(
        TreeChangeObserverFilter.TEXT_MARKER_CHANGES, this.treeChangeObserver_);
  }

  /** Initializes this module. */
  static init() {
    if (FindHandler.instance) {
      throw 'Error: Trying to create two instances of singleton FindHandler';
    }
    FindHandler.instance = new FindHandler();
  }

  /**
   * Uninitializes this module.
   * @private
   */
  uninit_() {
    chrome.automation.removeTreeChangeObserver(this.treeChangeObserver_);
  }

  /**
   * @param {Object} evt
   * @private
   */
  onTextMatch_(evt) {
    if (!evt.target.markers.some(
            marker => marker.flags[MarkerType.TEXT_MATCH])) {
      return;
    }

    // When a user types, a flurry of events gets sent from the tree updates
    // being applied. Drop all but the first. Note that when hitting enter,
    // there's only one marker changed ever sent.
    const delta = new Date() - this.lastFindMarkerReceived;
    this.lastFindMarkerReceived = new Date();
    if (delta < DROP_MATCH_WITHIN_TIME_MS) {
      return;
    }

    const range = CursorRange.fromNode(evt.target);
    ChromeVoxRange.set(range);
    new Output()
        .withRichSpeechAndBraille(range, null, OutputCustomEvent.NAVIGATE)
        .go();
  }
}

/** @type {FindHandler} */
FindHandler.instance;

// Local to module.

/**
 * The amount of time where a subsequent find text marker is dropped from
 * output.
 * @const {number}
 */
const DROP_MATCH_WITHIN_TIME_MS = 50;