Commit d3d7e29b authored by David Tseng's avatar David Tseng Committed by Commit Bot

Modernize c/b/r/c/a/select_to_speak/

- use ES6 classes
- manually expose window-scoped classes as needed (e.g. in tests)
- manually define prototypes when needed (expected at eval time by js2gtest)
- fix some undeclared vars
- manually extend test classes/add super() calls

Change-Id: I7770b19745ff9e4155067042478ea69fb3fbc3d0
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2039750
Commit-Queue: David Tseng <dtseng@chromium.org>
Reviewed-by: default avatarDominic Mazzoni <dmazzoni@chromium.org>
Reviewed-by: default avatarKatie Dektar <katie@chromium.org>
Cr-Commit-Position: refs/heads/master@{#739149}
parent 2238a4b8
......@@ -28,10 +28,12 @@ let SelectToSpeakCallbacks;
/**
* Class to handle user-input, from mouse, keyboard, and copy-paste events.
*/
class InputHandler {
/**
* @param {SelectToSpeakCallbacks} callbacks
* @constructor
*/
const InputHandler = function(callbacks) {
constructor(callbacks) {
/** @private {SelectToSpeakCallbacks} */
this.callbacks_ = callbacks;
......@@ -141,9 +143,8 @@ const InputHandler = function(callbacks) {
}, 0);
}
};
};
}
InputHandler.prototype = {
/**
* Set up event listeners for mouse and keyboard events. These are
* forwarded to us from the SelectToSpeakEventHandler so they should
......@@ -161,7 +162,7 @@ InputHandler.prototype = {
this.onClipboardDataChanged_.bind(this));
document.addEventListener('paste', this.onClipboardPaste_.bind(this));
document.addEventListener('copy', this.onClipboardCopy_.bind(this));
},
}
/**
* Change whether or not we are tracking the mouse.
......@@ -171,7 +172,7 @@ InputHandler.prototype = {
*/
setTrackingMouse(tracking) {
this.trackingMouse_ = tracking;
},
}
/**
* Gets the rect that has been drawn by clicking and dragging the mouse.
......@@ -181,7 +182,7 @@ InputHandler.prototype = {
return RectUtils.rectFromPoints(
this.mouseStart_.x, this.mouseStart_.y, this.mouseEnd_.x,
this.mouseEnd_.y);
},
}
/**
* Sets the date at which we last wanted the clipboard data to be read.
......@@ -189,7 +190,7 @@ InputHandler.prototype = {
*/
onRequestReadClipboardData() {
this.lastReadClipboardDataTime_ = new Date();
},
}
/**
* Called when the mouse is pressed and the user is in a mode where
......@@ -220,7 +221,7 @@ InputHandler.prototype = {
this.onMouseMove_(evt);
return false;
},
}
/**
* Called when the mouse is released and the user is in a
......@@ -252,7 +253,7 @@ InputHandler.prototype = {
false /* is no longer selecting */, ctrX, ctrY);
return false;
},
}
/**
* Visible for testing.
......@@ -275,7 +276,7 @@ InputHandler.prototype = {
// Some other key was pressed.
this.isSearchKeyDown_ = false;
}
},
}
/**
* Visible for testing.
......@@ -316,8 +317,9 @@ InputHandler.prototype = {
this.keysPressedTogether_.clear();
this.didTrackMouse_ = false;
}
},
};
}
}
// Number of milliseconds to wait after requesting a clipboard read
// before clipboard change and paste events are ignored.
......
......@@ -4,10 +4,57 @@
// Utilities for UMA metrics.
/**
* @constructor
class MetricsUtils {
constructor() {}
/**
* Records a cancel event if speech was in progress.
* @param {boolean} speaking Whether speech was in progress
* @public
*/
const MetricsUtils = function() {};
static recordCancelIfSpeaking(speaking) {
if (speaking) {
MetricsUtils.recordCancelEvent_();
}
}
/**
* Records an event that Select-to-Speak has begun speaking.
* @param {number} method The CrosSelectToSpeakStartSpeechMethod enum
* that reflects how this event was triggered by the user.
* @param {PrefsManager} prefsManager A PrefsManager with the users's current
* preferences.
* @public
*/
static recordStartEvent(method, prefsManager) {
chrome.metricsPrivate.recordUserAction(MetricsUtils.START_SPEECH_METRIC);
chrome.metricsPrivate.recordBoolean(
MetricsUtils.WORD_HIGHLIGHTING_METRIC,
prefsManager.wordHighlightingEnabled());
chrome.metricsPrivate.recordEnumerationValue(
MetricsUtils.START_SPEECH_METHOD_METRIC.METRIC_NAME, method,
MetricsUtils.START_SPEECH_METHOD_METRIC.EVENT_COUNT);
}
/**
* Records an event that Select-to-Speak speech has been canceled.
* @private
*/
static recordCancelEvent_() {
chrome.metricsPrivate.recordUserAction(MetricsUtils.CANCEL_SPEECH_METRIC);
}
/**
* Records a user-requested state change event from a given state.
* @param {number} changeType
* @public
*/
static recordSelectToSpeakStateChangeEvent(changeType) {
chrome.metricsPrivate.recordEnumerationValue(
MetricsUtils.STATE_CHANGE_METRIC.METRIC_NAME, changeType,
MetricsUtils.STATE_CHANGE_METRIC.EVENT_COUNT);
}
}
/**
* Defines an enumeration metric. The |EVENT_COUNT| must be kept in sync
......@@ -79,52 +126,3 @@ MetricsUtils.CANCEL_SPEECH_METRIC =
*/
MetricsUtils.WORD_HIGHLIGHTING_METRIC =
'Accessibility.CrosSelectToSpeak.WordHighlighting';
/**
* Records a cancel event if speech was in progress.
* @param {boolean} speaking Whether speech was in progress
* @public
*/
MetricsUtils.recordCancelIfSpeaking = function(speaking) {
if (speaking) {
MetricsUtils.recordCancelEvent_();
}
};
/**
* Records an event that Select-to-Speak has begun speaking.
* @param {number} method The CrosSelectToSpeakStartSpeechMethod enum
* that reflects how this event was triggered by the user.
* @param {PrefsManager} prefsManager A PrefsManager with the users's current
* preferences.
* @public
*/
MetricsUtils.recordStartEvent = function(method, prefsManager) {
chrome.metricsPrivate.recordUserAction(MetricsUtils.START_SPEECH_METRIC);
chrome.metricsPrivate.recordBoolean(
MetricsUtils.WORD_HIGHLIGHTING_METRIC,
prefsManager.wordHighlightingEnabled());
chrome.metricsPrivate.recordEnumerationValue(
MetricsUtils.START_SPEECH_METHOD_METRIC.METRIC_NAME, method,
MetricsUtils.START_SPEECH_METHOD_METRIC.EVENT_COUNT);
};
/**
* Records an event that Select-to-Speak speech has been canceled.
* @private
*/
MetricsUtils.recordCancelEvent_ = function() {
chrome.metricsPrivate.recordUserAction(MetricsUtils.CANCEL_SPEECH_METRIC);
};
/**
* Records a user-requested state change event from a given state.
* @param {number} changeType
* @public
*/
MetricsUtils.recordSelectToSpeakStateChangeEvent = function(changeType) {
chrome.metricsPrivate.recordEnumerationValue(
MetricsUtils.STATE_CHANGE_METRIC.METRIC_NAME, changeType,
MetricsUtils.STATE_CHANGE_METRIC.EVENT_COUNT);
};
......@@ -4,31 +4,16 @@
// Utilities for automation nodes in Select-to-Speak.
/**
* @constructor
*/
const NodeUtils = function() {};
class NodeUtils {
constructor() {}
/**
* Node state. Nodes can be on-screen like normal, or they may
* be invisible if they are in a tab that is not in the foreground
* or similar, or they may be invalid if they were removed from their
* root, i.e. if they were in a window that was closed.
* @enum {number}
*/
NodeUtils.NodeState = {
NODE_STATE_INVALID: 0,
NODE_STATE_INVISIBLE: 1,
NODE_STATE_NORMAL: 2,
};
/**
* Gets the current visiblity state for a given node.
/**
* Gets the current visibility state for a given node.
*
* @param {AutomationNode} node The starting node.
* @return {NodeUtils.NodeState} the current node state.
*/
NodeUtils.getNodeState = function(node) {
static getNodeState(node) {
if (node === undefined || node.root === null || node.root === undefined) {
// The node has been removed from the tree, perhaps because the
// window was closed.
......@@ -49,9 +34,9 @@ NodeUtils.getNodeState = function(node) {
// TODO: Also need a check for whether the window is minimized,
// which would also return NodeState.NODE_STATE_INVISIBLE.
return NodeUtils.NodeState.NODE_STATE_NORMAL;
};
}
/**
/**
* Returns true if a node should be ignored by Select-to-Speak, or if it
* is of interest. This does not deal with whether nodes have children --
* nodes are interesting if they have a name or a value and have an onscreen
......@@ -60,32 +45,32 @@ NodeUtils.getNodeState = function(node) {
* @param {boolean} includeOffscreen Whether to include offscreen nodes.
* @return {boolean} whether this node should be ignored.
*/
NodeUtils.shouldIgnoreNode = function(node, includeOffscreen) {
static shouldIgnoreNode(node, includeOffscreen) {
if (NodeUtils.isNodeInvisible(node, includeOffscreen)) {
return true;
}
return ParagraphUtils.isWhitespace(ParagraphUtils.getNodeName(node));
};
}
/**
/**
* Returns true if a node is invisible for any reason.
* @param {!AutomationNode} node The node to test
* @param {boolean} includeOffscreen Whether to include offscreen nodes
* as visible type nodes.
* @return {boolean} whether this node is invisible.
*/
NodeUtils.isNodeInvisible = function(node, includeOffscreen) {
static isNodeInvisible(node, includeOffscreen) {
return !node.location || node.state.invisible ||
(node.state.offscreen && !includeOffscreen);
};
}
/**
/**
* Gets the first window containing this node.
* @param {AutomationNode} node The node to find a window for.
* @return {AutomationNode|undefined} The node representing the nearest
* containing window.
*/
NodeUtils.getNearestContainingWindow = function(node) {
static getNearestContainingWindow(node) {
// Go upwards to root nodes' parents until we find the first window.
if (node.root.role == RoleType.ROOT_WEB_AREA) {
let nextRootParent = node;
......@@ -103,58 +88,58 @@ NodeUtils.getNearestContainingWindow = function(node) {
parent = parent.parent;
}
return parent;
};
}
/**
/**
* Gets the length of a node's name. Returns 0 if the name is
* undefined.
* @param {AutomationNode} node The node for which to check the name.
* @return {number} The length of the node's name
*/
NodeUtils.nameLength = function(node) {
static nameLength(node) {
return node.name ? node.name.length : 0;
};
}
/**
/**
* Returns true if a node is a text field type, but not for any other type,
* including contentEditables.
* @param {!AutomationNode} node The node to check
* @return {boolean} True if the node is a text field type.
*/
NodeUtils.isTextField = function(node) {
static isTextField(node) {
return node.role == RoleType.TEXT_FIELD ||
node.role == RoleType.TEXT_FIELD_WITH_COMBO_BOX;
};
}
/**
/**
* Gets the first (left-most) leaf node of a node. Returns undefined if
* none is found.
* @param {AutomationNode} node The node to search for the first leaf.
* @return {AutomationNode|undefined} The leaf node.
*/
NodeUtils.getFirstLeafChild = function(node) {
static getFirstLeafChild(node) {
let result = node.firstChild;
while (result && result.firstChild) {
result = result.firstChild;
}
return result;
};
}
/**
/**
* Gets the first (left-most) leaf node of a node. Returns undefined
* if none is found.
* @param {AutomationNode} node The node to search for the first leaf.
* @return {AutomationNode|undefined} The leaf node.
*/
NodeUtils.getLastLeafChild = function(node) {
static getLastLeafChild(node) {
let result = node.lastChild;
while (result && result.lastChild) {
result = result.lastChild;
}
return result;
};
}
/**
/**
* Finds all nodes within the subtree rooted at |node| that overlap
* a given rectangle.
* @param {!AutomationNode} node The starting node.
......@@ -164,7 +149,7 @@ NodeUtils.getLastLeafChild = function(node) {
* populated.
* @return {boolean} True if any matches are found.
*/
NodeUtils.findAllMatching = function(node, rect, nodes) {
static findAllMatching(node, rect, nodes) {
var found = false;
for (var c = node.firstChild; c; c = c.nextSibling) {
if (NodeUtils.findAllMatching(c, rect, nodes)) {
......@@ -195,17 +180,9 @@ NodeUtils.findAllMatching = function(node, rect, nodes) {
}
return false;
};
/**
* Class representing a position on the accessibility, made of a
* selected node and the offset of that selection.
* @typedef {{node: (!AutomationNode),
* offset: (number)}}
*/
NodeUtils.Position;
}
/**
/**
* Finds the deep equivalent node where a selection starts given a node
* object and selection offset. This is meant to be used in conjunction with
* the selectionStartObject/selectionStartOffset and
......@@ -217,7 +194,7 @@ NodeUtils.Position;
* @param {boolean} isStart whether this is the start or end of a selection.
* @return {!NodeUtils.Position} The node matching the selected offset.
*/
NodeUtils.getDeepEquivalentForSelection = function(parent, offset, isStart) {
static getDeepEquivalentForSelection(parent, offset, isStart) {
if (parent.children.length == 0) {
return {node: parent, offset};
}
......@@ -251,12 +228,15 @@ NodeUtils.getDeepEquivalentForSelection = function(parent, offset, isStart) {
}
}
} else if (index < 0) {
// Otherwise we are before the beginning of this parent. Find the previous
// leaf node and use that.
// Otherwise we are before the beginning of this parent. Find the
// previous leaf node and use that.
const previousNode = AutomationUtil.findNextNode(
parent, constants.Dir.BACKWARD, AutomationPredicate.leaf);
if (previousNode) {
return {node: previousNode, offset: NodeUtils.nameLength(previousNode)};
return {
node: previousNode,
offset: NodeUtils.nameLength(previousNode)
};
}
}
}
......@@ -337,4 +317,27 @@ NodeUtils.getDeepEquivalentForSelection = function(parent, offset, isStart) {
}
}
return {node, offset: NodeUtils.nameLength(node)};
}
}
/**
* Node state. Nodes can be on-screen like normal, or they may
* be invisible if they are in a tab that is not in the foreground
* or similar, or they may be invalid if they were removed from their
* root, i.e. if they were in a window that was closed.
* @enum {number}
*/
NodeUtils.NodeState = {
NODE_STATE_INVALID: 0,
NODE_STATE_INVISIBLE: 1,
NODE_STATE_NORMAL: 2,
};
/**
* Class representing a position on the accessibility, made of a
* selected node and the offset of that selection.
* @typedef {{node: (!AutomationNode),
* offset: (number)}}
*/
NodeUtils.Position;
......@@ -4,25 +4,18 @@
/**
* Test fixture for node_utils.js.
* @constructor
* @extends {testing.Test}
*/
function SelectToSpeakNodeUtilsUnitTest() {
testing.Test.call(this);
}
SelectToSpeakNodeUtilsUnitTest = class extends testing.Test {};
SelectToSpeakNodeUtilsUnitTest.prototype = {
__proto__: testing.Test.prototype,
/** @override */
extraLibraries: [
/** @override */
SelectToSpeakNodeUtilsUnitTest.prototype.extraLibraries = [
'test_support.js',
'paragraph_utils.js',
'node_utils.js',
'word_utils.js',
'rect_utils.js',
]
};
];
TEST_F('SelectToSpeakNodeUtilsUnitTest', 'GetNodeVisibilityState', function() {
const nodeWithoutRoot1 = {root: null};
......
......@@ -5,25 +5,24 @@
var AutomationNode = chrome.automation.AutomationNode;
var RoleType = chrome.automation.RoleType;
/**
* @constructor
*/
const ParagraphUtils = function() {};
class ParagraphUtils {
constructor() {}
/**
/**
* Gets the first ancestor of a node which is a paragraph or is not inline,
* or get the root node if none is found.
* @param {AutomationNode} node The node to get the parent for.
* @return {?AutomationNode} the parent paragraph or null if there is none.
*/
ParagraphUtils.getFirstBlockAncestor = function(node) {
static getFirstBlockAncestor(node) {
let parent = node.parent;
const root = node.root;
while (parent != null) {
if (parent == root) {
return parent;
}
if (parent.role == RoleType.PARAGRAPH || parent.role == RoleType.SVG_ROOT) {
if (parent.role == RoleType.PARAGRAPH ||
parent.role == RoleType.SVG_ROOT) {
return parent;
}
if (parent.display !== undefined && parent.display != 'inline' &&
......@@ -34,21 +33,22 @@ ParagraphUtils.getFirstBlockAncestor = function(node) {
parent = parent.parent;
}
return null;
};
}
/**
/**
* Determines whether two nodes are in the same block-like ancestor, i.e.
* whether they are in the same paragraph.
* @param {AutomationNode|undefined} first The first node to compare.
* @param {AutomationNode|undefined} second The second node to compare.
* @return {boolean} whether two nodes are in the same paragraph.
*/
ParagraphUtils.inSameParagraph = function(first, second) {
static inSameParagraph(first, second) {
if (first === undefined || second === undefined) {
return false;
}
// TODO: Clean up this check after crbug.com/774308 is resolved.
// At that point we will only need to check for display:block or inline-block.
// At that point we will only need to check for display:block or
// inline-block.
if (((first.display == 'block' || first.display == 'inline-block') &&
first.role != RoleType.STATIC_TEXT &&
first.role != RoleType.INLINE_TEXT_BOX) ||
......@@ -61,28 +61,28 @@ ParagraphUtils.inSameParagraph = function(first, second) {
const firstBlock = ParagraphUtils.getFirstBlockAncestor(first);
const secondBlock = ParagraphUtils.getFirstBlockAncestor(second);
return firstBlock != undefined && firstBlock == secondBlock;
};
}
/**
/**
* Determines whether a string is only whitespace.
* @param {string|undefined} name A string to test
* @return {boolean} whether the string is only whitespace
*/
ParagraphUtils.isWhitespace = function(name) {
static isWhitespace(name) {
if (name === undefined || name.length == 0) {
return true;
}
// Search for one or more whitespace characters
const re = /^\s+$/;
return re.exec(name) != null;
};
}
/**
/**
* Gets the text to be read aloud for a particular node.
* @param {!AutomationNode} node
* @return {string} The text to read for this node.
*/
ParagraphUtils.getNodeName = function(node) {
static getNodeName(node) {
if (node.role === RoleType.TEXT_FIELD &&
(node.children === undefined || node.children.length === 0) &&
node.value) {
......@@ -114,24 +114,24 @@ ParagraphUtils.getNodeName = function(node) {
stateString;
}
return node.name ? node.name : '';
};
}
/**
/**
* Determines the index into the parent name at which the inlineTextBox
* node name begins.
* @param {AutomationNode} inlineTextNode An inlineTextBox type node.
* @return {number} The character index into the parent node at which
* this node begins.
*/
ParagraphUtils.getStartCharIndexInParent = function(inlineTextNode) {
static getStartCharIndexInParent(inlineTextNode) {
let result = 0;
for (let i = 0; i < inlineTextNode.indexInParent; i++) {
result += inlineTextNode.parent.children[i].name.length;
}
return result;
};
}
/**
/**
* Determines the inlineTextBox child of a staticText node that appears
* at the given character index into the name of the staticText node. Uses
* the inlineTextBoxes name length to determine position. For example, if
......@@ -145,8 +145,7 @@ ParagraphUtils.getStartCharIndexInParent = function(inlineTextNode) {
* the last inlineTextBox in the staticText node if the index is too
* large.
*/
ParagraphUtils.findInlineTextNodeByCharacterIndex = function(
staticTextNode, index) {
static findInlineTextNodeByCharacterIndex(staticTextNode, index) {
if (staticTextNode.children.length == 0) {
return null;
}
......@@ -159,9 +158,9 @@ ParagraphUtils.findInlineTextNodeByCharacterIndex = function(
textLength += node.name.length;
}
return staticTextNode.children[staticTextNode.children.length - 1];
};
}
/**
/**
* Builds information about nodes in a group until it reaches the end of the
* group. It may return a NodeGroup with a single node, or a large group
* representing a paragraph of inline nodes.
......@@ -171,7 +170,7 @@ ParagraphUtils.findInlineTextNodeByCharacterIndex = function(
* up based on language.
* @return {ParagraphUtils.NodeGroup} info about the node group
*/
ParagraphUtils.buildNodeGroup = function(nodes, index, splitOnLanguage) {
static buildNodeGroup(nodes, index, splitOnLanguage) {
let node = nodes[index];
let next = nodes[index + 1];
const result = new ParagraphUtils.NodeGroup(
......@@ -189,7 +188,8 @@ ParagraphUtils.buildNodeGroup = function(nodes, index, splitOnLanguage) {
const name = ParagraphUtils.getNodeName(node);
if (!ParagraphUtils.isWhitespace(name)) {
let newNode;
if (node.role == RoleType.INLINE_TEXT_BOX && node.parent !== undefined) {
if (node.role == RoleType.INLINE_TEXT_BOX &&
node.parent !== undefined) {
if (node.parent.role == RoleType.STATIC_TEXT) {
// This is an inlineTextBox node with a staticText parent. If that
// parent is already added to the result, we can skip. This adds
......@@ -205,12 +205,12 @@ ParagraphUtils.buildNodeGroup = function(nodes, index, splitOnLanguage) {
}
} else {
// Not an staticText parent node. Add it directly.
newNode =
new ParagraphUtils.NodeGroupItem(node, result.text.length, false);
newNode = new ParagraphUtils.NodeGroupItem(
node, result.text.length, false);
}
} else {
// Not an inlineTextBox node. Add it directly, as each node in this list
// is relevant.
// Not an inlineTextBox node. Add it directly, as each node in this
// list is relevant.
newNode =
new ParagraphUtils.NodeGroupItem(node, result.text.length, false);
}
......@@ -222,8 +222,8 @@ ParagraphUtils.buildNodeGroup = function(nodes, index, splitOnLanguage) {
// Set currentLanguage if we don't have one yet.
// We have to do this before we consider stopping otherwise we miss out on
// the last language attribute of each NodeGroup which could be important if
// this NodeGroup only contains a single node, or if all previous nodes
// the last language attribute of each NodeGroup which could be important
// if this NodeGroup only contains a single node, or if all previous nodes
// lacked any language information.
if (!currentLanguage) {
currentLanguage = node.detectedLanguage;
......@@ -256,17 +256,20 @@ ParagraphUtils.buildNodeGroup = function(nodes, index, splitOnLanguage) {
}
result.endIndex = index;
return result;
};
}
}
/**
* Class representing a node group, which may be a single node or a
* full paragraph of nodes.
*
*/
ParagraphUtils.NodeGroup = class {
/**
* @param {?AutomationNode} blockParent The first block ancestor of
* this group. This may be the paragraph parent, for example.
* @constructor
*/
ParagraphUtils.NodeGroup = function(blockParent) {
constructor(blockParent) {
/**
* Full text of this paragraph.
* @type {string}
......@@ -300,6 +303,7 @@ ParagraphUtils.NodeGroup = function(blockParent) {
* @type {string|undefined}
*/
this.detectedLanguage = undefined;
}
};
/**
......@@ -307,15 +311,16 @@ ParagraphUtils.NodeGroup = function(blockParent) {
* a paragraph. Each Item in a NodeGroup has a start index within the
* total text, as well as the original AutomationNode it was associated
* with.
*
*/
ParagraphUtils.NodeGroupItem = class {
/**
* @param {!AutomationNode} node The AutomationNode associated with this item
* @param {number} startChar The index into the NodeGroup's text string where
* this item begins.
* @param {boolean=} opt_hasInlineText If this NodeGroupItem has inlineText
* children.
* @constructor
*/
ParagraphUtils.NodeGroupItem = function(node, startChar, opt_hasInlineText) {
constructor(node, startChar, opt_hasInlineText) {
/**
* @type {!AutomationNode}
*/
......@@ -329,11 +334,12 @@ ParagraphUtils.NodeGroupItem = function(node, startChar, opt_hasInlineText) {
this.startChar = startChar;
/**
* If this is a staticText node which has inlineTextBox children which should
* be selected. We cannot select the inlineTextBox children directly because
* they are not guarenteed to be stable.
* If this is a staticText node which has inlineTextBox children which
* should be selected. We cannot select the inlineTextBox children directly
* because they are not guaranteed to be stable.
* @type {boolean}
*/
this.hasInlineText =
opt_hasInlineText !== undefined ? opt_hasInlineText : false;
}
};
......@@ -4,19 +4,13 @@
/**
* Test fixture for paragraph_utils.js.
* @constructor
* @extends {testing.Test}
*/
function SelectToSpeakParagraphUnitTest() {
testing.Test.call(this);
}
SelectToSpeakParagraphUnitTest = class extends testing.Test {};
SelectToSpeakParagraphUnitTest.prototype = {
__proto__: testing.Test.prototype,
/** @override */
SelectToSpeakParagraphUnitTest.prototype.extraLibraries =
['test_support.js', 'paragraph_utils.js'];
/** @override */
extraLibraries: ['test_support.js', 'paragraph_utils.js']
};
TEST_F('SelectToSpeakParagraphUnitTest', 'GetFirstBlockAncestor', function() {
const root = {role: 'rootWebArea'};
......
......@@ -4,9 +4,9 @@
/**
* Manages getting and storing user preferences.
* @constructor
*/
const PrefsManager = function() {
class PrefsManager {
constructor() {
/** @private {?string} */
this.voiceNameFromPrefs_ = null;
......@@ -33,32 +33,13 @@ const PrefsManager = function() {
/** @private {boolean} */
this.migrationInProgress_ = false;
};
/**
* Constant representing the system TTS voice.
* @type {string}
* @public
*/
PrefsManager.SYSTEM_VOICE = 'select_to_speak_system_voice';
/**
* Default speech rate for both Select-to-Speak and global prefs.
* @type {number}
*/
PrefsManager.DEFAULT_RATE = 1.0;
/**
* Default speech pitch for both Select-to-Speak and global prefs.
* @type {number}
*/
PrefsManager.DEFAULT_PITCH = 1.0;
}
/**
/**
* Get the list of TTS voices, and set the default voice if not already set.
* @private
*/
PrefsManager.prototype.updateDefaultVoice_ = function() {
updateDefaultVoice_() {
var uiLocale = chrome.i18n.getMessage('@@ui_locale');
uiLocale = uiLocale.replace('_', '-').toLowerCase();
......@@ -105,9 +86,9 @@ PrefsManager.prototype.updateDefaultVoice_ = function() {
}
});
});
};
}
/**
/**
* Migrates Select-to-Speak rate and pitch settings to global Text-to-Speech
* settings. This is a one-time migration that happens on upgrade to M70.
* See http://crbug.com/866550.
......@@ -115,8 +96,7 @@ PrefsManager.prototype.updateDefaultVoice_ = function() {
* @param {string} pitchStr
* @private
*/
PrefsManager.prototype.migrateToGlobalTtsSettings_ = function(
rateStr, pitchStr) {
migrateToGlobalTtsSettings_(rateStr, pitchStr) {
if (this.migrationInProgress_) {
return;
}
......@@ -213,26 +193,26 @@ PrefsManager.prototype.migrateToGlobalTtsSettings_ = function(
console.log(error);
this.migrationInProgress_ = false;
});
};
}
/**
/**
* When TTS settings are successfully migrated, removes rate and pitch from
* chrome.storage.sync.
* @private
*/
PrefsManager.prototype.onTtsSettingsMigrationSuccess_ = function() {
onTtsSettingsMigrationSuccess_() {
chrome.storage.sync.remove('rate');
chrome.storage.sync.remove('pitch');
this.migrationInProgress_ = false;
};
}
/**
/**
* Loads preferences from chrome.storage, sets default values if
* necessary, and registers a listener to update prefs when they
* change.
* @public
*/
PrefsManager.prototype.initPreferences = function() {
initPreferences() {
var updatePrefs = () => {
chrome.storage.sync.get(
['voice', 'rate', 'pitch', 'wordHighlight', 'highlightColor'],
......@@ -265,15 +245,15 @@ PrefsManager.prototype.initPreferences = function() {
window.speechSynthesis.onvoiceschanged = () => {
this.updateDefaultVoice_();
};
};
}
/**
/**
* Generates the basic speech options for Select-to-Speak based on user
* preferences. Call for each chrome.tts.speak.
* @return {!TtsOptions} options The TTS options.
* @public
*/
PrefsManager.prototype.speechOptions = function() {
speechOptions() {
const options = {enqueue: true};
// To use the default (system) voice: don't specify options['voiceName'].
......@@ -302,32 +282,52 @@ PrefsManager.prototype.speechOptions = function() {
options['voiceName'] = this.voiceNameFromLocale_;
}
return options;
};
}
/**
/**
* Gets the user's word highlighting enabled preference.
* @return {boolean} True if word highlighting is enabled.
* @public
*/
PrefsManager.prototype.wordHighlightingEnabled = function() {
wordHighlightingEnabled() {
return this.wordHighlight_;
};
}
/**
/**
* Gets the user's word highlighting color preference.
* @return {string} Highlight color.
* @public
*/
PrefsManager.prototype.highlightColor = function() {
highlightColor() {
return this.highlightColor_;
};
}
/**
/**
* Gets the focus ring color. This is not currently a user preference but it
* could be in the future; stored here for similarity to highlight color.
* @return {string} Highlight color.
* @public
*/
PrefsManager.prototype.focusRingColor = function() {
focusRingColor() {
return this.color_;
};
}
}
/**
* Constant representing the system TTS voice.
* @type {string}
* @public
*/
PrefsManager.SYSTEM_VOICE = 'select_to_speak_system_voice';
/**
* Default speech rate for both Select-to-Speak and global prefs.
* @type {number}
*/
PrefsManager.DEFAULT_RATE = 1.0;
/**
* Default speech pitch for both Select-to-Speak and global prefs.
* @type {number}
*/
PrefsManager.DEFAULT_PITCH = 1.0;
......@@ -4,12 +4,10 @@
// Utilities for processing rectangles and coordinates.
/**
* @constructor
*/
const RectUtils = function() {};
class RectUtils {
constructor() {}
/**
/**
* Return the rect that encloses two points.
* @param {number} x1 The first x coordinate.
* @param {number} y1 The first y coordinate.
......@@ -17,22 +15,22 @@ const RectUtils = function() {};
* @param {number} y2 The second x coordinate.
* @return {{left: number, top: number, width: number, height: number}}
*/
RectUtils.rectFromPoints = function(x1, y1, x2, y2) {
static rectFromPoints(x1, y1, x2, y2) {
var left = Math.min(x1, x2);
var right = Math.max(x1, x2);
var top = Math.min(y1, y2);
var bottom = Math.max(y1, y2);
return {left, top, width: right - left, height: bottom - top};
};
}
/**
/**
* Returns true if |rect1| and |rect2| overlap. The rects must define
* left, top, width, and height.
* @param {{left: number, top: number, width: number, height: number}} rect1
* @param {{left: number, top: number, width: number, height: number}} rect2
* @return {boolean} True if the rects overlap.
*/
RectUtils.overlaps = function(rect1, rect2) {
static overlaps(rect1, rect2) {
var l1 = rect1.left;
var r1 = rect1.left + rect1.width;
var t1 = rect1.top;
......@@ -42,4 +40,5 @@ RectUtils.overlaps = function(rect1, rect2) {
var t2 = rect2.top;
var b2 = rect2.top + rect2.height;
return (l1 < r2 && r1 > l2 && t1 < b2 && b1 > t2);
};
}
}
......@@ -4,21 +4,14 @@
/**
* Test fixture for rect_utils.js.
* @constructor
* @extends {testing.Test}
*/
function SelectToSpeakRectUtilsUnitTest() {
testing.Test.call(this);
}
SelectToSpeakRectUtilsUnitTest = class extends testing.Test {};
SelectToSpeakRectUtilsUnitTest.prototype = {
__proto__: testing.Test.prototype,
/** @override */
extraLibraries: [
/** @override */
SelectToSpeakRectUtilsUnitTest.prototype.extraLibraries = [
'rect_utils.js',
]
};
];
TEST_F('SelectToSpeakRectUtilsUnitTest', 'Overlaps', function() {
var rect1 = {left: 0, top: 0, width: 100, height: 100};
......
......@@ -6,39 +6,25 @@ GEN_INCLUDE(['../chromevox/testing/callback_helper.js']);
/**
* Base class for browser tests for select-to-speak.
* @constructor
*/
function SelectToSpeakE2ETest() {
SelectToSpeakE2ETest = class extends testing.Test {
constructor() {
super();
this.callbackHelper_ = new CallbackHelper(this);
}
SelectToSpeakE2ETest.prototype = {
__proto__: testing.Test.prototype,
/**
* @override
* No UI in the background context.
*/
runAccessibilityChecks: false,
/** @override */
isAsync: true,
/** @override */
browsePreload: null,
}
/** @override */
testGenCppIncludes() {
GEN(`
#include "ash/accessibility/accessibility_delegate.h"
#include "ash/shell.h"
#include "base/bind.h"
#include "base/callback.h"
#include "chrome/browser/chromeos/accessibility/accessibility_manager.h"
#include "chrome/common/extensions/extension_constants.h"
#include "ash/keyboard/ui/keyboard_util.h"
#include "ash/accessibility/accessibility_delegate.h"
#include "ash/shell.h"
#include "base/bind.h"
#include "base/callback.h"
#include "chrome/browser/chromeos/accessibility/accessibility_manager.h"
#include "chrome/common/extensions/extension_constants.h"
#include "ash/keyboard/ui/keyboard_util.h"
`);
},
}
/** @override */
testGenPreamble() {
......@@ -52,7 +38,7 @@ SelectToSpeakE2ETest.prototype = {
chromeos::AccessibilityManager::Get()->SetSelectToSpeakEnabled(true);
WaitForExtension(extension_misc::kSelectToSpeakExtensionId, load_cb);
`);
},
}
/**
* Creates a callback that optionally calls {@code opt_callback} when
......@@ -64,7 +50,7 @@ SelectToSpeakE2ETest.prototype = {
*/
newCallback(opt_callback) {
return this.callbackHelper_.wrap(opt_callback);
},
}
/**
* Asserts that two strings are equal, collapsing repeated spaces and
......@@ -76,7 +62,7 @@ SelectToSpeakE2ETest.prototype = {
assertEquals(
first.replace(/\s+/g, ' ').replace(/^\s/, '').replace(/\s$/, ''),
second.replace(/\s+/g, ' ').replace(/^\s/, '').replace(/\s$/, ''));
},
}
/**
* From chromevox_next_e2e_test_base.js
......@@ -97,7 +83,7 @@ SelectToSpeakE2ETest.prototype = {
var createParams = {active: true, url};
chrome.tabs.create(createParams, function(unused_tab) {
chrome.automation.getTree(function(returnedRootNode) {
rootNode = returnedRootNode;
const rootNode = returnedRootNode;
if (rootNode.docLoaded) {
callback && callback(desktopRootNode);
callback = null;
......@@ -113,7 +99,7 @@ SelectToSpeakE2ETest.prototype = {
});
});
}.bind(this));
},
}
/**
* Helper function to find a staticText node from a root
......@@ -123,5 +109,17 @@ SelectToSpeakE2ETest.prototype = {
*/
findTextNode(root, text) {
return root.find({role: 'staticText', attributes: {name: text}});
},
}
};
/**
* @override
* No UI in the background context.
*/
SelectToSpeakE2ETest.prototype.runAccessibilityChecks = false;
/** @override */
SelectToSpeakE2ETest.prototype.isAsync = true;
/** @override */
SelectToSpeakE2ETest.prototype.browsePreload = null;
......@@ -8,17 +8,13 @@ GEN_INCLUDE(['mock_tts.js']);
/**
* Browser tests for select-to-speak's feature to speak text
* at the press of a keystroke.
* @constructor
* @extends {SelectToSpeakE2ETest}
*/
function SelectToSpeakKeystrokeSelectionTest() {
SelectToSpeakE2ETest.call(this);
SelectToSpeakKeystrokeSelectionTest = class extends SelectToSpeakE2ETest {
constructor() {
super();
this.mockTts = new MockTts();
chrome.tts = this.mockTts;
}
SelectToSpeakKeystrokeSelectionTest.prototype = {
__proto__: SelectToSpeakE2ETest.prototype,
}
/**
* Function to trigger select-to-speak to read selected text at a
......@@ -35,7 +31,7 @@ SelectToSpeakKeystrokeSelectionTest.prototype = {
selectToSpeak.fireMockKeyUpEvent(
{keyCode: SelectToSpeak.READ_SELECTION_KEY_CODE});
selectToSpeak.fireMockKeyUpEvent({keyCode: SelectToSpeak.SEARCH_KEY_CODE});
},
}
/**
* Function to load a simple webpage, select some of the single text
......@@ -63,7 +59,7 @@ SelectToSpeakKeystrokeSelectionTest.prototype = {
focusOffset
});
}, expected);
},
}
/**
* Function to load given html using a data url, have the caller set a
......@@ -98,7 +94,7 @@ SelectToSpeakKeystrokeSelectionTest.prototype = {
false);
setFocusCallback(desktop);
});
},
}
generateHtmlWithSelection(selectionCode, bodyHtml) {
return 'data:text/html;charset=utf-8,' +
......
......@@ -8,17 +8,13 @@ GEN_INCLUDE(['mock_tts.js']);
/**
* Browser tests for select-to-speak's feature to speak text
* by holding down a key and clicking or dragging with the mouse.
* @constructor
* @extends {SelectToSpeakE2ETest}
*/
function SelectToSpeakMouseSelectionTest() {
SelectToSpeakE2ETest.call(this);
SelectToSpeakMouseSelectionTest = class extends SelectToSpeakE2ETest {
constructor() {
super();
this.mockTts = new MockTts();
chrome.tts = this.mockTts;
}
SelectToSpeakMouseSelectionTest.prototype = {
__proto__: SelectToSpeakE2ETest.prototype,
}
/**
* Triggers speech using the search key and clicking with the mouse.
......@@ -31,7 +27,7 @@ SelectToSpeakMouseSelectionTest.prototype = {
selectToSpeak.fireMockMouseDownEvent(downEvent);
selectToSpeak.fireMockMouseUpEvent(upEvent);
selectToSpeak.fireMockKeyUpEvent({keyCode: SelectToSpeak.SEARCH_KEY_CODE});
},
}
tapTrayButton(desktop, callback) {
const button = desktop.find({
......@@ -45,7 +41,7 @@ SelectToSpeakMouseSelectionTest.prototype = {
callback();
});
button.doDefault();
},
}
};
TEST_F('SelectToSpeakMouseSelectionTest', 'SpeaksNodeWhenClicked', function() {
......
......@@ -2,14 +2,11 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
/**
* @constructor
*/
const SelectToSpeakOptionsPage = function() {
class SelectToSpeakOptionsPage {
constructor() {
this.init_();
};
}
SelectToSpeakOptionsPage.prototype = {
/**
* Translate the page and sync all of the control values to the
* values loaded from chrome.storage.
......@@ -39,7 +36,7 @@ SelectToSpeakOptionsPage.prototype = {
this.setUpTtsButtonClickListener_();
chrome.metricsPrivate.recordUserAction(
'Accessibility.CrosSelectToSpeak.LoadSettings');
},
}
/**
* Processes an HTML DOM, replacing text content with translated text messages
......@@ -64,7 +61,7 @@ SelectToSpeakOptionsPage.prototype = {
}
elts[i].classList.add('i18n-processed');
}
},
}
/**
* Populate a select element with the list of TTS voices.
......@@ -108,7 +105,7 @@ SelectToSpeakOptionsPage.prototype = {
select.updateFunction();
}
});
},
}
/**
* Populate a checkbox with its current setting.
......@@ -149,7 +146,7 @@ SelectToSpeakOptionsPage.prototype = {
checkbox.updateFunction = updateFromPref;
updateFromPref();
chrome.storage.onChanged.addListener(updateFromPref);
},
}
/**
* Given the id of an HTML select element and the name of a chrome.storage
......@@ -189,7 +186,7 @@ SelectToSpeakOptionsPage.prototype = {
element.updateFunction = updateFromPref;
updateFromPref();
chrome.storage.onChanged.addListener(updateFromPref);
},
}
/**
* Sets up the highlight listeners and preferences.
......@@ -215,7 +212,7 @@ SelectToSpeakOptionsPage.prototype = {
checkbox.click();
}
});
},
}
/**
* Sets up a listener on the TTS settings button.
......@@ -228,6 +225,7 @@ SelectToSpeakOptionsPage.prototype = {
'manageAccessibility/tts');
});
}
};
}
new SelectToSpeakOptionsPage();
......@@ -12,12 +12,10 @@ GEN_INCLUDE(['mock_storage.js']);
/**
* Browser tests for select-to-speak's feature to speak text
* at the press of a keystroke.
* @constructor
* @extends {SelectToSpeakE2ETest}
*/
function SelectToSpeakPrefsTest() {
SelectToSpeakE2ETest.call(this);
SelectToSpeakPrefsTest = class extends SelectToSpeakE2ETest {
constructor() {
super();
this.mockStorage_ = MockStorage;
chrome.storage = this.mockStorage_;
......@@ -35,15 +33,12 @@ function SelectToSpeakPrefsTest() {
};
this.resetStorage();
}
SelectToSpeakPrefsTest.prototype = {
__proto__: SelectToSpeakE2ETest.prototype,
}
resetStorage() {
this.mockStorage_.clear();
selectToSpeak.prefsManager_.initPreferences();
},
}
// This must be done before setting STS rate and pitch for tests to work
// properly.
......@@ -53,12 +48,12 @@ SelectToSpeakPrefsTest.prototype = {
'settings.tts.speech_rate', rate, '', unused);
this.mockSettingsPrivate_.setPref(
'settings.tts.speech_pitch', pitch, '', unused);
},
}
setStsRateAndPitch(rate, pitch) {
this.mockStorage_.sync.set({rate});
this.mockStorage_.sync.set({pitch});
},
}
ensurePrefsRemovedAndGlobalSetTo(rate, pitch) {
const onPrefsRemovedFromStorage = this.newCallback(() => {
......
......@@ -4,19 +4,13 @@
/**
* Test fixture for select_to_speak.js.
* @constructor
* @extends {testing.Test}
*/
function SelectToSpeakUnitTest() {
testing.Test.call(this);
}
SelectToSpeakUnitTest = class extends testing.Test {};
SelectToSpeakUnitTest.prototype = {
__proto__: testing.Test.prototype,
/** @override */
SelectToSpeakUnitTest.prototype.extraLibraries =
['test_support.js', 'select_to_speak.js'];
/** @override */
extraLibraries: ['test_support.js', 'select_to_speak.js']
};
TEST_F('SelectToSpeakUnitTest', 'getGSuiteAppRoot', function() {
const root = {url: 'https://docs.google.com/presentation/p/cats_r_awesome'};
......
......@@ -4,28 +4,10 @@
// Utilities for processing words within strings and nodes.
/**
* @constructor
*/
const WordUtils = function() {};
/**
* Regular expression to find the start of the next word after a word boundary.
* We cannot use \b\W to find the next word because it does not match many
* unicode characters.
* @type {RegExp}
*/
WordUtils.WORD_START_REGEXP = /\b\S/;
class WordUtils {
constructor() {}
/**
* Regular expression to find the end of the next word, which is followed by
* whitespace. We cannot use \w\b to find the end of the previous word because
* \w does not know about many unicode characters.
* @type {RegExp}
*/
WordUtils.WORD_END_REGEXP = /\S\s/;
/**
/**
* Searches through text starting at an index to find the next word's
* start boundary.
* @param {string|undefined} text The string to search through
......@@ -35,7 +17,7 @@ WordUtils.WORD_END_REGEXP = /\S\s/;
* are searching through.
* @return {number} The index of the next word's start
*/
WordUtils.getNextWordStart = function(text, indexAfter, nodeGroupItem) {
static getNextWordStart(text, indexAfter, nodeGroupItem) {
if (nodeGroupItem.hasInlineText && nodeGroupItem.node.children.length > 0) {
const node = ParagraphUtils.findInlineTextNodeByCharacterIndex(
nodeGroupItem.node, indexAfter - nodeGroupItem.startChar);
......@@ -55,9 +37,9 @@ WordUtils.getNextWordStart = function(text, indexAfter, nodeGroupItem) {
return WordUtils.nextWordHelper(
text, indexAfter, WordUtils.WORD_START_REGEXP, indexAfter);
}
};
}
/**
/**
* Searches through text starting at an index to find the next word's
* end boundary.
* @param {string|undefined} text The string to search through
......@@ -67,7 +49,7 @@ WordUtils.getNextWordStart = function(text, indexAfter, nodeGroupItem) {
* are searching through.
* @return {number} The index of the next word's end
*/
WordUtils.getNextWordEnd = function(text, indexAfter, nodeGroupItem) {
static getNextWordEnd(text, indexAfter, nodeGroupItem) {
if (nodeGroupItem.hasInlineText && nodeGroupItem.node.children.length > 0) {
const node = ParagraphUtils.findInlineTextNodeByCharacterIndex(
nodeGroupItem.node, indexAfter - nodeGroupItem.startChar + 1);
......@@ -90,9 +72,9 @@ WordUtils.getNextWordEnd = function(text, indexAfter, nodeGroupItem) {
text, indexAfter, WordUtils.WORD_END_REGEXP, text.length - 1) +
1;
}
};
}
/**
/**
* Searches through text to find the first index of a regular expression
* after a given starting index. Returns a default value if no match is
* found.
......@@ -104,7 +86,7 @@ WordUtils.getNextWordEnd = function(text, indexAfter, nodeGroupItem) {
* @return {number} The index found by the regular expression, or -1
* if none found.
*/
WordUtils.nextWordHelper = function(text, indexAfter, re, defaultValue) {
static nextWordHelper(text, indexAfter, re, defaultValue) {
if (text === undefined) {
return defaultValue;
}
......@@ -113,4 +95,21 @@ WordUtils.nextWordHelper = function(text, indexAfter, re, defaultValue) {
return indexAfter + result.index;
}
return defaultValue;
};
}
}
/**
* Regular expression to find the start of the next word after a word boundary.
* We cannot use \b\W to find the next word because it does not match many
* unicode characters.
* @type {RegExp}
*/
WordUtils.WORD_START_REGEXP = /\b\S/;
/**
* Regular expression to find the end of the next word, which is followed by
* whitespace. We cannot use \w\b to find the end of the previous word because
* \w does not know about many unicode characters.
* @type {RegExp}
*/
WordUtils.WORD_END_REGEXP = /\S\s/;
......@@ -4,23 +4,16 @@
/**
* Test fixture for word_utils.js.
* @constructor
* @extends {testing.Test}
*/
function SelectToSpeakWordUtilsUnitTest() {
testing.Test.call(this);
}
SelectToSpeakWordUtilsUnitTest = class extends testing.Test {};
SelectToSpeakWordUtilsUnitTest.prototype = {
__proto__: testing.Test.prototype,
/** @override */
extraLibraries: [
/** @override */
SelectToSpeakWordUtilsUnitTest.prototype.extraLibraries = [
'test_support.js',
'paragraph_utils.js',
'word_utils.js',
]
};
];
TEST_F(
'SelectToSpeakWordUtilsUnitTest', 'getNextWordStartWithoutWordStarts',
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment