| Index: chrome/browser/resources/access_chromevox/common/editable_text.js
|
| ===================================================================
|
| --- chrome/browser/resources/access_chromevox/common/editable_text.js (revision 0)
|
| +++ chrome/browser/resources/access_chromevox/common/editable_text.js (revision 0)
|
| @@ -0,0 +1,789 @@
|
| +// Copyright (c) 2011 The Chromium Authors. All rights reserved.
|
| +// Use of this source code is governed by a BSD-style license that can be
|
| +// found in the LICENSE file.
|
| +
|
| +goog.provide('cvox.ChromeVoxEditableHTMLInput');
|
| +goog.provide('cvox.ChromeVoxEditableTextArea');
|
| +goog.provide('cvox.ChromeVoxEditableTextBase');
|
| +
|
| +/**
|
| + * @fileoverview Gives the user spoken feedback as they type, select text,
|
| + * and move the cursor in editable text controls, including multiline
|
| + * controls.
|
| + *
|
| + * The majority of the code is in ChromeVoxEditableTextBase, a generalized
|
| + * class that takes the current state in the form of a text string, a
|
| + * cursor start location and a cursor end location, and calls a speak
|
| + * method with the resulting text to be spoken. If the control is multiline,
|
| + * information about line breaks (including automatic ones) is also needed.
|
| + *
|
| + * Two subclasses, ChromeVoxEditableHTMLInput and
|
| + * ChromeVoxEditableTextArea, take a HTML input (type=text) or HTML
|
| + * textarea node (respectively) in the constructor, and automatically
|
| + * handle retrieving the current state of the control, including
|
| + * computing line break information for a textarea using an offscreen
|
| + * shadow object. It is still the responsibility of the user of this
|
| + * class to trap key and focus events and call this class's update
|
| + * method.
|
| + */
|
| +
|
| +/**
|
| + * A class representing an abstracted editable text control.
|
| + * @param {string} value The string value of the editable text control.
|
| + * @param {number} start The 0-based start cursor/selection index.
|
| + * @param {number} end The 0-based end cursor/selection index.
|
| + * @param {Object} tts A TTS object implementing speak() and stop() methods.
|
| + * @constructor
|
| + */
|
| +cvox.ChromeVoxEditableTextBase = function(value, start, end, tts) {
|
| + this.value = value;
|
| + this.start = start;
|
| + this.end = end;
|
| + this.tts = tts;
|
| +};
|
| +
|
| +/**
|
| + * Whether or not the text field is multiline.
|
| + * @type {boolean}
|
| + */
|
| +cvox.ChromeVoxEditableTextBase.prototype.multiline = false;
|
| +
|
| +/**
|
| + * Whether or not moving the cursor from one character to another considers
|
| + * the cursor to be a block (true) or an i-beam (false).
|
| + *
|
| + * If the cursor is a block, then the value of the character to the right
|
| + * of the cursor index is always read when the cursor moves, no matter what
|
| + * the previous cursor location was - this is how PC screenreaders work.
|
| + *
|
| + * If the cursor is an i-beam, moving the cursor by one character reads the
|
| + * character that was crossed over, which may be the character to the left or
|
| + * right of the new cursor index depending on the direction.
|
| + *
|
| + * @type {boolean}
|
| + */
|
| +cvox.ChromeVoxEditableTextBase.prototype.cursorIsBlock = false;
|
| +
|
| +/**
|
| + * The maximum number of characters that are short enough to speak in response
|
| + * to an event. For example, if the user selects "Hello", we will speak
|
| + * "Hello, selected", but if the user selects 1000 characters, we will speak
|
| + * "text selected" instead.
|
| + * @type {number}
|
| + */
|
| +cvox.ChromeVoxEditableTextBase.prototype.maxShortPhraseLen = 60;
|
| +
|
| +/**
|
| + * Describe the current state of the text control.
|
| + */
|
| +cvox.ChromeVoxEditableTextBase.prototype.describe = function() {
|
| + this.speak(this.getDescription());
|
| +};
|
| +
|
| +/**
|
| + * Get a speakable text string describing the current state of the
|
| + * text control and its title and/or label.
|
| + * @return {string} The speakable description.
|
| + */
|
| +cvox.ChromeVoxEditableTextBase.prototype.getDescription = function() {
|
| + var speech = '';
|
| + if (this.multiline) {
|
| + speech += 'multiline editable text. ';
|
| + if (this.start == this.end) {
|
| + // It's a cursor: read the current line.
|
| + var line = this.getLine(this.getLineIndex(this.start));
|
| + if (line) {
|
| + speech += line;
|
| + } else {
|
| + speech += 'blank.';
|
| + }
|
| + }
|
| + } else {
|
| + if (this.value.length <= this.maxShortPhraseLen) {
|
| + speech += this.value + ', editable text.';
|
| + } else {
|
| + speech += 'editable text.';
|
| + }
|
| + }
|
| + return speech;
|
| +};
|
| +
|
| +/**
|
| + * Get the line number corresponding to a particular index.
|
| + * Default implementation that can be overridden by subclasses.
|
| + * @param {number} index The 0-based character index.
|
| + * @return {number} The 0-based line number corresponding to that character.
|
| + */
|
| +cvox.ChromeVoxEditableTextBase.prototype.getLineIndex = function(index) {
|
| + return 0;
|
| +};
|
| +
|
| +/**
|
| + * Get the start character index of a line.
|
| + * Default implementation that can be overridden by subclasses.
|
| + * @param {number} index The 0-based line index.
|
| + * @return {number} The 0-based index of the first character in this line.
|
| + */
|
| +cvox.ChromeVoxEditableTextBase.prototype.getLineStart = function(index) {
|
| + return 0;
|
| +};
|
| +
|
| +/**
|
| + * Get the end character index of a line.
|
| + * Default implementation that can be overridden by subclasses.
|
| + * @param {number} index The 0-based line index.
|
| + * @return {number} The 0-based index of the end of this line.
|
| + */
|
| +cvox.ChromeVoxEditableTextBase.prototype.getLineEnd = function(index) {
|
| + return this.value.length;
|
| +};
|
| +
|
| +/**
|
| + * Get the full text of the current line.
|
| + * @param {number} index The 0-based line index.
|
| + * @return {string} The text of the line.
|
| + */
|
| +cvox.ChromeVoxEditableTextBase.prototype.getLine = function(index) {
|
| + var lineStart = this.getLineStart(index);
|
| + var lineEnd = this.getLineEnd(index);
|
| + var line = this.value.substr(lineStart, lineEnd - lineStart);
|
| + return line.replace(/^\s+|\s+$/g, '');
|
| +};
|
| +
|
| +/**
|
| + * @param {string} ch The character to test.
|
| + * @return {boolean} True if a character is whitespace.
|
| + */
|
| +cvox.ChromeVoxEditableTextBase.prototype.isWhitespaceChar = function(ch) {
|
| + return ch == ' ' || ch == '\n' || ch == '\r' || ch == '\t';
|
| +};
|
| +
|
| +/**
|
| + * @param {string} ch The character to test.
|
| + * @return {boolean} True if a character breaks a word, used to determine
|
| + * if the previous word should be spoken.
|
| + */
|
| +cvox.ChromeVoxEditableTextBase.prototype.isWordBreakChar = function(ch) {
|
| + return ch == ' ' || ch == '\n' || ch == '\r' || ch == '\t' ||
|
| + ch == ',' || ch == '.' || ch == '/';
|
| +};
|
| +
|
| +
|
| +
|
| +/**
|
| + * Speak text, but if it's a single character, describe the character.
|
| + * TODO(dmazzoni) make this more general, for use outside editable text.
|
| + * @param {string} ch The character to speak.
|
| + * @return {string} ch The character to speak.
|
| + */
|
| +cvox.ChromeVoxEditableTextBase.prototype.describeChar = function(ch) {
|
| + if (ch.length != 1) {
|
| + return ch;
|
| + }
|
| +
|
| + switch (ch) {
|
| + case ' ': return 'space.';
|
| + case '`': return 'backtick.';
|
| + case '~': return 'tilde.';
|
| + case '!': return 'bang.';
|
| + case '@': return 'at.';
|
| + case '#': return 'pound.';
|
| + case '$': return 'dollar.';
|
| + case '%': return 'percent.';
|
| + case '^': return 'caret.';
|
| + case '&': return 'ampersand.';
|
| + case '*': return 'asterisk.';
|
| + case '(': return 'open paren.';
|
| + case ')': return 'close paren.';
|
| + case '-': return 'hyphen.';
|
| + case '_': return 'underscore.';
|
| + case '=': return 'equals.';
|
| + case '+': return 'plus.';
|
| + case '[': return 'left bracket.';
|
| + case ']': return 'right bracket.';
|
| + case '{': return 'left brace.';
|
| + case '}': return 'right brace.';
|
| + case '|': return 'pipe.';
|
| + case ';': return 'semicolon.';
|
| + case ':': return 'colon.';
|
| + case ',': return 'comma.';
|
| + case '.': return 'period.';
|
| + case '<': return 'less than.';
|
| + case '>': return 'greater than.';
|
| + case '/': return 'slash.';
|
| + case '?': return 'question mark.';
|
| + case '\t': return 'tab.';
|
| + case '\r': return 'return.';
|
| + case '\n': return 'return.';
|
| + case '\\': return 'backslash.';
|
| + default:
|
| + return ch.toUpperCase() + '.';
|
| + break;
|
| + }
|
| +};
|
| +
|
| +/**
|
| + * Speak text, but if it's a single character, describe the character.
|
| + * @param {string} str The string to speak.
|
| + */
|
| +cvox.ChromeVoxEditableTextBase.prototype.speak = function(str) {
|
| + if (str.length == 1) {
|
| + this.tts.speak(this.describeChar(str), 0, {});
|
| + } else if (str.length > 1) {
|
| + this.tts.speak(str, 0, {});
|
| + }
|
| +};
|
| +
|
| +/**
|
| + * Return the state as an opaque object so that a client can restore it
|
| + * to this state later without needing to know about its internal fields.
|
| + *
|
| + * @return {Object} The state as an opaque object.
|
| + */
|
| +cvox.ChromeVoxEditableTextBase.prototype.saveState = function() {
|
| + return { 'value': this.value, 'start': this.start, 'end': this.end };
|
| +};
|
| +
|
| +/**
|
| + * Restore the state that was previously saved using saveState, without
|
| + * speaking any feedback.
|
| + *
|
| + * @param {Object} state A state returned by saveState.
|
| + */
|
| +cvox.ChromeVoxEditableTextBase.prototype.restoreState = function(state) {
|
| + this.value = state.value;
|
| + this.start = state.start;
|
| + this.end = state.end;
|
| +};
|
| +
|
| +/**
|
| + * Check if the underlying text control has changed and an update is needed.
|
| + * The default implementation always returns false, but subclasses that
|
| + * track an INPUT or TEXTAREA element will return true if the underlying
|
| + * element has changed.
|
| + *
|
| + * @return {boolean} True if the object needs to be updated.
|
| + */
|
| +cvox.ChromeVoxEditableTextBase.prototype.needsUpdate = function() {
|
| + return false;
|
| +};
|
| +
|
| +/**
|
| + * Update the state of the text and selection and describe any changes as
|
| + * appropriate.
|
| + * @param {string} newValue The new string value of the editable text control.
|
| + * @param {number} newStart The new 0-based start cursor/selection index.
|
| + * @param {number} newEnd The new 0-based end cursor/selection index.
|
| + */
|
| +cvox.ChromeVoxEditableTextBase.prototype.changed = function(
|
| + newValue, newStart, newEnd) {
|
| + if (newValue == this.value && newStart == this.start && newEnd == this.end) {
|
| + return;
|
| + }
|
| +
|
| + if (newValue == this.value) {
|
| + this.describeSelectionChanged(newStart, newEnd);
|
| + } else {
|
| + this.describeTextChanged(newValue, newStart, newEnd);
|
| + }
|
| +
|
| + this.value = newValue;
|
| + this.start = newStart;
|
| + this.end = newEnd;
|
| +};
|
| +
|
| +/**
|
| + * Describe a change in the selection or cursor position when the text
|
| + * stays the same.
|
| + * @param {number} newStart The new 0-based start cursor/selection index.
|
| + * @param {number} newEnd The new 0-based end cursor/selection index.
|
| + */
|
| +cvox.ChromeVoxEditableTextBase.prototype.describeSelectionChanged =
|
| + function(newStart, newEnd) {
|
| + if (newStart == newEnd) {
|
| + // It's currently a cursor.
|
| + if (this.start != this.end) {
|
| + // It was previously a selection, so just announce 'unselected'.
|
| + this.speak('Unselected.');
|
| + } else if (this.getLineIndex(this.start) != this.getLineIndex(newStart)) {
|
| + // Moved to a different line; read it.
|
| + this.speak(this.getLine(this.getLineIndex(newStart)));
|
| + } else if (this.start == newStart + 1 || this.start == newStart - 1) {
|
| + // Moved by one character; read it.
|
| + if (this.cursorIsBlock) {
|
| + if (newStart == this.value.length) {
|
| + this.speak('end');
|
| + } else {
|
| + this.speak(this.value.substr(newStart, 1));
|
| + }
|
| + } else {
|
| + this.speak(this.value.substr(Math.min(this.start, newStart), 1));
|
| + }
|
| + } else {
|
| + // Moved by more than one character. Read all characters crossed.
|
| + this.speak(this.value.substr(Math.min(this.start, newStart),
|
| + Math.abs(this.start - newStart)));
|
| + }
|
| + } else {
|
| + // It's currently a selection.
|
| + if (this.start + 1 == newStart &&
|
| + this.end == this.value.length &&
|
| + newEnd == this.value.length) {
|
| + // Autocomplete: the user typed one character of autocompleted text.
|
| + this.speak(this.describeChar(this.value.substr(this.start, 1)) +
|
| + ', ' +
|
| + this.describeChar(this.value.substr(newStart)));
|
| + } else if (this.start == this.end) {
|
| + // It was previously a cursor.
|
| + this.speak(this.describeChar(
|
| + this.value.substr(newStart, newEnd - newStart)) +
|
| + ', selected.');
|
| + } else if (this.start == newStart && this.end < newEnd) {
|
| + this.speak(this.describeChar(
|
| + this.value.substr(this.end, newEnd - this.end)) +
|
| + ', added to selection.');
|
| + } else if (this.start == newStart && this.end > newEnd) {
|
| + this.speak(this.describeChar(
|
| + this.value.substr(newEnd, this.end - newEnd)) +
|
| + ', removed from selection.');
|
| + } else if (this.end == newEnd && this.start > newStart) {
|
| + this.speak(this.describeChar(
|
| + this.value.substr(newStart, this.start - newStart)) +
|
| + ', added to selection.');
|
| + } else if (this.end == newEnd && this.start < newStart) {
|
| + this.speak(this.describeChar(
|
| + this.value.substr(this.start, newStart - this.start)) +
|
| + ', removed from selection.');
|
| + } else {
|
| + // The selection changed but it wasn't an obvious extension of
|
| + // a previous selection. Just read the new selection.
|
| + this.speak(this.describeChar(
|
| + this.value.substr(newStart, newEnd - newStart)) +
|
| + ', selected.');
|
| + }
|
| + }
|
| +};
|
| +
|
| +/**
|
| + * Describe a change where the text changes.
|
| + * @param {string} newValue The new string value of the editable text control.
|
| + * @param {number} newStart The new 0-based start cursor/selection index.
|
| + * @param {number} newEnd The new 0-based end cursor/selection index.
|
| + */
|
| +cvox.ChromeVoxEditableTextBase.prototype.describeTextChanged = function(
|
| + newValue, newStart, newEnd) {
|
| + var value = this.value;
|
| + var len = value.length;
|
| + var newLen = newValue.length;
|
| + var autocompleteSuffix = '';
|
| + var savedValue = newValue;
|
| +
|
| + // First, see if there's a selection at the end that might have been
|
| + // added by autocomplete. If so, strip it off into a separate variable.
|
| + if (newStart < newEnd && newEnd == newLen) {
|
| + autocompleteSuffix = newValue.substr(newStart);
|
| + newValue = newValue.substr(0, newStart);
|
| + newEnd = newStart;
|
| + }
|
| +
|
| + // Now see if the previous selection (if any) was deleted
|
| + // and any new text was inserted at that character position.
|
| + // This handles pasting and entering text by typing, both from
|
| + // a cursor and from a selection.
|
| + var prefixLen = this.start;
|
| + var suffixLen = len - this.end;
|
| + if (newLen >= prefixLen + suffixLen + (newEnd - newStart) &&
|
| + newValue.substr(0, prefixLen) == value.substr(0, prefixLen) &&
|
| + newValue.substr(newLen - suffixLen) == value.substr(this.end)) {
|
| + this.describeTextChangedHelper(
|
| + newValue, newStart, newEnd, prefixLen, suffixLen, autocompleteSuffix);
|
| + return;
|
| + }
|
| +
|
| + // Next, see if one or more characters were deleted from the previous
|
| + // cursor position and the new cursor is in the expected place. This
|
| + // handles backspace, forward-delete, and similar shortcuts that delete
|
| + // a word or line.
|
| + prefixLen = newStart;
|
| + suffixLen = newLen - newEnd;
|
| + if (this.start == this.end &&
|
| + newStart == newEnd &&
|
| + newValue.substr(0, prefixLen) == value.substr(0, prefixLen) &&
|
| + newValue.substr(newLen - suffixLen) == value.substr(len - suffixLen)) {
|
| + this.describeTextChangedHelper(
|
| + newValue, newStart, newEnd, prefixLen, suffixLen, autocompleteSuffix);
|
| + return;
|
| + }
|
| +
|
| + // If all else fails, we assume the change was not the result of a normal
|
| + // user editing operation, so we'll have to speak feedback based only
|
| + // on the changes to the text, not the cursor position / selection.
|
| + // First, restore the autocomplete text if any.
|
| + newValue += autocompleteSuffix;
|
| +
|
| + // If the text is short, just speak the whole thing.
|
| + if (newLen <= this.maxShortPhraseLen) {
|
| + this.describeTextChangedHelper(newValue, newStart, newEnd, 0, 0, '');
|
| + return;
|
| + }
|
| +
|
| + // Otherwise, look for the common prefix and suffix, but back up so
|
| + // that we can speak complete words, to be minimally confusing.
|
| + prefixLen = 0;
|
| + while (prefixLen < len &&
|
| + prefixLen < newLen &&
|
| + value[prefixLen] == newValue[prefixLen]) {
|
| + prefixLen++;
|
| + }
|
| + while (prefixLen > 0 && !this.isWordBreakChar(value[prefixLen - 1])) {
|
| + prefixLen--;
|
| + }
|
| +
|
| + suffixLen = 0;
|
| + while (suffixLen < (len - prefixLen) &&
|
| + suffixLen < (newLen - prefixLen) &&
|
| + value[len - suffixLen - 1] == newValue[newLen - suffixLen - 1]) {
|
| + suffixLen++;
|
| + }
|
| + while (suffixLen > 0 && !this.isWordBreakChar(value[len - suffixLen])) {
|
| + suffixLen--;
|
| + }
|
| +
|
| + this.describeTextChangedHelper(
|
| + newValue, newStart, newEnd, prefixLen, suffixLen, '');
|
| +};
|
| +
|
| +/**
|
| + * The function called by describeTextChanged after it's figured out
|
| + * what text was deleted, what text was inserted, and what additional
|
| + * autocomplete text was added.
|
| + * @param {string} newValue The new string value of the editable text control.
|
| + * @param {number} newStart The new 0-based start cursor/selection index.
|
| + * @param {number} newEnd The new 0-based end cursor/selection index.
|
| + * @param {number} prefixLen The number of characters in the common prefix
|
| + * of this.value and newValue.
|
| + * @param {number} suffixLen The number of characters in the common suffix
|
| + * of this.value and newValue.
|
| + * @param {string} autocompleteSuffix The autocomplete string that was added
|
| + * to the end, if any. It should be spoken at the end of the utterance
|
| + * describing the change.
|
| + */
|
| +cvox.ChromeVoxEditableTextBase.prototype.describeTextChangedHelper = function(
|
| + newValue, newStart, newEnd, prefixLen, suffixLen, autocompleteSuffix) {
|
| + var len = this.value.length;
|
| + var newLen = newValue.length;
|
| + var deletedLen = len - prefixLen - suffixLen;
|
| + var deleted = this.value.substr(prefixLen, deletedLen);
|
| + var insertedLen = newLen - prefixLen - suffixLen;
|
| + var inserted = newValue.substr(prefixLen, insertedLen);
|
| + var utterance = '';
|
| +
|
| + if (insertedLen > 1) {
|
| + utterance = inserted;
|
| + } else if (insertedLen == 1) {
|
| + if (this.isWordBreakChar(inserted) &&
|
| + prefixLen > 0 &&
|
| + !this.isWordBreakChar(newValue.substr(prefixLen - 1, 1))) {
|
| + // Speak previous word.
|
| + var index = prefixLen;
|
| + while (index > 0 && !this.isWordBreakChar(newValue[index - 1])) {
|
| + index--;
|
| + }
|
| + if (index < prefixLen) {
|
| + utterance = newValue.substr(index, prefixLen + 1 - index);
|
| + } else {
|
| + utterance = this.describeChar(inserted);
|
| + }
|
| + } else {
|
| + utterance = this.describeChar(inserted);
|
| + }
|
| + } else if (deletedLen > 1 && !autocompleteSuffix) {
|
| + utterance = deleted + ', deleted.';
|
| + } else if (deletedLen == 1) {
|
| + utterance = this.describeChar(deleted);
|
| + }
|
| +
|
| + if (autocompleteSuffix && utterance) {
|
| + utterance += ', ' + autocompleteSuffix;
|
| + } else if (autocompleteSuffix) {
|
| + utterance = autocompleteSuffix;
|
| + }
|
| +
|
| + this.speak(utterance);
|
| +};
|
| +
|
| +/******************************************/
|
| +
|
| +/**
|
| + * A subclass of ChromeVoxEditableTextBase a text element that's part of
|
| + * the webpage DOM. Contains common code shared by both EditableHTMLInput
|
| + * and EditableTextArea, but that might not apply to a non-DOM text box.
|
| + * @extends {cvox.ChromeVoxEditableTextBase}
|
| + * @constructor
|
| + */
|
| +cvox.ChromeVoxEditableElement = function() {
|
| + this.justSpokeDescription = false;
|
| +};
|
| +goog.inherits(cvox.ChromeVoxEditableElement,
|
| + cvox.ChromeVoxEditableTextBase);
|
| +
|
| +/**
|
| + * @type boolean
|
| + */
|
| +cvox.ChromeVoxEditableElement.prototype.justSpokeDescription = false;
|
| +
|
| +/**
|
| + * Update the state of the text and selection and describe any changes as
|
| + * appropriate.
|
| + * @param {string} newValue The new string value of the editable text control.
|
| + * @param {number} newStart The new 0-based start cursor/selection index.
|
| + * @param {number} newEnd The new 0-based end cursor/selection index.
|
| + */
|
| +cvox.ChromeVoxEditableElement.prototype.changed = function(
|
| + newValue, newStart, newEnd) {
|
| + // Ignore changes to the cursor and selection if they happen immediately
|
| + // after the description was just spoken. This avoid double-speaking when,
|
| + // for example, a text field is focused and then a moment later the
|
| + // contents are selected. If the value changes, though, this change will
|
| + // not be ignored.
|
| + if (this.justSpokeDescription && this.value == newValue) {
|
| + this.value = newValue;
|
| + this.start = newStart;
|
| + this.end = newEnd;
|
| + this.justSpokeDescription = false;
|
| + }
|
| +
|
| + cvox.ChromeVoxEditableTextBase.prototype.changed.apply(
|
| + this, [newValue, newStart, newEnd]);
|
| +};
|
| +
|
| +/**
|
| + * Get a speakable text string describing the current state of the
|
| + * text control and its title and/or label.
|
| + * @return {string} The speakable description.
|
| + */
|
| +cvox.ChromeVoxEditableElement.prototype.getDescription = function() {
|
| + var speech = '';
|
| +
|
| + if (this.node.title) {
|
| + speech = cvox.DomUtil.getTitle(this.node) + '. ';
|
| + }
|
| +
|
| + // Find the label and use heuristics if there was no title.
|
| + speech = cvox.DomUtil.getLabel(this.node, (speech.length < 1));
|
| +
|
| + this.justSpokeDescription = true;
|
| +
|
| + return speech + ' ' +
|
| + cvox.ChromeVoxEditableTextBase.prototype.getDescription.apply(this);
|
| +};
|
| +
|
| +/******************************************/
|
| +
|
| +/**
|
| + * A subclass of ChromeVoxEditableElement for an HTMLInputElement.
|
| + * @param {HTMLInputElement} node The HTMLInputElement node.
|
| + * @param {Object} tts A TTS object implementing speak() and stop() methods.
|
| + * @extends {cvox.ChromeVoxEditableElement}
|
| + * @constructor
|
| + */
|
| +cvox.ChromeVoxEditableHTMLInput = function(node, tts) {
|
| + this.node = node;
|
| + this.value = node.value;
|
| + this.start = node.selectionStart;
|
| + this.end = node.selectionEnd;
|
| + this.tts = tts;
|
| +
|
| + if (this.node.type == 'password') {
|
| + this.value = this.value.replace(/./g, '*');
|
| + }
|
| +};
|
| +goog.inherits(cvox.ChromeVoxEditableHTMLInput,
|
| + cvox.ChromeVoxEditableElement);
|
| +
|
| +/**
|
| + * Update the state of the text and selection and describe any changes as
|
| + * appropriate.
|
| + */
|
| +cvox.ChromeVoxEditableHTMLInput.prototype.update = function() {
|
| + var newValue = this.node.value;
|
| + if (this.node.type == 'password') {
|
| + newValue = newValue.replace(/./g, '*');
|
| + }
|
| +
|
| + this.changed(newValue, this.node.selectionStart, this.node.selectionEnd);
|
| +};
|
| +
|
| +/**
|
| + * @return {boolean} True if the object needs to be updated.
|
| + */
|
| +cvox.ChromeVoxEditableHTMLInput.prototype.needsUpdate = function() {
|
| + var newValue = this.node.value;
|
| + if (this.node.type == 'password') {
|
| + newValue = newValue.replace(/./g, '*');
|
| + }
|
| +
|
| + return (this.value != newValue ||
|
| + this.start != this.node.selectionStart ||
|
| + this.end != this.node.selectionEnd);
|
| +};
|
| +
|
| +/******************************************/
|
| +
|
| +/**
|
| + * A subclass of ChromeVoxEditableElement for an HTMLTextAreaElement.
|
| + * @param {HTMLTextAreaElement} node The HTMLTextAreaElement node.
|
| + * @param {Object} tts A TTS object implementing speak() and stop() methods.
|
| + * @extends {cvox.ChromeVoxEditableElement}
|
| + * @constructor
|
| + */
|
| +cvox.ChromeVoxEditableTextArea = function(node, tts) {
|
| + this.node = node;
|
| + this.value = node.value;
|
| + this.start = node.selectionStart;
|
| + this.end = node.selectionEnd;
|
| + this.tts = tts;
|
| + this.multiline = true;
|
| + this.shadowIsCurrent = false;
|
| + this.characterToLineMap = {};
|
| + this.lines = {};
|
| +};
|
| +goog.inherits(cvox.ChromeVoxEditableTextArea,
|
| + cvox.ChromeVoxEditableElement);
|
| +
|
| +/**
|
| + * An offscreen div used to compute the line numbers. A single div is
|
| + * shared by all instances of the class.
|
| + */
|
| +cvox.ChromeVoxEditableTextArea.shadow;
|
| +
|
| +/**
|
| + * Update the state of the text and selection and describe any changes as
|
| + * appropriate.
|
| + */
|
| +cvox.ChromeVoxEditableTextArea.prototype.update = function() {
|
| + if (this.node.value != this.value) {
|
| + this.shadowIsCurrent = false;
|
| + }
|
| +
|
| + this.changed(
|
| + this.node.value, this.node.selectionStart, this.node.selectionEnd);
|
| +};
|
| +
|
| +/**
|
| + * @return {boolean} True if the object needs to be updated.
|
| + */
|
| +cvox.ChromeVoxEditableTextArea.prototype.needsUpdate = function() {
|
| + return (this.value != this.node.value ||
|
| + this.start != this.node.selectionStart ||
|
| + this.end != this.node.selectionEnd);
|
| +};
|
| +
|
| +/**
|
| + * Get the line number corresponding to a particular index.
|
| + * @param {number} index The 0-based character index.
|
| + * @return {number} The 0-based line number corresponding to that character.
|
| + */
|
| +cvox.ChromeVoxEditableTextArea.prototype.getLineIndex = function(index) {
|
| + if (!this.shadowIsCurrent) {
|
| + this.updateShadow();
|
| + }
|
| +
|
| + return this.characterToLineMap[index];
|
| +};
|
| +
|
| +/**
|
| + * Get the start character index of a line.
|
| + * @param {number} index The 0-based line index.
|
| + * @return {number} The 0-based index of the first character in this line.
|
| + */
|
| +cvox.ChromeVoxEditableTextArea.prototype.getLineStart = function(index) {
|
| + if (!this.shadowIsCurrent) {
|
| + this.updateShadow();
|
| + }
|
| +
|
| + return this.lines[index].startIndex;
|
| +};
|
| +
|
| +/**
|
| + * Get the end character index of a line.
|
| + * @param {number} index The 0-based line index.
|
| + * @return {number} The 0-based index of the end of this line.
|
| + */
|
| +cvox.ChromeVoxEditableTextArea.prototype.getLineEnd = function(index) {
|
| + if (!this.shadowIsCurrent) {
|
| + this.updateShadow();
|
| + }
|
| +
|
| + return this.lines[index].endIndex;
|
| +};
|
| +
|
| +/**
|
| + * Update the shadow object, an offscreen div used to compute line numbers.
|
| + */
|
| +cvox.ChromeVoxEditableTextArea.prototype.updateShadow = function() {
|
| + var shadow = cvox.ChromeVoxEditableTextArea.shadow;
|
| + if (!shadow) {
|
| + shadow = document.createElement('div');
|
| + document.body.appendChild(shadow);
|
| + cvox.ChromeVoxEditableTextArea.shadow = shadow;
|
| + }
|
| +
|
| + while (shadow.childNodes.length) {
|
| + shadow.removeChild(shadow.childNodes[0]);
|
| + }
|
| +
|
| + shadow.style.cssText = window.getComputedStyle(this.node, null).cssText;
|
| + shadow.style.visibility = 'hidden';
|
| + shadow.style.position = 'absolute';
|
| + shadow.style.top = -9999;
|
| + shadow.style.left = -9999;
|
| +
|
| + var shadowWrap = document.createElement('div');
|
| + shadow.appendChild(shadowWrap);
|
| +
|
| + var text = this.node.value;
|
| + var outputHtml = '';
|
| + var lastWasWhitespace = false;
|
| + var currentSpan = null;
|
| + for (var i = 0; i < text.length; i++) {
|
| + var ch = text[i];
|
| + var isWhitespace = this.isWhitespaceChar(ch);
|
| + if ((isWhitespace != lastWasWhitespace) || i == 0) {
|
| + currentSpan = document.createElement('span');
|
| + currentSpan.startIndex = i;
|
| + shadowWrap.appendChild(currentSpan);
|
| + }
|
| + currentSpan.innerText += ch;
|
| + currentSpan.endIndex = i;
|
| + lastWasWhitespace = isWhitespace;
|
| + }
|
| + if (currentSpan) {
|
| + currentSpan.endIndex = text.length;
|
| + } else {
|
| + currentSpan = document.createElement('span');
|
| + currentSpan.startIndex = 0;
|
| + currentSpan.endIndex = 0;
|
| + shadowWrap.appendChild(currentSpan);
|
| + }
|
| +
|
| + this.characterToLineMap = {};
|
| + this.lines = {};
|
| + var firstSpan = shadowWrap.childNodes[0];
|
| + var lineIndex = -1;
|
| + var lineOffset = -1;
|
| + for (var n = firstSpan; n; n = n.nextSibling) {
|
| + if (n.offsetTop > lineOffset) {
|
| + lineIndex++;
|
| + this.lines[lineIndex] = {};
|
| + this.lines[lineIndex].startIndex = n.startIndex;
|
| + lineOffset = n.offsetTop;
|
| + }
|
| + this.lines[lineIndex].endIndex = n.endIndex;
|
| + for (var j = n.startIndex; j <= n.endIndex; j++) {
|
| + this.characterToLineMap[j] = lineIndex;
|
| + }
|
| + }
|
| +
|
| + this.shadowIsCurrent = true;
|
| +};
|
|
|
| Property changes on: chrome/browser/resources/access_chromevox/common/editable_text.js
|
| ___________________________________________________________________
|
| Added: svn:executable
|
| + *
|
| Added: svn:eol-style
|
| + LF
|
|
|
|
|