accessible/jsat/Presentation.jsm
author Kris Maglione <maglione.k@gmail.com>
Tue, 24 Apr 2018 20:18:09 -0700
changeset 415693 95b7f4ae23fdaf6d806ffac8a402e5f043dbd1c5
parent 415519 b92a5613a631a85996ce7d60e8a455b25355405e
child 415695 e316258ed9aeeed1257b138dbaa1f03899828b84
permissions -rw-r--r--
Bug 1456686: Part 1 - Fix unused and shadowed explicit imports. r=standard8 These issues were previously ignored due to the nature of our global import rules. They need to be fixed before that rule can be updated. MozReview-Commit-ID: DCChktTc5TW

/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
 * You can obtain one at http://mozilla.org/MPL/2.0/. */

/* exported Presentation */

"use strict";

ChromeUtils.import("resource://gre/modules/accessibility/Utils.jsm");
ChromeUtils.defineModuleGetter(this, "Logger", // jshint ignore:line
  "resource://gre/modules/accessibility/Utils.jsm");
ChromeUtils.defineModuleGetter(this, "PivotContext", // jshint ignore:line
  "resource://gre/modules/accessibility/Utils.jsm");
ChromeUtils.defineModuleGetter(this, "UtteranceGenerator", // jshint ignore:line
  "resource://gre/modules/accessibility/OutputGenerator.jsm");
ChromeUtils.defineModuleGetter(this, "States", // jshint ignore:line
  "resource://gre/modules/accessibility/Constants.jsm");
ChromeUtils.defineModuleGetter(this, "AndroidEvents", // jshint ignore:line
  "resource://gre/modules/accessibility/Constants.jsm");

var EXPORTED_SYMBOLS = ["Presentation"]; // jshint ignore:line

class AndroidPresentor {
  constructor() {
    this.type = "Android";
    this.displayedAccessibles = new WeakMap();
  }

  /**
   * The virtual cursor's position changed.
   * @param {PivotContext} aContext the context object for the new pivot
   *   position.
   * @param {int} aReason the reason for the pivot change.
   *   See nsIAccessiblePivot.
   * @param {bool} aIsFromUserInput the pivot change was invoked by the user
   */
  pivotChanged(aPosition, aOldPosition, aReason, aStartOffset, aEndOffset, aIsUserInput) {
    let context = new PivotContext(
      aPosition, aOldPosition, aStartOffset, aEndOffset);
    if (!context.accessible) {
      return null;
    }

    let androidEvents = [];

    let isExploreByTouch = (aReason == Ci.nsIAccessiblePivot.REASON_POINT &&
                            Utils.AndroidSdkVersion >= 14);
    let focusEventType = (Utils.AndroidSdkVersion >= 16) ?
      AndroidEvents.ANDROID_VIEW_ACCESSIBILITY_FOCUSED :
      AndroidEvents.ANDROID_VIEW_FOCUSED;

    if (isExploreByTouch) {
      // This isn't really used by TalkBack so this is a half-hearted attempt
      // for now.
      androidEvents.push({eventType: AndroidEvents.ANDROID_VIEW_HOVER_EXIT, text: []});
    }

    if (aReason === Ci.nsIAccessiblePivot.REASON_TEXT) {
      if (Utils.AndroidSdkVersion >= 16) {
        let adjustedText = context.textAndAdjustedOffsets;

        androidEvents.push({
          eventType: AndroidEvents.ANDROID_VIEW_TEXT_TRAVERSED_AT_MOVEMENT_GRANULARITY,
          text: [adjustedText.text],
          fromIndex: adjustedText.startOffset,
          toIndex: adjustedText.endOffset
        });
      }
    } else {
      let state = Utils.getState(context.accessible);
      androidEvents.push({eventType: (isExploreByTouch) ?
                           AndroidEvents.ANDROID_VIEW_HOVER_ENTER : focusEventType,
                         text: Utils.localize(UtteranceGenerator.genForContext(
                           context)),
                         bounds: context.bounds,
                         clickable: context.accessible.actionCount > 0,
                         checkable: state.contains(States.CHECKABLE),
                         checked: state.contains(States.CHECKED)});
    }

    try {
      context.accessibleForBounds.scrollTo(
        Ci.nsIAccessibleScrollType.SCROLL_TYPE_ANYWHERE);
    } catch (e) {}

    if (context.accessible) {
      this.displayedAccessibles.set(context.accessible.document.window, context);
    }

    return androidEvents;
  }

  /**
   * An object's action has been invoked.
   * @param {nsIAccessible} aObject the object that has been invoked.
   * @param {string} aActionName the name of the action.
   */
  actionInvoked(aObject, aActionName) {
    let state = Utils.getState(aObject);

    // Checkable objects use TalkBack's text derived from the event state,
    // so we don't populate the text here.
    let text = null;
    if (!state.contains(States.CHECKABLE)) {
      text = Utils.localize(UtteranceGenerator.genForAction(aObject,
        aActionName));
    }

    return [{
      eventType: AndroidEvents.ANDROID_VIEW_CLICKED,
      text,
      checked: state.contains(States.CHECKED)
    }];
  }

  /**
   * Text has changed, either by the user or by the system. TODO.
   */
  textChanged(aAccessible, aIsInserted, aStart, aLength, aText, aModifiedText) {
    let androidEvent = {
      eventType: AndroidEvents.ANDROID_VIEW_TEXT_CHANGED,
      text: [aText],
      fromIndex: aStart,
      removedCount: 0,
      addedCount: 0
    };

    if (aIsInserted) {
      androidEvent.addedCount = aLength;
      androidEvent.beforeText =
        aText.substring(0, aStart) + aText.substring(aStart + aLength);
    } else {
      androidEvent.removedCount = aLength;
      androidEvent.beforeText =
        aText.substring(0, aStart) + aModifiedText + aText.substring(aStart);
    }

    return [androidEvent];
  }

