Index: chrome/browser/resources/chromeos/select_to_speak/background.js |
diff --git a/chrome/browser/resources/chromeos/select_to_speak/background.js b/chrome/browser/resources/chromeos/select_to_speak/background.js |
index 6853db73b68936cd3ee35d8b0ac4811bd738f192..5302281f654bed98d2e45772ed579229462c1e8b 100644 |
--- a/chrome/browser/resources/chromeos/select_to_speak/background.js |
+++ b/chrome/browser/resources/chromeos/select_to_speak/background.js |
@@ -1,3 +1,183 @@ |
// Copyright 2016 The Chromium Authors. All rights reserved. |
// Use of this source code is governed by a BSD-style license that can be |
// found in the LICENSE file. |
+ |
+var RoleType = chrome.automation.RoleType; |
+ |
+/** |
+ * @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
|
+ */ |
+SelectToSpeak = function() { |
+ this.node_ = null; |
+ this.down_ = false; |
+ |
+ chrome.automation.getDesktop(function(d) { |
+ 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.
|
+ d.addEventListener('mouseDragged', this.onMouseDragged_.bind(this), true); |
+ 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
|
+ d.addEventListener('mouseReleased', this.onMouseReleased_.bind(this), true); |
+ d.addEventListener('mouseCanceled', this.onMouseCanceled_.bind(this), true); |
+ }.bind(this)); |
+}; |
+ |
+SelectToSpeak.prototype = { |
+ /** |
+ * Called when the mouse is pressed and the user is in a mode where |
+ * select-to-speak is capturing mouse events (for example holding down |
+ * Search). |
+ * |
+ * @param {!AutomationEvent} evt |
+ */ |
+ onMousePressed_: function(evt) { |
+ this.down_ = true; |
+ this.mouseStart_ = {x: evt.mouseX, y: evt.mouseY}; |
+ this.startNode_ = evt.target; |
+ this.speechQueue_ = []; |
+ chrome.tts.stop(); |
+ this.onMouseDragged_(evt); |
+ }, |
+ |
+ /** |
+ * Called when the mouse is moved or dragged and the user is in a |
+ * 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
|
+ * holding down Search). |
+ * |
+ * @param {!AutomationEvent} evt |
+ */ |
+ onMouseDragged_: function(evt) { |
+ 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
|
+ return; |
+ |
+ var rect = {left: Math.floor(this.mouseStart_.x), |
+ 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.
|
+ width: Math.floor(evt.mouseX - this.mouseStart_.x), |
+ height: Math.floor(evt.mouseY - this.mouseStart_.y)}; |
+ chrome.accessibilityPrivate.setFocusRing([rect]); |
+ }, |
+ |
+ /** |
+ * Called when the mouse is moved or dragged and the user is in a |
+ * 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.
|
+ * holding down Search). |
+ * |
+ * @param {!AutomationEvent} evt |
+ */ |
+ onMouseReleased_: function(evt) { |
+ this.onMouseDragged_(evt); |
+ this.down_ = false; |
+ |
+ 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
|
+ |
+ // Walk up to the nearest window, web area, or dialog that the |
+ // hit node is contained inside. Only speak objects within that |
+ // container. In the future we might include other container-like |
+ // roles here. |
+ var root = this.startNode_; |
+ while (root.parent && |
+ root.role != RoleType.window && |
+ root.role != RoleType.rootWebArea && |
+ root.role != RoleType.desktop && |
+ root.role != RoleType.dialog) { |
+ root = root.parent; |
+ } |
+ |
+ var rect = {left: Math.floor(this.mouseStart_.x), |
+ top: Math.floor(this.mouseStart_.y), |
+ width: Math.floor(evt.mouseX - this.mouseStart_.x), |
+ height: Math.floor(evt.mouseY - this.mouseStart_.y)}; |
+ var nodes = []; |
+ this.findAllMatching_(root, rect, nodes); |
+ |
+ this.speechQueue_ = nodes; |
+ this.speakNextInQueue_(); |
+ }, |
+ |
+ /** |
+ * Called when the user cancels select-to-speak's capturing of mouse |
+ * events (for example by releasing Search while the mouse is still down). |
+ * |
+ * @param {!AutomationEvent} evt |
+ */ |
+ onMouseCanceled_: function(evt) { |
+ this.down_ = false; |
+ chrome.accessibilityPrivate.setFocusRing([]); |
+ this.speechQueue_ = []; |
+ chrome.tts.stop(); |
+ }, |
+ |
+ /** |
+ * Returns true if |rect1| and |rect2| overlap. The rects must define |
+ * left, top, width, and height. |
+ * @param {Object} rect1 |
+ * @param {Object} rect2 |
+ * @return {boolean} True if the rects overlap. |
+ */ |
+ 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.
|
+ var l1 = rect1.left; |
+ var r1 = rect1.left + rect1.width; |
+ var t1 = rect1.top; |
+ var b1 = rect1.top + rect1.height; |
+ var l2 = rect2.left; |
+ var r2 = rect2.left + rect2.width; |
+ var t2 = rect2.top; |
+ var b2 = rect2.top + rect2.height; |
+ 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
|
+ }, |
+ |
+ /** |
+ * Finds all nodes within the subtree rooted at |node| that overlap |
+ * a given rectangle. |
+ * @param {AutomationNode} The starting node. |
+ * @param {Object} rect The bounding box to search. |
+ * @param {Array<AutomationNode>} nodes The matching node array to be |
+ * populated. |
+ * @return {boolean} True if any matches are found. |
+ */ |
+ findAllMatching_: function(node, rect, nodes) { |
+ var found = false; |
+ for (var c = node.firstChild; c; c = c.nextSibling) { |
+ if (this.findAllMatching_(c, rect, nodes)) |
+ found = true; |
+ } |
+ |
+ if (found) |
+ return true; |
+ |
+ 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
|
+ return false; |
+ |
+ if (this.overlaps_(node.location, rect)) { |
+ nodes.push(node); |
+ return true; |
+ } |
+ |
+ return false; |
+ }, |
+ |
+ /** |
+ * Pop the next matching node from the queue and speak and highlight it. |
+ */ |
+ speakNextInQueue_: function() { |
+ 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.
|
+ chrome.accessibilityPrivate.setFocusRing([]); |
+ return; |
+ } |
+ |
+ var node = this.speechQueue_.shift(); |
+ chrome.accessibilityPrivate.setFocusRing([node.location]); |
+ chrome.tts.speak(node.name, { |
+ 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.
|
+ '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
|
+ onEvent: (function(event) { |
+ if (event.type == 'end') { |
+ this.speakNextInQueue_(); |
+ } else if (event.type == 'interrupted' || |
+ event.type == 'cancelled') { |
+ chrome.accessibilityPrivate.setFocusRing([]); |
+ } |
+ }).bind(this) |
+ }); |
+ } |
+}; |
+ |
+new SelectToSpeak(); |