Remove the old signature of NotificationManager::closePersistent().
[chromium-blink-merge.git] / chrome / browser / resources / chromeos / chromevox / cvox2 / background / background.js
blob7383d5137e14cbb048fb867c1c47d0ad60897d0a
1 // Copyright 2014 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.
5 /**
6  * @fileoverview The entry point for all ChromeVox2 related code for the
7  * background page.
8  */
10 goog.provide('Background');
11 goog.provide('global');
13 goog.require('AutomationPredicate');
14 goog.require('AutomationUtil');
15 goog.require('Output');
16 goog.require('Output.EventType');
17 goog.require('cursors.Cursor');
18 goog.require('cvox.ChromeVoxEditableTextBase');
20 goog.scope(function() {
21 var AutomationNode = chrome.automation.AutomationNode;
22 var Dir = AutomationUtil.Dir;
23 var EventType = chrome.automation.EventType;
25 /**
26  * ChromeVox2 background page.
27  * @constructor
28  */
29 Background = function() {
30   /**
31    * A list of site substring patterns to use with ChromeVox next. Keep these
32    * strings relatively specific.
33    * @type {!Array<string>}
34    * @private
35    */
36   this.whitelist_ = ['chromevox_next_test'];
38   /**
39    * @type {cursors.Range}
40    * @private
41    */
42   this.currentRange_ = null;
44   /**
45    * Whether ChromeVox Next is active.
46    * @type {boolean}
47    * @private
48    */
49   this.active_ = false;
51   // Manually bind all functions to |this|.
52   for (var func in this) {
53     if (typeof(this[func]) == 'function')
54       this[func] = this[func].bind(this);
55   }
57   /**
58    * Maps an automation event to its listener.
59    * @type {!Object<EventType, function(Object) : void>}
60    */
61   this.listeners_ = {
62     alert: this.onEventDefault,
63     focus: this.onEventDefault,
64     hover: this.onEventDefault,
65     menuStart: this.onEventDefault,
66     menuEnd: this.onEventDefault,
67     menuListValueChanged: this.onEventDefault,
68     loadComplete: this.onLoadComplete,
69     textChanged: this.onTextOrTextSelectionChanged,
70     textSelectionChanged: this.onTextOrTextSelectionChanged,
71     valueChanged: this.onEventDefault
72   };
74   // Register listeners for ...
75   // Desktop.
76   chrome.automation.getDesktop(this.onGotDesktop);
79 Background.prototype = {
80   /**
81    * Handles all setup once a new automation tree appears.
82    * @param {chrome.automation.AutomationNode} desktop
83    */
84   onGotDesktop: function(desktop) {
85     // Register all automation event listeners.
86     for (var eventType in this.listeners_)
87       desktop.addEventListener(eventType, this.listeners_[eventType], true);
89     // The focused state gets set on the containing webView node.
90     var webView = desktop.find({role: chrome.automation.RoleType.webView,
91                                 state: {focused: true}});
92     if (webView) {
93       var root = webView.find({role: chrome.automation.RoleType.rootWebArea});
94       if (root) {
95         this.onLoadComplete(
96             {target: root,
97              type: chrome.automation.EventType.loadComplete});
98       }
99     }
100   },
102   /**
103    * Handles chrome.commands.onCommand.
104    * @param {string} command
105    */
106   onGotCommand: function(command) {
107     if (command == 'toggleChromeVoxVersion') {
108       this.toggleChromeVoxVersion();
109       return;
110     }
112     if (!this.active_ || !this.currentRange_)
113       return;
115     var current = this.currentRange_;
116     var dir = Dir.FORWARD;
117     var pred = null;
118     switch (command) {
119       case 'nextHeading':
120         dir = Dir.FORWARD;
121         pred = AutomationPredicate.heading;
122         break;
123       case 'previousHeading':
124         dir = Dir.BACKWARD;
125         pred = AutomationPredicate.heading;
126         break;
127       case 'nextCharacter':
128         current = current.move(cursors.Unit.CHARACTER, Dir.FORWARD);
129         break;
130       case 'previousCharacter':
131         current = current.move(cursors.Unit.CHARACTER, Dir.BACKWARD);
132         break;
133       case 'nextWord':
134         current = current.move(cursors.Unit.WORD, Dir.FORWARD);
135         break;
136       case 'previousWord':
137         current = current.move(cursors.Unit.WORD, Dir.BACKWARD);
138         break;
139       case 'nextLine':
140         current = current.move(cursors.Unit.LINE, Dir.FORWARD);
141         break;
142       case 'previousLine':
143         current = current.move(cursors.Unit.LINE, Dir.BACKWARD);
144         break;
145       case 'nextLink':
146         dir = Dir.FORWARD;
147         pred = AutomationPredicate.link;
148         break;
149       case 'previousLink':
150         dir = Dir.BACKWARD;
151         pred = AutomationPredicate.link;
152         break;
153       case 'nextElement':
154         current = current.move(cursors.Unit.NODE, Dir.FORWARD);
155         break;
156       case 'previousElement':
157         current = current.move(cursors.Unit.NODE, Dir.BACKWARD);
158         break;
159       case 'goToBeginning':
160       var node =
161           AutomationUtil.findNodePost(current.getStart().getNode().root,
162                                       Dir.FORWARD,
163                                       AutomationPredicate.leaf);
164         if (node)
165           current = cursors.Range.fromNode(node);
166         break;
167       case 'goToEnd':
168         var node =
169             AutomationUtil.findNodePost(current.getStart().getNode().root,
170                                         Dir.BACKWARD,
171                                         AutomationPredicate.leaf);
172         if (node)
173           current = cursors.Range.fromNode(node);
174         break;
175       case 'doDefault':
176         if (this.currentRange_)
177           this.currentRange_.getStart().getNode().doDefault();
178         break;
179       case 'continuousRead':
180         global.isReadingContinuously = true;
181         var continueReading = function(prevRange) {
182           if (!global.isReadingContinuously)
183             return;
185           new Output().withSpeechAndBraille(
186                   this.currentRange_, prevRange, Output.EventType.NAVIGATE)
187               .onSpeechEnd(function() { continueReading(prevRange); })
188               .go();
189           prevRange = this.currentRange_;
190           this.currentRange_ =
191               this.currentRange_.move(cursors.Unit.NODE, Dir.FORWARD);
193           if (this.currentRange_.equals(prevRange))
194             global.isReadingContinuously = false;
195         }.bind(this);
197         continueReading(null);
198         return;
199     }
201     if (pred) {
202       var node = AutomationUtil.findNextNode(
203           current.getBound(dir).getNode(), dir, pred);
205       if (node)
206         current = cursors.Range.fromNode(node);
207     }
209     if (current) {
210       // TODO(dtseng): Figure out what it means to focus a range.
211       current.getStart().getNode().focus();
213       var prevRange = this.currentRange_;
214       this.currentRange_ = current;
215       new Output().withSpeechAndBraille(
216               this.currentRange_, prevRange, Output.EventType.NAVIGATE)
217           .go();
218     }
219   },
221   /**
222    * Provides all feedback once ChromeVox's focus changes.
223    * @param {Object} evt
224    */
225   onEventDefault: function(evt) {
226     var node = evt.target;
228     if (!node)
229       return;
231     var prevRange = this.currentRange_;
232     this.currentRange_ = cursors.Range.fromNode(node);
234     // Don't process nodes inside of web content if ChromeVox Next is inactive.
235     if (node.root.role != chrome.automation.RoleType.desktop && !this.active_)
236       return;
238     new Output().withSpeechAndBraille(
239             this.currentRange_, prevRange, evt.type)
240         .go();
241   },
243   /**
244    * Provides all feedback once a load complete event fires.
245    * @param {Object} evt
246    */
247   onLoadComplete: function(evt) {
248     var next = this.isWhitelisted_(evt.target.attributes.url);
249     this.toggleChromeVoxVersion({next: next, classic: !next});
250     // Don't process nodes inside of web content if ChromeVox Next is inactive.
251     if (evt.target.root.role != chrome.automation.RoleType.desktop &&
252         !this.active_)
253       return;
255     if (this.currentRange_)
256       return;
258     var root = evt.target;
259     var webView = root;
260     while (webView && webView.role != chrome.automation.RoleType.webView)
261       webView = webView.parent;
263     if (!webView || !webView.state.focused)
264       return;
266     var node = AutomationUtil.findNodePost(root,
267         Dir.FORWARD,
268         AutomationPredicate.leaf);
270     if (node)
271       this.currentRange_ = cursors.Range.fromNode(node);
273     if (this.currentRange_)
274       new Output().withSpeechAndBraille(
275               this.currentRange_, null, evt.type)
276           .go();
277   },
279   /**
280    * Provides all feedback once a text selection change event fires.
281    * @param {Object} evt
282    */
283   onTextOrTextSelectionChanged: function(evt) {
284     // Don't process nodes inside of web content if ChromeVox Next is inactive.
285     if (evt.target.root.role != chrome.automation.RoleType.desktop &&
286         !this.active_)
287       return;
289     if (!evt.target.state.focused)
290       return;
292     if (!this.currentRange_) {
293       this.onEventDefault(evt);
294       this.currentRange_ = cursors.Range.fromNode(evt.target);
295     }
297     var textChangeEvent = new cvox.TextChangeEvent(
298         evt.target.attributes.value,
299         evt.target.attributes.textSelStart,
300         evt.target.attributes.textSelEnd,
301         true);  // triggered by user
302     if (!this.editableTextHandler ||
303         evt.target != this.currentRange_.getStart().getNode()) {
304       this.editableTextHandler =
305           new cvox.ChromeVoxEditableTextBase(
306               textChangeEvent.value,
307               textChangeEvent.start,
308               textChangeEvent.end,
309               evt.target.state['protected'],
310               cvox.ChromeVox.tts);
311     }
313     this.editableTextHandler.changed(textChangeEvent);
314     new Output().withBraille(
315             this.currentRange_, null, evt.type)
316         .go();
317   },
319   /**
320    * @private
321    * @param {string} url
322    * @return {boolean} Whether the given |url| is whitelisted.
323    */
324   isWhitelisted_: function(url) {
325     return this.whitelist_.some(function(item) {
326       return url.indexOf(item) != -1;
327     }.bind(this));
328   },
330   /**
331    * Disables classic ChromeVox.
332    * @param {number} tabId The tab where ChromeVox classic is running in.
333    */
334   disableClassicChromeVox_: function(tabId) {
335     chrome.tabs.executeScript(
336           tabId,
337           {'code': 'try { window.disableChromeVox(); } catch(e) { }\n',
338            'allFrames': true});
339   },
341   /**
342    * Toggles between ChromeVox Next and Classic.
343    * @param {{classic: boolean, next: boolean}=} opt_options Forceably set.
344   */
345   toggleChromeVoxVersion: function(opt_options) {
346     if (!opt_options) {
347       opt_options = {};
348       opt_options.next = !this.active_;
349       opt_options.classic = !opt_options.next;
350     }
352     if (opt_options.next) {
353       if (!chrome.commands.onCommand.hasListener(this.onGotCommand))
354           chrome.commands.onCommand.addListener(this.onGotCommand);
355         this.active_ = true;
356     } else {
357       if (chrome.commands.onCommand.hasListener(this.onGotCommand))
358         chrome.commands.onCommand.removeListener(this.onGotCommand);
359       this.active_ = false;
360     }
362     chrome.tabs.query({active: true}, function(tabs) {
363       if (opt_options.classic) {
364         // This case should do nothing because Classic gets injected by the
365         // extension system via our manifest. Once ChromeVox Next is enabled
366         // for tabs, re-enable.
367         // cvox.ChromeVox.injectChromeVoxIntoTabs(tabs);
368       } else {
369         tabs.forEach(function(tab) {
370           this.disableClassicChromeVox_(tab.id);
371         }.bind(this));
372       }
373     }.bind(this));
374   }
377 /** @type {Background} */
378 global.backgroundObj = new Background();
380 });  // goog.scope