OLD | NEW |
---|---|
1 // Copyright 2016 The Chromium Authors. All rights reserved. | 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 | 2 // Use of this source code is governed by a BSD-style license that can be |
3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
4 | |
5 var RoleType = chrome.automation.RoleType; | |
6 | |
7 /** | |
8 * @constructor | |
David Tseng
2016/11/22 19:01:09
Are you planning on compiling this file (at least
dmazzoni
2016/11/28 18:51:12
Yes. I finished adding that to this change now. To
| |
9 */ | |
10 SelectToSpeak = function() { | |
11 this.node_ = null; | |
12 this.down_ = false; | |
13 | |
14 chrome.automation.getDesktop(function(d) { | |
15 d.addEventListener('mousePressed', this.onMousePressed_.bind(this), true); | |
David Tseng
2016/11/22 19:01:10
chrome.automation.EventType
dmazzoni
2016/11/28 18:51:12
Done.
| |
16 d.addEventListener('mouseDragged', this.onMouseDragged_.bind(this), true); | |
17 d.addEventListener('mouseMoved', this.onMouseDragged_.bind(this), true); | |
David Tseng
2016/11/22 19:01:09
onMouseDraggedOrMoved? (or were these supposed to
dmazzoni
2016/11/28 18:51:13
Actually we don't need to handle mouseMoved. Fixed
| |
18 d.addEventListener('mouseReleased', this.onMouseReleased_.bind(this), true); | |
19 d.addEventListener('mouseCanceled', this.onMouseCanceled_.bind(this), true); | |
20 }.bind(this)); | |
21 }; | |
22 | |
23 SelectToSpeak.prototype = { | |
24 /** | |
25 * Called when the mouse is pressed and the user is in a mode where | |
26 * select-to-speak is capturing mouse events (for example holding down | |
27 * Search). | |
28 * | |
29 * @param {!AutomationEvent} evt | |
30 */ | |
31 onMousePressed_: function(evt) { | |
32 this.down_ = true; | |
33 this.mouseStart_ = {x: evt.mouseX, y: evt.mouseY}; | |
34 this.startNode_ = evt.target; | |
35 this.speechQueue_ = []; | |
36 chrome.tts.stop(); | |
37 this.onMouseDragged_(evt); | |
38 }, | |
39 | |
40 /** | |
41 * Called when the mouse is moved or dragged and the user is in a | |
42 * mode where select-to-speak is capturing mouse events (for example | |
David Tseng
2016/11/22 19:01:10
Select to speak only captures if search is down, r
dmazzoni
2016/11/28 18:51:13
Currently yes but I wanted to write the comment in
| |
43 * holding down Search). | |
44 * | |
45 * @param {!AutomationEvent} evt | |
46 */ | |
47 onMouseDragged_: function(evt) { | |
48 if (!this.down_) | |
David Tseng
2016/11/22 19:01:10
Doesn't a mouse drag already mean a mouse down? I
dmazzoni
2016/11/28 18:51:12
This is still needed to handle a race condition wh
| |
49 return; | |
50 | |
51 var rect = {left: Math.floor(this.mouseStart_.x), | |
52 top: Math.floor(this.mouseStart_.y), | |
David Tseng
2016/11/22 19:01:10
How does this work if I drag up or to the left? Do
dmazzoni
2016/11/28 18:51:12
Good point. Fixed.
| |
53 width: Math.floor(evt.mouseX - this.mouseStart_.x), | |
54 height: Math.floor(evt.mouseY - this.mouseStart_.y)}; | |
55 chrome.accessibilityPrivate.setFocusRing([rect]); | |
56 }, | |
57 | |
58 /** | |
59 * Called when the mouse is moved or dragged and the user is in a | |
60 * mode where select-to-speak is capturing mouse events (for example | |
David Tseng
2016/11/22 19:01:10
Update the comment
dmazzoni
2016/11/28 18:51:12
Done.
| |
61 * holding down Search). | |
62 * | |
63 * @param {!AutomationEvent} evt | |
64 */ | |
65 onMouseReleased_: function(evt) { | |
66 this.onMouseDragged_(evt); | |
67 this.down_ = false; | |
68 | |
69 chrome.accessibilityPrivate.setFocusRing([]); | |
David Tseng
2016/11/22 19:01:10
I would have expected this to happen when s2s goes
dmazzoni
2016/11/28 18:51:12
That's handled in onMouseCanceled.
For onMouseRel
| |
70 | |
71 // Walk up to the nearest window, web area, or dialog that the | |
72 // hit node is contained inside. Only speak objects within that | |
73 // container. In the future we might include other container-like | |
74 // roles here. | |
75 var root = this.startNode_; | |
76 while (root.parent && | |
77 root.role != RoleType.window && | |
78 root.role != RoleType.rootWebArea && | |
79 root.role != RoleType.desktop && | |
80 root.role != RoleType.dialog) { | |
81 root = root.parent; | |
82 } | |
83 | |
84 var rect = {left: Math.floor(this.mouseStart_.x), | |
85 top: Math.floor(this.mouseStart_.y), | |
86 width: Math.floor(evt.mouseX - this.mouseStart_.x), | |
87 height: Math.floor(evt.mouseY - this.mouseStart_.y)}; | |
88 var nodes = []; | |
89 this.findAllMatching_(root, rect, nodes); | |
90 | |
91 this.speechQueue_ = nodes; | |
92 this.speakNextInQueue_(); | |
93 }, | |
94 | |
95 /** | |
96 * Called when the user cancels select-to-speak's capturing of mouse | |
97 * events (for example by releasing Search while the mouse is still down). | |
98 * | |
99 * @param {!AutomationEvent} evt | |
100 */ | |
101 onMouseCanceled_: function(evt) { | |
102 this.down_ = false; | |
103 chrome.accessibilityPrivate.setFocusRing([]); | |
104 this.speechQueue_ = []; | |
105 chrome.tts.stop(); | |
106 }, | |
107 | |
108 /** | |
109 * Returns true if |rect1| and |rect2| overlap. The rects must define | |
110 * left, top, width, and height. | |
111 * @param {Object} rect1 | |
112 * @param {Object} rect2 | |
113 * @return {boolean} True if the rects overlap. | |
114 */ | |
115 overlaps_: function(rect1, rect2) { | |
David Tseng
2016/11/22 19:01:09
No need to define this function in this class; mak
dmazzoni
2016/11/28 18:51:12
Done.
| |
116 var l1 = rect1.left; | |
117 var r1 = rect1.left + rect1.width; | |
118 var t1 = rect1.top; | |
119 var b1 = rect1.top + rect1.height; | |
120 var l2 = rect2.left; | |
121 var r2 = rect2.left + rect2.width; | |
122 var t2 = rect2.top; | |
123 var b2 = rect2.top + rect2.height; | |
124 return (l1 < r2 && r1 > l2 && t1 < b2 && b1 > t2); | |
David Tseng
2016/11/22 19:01:09
Doesn't look right. What happens if the two rects
dmazzoni
2016/11/28 18:51:13
Both of those cases work as long as the rects have
| |
125 }, | |
126 | |
127 /** | |
128 * Finds all nodes within the subtree rooted at |node| that overlap | |
129 * a given rectangle. | |
130 * @param {AutomationNode} The starting node. | |
131 * @param {Object} rect The bounding box to search. | |
132 * @param {Array<AutomationNode>} nodes The matching node array to be | |
133 * populated. | |
134 * @return {boolean} True if any matches are found. | |
135 */ | |
136 findAllMatching_: function(node, rect, nodes) { | |
137 var found = false; | |
138 for (var c = node.firstChild; c; c = c.nextSibling) { | |
139 if (this.findAllMatching_(c, rect, nodes)) | |
140 found = true; | |
141 } | |
142 | |
143 if (found) | |
144 return true; | |
145 | |
146 if (!node.name) | |
David Tseng
2016/11/22 19:01:10
This is going to give some strange results I think
dmazzoni
2016/11/28 18:51:13
I think it's actually useful that it reads the alt
| |
147 return false; | |
148 | |
149 if (this.overlaps_(node.location, rect)) { | |
150 nodes.push(node); | |
151 return true; | |
152 } | |
153 | |
154 return false; | |
155 }, | |
156 | |
157 /** | |
158 * Pop the next matching node from the queue and speak and highlight it. | |
159 */ | |
160 speakNextInQueue_: function() { | |
161 if (!this.speechQueue_ || this.speechQueue_.length == 0) { | |
David Tseng
2016/11/22 19:01:10
Looks like type checking would help here. Set this
dmazzoni
2016/11/28 18:51:12
Done.
| |
162 chrome.accessibilityPrivate.setFocusRing([]); | |
163 return; | |
164 } | |
165 | |
166 var node = this.speechQueue_.shift(); | |
167 chrome.accessibilityPrivate.setFocusRing([node.location]); | |
168 chrome.tts.speak(node.name, { | |
169 lang: 'en-US', | |
David Tseng
2016/11/22 19:01:10
Maybe we should think about starting some code sha
dmazzoni
2016/11/28 18:51:13
Agreed, perhaps some common utility libraries.
| |
170 'enqueue': false, | |
David Tseng
2016/11/22 19:01:09
Can we let the native tts queue handle things?
dmazzoni
2016/11/28 18:51:12
Done, it just means slightly trickier logic inside
| |
171 onEvent: (function(event) { | |
172 if (event.type == 'end') { | |
173 this.speakNextInQueue_(); | |
174 } else if (event.type == 'interrupted' || | |
175 event.type == 'cancelled') { | |
176 chrome.accessibilityPrivate.setFocusRing([]); | |
177 } | |
178 }).bind(this) | |
179 }); | |
180 } | |
181 }; | |
182 | |
183 new SelectToSpeak(); | |
OLD | NEW |