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