chromium/chrome/browser/resources/chromeos/accessibility/chromevox/background/panel/i_search.ts

// 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 The logic behind incremental search.
 */
import {AutomationPredicate} from '/common/automation_predicate.js';
import {AutomationUtil} from '/common/automation_util.js';
import {constants} from '/common/constants.js';
import {Cursor} from '/common/cursors/cursor.js';
import {TestImportManager} from '/common/testing/test_import_manager.js';

import {ISearchHandler} from './i_search_handler.js';

type AutomationNode = chrome.automation.AutomationNode;
import Dir = constants.Dir;

/** Controls an incremental search. */
export class ISearch {
  private callbackId_: number = 0;
  private handler_?: ISearchHandler | null = null;

  cursor: Cursor;

  constructor(cursor: Cursor) {
    if (!cursor.node) {
      throw 'Incremental search started from invalid range.';
    }

    const leaf: AutomationNode = AutomationUtil.findNodePre(
        cursor.node, Dir.FORWARD, AutomationPredicate.leaf) || cursor.node;

    this.cursor = Cursor.fromNode(leaf);
  }

  set handler(handler: ISearchHandler | null) {
    this.handler_ = handler;
  }

  /** Performs a search. */
  search(searchStr: string, dir: Dir, nextObject?: boolean): void {
    clearTimeout(this.callbackId_);
    const step = (): void => {
      searchStr = searchStr.toLocaleLowerCase();
      const node = this.cursor.node;
      let result: AutomationNode | null = node;

      if (nextObject) {
        // We want to start/continue the search at the next object.
        result =
            AutomationUtil.findNextNode(node, dir, AutomationPredicate.object);
      }

      // TODO(b/314203187): Not null asserted, check that this is correct.
      do {
        // Ask native to search the underlying data for a performance boost.
        result = result!.getNextTextMatch(searchStr, dir === Dir.BACKWARD);
      } while (result && !AutomationPredicate.object(result));

      if (result) {
        this.cursor = Cursor.fromNode(result);
        const start = result.name!.toLocaleLowerCase().indexOf(searchStr);
        const end = start + searchStr.length;
        this.handler_!.onSearchResultChanged(result, start, end);
      } else {
        this.handler_!.onSearchReachedBoundary(this.cursor.node);
      }
    };

    this.callbackId_ = setTimeout(() => step(), 0);
  }

  clear(): void {
    clearTimeout(this.callbackId_);
  }
}

TestImportManager.exportForTesting(ISearch);