Update To 11.40.268.0
[platform/framework/web/crosswalk.git] / src / chrome / browser / resources / chromeos / chromevox / cvox2 / background / background.js
index 701cff2..5bb97e2 100644 (file)
@@ -7,33 +7,66 @@
  * background page.
  */
 
-goog.provide('cvox2.Background');
-goog.provide('cvox2.global');
+goog.provide('Background');
+goog.provide('global');
 
+goog.require('AutomationPredicate');
+goog.require('AutomationUtil');
+goog.require('Output');
+goog.require('cursors.Cursor');
 goog.require('cvox.TabsApiHandler');
 
+goog.scope(function() {
+var AutomationNode = chrome.automation.AutomationNode;
+var Dir = AutomationUtil.Dir;
+var EventType = chrome.automation.EventType;
+
 /** Classic Chrome accessibility API. */
-cvox2.global.accessibility =
+global.accessibility =
     chrome.accessibilityPrivate || chrome.experimental.accessibility;
 
 /**
  * ChromeVox2 background page.
+ * @constructor
  */
-cvox2.Background = function() {
+Background = function() {
   /**
    * A list of site substring patterns to use with ChromeVox next. Keep these
    * strings relatively specific.
    * @type {!Array.<string>}
+   * @private
    */
   this.whitelist_ = ['http://www.chromevox.com/', 'chromevox_next_test'];
 
-  /** @type {cvox.TabsApiHandler} @private */
+  /**
+   * @type {cvox.TabsApiHandler}
+   * @private
+   */
   this.tabsHandler_ = new cvox.TabsApiHandler(cvox.ChromeVox.tts,
                                               cvox.ChromeVox.braille,
                                               cvox.ChromeVox.earcons);
 
+  /**
+   * @type {cursors.Range}
+   * @private
+   */
+  this.currentRange_ = null;
+
+  /**
+   * Whether ChromeVox Next is active.
+   * @type {boolean}
+   * @private
+   */
+  this.active_ = false;
+
+  /**
+   * @type {!Output}
+   * @private
+   */
+  this.output_ = new Output();
+
   // Only needed with unmerged ChromeVox classic loaded before.
-  cvox2.global.accessibility.setAccessibilityEnabled(false);
+  global.accessibility.setAccessibilityEnabled(false);
 
   // Manually bind all functions to |this|.
   for (var func in this) {
@@ -41,61 +74,56 @@ cvox2.Background = function() {
       this[func] = this[func].bind(this);
   }
 
+  /**
+   * Maps an automation event to its listener.
+   * @type {!Object.<EventType, function(Object) : void>}
+   */
+  this.listeners_ = {
+    focus: this.onFocus,
+    loadComplete: this.onLoadComplete
+  };
+
   // Register listeners for ...
   // Desktop.
   chrome.automation.getDesktop(this.onGotTree);
 
   // Tabs.
   chrome.tabs.onUpdated.addListener(this.onTabUpdated);
+
+  // Commands.
+  chrome.commands.onCommand.addListener(this.onGotCommand);
 };
 
-cvox2.Background.prototype = {
+Background.prototype = {
   /**
    * Handles chrome.tabs.onUpdated.
    * @param {number} tabId
    * @param {Object} changeInfo
    */
   onTabUpdated: function(tabId, changeInfo) {
+    if (changeInfo.status != 'complete')
+      return;
     chrome.tabs.get(tabId, function(tab) {
       if (!tab.url)
         return;
 
-      if (!this.isWhitelisted_(tab.url)) {
-        chrome.commands.onCommand.removeListener(this.onGotCommand);
-        cvox.ChromeVox.background.injectChromeVoxIntoTabs([tab], true);
-        return;
-      }
-
-      if (!chrome.commands.onCommand.hasListeners()) {
-        chrome.commands.onCommand.addListener(this.onGotCommand);
-      }
-
-      this.disableClassicChromeVox_(tab.id);
-
-      chrome.automation.getTree(this.onGotTree.bind(this));
+      var next = this.isWhitelisted_(tab.url);
+      this.toggleChromeVoxVersion({next: next, classic: !next});
     }.bind(this));
   },
 
   /**
    * Handles all setup once a new automation tree appears.
-   * @param {AutomationTree} tree The new automation tree.
+   * @param {chrome.automation.AutomationNode} root
    */
   onGotTree: function(root) {
     // Register all automation event listeners.
-    root.addEventListener(chrome.automation.EventType.focus,
-                          this.onAutomationEvent.bind(this),
-                          true);
-  },
+    for (var eventType in this.listeners_)
+      root.addEventListener(eventType, this.listeners_[eventType], true);
 
-  /**
-   * A generic handler for all desktop automation events.
-   * @param {AutomationEvent} evt The event.
-   */
-  onAutomationEvent: function(evt) {
-    var output = evt.target.attributes.name + ' ' + evt.target.role;
-    cvox.ChromeVox.tts.speak(output, cvox.AbstractTts.QUEUE_MODE_FLUSH);
-    cvox.ChromeVox.braille.write(cvox.NavBraille.fromText(output));
-    chrome.accessibilityPrivate.setFocusRing([evt.target.location]);
+    if (root.attributes.docLoaded) {
+      this.onLoadComplete({target: root});
+    }
   },
 
   /**
@@ -103,6 +131,121 @@ cvox2.Background.prototype = {
    * @param {string} command
    */
   onGotCommand: function(command) {
+    if (command == 'toggleChromeVoxVersion') {
+      this.toggleChromeVoxVersion();
+      return;
+    }
+
+    if (!this.active_ || !this.currentRange_)
+      return;
+
+    var current = this.currentRange_;
+    var dir = Dir.FORWARD;
+    var pred = null;
+    switch (command) {
+      case 'nextHeading':
+        dir = Dir.FORWARD;
+        pred = AutomationPredicate.heading;
+        break;
+      case 'previousHeading':
+        dir = Dir.BACKWARD;
+        pred = AutomationPredicate.heading;
+        break;
+      case 'nextCharacter':
+        current = current.move(cursors.Unit.CHARACTER, Dir.FORWARD);
+        break;
+      case 'previousCharacter':
+        current = current.move(cursors.Unit.CHARACTER, Dir.BACKWARD);
+        break;
+      case 'nextWord':
+        current = current.move(cursors.Unit.WORD, Dir.FORWARD);
+        break;
+      case 'previousWord':
+        current = current.move(cursors.Unit.WORD, Dir.BACKWARD);
+        break;
+      case 'nextLine':
+        current = current.move(cursors.Unit.LINE, Dir.FORWARD);
+        break;
+      case 'previousLine':
+        current = current.move(cursors.Unit.LINE, Dir.BACKWARD);
+        break;
+      case 'nextLink':
+        dir = Dir.FORWARD;
+        pred = AutomationPredicate.link;
+        break;
+      case 'previousLink':
+        dir = Dir.BACKWARD;
+        pred = AutomationPredicate.link;
+        break;
+      case 'nextElement':
+        current = current.move(cursors.Unit.NODE, Dir.FORWARD);
+        break;
+      case 'previousElement':
+        current = current.move(cursors.Unit.NODE, Dir.BACKWARD);
+        break;
+      case 'goToBeginning':
+      var node = AutomationUtil.findNodePost(current.getStart().getNode().root,
+            Dir.FORWARD,
+            AutomationPredicate.leaf);
+      if (node)
+        current = cursors.Range.fromNode(node);
+        break;
+      case 'goToEnd':
+      var node =
+          AutomationUtil.findNodePost(current.getStart().getNode().root,
+            Dir.BACKWARD,
+            AutomationPredicate.leaf);
+      if (node)
+        current = cursors.Range.fromNode(node);
+        break;
+    }
+
+    if (pred) {
+      var node = AutomationUtil.findNextNode(
+          current.getBound(dir).getNode(), dir, pred);
+
+      if (node)
+        current = cursors.Range.fromNode(node);
+    }
+
+    if (current) {
+      // TODO(dtseng): Figure out what it means to focus a range.
+      current.getStart().getNode().focus();
+
+      this.currentRange_ = current;
+      this.output_.output(this.currentRange_);
+    }
+  },
+
+  /**
+   * Provides all feedback once ChromeVox's focus changes.
+   * @param {Object} evt
+   */
+  onFocus: function(evt) {
+    var node = evt.target;
+    if (!node)
+      return;
+
+    this.currentRange_ = cursors.Range.fromNode(node);
+    this.output_.output(this.currentRange_);
+  },
+
+  /**
+   * Provides all feedback once a load complete event fires.
+   * @param {Object} evt
+   */
+  onLoadComplete: function(evt) {
+    if (this.currentRange_)
+      return;
+
+    var node = AutomationUtil.findNodePost(evt.target,
+        Dir.FORWARD,
+        AutomationPredicate.leaf);
+    if (node)
+      this.currentRange_ = cursors.Range.fromNode(node);
+
+    if (this.currentRange_)
+      this.output_.output(this.currentRange_);
   },
 
   /**
@@ -118,15 +261,89 @@ cvox2.Background.prototype = {
 
   /**
    * Disables classic ChromeVox.
-   * @param {number} tabId The tab where ChromeVox classic is running.
+   * @param {number} tabId The tab where ChromeVox classic is running in.
    */
   disableClassicChromeVox_: function(tabId) {
     chrome.tabs.executeScript(
           tabId,
           {'code': 'try { window.disableChromeVox(); } catch(e) { }\n',
            'allFrames': true});
+  },
+
+  /**
+   * Toggles between ChromeVox Next and Classic.
+   * @param {{classic: boolean, next: boolean}=} opt_options Forceably set.
+  */
+  toggleChromeVoxVersion: function(opt_options) {
+    if (!opt_options) {
+      opt_options = {};
+      opt_options.next = !this.active_;
+      opt_options.classic = !opt_options.next;
+    }
+
+    if (opt_options.next) {
+      chrome.automation.getTree(this.onGotTree);
+      this.active_ = true;
+    } else {
+      if (this.active_) {
+        for (var eventType in this.listeners_) {
+          this.currentRange_.getStart().getNode().root.removeEventListener(
+              eventType, this.listeners_[eventType], true);
+        }
+      }
+      this.active_ = false;
+    }
+
+    chrome.tabs.query({active: true}, function(tabs) {
+      if (opt_options.classic) {
+        cvox.ChromeVox.injectChromeVoxIntoTabs(tabs);
+      } else {
+        tabs.forEach(function(tab) {
+          this.disableClassicChromeVox_(tab.id);
+        }.bind(this));
+      }
+    }.bind(this));
+  },
+
+  /**
+   * Handles output of a Range.
+   * @param {!cursors.Range} range Current location.
+   */
+  handleOutput: function(range) {
+    // TODO(dtseng): This is just placeholder logic for generating descriptions
+    // pending further design discussion.
+    function getCursorDesc(cursor) {
+      var node = cursor.getNode();
+      var container = node;
+      while (container &&
+          (container.role == chrome.automation.RoleType.inlineTextBox ||
+          container.role == chrome.automation.RoleType.staticText))
+        container = container.parent();
+
+      var role = container ? container.role : node.role;
+      return [node.attributes.name, node.attributes.value, role].join(', ');
+    }
+
+    // Walk the range and collect descriptions.
+    var output = '';
+    var cursor = range.getStart();
+    var nodeLocations = [];
+    while (cursor.getNode() != range.getEnd().getNode()) {
+      output += getCursorDesc(cursor);
+      nodeLocations.push(cursor.getNode().location);
+      cursor = cursor.move(
+          cursors.Unit.NODE, cursors.Movement.DIRECTIONAL, Dir.FORWARD);
+    }
+    output += getCursorDesc(range.getEnd());
+    nodeLocations.push(range.getEnd().getNode().location);
+
+    cvox.ChromeVox.tts.speak(output, cvox.QueueMode.FLUSH);
+    cvox.ChromeVox.braille.write(cvox.NavBraille.fromText(output));
+    chrome.accessibilityPrivate.setFocusRing(nodeLocations);
   }
 };
 
-/** @type {cvox2.Background} */
-cvox2.global.backgroundObj = new cvox2.Background();
+/** @type {Background} */
+global.backgroundObj = new Background();
+
+});  // goog.scope