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

Unified Diff: chrome/browser/resources/chromeos/select_to_speak/select_to_speak.js

Issue 2509883002: Select-to-speak extension code (Closed)
Patch Set: Improve @private syntax 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 side-by-side diff with in-line comments
Download patch
Index: chrome/browser/resources/chromeos/select_to_speak/select_to_speak.js
diff --git a/chrome/browser/resources/chromeos/select_to_speak/select_to_speak.js b/chrome/browser/resources/chromeos/select_to_speak/select_to_speak.js
new file mode 100644
index 0000000000000000000000000000000000000000..330f03f7eb7dfdb3a4b6f0d224ee750ca9957b77
--- /dev/null
+++ b/chrome/browser/resources/chromeos/select_to_speak/select_to_speak.js
@@ -0,0 +1,212 @@
+// 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 AutomationEvent = chrome.automation.AutomationEvent;
+var AutomationNode = chrome.automation.AutomationNode;
+var EventType = chrome.automation.EventType;
+var RoleType = chrome.automation.RoleType;
+
+/**
+ * Return the rect that encloses two points.
+ * @param {number} x1 The first x coordinate.
+ * @param {number} y1 The first y coordinate.
+ * @param {number} x2 The second x coordinate.
+ * @param {number} y2 The second x coordinate.
+ * @return {{left: number, top: number, width: number, height: number}}
+ */
+function rectFromPoints(x1, y1, x2, y2) {
+ var left = Math.min(x1, x2);
+ var right = Math.max(x1, x2);
+ var top = Math.min(y1, y2);
+ var bottom = Math.max(y1, y2);
+ return {left: left,
+ top: top,
+ width: right - left,
+ height: bottom - top};
+}
+
+/**
+ * Returns true if |rect1| and |rect2| overlap. The rects must define
+ * left, top, width, and height.
+ * @param {{left: number, top: number, width: number, height: number}} rect1
+ * @param {{left: number, top: number, width: number, height: number}} rect2
+ * @return {boolean} True if the rects overlap.
+ */
+function overlaps(rect1, rect2) {
+ 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);
+}
+
+/**
+ * @constructor
+ */
+var SelectToSpeak = function() {
+ /** @private { AutomationNode } */
+ this.node_ = null;
+
+ /** @private { boolean } */
+ this.down_ = false;
+
+ /** @private {{x: number, y: number}} */
+ this.mouseStart_ = {x: 0, y: 0};
+
+ chrome.automation.getDesktop(function(desktop) {
+ desktop.addEventListener(
+ EventType.mousePressed, this.onMousePressed_.bind(this), true);
+ desktop.addEventListener(
+ EventType.mouseDragged, this.onMouseDragged_.bind(this), true);
+ desktop.addEventListener(
+ EventType.mouseReleased, this.onMouseReleased_.bind(this), true);
+ desktop.addEventListener(
+ EventType.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;
+ 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
+ * holding down Search).
+ *
+ * @param {!AutomationEvent} evt
+ */
+ onMouseDragged_: function(evt) {
+ if (!this.down_)
+ return;
+
+ var rect = rectFromPoints(
+ this.mouseStart_.x, this.mouseStart_.y,
+ evt.mouseX, evt.mouseY);
+ chrome.accessibilityPrivate.setFocusRing([rect]);
+ },
+
+ /**
+ * Called when the mouse is released and the user is in a
+ * mode where select-to-speak is capturing mouse events (for example
+ * holding down Search).
+ *
+ * @param {!AutomationEvent} evt
+ */
+ onMouseReleased_: function(evt) {
+ this.onMouseDragged_(evt);
+ this.down_ = false;
+
+ chrome.accessibilityPrivate.setFocusRing([]);
+
+ // 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 = rectFromPoints(
+ this.mouseStart_.x, this.mouseStart_.y,
+ evt.mouseX, evt.mouseY);
+ var nodes = [];
+ this.findAllMatching_(root, rect, nodes);
+ this.startSpeechQueue_(nodes);
+ },
+
+ /**
+ * 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([]);
+ chrome.tts.stop();
+ },
+
+ /**
+ * Finds all nodes within the subtree rooted at |node| that overlap
+ * a given rectangle.
+ * @param {AutomationNode} node The starting node.
+ * @param {{left: number, top: number, width: number, height: number}} 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 || !node.location)
+ return false;
+
+ if (overlaps(node.location, rect)) {
+ nodes.push(node);
+ return true;
+ }
+
+ return false;
+ },
+
+ /**
+ * Enqueue speech commands for all of the given nodes.
+ * @param {Array<AutomationNode>} nodes The nodes to speak.
+ */
+ startSpeechQueue_: function(nodes) {
+ chrome.tts.stop();
+ for (var i = 0; i < nodes.length; i++) {
+ var node = nodes[i];
+ var isLast = (i == nodes.length - 1);
+ chrome.tts.speak(node.name, {
+ lang: 'en-US',
+ 'enqueue': true,
+ onEvent: (function(node, isLast, event) {
+ if (event.type == 'start') {
+ chrome.accessibilityPrivate.setFocusRing([node.location]);
+ } else if (event.type == 'interrupted' ||
+ event.type == 'cancelled') {
+ chrome.accessibilityPrivate.setFocusRing([]);
+ } else if (event.type == 'end') {
+ if (isLast) {
+ chrome.accessibilityPrivate.setFocusRing([]);
+ }
+ }
+ }).bind(this, node, isLast)
+ });
+ }
+ }
+};
+
+new SelectToSpeak();

Powered by Google App Engine
This is Rietveld 408576698