  /**
   * Text selection has changed. TODO.
   */
  textSelectionChanged(aText, aStart, aEnd, aOldStart, aOldEnd, aIsFromUserInput) {
    let androidEvents = [];

    if (Utils.AndroidSdkVersion >= 14 && !aIsFromUserInput) {
      androidEvents.push({
        eventType: AndroidEvents.ANDROID_VIEW_TEXT_SELECTION_CHANGED,
        text: [aText],
        fromIndex: aStart,
        toIndex: aEnd,
        itemCount: aText.length
      });
    }

    if (Utils.AndroidSdkVersion >= 16 && aIsFromUserInput) {
      let [from, to] = aOldStart < aStart ?
        [aOldStart, aStart] : [aStart, aOldStart];
      androidEvents.push({
        eventType: AndroidEvents.ANDROID_VIEW_TEXT_TRAVERSED_AT_MOVEMENT_GRANULARITY,
        text: [aText],
        fromIndex: from,
        toIndex: to
      });
    }

    return androidEvents;
  }

  /**
   * Selection has changed.
   * XXX: Implement android event?
   * @param {nsIAccessible} aObject the object that has been selected.
   */
  selectionChanged(aObject) {
    return "todo.selection-changed";
  }

  /**
   * Name has changed.
   * XXX: Implement android event?
   * @param {nsIAccessible} aAccessible the object whose value has changed.
   */
  nameChanged(aAccessible) {
    return "todo.name-changed";
  }

  /**
   * Value has changed.
   * XXX: Implement android event?
   * @param {nsIAccessible} aAccessible the object whose value has changed.
   */
  valueChanged(aAccessible) {
    return "todo.value-changed";
  }

  /**
   * The tab, or the tab's document state has changed.
   * @param {nsIAccessible} aDocObj the tab document accessible that has had its
   *    state changed, or null if the tab has no associated document yet.
   * @param {string} aPageState the state name for the tab, valid states are:
   *    'newtab', 'loading', 'newdoc', 'loaded', 'stopped', and 'reload'.
   */
  tabStateChanged(aDocObj, aPageState) {
    return this.announce(
      UtteranceGenerator.genForTabStateChange(aDocObj, aPageState));
  }

  /**
   * The current tab has changed.
   * @param {PivotContext} aDocContext context object for tab's
   *   document.
   * @param {PivotContext} aVCContext context object for tab's current
   *   virtual cursor position.
   */
  tabSelected(aDocContext, aVCContext) {
    return this.pivotChanged(aVCContext, Ci.nsIAccessiblePivot.REASON_NONE);
  }

  /**
   * The viewport has changed, either a scroll, pan, zoom, or
   *    landscape/portrait toggle.
   * @param {Window} aWindow window of viewport that changed.
   */
  viewportChanged(aWindow) {
    let currentContext = this.displayedAccessibles.get(aWindow);

    if (Utils.AndroidSdkVersion < 14) {
      return null;
    }

    let events = [{
      eventType: AndroidEvents.ANDROID_VIEW_SCROLLED,
      text: [],
      scrollX: aWindow.scrollX,
      scrollY: aWindow.scrollY,
      maxScrollX: aWindow.scrollMaxX,
      maxScrollY: aWindow.scrollMaxY
    }];

    if (Utils.AndroidSdkVersion >= 16 && currentContext) {
      let currentAcc = currentContext.accessibleForBounds;
      if (Utils.isAliveAndVisible(currentAcc)) {
        events.push({
          eventType: AndroidEvents.ANDROID_VIEW_ACCESSIBILITY_FOCUSED,
          bounds: Utils.getBounds(currentAcc)
        });
      }
    }

    return events;
  }

  /**
   * We have entered or left text editing mode.
   */
  editingModeChanged(aIsEditing) {
    return this.announce(UtteranceGenerator.genForEditingMode(aIsEditing));
  }

  /**
   * Announce something. Typically an app state change.
   */
  announce(aAnnouncement) {
    let localizedAnnouncement = Utils.localize(aAnnouncement).join(" ");
    return [{
      eventType: (Utils.AndroidSdkVersion >= 16) ?
        AndroidEvents.ANDROID_ANNOUNCEMENT :
        AndroidEvents.ANDROID_VIEW_TEXT_CHANGED,
      text: [localizedAnnouncement],
      addedCount: localizedAnnouncement.length,
      removedCount: 0,
      fromIndex: 0
    }];
  }


  /**
   * User tried to move cursor forward or backward with no success.
   * @param {string} aMoveMethod move method that was used (eg. 'moveNext').
   */
  noMove(aMoveMethod) {
    return [{
      eventType: AndroidEvents.ANDROID_VIEW_ACCESSIBILITY_FOCUSED,
      exitView: aMoveMethod,
      text: [""]
    }];
  }

  /**
   * Announce a live region.
   * @param  {PivotContext} aContext context object for an accessible.
   * @param  {boolean} aIsPolite A politeness level for a live region.
   * @param  {boolean} aIsHide An indicator of hide/remove event.
   * @param  {string} aModifiedText Optional modified text.
   */
  liveRegion(aAccessible, aIsPolite, aIsHide, aModifiedText) {
    let context = !aModifiedText ?
      new PivotContext(aAccessible, null, -1, -1, true, !!aIsHide) : null;
    return this.announce(
      UtteranceGenerator.genForLiveRegion(context, aIsHide, aModifiedText));
  }
}

const Presentation = new AndroidPresentor();