Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(155)

Side by Side Diff: chrome/browser/resources/chromeos/select_to_speak/select_to_speak.js

Issue 2509883002: Select-to-speak extension code (Closed)
Patch Set: Added unit test fixture Created 4 years ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
(Empty)
1 // Copyright 2016 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 var AutomationEvent = chrome.automation.AutomationEvent;
6 var AutomationNode = chrome.automation.AutomationNode;
7 var EventType = chrome.automation.EventType;
8 var RoleType = chrome.automation.RoleType;
9
10 /**
11 * Return the rect that encloses two points.
12 * @param {number} x1 The first x coordinate.
13 * @param {number} y1 The first y coordinate.
14 * @param {number} x2 The second x coordinate.
15 * @param {number} y2 The second x coordinate.
16 * @return {{left: number, top: number, width: number, height: number}}
17 */
18 function rectFromPoints(x1, y1, x2, y2) {
19 var left = Math.min(x1, x2);
20 var right = Math.max(x1, x2);
21 var top = Math.min(y1, y2);
22 var bottom = Math.max(y1, y2);
23 return {left: left,
24 top: top,
25 width: right - left,
26 height: bottom - top};
27 }
28
29 /**
30 * Returns true if |rect1| and |rect2| overlap. The rects must define
31 * left, top, width, and height.
32 * @param {{left: number, top: number, width: number, height: number}} rect1
33 * @param {{left: number, top: number, width: number, height: number}} rect2
34 * @return {boolean} True if the rects overlap.
35 */
36 function overlaps(rect1, rect2) {
37 var l1 = rect1.left;
38 var r1 = rect1.left + rect1.width;
39 var t1 = rect1.top;
40 var b1 = rect1.top + rect1.height;
41 var l2 = rect2.left;
42 var r2 = rect2.left + rect2.width;
43 var t2 = rect2.top;
44 var b2 = rect2.top + rect2.height;
45 return (l1 < r2 && r1 > l2 && t1 < b2 && b1 > t2);
46 }
47
48 /**
49 * @constructor
50 */
51 var SelectToSpeak = function() {
52 /** @type { AutomationNode } @private */
53 this.node_ = null;
54
55 /** @type { boolean } @prinivate */
Dan Beam 2016/11/30 23:58:41 @private {boolean}
dmazzoni 2016/12/01 04:10:32 Thanks, didn't know about that shorthand.
56 this.down_ = false;
57
58 /** @type {{x: number, y: number}} @private */
59 this.mouseStart_ = {x: 0, y: 0};
60
61 chrome.automation.getDesktop(function(desktop) {
62 desktop.addEventListener(
63 EventType.mousePressed, this.onMousePressed_.bind(this), true);
64 desktop.addEventListener(
65 EventType.mouseDragged, this.onMouseDragged_.bind(this), true);
66 desktop.addEventListener(
67 EventType.mouseReleased, this.onMouseReleased_.bind(this), true);
68 desktop.addEventListener(
69 EventType.mouseCanceled, this.onMouseCanceled_.bind(this), true);
70 }.bind(this));
71 };
72
73 SelectToSpeak.prototype = {
74 /**
75 * Called when the mouse is pressed and the user is in a mode where
76 * select-to-speak is capturing mouse events (for example holding down
77 * Search).
78 *
79 * @param {!AutomationEvent} evt
80 */
81 onMousePressed_: function(evt) {
82 this.down_ = true;
83 this.mouseStart_ = {x: evt.mouseX, y: evt.mouseY};
84 this.startNode_ = evt.target;
85 chrome.tts.stop();
86 this.onMouseDragged_(evt);
87 },
88
89 /**
90 * Called when the mouse is moved or dragged and the user is in a
91 * mode where select-to-speak is capturing mouse events (for example
92 * holding down Search).
93 *
94 * @param {!AutomationEvent} evt
95 */
96 onMouseDragged_: function(evt) {
97 if (!this.down_)
98 return;
99
100 var rect = rectFromPoints(
101 this.mouseStart_.x, this.mouseStart_.y,
102 evt.mouseX, evt.mouseY);
103 chrome.accessibilityPrivate.setFocusRing([rect]);
104 },
105
106 /**
107 * Called when the mouse is released and the user is in a
108 * mode where select-to-speak is capturing mouse events (for example
109 * holding down Search).
110 *
111 * @param {!AutomationEvent} evt
112 */
113 onMouseReleased_: function(evt) {
114 this.onMouseDragged_(evt);
115 this.down_ = false;
116
117 chrome.accessibilityPrivate.setFocusRing([]);
118
119 // Walk up to the nearest window, web area, or dialog that the
120 // hit node is contained inside. Only speak objects within that
121 // container. In the future we might include other container-like
122 // roles here.
123 var root = this.startNode_;
124 while (root.parent &&
125 root.role != RoleType.window &&
126 root.role != RoleType.rootWebArea &&
127 root.role != RoleType.desktop &&
128 root.role != RoleType.dialog) {
129 root = root.parent;
130 }
131
132 var rect = rectFromPoints(
133 this.mouseStart_.x, this.mouseStart_.y,
134 evt.mouseX, evt.mouseY);
135 var nodes = [];
136 this.findAllMatching_(root, rect, nodes);
137 this.startSpeechQueue_(nodes);
138 },
139
140 /**
141 * Called when the user cancels select-to-speak's capturing of mouse
142 * events (for example by releasing Search while the mouse is still down).
143 *
144 * @param {!AutomationEvent} evt
145 */
146 onMouseCanceled_: function(evt) {
147 this.down_ = false;
148 chrome.accessibilityPrivate.setFocusRing([]);
149 chrome.tts.stop();
150 },
151
152 /**
153 * Finds all nodes within the subtree rooted at |node| that overlap
154 * a given rectangle.
155 * @param {AutomationNode} node The starting node.
156 * @param {{left: number, top: number, width: number, height: number}} rect
157 * The bounding box to search.
158 * @param {Array<AutomationNode>} nodes The matching node array to be
159 * populated.
160 * @return {boolean} True if any matches are found.
161 */
162 findAllMatching_: function(node, rect, nodes) {
163 var found = false;
164 for (var c = node.firstChild; c; c = c.nextSibling) {
165 if (this.findAllMatching_(c, rect, nodes))
166 found = true;
167 }
168
169 if (found)
170 return true;
171
172 if (!node.name || !node.location)
173 return false;
174
175 if (overlaps(node.location, rect)) {
176 nodes.push(node);
177 return true;
178 }
179
180 return false;
181 },
182
183 /**
184 * Enqueue speech commands for all of the given nodes.
185 * @param {Array<AutomationNode>} nodes The nodes to speak.
186 */
187 startSpeechQueue_: function(nodes) {
188 chrome.tts.stop();
189 for (var i = 0; i < nodes.length; i++) {
190 var node = nodes[i];
191 var isLast = (i == nodes.length - 1);
192 chrome.tts.speak(node.name, {
193 lang: 'en-US',
194 'enqueue': true,
195 onEvent: (function(node, isLast, event) {
196 if (event.type == 'start') {
197 chrome.accessibilityPrivate.setFocusRing([node.location]);
198 } else if (event.type == 'interrupted' ||
199 event.type == 'cancelled') {
200 chrome.accessibilityPrivate.setFocusRing([]);
201 } else if (event.type == 'end') {
202 if (isLast) {
203 chrome.accessibilityPrivate.setFocusRing([]);
204 }
205 }
206 }).bind(this, node, isLast)
207 });
208 }
209 }
210 };
211
212 new SelectToSpeak();
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698