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.
6 * @fileoverview Watches for events in the browser such as focus changes.
10 goog.provide('cvox.ChromeVoxEventWatcher');
11 goog.provide('cvox.ChromeVoxEventWatcherUtil');
13 goog.require('cvox.ActiveIndicator');
14 goog.require('cvox.ApiImplementation');
15 goog.require('cvox.AriaUtil');
16 goog.require('cvox.ChromeVox');
17 goog.require('cvox.ChromeVoxEditableTextBase');
18 goog.require('cvox.ChromeVoxEventSuspender');
19 goog.require('cvox.ChromeVoxHTMLDateWidget');
20 goog.require('cvox.ChromeVoxHTMLMediaWidget');
21 goog.require('cvox.ChromeVoxHTMLTimeWidget');
22 goog.require('cvox.ChromeVoxKbHandler');
23 goog.require('cvox.ChromeVoxUserCommands');
24 goog.require('cvox.DomUtil');
25 goog.require('cvox.Focuser');
26 goog.require('cvox.History');
27 goog.require('cvox.LiveRegions');
28 goog.require('cvox.LiveRegionsDeprecated');
29 goog.require('cvox.NavigationSpeaker');
30 goog.require('cvox.PlatformFilter'); // TODO: Find a better place for this.
31 goog.require('cvox.PlatformUtil');
32 goog.require('cvox.TextHandlerInterface');
33 goog.require('cvox.UserEventDetail');
38 cvox.ChromeVoxEventWatcher = function() {
42 * The maximum amount of time to wait before processing events.
43 * A max time is needed so that even if a page is constantly updating,
44 * events will still go through.
49 cvox.ChromeVoxEventWatcher.MAX_WAIT_TIME_MS_ = 50;
52 * As long as the MAX_WAIT_TIME_ has not been exceeded, the event processor
53 * will wait this long after the last event was received before starting to
59 cvox.ChromeVoxEventWatcher.WAIT_TIME_MS_ = 10;
62 * Amount of time in ms to wait before considering a subtree modified event to
63 * be the start of a new burst of subtree modified events.
68 cvox.ChromeVoxEventWatcher.SUBTREE_MODIFIED_BURST_DURATION_ = 1000;
72 * Number of subtree modified events that are part of the same burst to process
73 * before we give up on processing any more events from that burst.
78 cvox.ChromeVoxEventWatcher.SUBTREE_MODIFIED_BURST_COUNT_LIMIT_ = 3;
82 * Maximum number of live regions that we will attempt to process.
87 cvox.ChromeVoxEventWatcher.MAX_LIVE_REGIONS_ = 5;
91 * Whether or not ChromeVox should echo keys.
92 * It is useful to turn this off in case the system is already echoing keys (for
93 * example, in Android).
97 cvox.ChromeVoxEventWatcher.shouldEchoKeys = true;
101 * Whether or not the next utterance should flush all previous speech.
102 * Immediately after a key down or user action, we make the next speech
103 * flush, but otherwise it's better to do a category flush, so if a single
104 * user action generates both a focus change and a live region change,
108 cvox.ChromeVoxEventWatcher.shouldFlushNextUtterance = false;
112 * Inits the event watcher and adds listeners.
113 * @param {!Document|!Window} doc The DOM document to add event listeners to.
115 cvox.ChromeVoxEventWatcher.init = function(doc) {
119 cvox.ChromeVoxEventWatcher.lastFocusedNode = null;
124 cvox.ChromeVoxEventWatcher.announcedMouseOverNode = null;
129 cvox.ChromeVoxEventWatcher.pendingMouseOverNode = null;
134 cvox.ChromeVoxEventWatcher.mouseOverTimeoutId = null;
139 cvox.ChromeVoxEventWatcher.lastFocusedNodeValue = null;
144 cvox.ChromeVoxEventWatcher.eventToEat = null;
149 cvox.ChromeVoxEventWatcher.currentTextControl = null;
152 * @type {cvox.ChromeVoxEditableTextBase}
154 cvox.ChromeVoxEventWatcher.currentTextHandler = null;
157 * Array of event listeners we've added so we can unregister them if needed.
161 cvox.ChromeVoxEventWatcher.listeners_ = [];
164 * The mutation observer we use to listen for live regions.
165 * @type {MutationObserver}
168 cvox.ChromeVoxEventWatcher.mutationObserver_ = null;
171 * Whether or not mouse hover events should trigger focusing.
174 cvox.ChromeVoxEventWatcher.focusFollowsMouse = false;
177 * The delay before a mouseover triggers focusing or announcing anything.
180 cvox.ChromeVoxEventWatcher.mouseoverDelayMs = 500;
183 * Array of events that need to be processed.
184 * @type {Array.<Event>}
187 cvox.ChromeVoxEventWatcher.events_ = new Array();
190 * The time when the last event was received.
193 cvox.ChromeVoxEventWatcher.lastEventTime = 0;
196 * The timestamp for the first unprocessed event.
199 cvox.ChromeVoxEventWatcher.firstUnprocessedEventTime = -1;
202 * Whether or not queue processing is scheduled to run.
206 cvox.ChromeVoxEventWatcher.queueProcessingScheduled_ = false;
209 * A list of callbacks to be called when the EventWatcher has
210 * completed processing all events in its queue.
211 * @type {Array.<function()>}
214 cvox.ChromeVoxEventWatcher.readyCallbacks_ = new Array();
218 * tracks whether we've received two or more key up's while pass through mode
223 cvox.ChromeVoxEventWatcher.secondPassThroughKeyUp_ = false;
226 * Whether or not the ChromeOS Search key (keyCode == 91) is being held.
228 * We must track this manually because on ChromeOS, the Search key being held
229 * down does not cause keyEvent.metaKey to be set.
231 * TODO (clchen, dmazzoni): Refactor this since there are edge cases
232 * where manually tracking key down and key up can fail (such as when
233 * the user switches tabs before letting go of the key being held).
237 cvox.ChromeVox.searchKeyHeld = false;
240 * The mutation observer that listens for chagnes to text controls
241 * that might not send other events.
242 * @type {MutationObserver}
245 cvox.ChromeVoxEventWatcher.textMutationObserver_ = null;
247 cvox.ChromeVoxEventWatcher.addEventListeners_(doc);
250 * The time when the last burst of subtree modified events started
254 cvox.ChromeVoxEventWatcher.lastSubtreeModifiedEventBurstTime_ = 0;
257 * The number of subtree modified events in the current burst.
261 cvox.ChromeVoxEventWatcher.subtreeModifiedEventsCount_ = 0;
266 * Stores state variables in a provided object.
268 * @param {Object} store The object.
270 cvox.ChromeVoxEventWatcher.storeOn = function(store) {
271 store['searchKeyHeld'] = cvox.ChromeVox.searchKeyHeld;
275 * Updates the object with state variables from an earlier storeOn call.
277 * @param {Object} store The object.
279 cvox.ChromeVoxEventWatcher.readFrom = function(store) {
280 cvox.ChromeVox.searchKeyHeld = store['searchKeyHeld'];
284 * Adds an event to the events queue and updates the time when the last
285 * event was received.
287 * @param {Event} evt The event to be added to the events queue.
288 * @param {boolean=} opt_ignoreVisibility Whether to ignore visibility
289 * checking on the document. By default, this is set to false (so an
290 * invisible document would result in this event not being added).
292 cvox.ChromeVoxEventWatcher.addEvent = function(evt, opt_ignoreVisibility) {
293 // Don't add any events to the events queue if ChromeVox is inactive or the
294 // page is hidden unless specified to not do so.
295 if (!cvox.ChromeVox.isActive ||
296 (document.webkitHidden && !opt_ignoreVisibility)) {
299 cvox.ChromeVoxEventWatcher.events_.push(evt);
300 cvox.ChromeVoxEventWatcher.lastEventTime = new Date().getTime();
301 if (cvox.ChromeVoxEventWatcher.firstUnprocessedEventTime == -1) {
302 cvox.ChromeVoxEventWatcher.firstUnprocessedEventTime = new Date().getTime();
304 if (!cvox.ChromeVoxEventWatcher.queueProcessingScheduled_) {
305 cvox.ChromeVoxEventWatcher.queueProcessingScheduled_ = true;
306 window.setTimeout(cvox.ChromeVoxEventWatcher.processQueue_,
307 cvox.ChromeVoxEventWatcher.WAIT_TIME_MS_);
312 * Adds a callback to be called when the event watcher has finished
313 * processing all pending events.
314 * @param {Function} cb The callback.
316 cvox.ChromeVoxEventWatcher.addReadyCallback = function(cb) {
317 cvox.ChromeVoxEventWatcher.readyCallbacks_.push(cb);
318 cvox.ChromeVoxEventWatcher.maybeCallReadyCallbacks_();
322 * Returns whether or not there are pending events.
323 * @return {boolean} Whether or not there are pending events.
326 cvox.ChromeVoxEventWatcher.hasPendingEvents_ = function() {
327 return cvox.ChromeVoxEventWatcher.firstUnprocessedEventTime != -1 ||
328 cvox.ChromeVoxEventWatcher.queueProcessingScheduled_;
333 * A bit used to make sure only one ready callback is pending at a time.
336 cvox.ChromeVoxEventWatcher.readyCallbackRunning_ = false;
339 * Checks if the event watcher has pending events. If not, call the oldest
340 * readyCallback in a loop until exhausted or until there are pending events.
343 cvox.ChromeVoxEventWatcher.maybeCallReadyCallbacks_ = function() {
344 if (!cvox.ChromeVoxEventWatcher.readyCallbackRunning_) {
345 cvox.ChromeVoxEventWatcher.readyCallbackRunning_ = true;
346 window.setTimeout(function() {
347 cvox.ChromeVoxEventWatcher.readyCallbackRunning_ = false;
348 if (!cvox.ChromeVoxEventWatcher.hasPendingEvents_() &&
349 !cvox.ChromeVoxEventWatcher.queueProcessingScheduled_ &&
350 cvox.ChromeVoxEventWatcher.readyCallbacks_.length > 0) {
351 cvox.ChromeVoxEventWatcher.readyCallbacks_.shift()();
352 cvox.ChromeVoxEventWatcher.maybeCallReadyCallbacks_();
360 * Add all of our event listeners to the document.
361 * @param {!Document|!Window} doc The DOM document to add event listeners to.
364 cvox.ChromeVoxEventWatcher.addEventListeners_ = function(doc) {
365 // We always need key down listeners to intercept activate/deactivate.
366 cvox.ChromeVoxEventWatcher.addEventListener_(doc,
367 'keydown', cvox.ChromeVoxEventWatcher.keyDownEventWatcher, true);
369 // If ChromeVox isn't active, skip all other event listeners.
370 if (!cvox.ChromeVox.isActive || cvox.ChromeVox.entireDocumentIsHidden) {
373 cvox.ChromeVoxEventWatcher.addEventListener_(doc,
374 'keypress', cvox.ChromeVoxEventWatcher.keyPressEventWatcher, true);
375 cvox.ChromeVoxEventWatcher.addEventListener_(doc,
376 'keyup', cvox.ChromeVoxEventWatcher.keyUpEventWatcher, true);
377 // Listen for our own events to handle public user commands if the web app
378 // doesn't do it for us.
379 cvox.ChromeVoxEventWatcher.addEventListener_(doc,
380 cvox.UserEventDetail.Category.JUMP,
381 cvox.ChromeVoxUserCommands.handleChromeVoxUserEvent,
384 cvox.ChromeVoxEventWatcher.addEventListener_(doc,
385 'focus', cvox.ChromeVoxEventWatcher.focusEventWatcher, true);
386 cvox.ChromeVoxEventWatcher.addEventListener_(doc,
387 'blur', cvox.ChromeVoxEventWatcher.blurEventWatcher, true);
388 cvox.ChromeVoxEventWatcher.addEventListener_(doc,
389 'change', cvox.ChromeVoxEventWatcher.changeEventWatcher, true);
390 cvox.ChromeVoxEventWatcher.addEventListener_(doc,
391 'copy', cvox.ChromeVoxEventWatcher.clipboardEventWatcher, true);
392 cvox.ChromeVoxEventWatcher.addEventListener_(doc,
393 'cut', cvox.ChromeVoxEventWatcher.clipboardEventWatcher, true);
394 cvox.ChromeVoxEventWatcher.addEventListener_(doc,
395 'paste', cvox.ChromeVoxEventWatcher.clipboardEventWatcher, true);
396 cvox.ChromeVoxEventWatcher.addEventListener_(doc,
397 'select', cvox.ChromeVoxEventWatcher.selectEventWatcher, true);
399 // TODO(dtseng): Experimental, see:
400 // https://developers.google.com/chrome/whitepapers/pagevisibility
401 cvox.ChromeVoxEventWatcher.addEventListener_(doc, 'webkitvisibilitychange',
402 cvox.ChromeVoxEventWatcher.visibilityChangeWatcher, true);
403 cvox.ChromeVoxEventWatcher.events_ = new Array();
404 cvox.ChromeVoxEventWatcher.queueProcessingScheduled_ = false;
406 // Handle mouse events directly without going into the events queue.
407 cvox.ChromeVoxEventWatcher.addEventListener_(doc,
408 'mouseover', cvox.ChromeVoxEventWatcher.mouseOverEventWatcher, true);
409 cvox.ChromeVoxEventWatcher.addEventListener_(doc,
410 'mouseout', cvox.ChromeVoxEventWatcher.mouseOutEventWatcher, true);
412 // With the exception of non-Android, click events go through the event queue.
413 cvox.ChromeVoxEventWatcher.addEventListener_(doc,
414 'click', cvox.ChromeVoxEventWatcher.mouseClickEventWatcher, true);
416 if (typeof(window.WebKitMutationObserver) != 'undefined') {
417 cvox.ChromeVoxEventWatcher.mutationObserver_ =
418 new window.WebKitMutationObserver(
419 cvox.ChromeVoxEventWatcher.mutationHandler);
420 var observerTarget = null;
421 if (doc.documentElement) {
422 observerTarget = doc.documentElement;
423 } else if (doc.document && doc.document.documentElement) {
424 observerTarget = doc.document.documentElement;
426 if (observerTarget) {
427 cvox.ChromeVoxEventWatcher.mutationObserver_.observe(
429 /** @type {!MutationObserverInit} */ ({
434 attributeOldValue: true,
435 characterDataOldValue: true
439 cvox.ChromeVoxEventWatcher.addEventListener_(doc, 'DOMSubtreeModified',
440 cvox.ChromeVoxEventWatcher.subtreeModifiedEventWatcher, true);
446 * Remove all registered event watchers.
447 * @param {!Document|!Window} doc The DOM document to add event listeners to.
449 cvox.ChromeVoxEventWatcher.cleanup = function(doc) {
450 for (var i = 0; i < cvox.ChromeVoxEventWatcher.listeners_.length; i++) {
451 var listener = cvox.ChromeVoxEventWatcher.listeners_[i];
452 doc.removeEventListener(
453 listener.type, listener.listener, listener.useCapture);
455 cvox.ChromeVoxEventWatcher.listeners_ = [];
456 if (cvox.ChromeVoxEventWatcher.currentDateHandler) {
457 cvox.ChromeVoxEventWatcher.currentDateHandler.shutdown();
459 if (cvox.ChromeVoxEventWatcher.currentTimeHandler) {
460 cvox.ChromeVoxEventWatcher.currentTimeHandler.shutdown();
462 if (cvox.ChromeVoxEventWatcher.currentMediaHandler) {
463 cvox.ChromeVoxEventWatcher.currentMediaHandler.shutdown();
465 if (cvox.ChromeVoxEventWatcher.mutationObserver_) {
466 cvox.ChromeVoxEventWatcher.mutationObserver_.disconnect();
468 cvox.ChromeVoxEventWatcher.mutationObserver_ = null;
472 * Add one event listener and save the data so it can be removed later.
473 * @param {!Document|!Window} doc The DOM document to add event listeners to.
474 * @param {string} type The event type.
475 * @param {EventListener|function(Event):(boolean|undefined)} listener
476 * The function to be called when the event is fired.
477 * @param {boolean} useCapture Whether this listener should capture events
478 * before they're sent to targets beneath it in the DOM tree.
481 cvox.ChromeVoxEventWatcher.addEventListener_ = function(doc, type,
482 listener, useCapture) {
483 cvox.ChromeVoxEventWatcher.listeners_.push(
484 {'type': type, 'listener': listener, 'useCapture': useCapture});
485 doc.addEventListener(type, listener, useCapture);
489 * Return the last focused node.
490 * @return {Object} The last node that was focused.
492 cvox.ChromeVoxEventWatcher.getLastFocusedNode = function() {
493 return cvox.ChromeVoxEventWatcher.lastFocusedNode;
497 * Sets the last focused node.
498 * @param {Element} element The last focused element.
502 cvox.ChromeVoxEventWatcher.setLastFocusedNode_ = function(element) {
503 cvox.ChromeVoxEventWatcher.lastFocusedNode = element;
504 cvox.ChromeVoxEventWatcher.lastFocusedNodeValue = !element ? null :
505 cvox.DomUtil.getControlValueAndStateString(element);
509 * Called when there's any mutation of the document. We use this to
510 * handle live region updates.
511 * @param {Array.<MutationRecord>} mutations The mutations.
512 * @return {boolean} True if the default action should be performed.
514 cvox.ChromeVoxEventWatcher.mutationHandler = function(mutations) {
515 if (cvox.ChromeVoxEventSuspender.areEventsSuspended()) {
519 cvox.ChromeVox.navigationManager.updateIndicatorIfChanged();
521 cvox.LiveRegions.processMutations(
523 function(assertive, navDescriptions) {
524 var evt = new window.Event('LiveRegion');
525 evt.navDescriptions = navDescriptions;
526 evt.assertive = assertive;
527 cvox.ChromeVoxEventWatcher.addEvent(evt, true);
534 * Handles mouseclick events.
535 * Mouseclick events are only triggered if the user touches the mouse;
536 * we use it to determine whether or not we should bother trying to sync to a
538 * @param {Event} evt The mouseclick event to process.
539 * @return {boolean} True if the default action should be performed.
541 cvox.ChromeVoxEventWatcher.mouseClickEventWatcher = function(evt) {
546 if (cvox.ChromeVox.host.mustRedispatchClickEvent()) {
547 cvox.ChromeVoxUserCommands.wasMouseClicked = true;
548 evt.stopPropagation();
549 evt.preventDefault();
550 // Since the click event was caught and we are re-dispatching it, we also
551 // need to refocus the current node because the current node has already
552 // been blurred by the window getting the click event in the first place.
553 // Failing to restore focus before clicking can cause odd problems such as
554 // the soft IME not coming up in Android (it only shows up if the click
555 // happens in a focused text field).
556 cvox.Focuser.setFocus(cvox.ChromeVox.navigationManager.getCurrentNode());
557 cvox.ChromeVox.tts.speak(
558 cvox.ChromeVox.msgs.getMsg('element_clicked'),
559 cvox.ChromeVoxEventWatcher.queueMode_(),
560 cvox.AbstractTts.PERSONALITY_ANNOTATION);
561 var targetNode = cvox.ChromeVox.navigationManager.getCurrentNode();
562 // If the targetNode has a defined onclick function, just call it directly
563 // rather than try to generate a click event and dispatching it.
564 // While both work equally well on standalone Chrome, when dealing with
565 // embedded WebViews, generating a click event and sending it is not always
566 // reliable since the framework may swallow the event.
567 cvox.DomUtil.clickElem(targetNode, false, true);
570 cvox.ChromeVoxEventWatcher.addEvent(evt);
572 cvox.ChromeVoxUserCommands.wasMouseClicked = true;
577 * Handles mouseover events.
578 * Mouseover events are only triggered if the user touches the mouse, so
579 * for users who only use the keyboard, this will have no effect.
581 * @param {Event} evt The mouseover event to process.
582 * @return {boolean} True if the default action should be performed.
584 cvox.ChromeVoxEventWatcher.mouseOverEventWatcher = function(evt) {
585 // Chrome simulates the meta key for mouse events generated from
586 // touch exploration.
587 var isTouchEvent = (evt.metaKey);
589 var mouseoverDelayMs = cvox.ChromeVoxEventWatcher.mouseoverDelayMs;
591 mouseoverDelayMs = 0;
592 } else if (!cvox.ChromeVoxEventWatcher.focusFollowsMouse) {
596 if (cvox.DomUtil.isDescendantOfNode(
597 cvox.ChromeVoxEventWatcher.announcedMouseOverNode, evt.target)) {
601 if (evt.target == cvox.ChromeVoxEventWatcher.pendingMouseOverNode) {
605 cvox.ChromeVoxEventWatcher.pendingMouseOverNode = evt.target;
606 if (cvox.ChromeVoxEventWatcher.mouseOverTimeoutId) {
607 window.clearTimeout(cvox.ChromeVoxEventWatcher.mouseOverTimeoutId);
608 cvox.ChromeVoxEventWatcher.mouseOverTimeoutId = null;
611 if (evt.target.tagName && (evt.target.tagName == 'BODY')) {
612 cvox.ChromeVoxEventWatcher.pendingMouseOverNode = null;
613 cvox.ChromeVoxEventWatcher.announcedMouseOverNode = null;
617 // Only focus and announce if the mouse stays over the same target
618 // for longer than the given delay.
619 cvox.ChromeVoxEventWatcher.mouseOverTimeoutId = window.setTimeout(
621 cvox.ChromeVoxEventWatcher.mouseOverTimeoutId = null;
622 if (evt.target != cvox.ChromeVoxEventWatcher.pendingMouseOverNode) {
625 cvox.ChromeVoxEventWatcher.shouldFlushNextUtterance = true;
626 cvox.ChromeVox.navigationManager.stopReading(true);
627 var target = /** @type {Node} */(evt.target);
628 cvox.Focuser.setFocus(target);
629 cvox.ApiImplementation.syncToNode(
630 target, true, cvox.ChromeVoxEventWatcher.queueMode_());
631 cvox.ChromeVoxEventWatcher.announcedMouseOverNode = target;
632 }, mouseoverDelayMs);
638 * Handles mouseout events.
640 * @param {Event} evt The mouseout event to process.
641 * @return {boolean} True if the default action should be performed.
643 cvox.ChromeVoxEventWatcher.mouseOutEventWatcher = function(evt) {
644 if (evt.target == cvox.ChromeVoxEventWatcher.pendingMouseOverNode) {
645 cvox.ChromeVoxEventWatcher.pendingMouseOverNode = null;
646 if (cvox.ChromeVoxEventWatcher.mouseOverTimeoutId) {
647 window.clearTimeout(cvox.ChromeVoxEventWatcher.mouseOverTimeoutId);
648 cvox.ChromeVoxEventWatcher.mouseOverTimeoutId = null;
657 * Watches for focus events.
659 * @param {Event} evt The focus event to add to the queue.
660 * @return {boolean} True if the default action should be performed.
662 cvox.ChromeVoxEventWatcher.focusEventWatcher = function(evt) {
663 // First remove any dummy spans. We create dummy spans in UserCommands in
664 // order to sync the browser's default tab action with the user's current
665 // navigation position.
666 cvox.ChromeVoxUserCommands.removeTabDummySpan();
668 if (!cvox.ChromeVoxEventSuspender.areEventsSuspended()) {
669 cvox.ChromeVoxEventWatcher.addEvent(evt);
670 } else if (evt.target && evt.target.nodeType == Node.ELEMENT_NODE) {
671 cvox.ChromeVoxEventWatcher.setLastFocusedNode_(
672 /** @type {Element} */(evt.target));
678 * Handles for focus events passed to it from the events queue.
680 * @param {Event} evt The focus event to handle.
682 cvox.ChromeVoxEventWatcher.focusHandler = function(evt) {
684 evt.target.hasAttribute &&
685 evt.target.getAttribute('aria-hidden') == 'true' &&
686 evt.target.getAttribute('chromevoxignoreariahidden') != 'true') {
687 cvox.ChromeVoxEventWatcher.setLastFocusedNode_(null);
688 cvox.ChromeVoxEventWatcher.setUpTextHandler();
691 if (evt.target && evt.target != window) {
692 var target = /** @type {Element} */(evt.target);
693 var parentControl = cvox.DomUtil.getSurroundingControl(target);
695 parentControl == cvox.ChromeVoxEventWatcher.lastFocusedNode) {
696 cvox.ChromeVoxEventWatcher.handleControlChanged(target);
701 cvox.ChromeVoxEventWatcher.setLastFocusedNode_(
702 /** @type {Element} */(parentControl));
704 cvox.ChromeVoxEventWatcher.setLastFocusedNode_(target);
707 var queueMode = cvox.ChromeVoxEventWatcher.queueMode_();
709 if (cvox.ChromeVoxEventWatcher.getInitialVisibility() ||
710 cvox.ChromeVoxEventWatcher.handleDialogFocus(target)) {
711 queueMode = cvox.AbstractTts.QUEUE_MODE_QUEUE;
714 if (cvox.ChromeVox.navigationManager.clearPageSel(true)) {
715 queueMode = cvox.AbstractTts.QUEUE_MODE_QUEUE;
718 // Navigate to this control so that it will be the same for focus as for
719 // regular navigation.
720 cvox.ApiImplementation.syncToNode(
721 target, !document.webkitHidden, queueMode);
723 if ((evt.target.constructor == HTMLVideoElement) ||
724 (evt.target.constructor == HTMLAudioElement)) {
725 cvox.ChromeVoxEventWatcher.setUpMediaHandler_();
728 if (evt.target.hasAttribute) {
729 var inputType = evt.target.getAttribute('type');
732 cvox.ChromeVoxEventWatcher.setUpTimeHandler_();
737 cvox.ChromeVoxEventWatcher.setUpDateHandler_();
741 cvox.ChromeVoxEventWatcher.setUpTextHandler();
743 cvox.ChromeVoxEventWatcher.setLastFocusedNode_(null);
749 * Watches for blur events.
751 * @param {Event} evt The blur event to add to the queue.
752 * @return {boolean} True if the default action should be performed.
754 cvox.ChromeVoxEventWatcher.blurEventWatcher = function(evt) {
755 window.setTimeout(function() {
756 if (!document.activeElement) {
757 cvox.ChromeVoxEventWatcher.setLastFocusedNode_(null);
758 cvox.ChromeVoxEventWatcher.addEvent(evt);
765 * Watches for key down events.
767 * @param {Event} evt The keydown event to add to the queue.
768 * @return {boolean} True if the default action should be performed.
770 cvox.ChromeVoxEventWatcher.keyDownEventWatcher = function(evt) {
771 cvox.ChromeVoxEventWatcher.shouldFlushNextUtterance = true;
773 if (cvox.ChromeVox.passThroughMode) {
777 if (cvox.ChromeVox.isChromeOS && evt.keyCode == 91) {
778 cvox.ChromeVox.searchKeyHeld = true;
781 // Store some extra ChromeVox-specific properties in the event.
783 cvox.ChromeVox.searchKeyHeld && cvox.ChromeVox.isActive;
784 evt.stickyMode = cvox.ChromeVox.isStickyModeOn() && cvox.ChromeVox.isActive;
785 evt.keyPrefix = cvox.ChromeVox.keyPrefixOn && cvox.ChromeVox.isActive;
787 cvox.ChromeVox.keyPrefixOn = false;
789 cvox.ChromeVoxEventWatcher.eventToEat = null;
790 if (!cvox.ChromeVoxKbHandler.basicKeyDownActionsListener(evt) ||
791 cvox.ChromeVoxEventWatcher.handleControlAction(evt)) {
792 // Swallow the event immediately to prevent the arrow keys
793 // from driving controls on the web page.
794 evt.preventDefault();
795 evt.stopPropagation();
796 // Also mark this as something to be swallowed when the followup
797 // keypress/keyup counterparts to this event show up later.
798 cvox.ChromeVoxEventWatcher.eventToEat = evt;
801 cvox.ChromeVoxEventWatcher.addEvent(evt);
806 * Watches for key up events.
808 * @param {Event} evt The event to add to the queue.
809 * @return {boolean} True if the default action should be performed.
810 * @this {cvox.ChromeVoxEventWatcher}
812 cvox.ChromeVoxEventWatcher.keyUpEventWatcher = function(evt) {
813 if (evt.keyCode == 91) {
814 cvox.ChromeVox.searchKeyHeld = false;
817 if (cvox.ChromeVox.passThroughMode) {
818 if (!evt.ctrlKey && !evt.altKey && !evt.metaKey && !evt.shiftKey &&
819 !cvox.ChromeVox.searchKeyHeld) {
820 // Only reset pass through on the second key up without modifiers since
821 // the first one is from the pass through shortcut itself.
822 if (this.secondPassThroughKeyUp_) {
823 this.secondPassThroughKeyUp_ = false;
824 cvox.ChromeVox.passThroughMode = false;
826 this.secondPassThroughKeyUp_ = true;
832 if (cvox.ChromeVoxEventWatcher.eventToEat &&
833 evt.keyCode == cvox.ChromeVoxEventWatcher.eventToEat.keyCode) {
834 evt.stopPropagation();
835 evt.preventDefault();
839 cvox.ChromeVoxEventWatcher.addEvent(evt);
845 * Watches for key press events.
847 * @param {Event} evt The event to add to the queue.
848 * @return {boolean} True if the default action should be performed.
850 cvox.ChromeVoxEventWatcher.keyPressEventWatcher = function(evt) {
851 var url = document.location.href;
852 // Use ChromeVox.typingEcho as default value.
853 var speakChar = cvox.TypingEcho.shouldSpeakChar(cvox.ChromeVox.typingEcho);
855 if (typeof cvox.ChromeVox.keyEcho[url] !== 'undefined') {
856 speakChar = cvox.ChromeVox.keyEcho[url];
859 // Directly handle typed characters here while key echo is on. This
860 // skips potentially costly computations (especially for content editable).
861 // This is done deliberately for the sake of responsiveness and in some cases
862 // (e.g. content editable), to have characters echoed properly.
863 if (cvox.ChromeVoxEditableTextBase.eventTypingEcho && (speakChar &&
864 cvox.DomPredicates.editTextPredicate([document.activeElement])) &&
865 document.activeElement.type !== 'password') {
866 cvox.ChromeVox.tts.speak(String.fromCharCode(evt.charCode), 0);
868 cvox.ChromeVoxEventWatcher.addEvent(evt);
869 if (cvox.ChromeVoxEventWatcher.eventToEat &&
870 evt.keyCode == cvox.ChromeVoxEventWatcher.eventToEat.keyCode) {
871 evt.preventDefault();
872 evt.stopPropagation();
879 * Watches for change events.
881 * @param {Event} evt The event to add to the queue.
882 * @return {boolean} True if the default action should be performed.
884 cvox.ChromeVoxEventWatcher.changeEventWatcher = function(evt) {
885 cvox.ChromeVoxEventWatcher.addEvent(evt);
889 // TODO(dtseng): ChromeVoxEditableText interrupts cut and paste announcements.
891 * Watches for cut, copy, and paste events.
893 * @param {Event} evt The event to process.
894 * @return {boolean} True if the default action should be performed.
896 cvox.ChromeVoxEventWatcher.clipboardEventWatcher = function(evt) {
897 cvox.ChromeVox.tts.speak(cvox.ChromeVox.msgs.getMsg(evt.type).toLowerCase());
901 text = evt.clipboardData.getData('text');
905 text = window.getSelection().toString();
908 cvox.ChromeVox.tts.speak(text, cvox.AbstractTts.QUEUE_MODE_QUEUE);
909 cvox.ChromeVox.navigationManager.clearPageSel();
914 * Handles change events passed to it from the events queue.
916 * @param {Event} evt The event to handle.
918 cvox.ChromeVoxEventWatcher.changeHandler = function(evt) {
919 if (cvox.ChromeVoxEventWatcher.setUpTextHandler()) {
922 if (document.activeElement == evt.target) {
923 cvox.ChromeVoxEventWatcher.handleControlChanged(document.activeElement);
928 * Watches for select events.
930 * @param {Event} evt The event to add to the queue.
931 * @return {boolean} True if the default action should be performed.
933 cvox.ChromeVoxEventWatcher.selectEventWatcher = function(evt) {
934 cvox.ChromeVoxEventWatcher.addEvent(evt);
939 * Watches for DOM subtree modified events.
941 * @param {Event} evt The event to add to the queue.
942 * @return {boolean} True if the default action should be performed.
944 cvox.ChromeVoxEventWatcher.subtreeModifiedEventWatcher = function(evt) {
945 if (!evt || !evt.target) {
948 cvox.ChromeVoxEventWatcher.addEvent(evt);
953 * Listens for WebKit visibility change events.
955 cvox.ChromeVoxEventWatcher.visibilityChangeWatcher = function() {
956 cvox.ChromeVoxEventWatcher.initialVisibility = !document.webkitHidden;
957 if (document.webkitHidden) {
958 cvox.ChromeVox.navigationManager.stopReading(true);
963 * Gets the initial visibility of the page.
964 * @return {boolean} True if the page is visible and this is the first request
965 * for visibility state.
967 cvox.ChromeVoxEventWatcher.getInitialVisibility = function() {
968 var ret = cvox.ChromeVoxEventWatcher.initialVisibility;
969 cvox.ChromeVoxEventWatcher.initialVisibility = false;
974 * Speaks the text of one live region.
975 * @param {boolean} assertive True if it's an assertive live region.
976 * @param {Array.<cvox.NavDescription>} messages An array of navDescriptions
977 * representing the description of the live region changes.
980 cvox.ChromeVoxEventWatcher.speakLiveRegion_ = function(
981 assertive, messages) {
982 var queueMode = cvox.ChromeVoxEventWatcher.queueMode_();
983 var descSpeaker = new cvox.NavigationSpeaker();
984 descSpeaker.speakDescriptionArray(messages, queueMode, null);
988 * Handles DOM subtree modified events passed to it from the events queue.
989 * If the change involves an ARIA live region, then speak it.
991 * @param {Event} evt The event to handle.
993 cvox.ChromeVoxEventWatcher.subtreeModifiedHandler = function(evt) {
994 // Subtree modified events can happen in bursts. If several events happen at
995 // the same time, trying to process all of them will slow ChromeVox to
996 // a crawl and make the page itself unresponsive (ie, Google+).
997 // Before processing subtree modified events, make sure that it is not part of
998 // a large burst of events.
999 // TODO (clchen): Revisit this after the DOM mutation events are
1000 // available in Chrome.
1001 var currentTime = new Date().getTime();
1003 if ((cvox.ChromeVoxEventWatcher.lastSubtreeModifiedEventBurstTime_ +
1004 cvox.ChromeVoxEventWatcher.SUBTREE_MODIFIED_BURST_DURATION_) >
1006 cvox.ChromeVoxEventWatcher.subtreeModifiedEventsCount_++;
1007 if (cvox.ChromeVoxEventWatcher.subtreeModifiedEventsCount_ >
1008 cvox.ChromeVoxEventWatcher.SUBTREE_MODIFIED_BURST_COUNT_LIMIT_) {
1012 cvox.ChromeVoxEventWatcher.lastSubtreeModifiedEventBurstTime_ = currentTime;
1013 cvox.ChromeVoxEventWatcher.subtreeModifiedEventsCount_ = 1;
1016 if (!evt || !evt.target) {
1019 var target = /** @type {Element} */ (evt.target);
1020 var regions = cvox.AriaUtil.getLiveRegions(target);
1021 for (var i = 0; (i < regions.length) &&
1022 (i < cvox.ChromeVoxEventWatcher.MAX_LIVE_REGIONS_); i++) {
1023 cvox.LiveRegionsDeprecated.updateLiveRegion(
1024 regions[i], cvox.ChromeVoxEventWatcher.queueMode_(), false);
1029 * Sets up the text handler.
1030 * @return {boolean} True if an editable text control has focus.
1032 cvox.ChromeVoxEventWatcher.setUpTextHandler = function() {
1033 var currentFocus = document.activeElement;
1035 currentFocus.hasAttribute &&
1036 currentFocus.getAttribute('aria-hidden') == 'true' &&
1037 currentFocus.getAttribute('chromevoxignoreariahidden') != 'true') {
1038 currentFocus = null;
1041 if (currentFocus != cvox.ChromeVoxEventWatcher.currentTextControl) {
1042 if (cvox.ChromeVoxEventWatcher.currentTextControl) {
1043 cvox.ChromeVoxEventWatcher.currentTextControl.removeEventListener(
1044 'input', cvox.ChromeVoxEventWatcher.changeEventWatcher, false);
1045 cvox.ChromeVoxEventWatcher.currentTextControl.removeEventListener(
1046 'click', cvox.ChromeVoxEventWatcher.changeEventWatcher, false);
1047 if (cvox.ChromeVoxEventWatcher.textMutationObserver_) {
1048 cvox.ChromeVoxEventWatcher.textMutationObserver_.disconnect();
1049 cvox.ChromeVoxEventWatcher.textMutationObserver_ = null;
1052 cvox.ChromeVoxEventWatcher.currentTextControl = null;
1053 if (cvox.ChromeVoxEventWatcher.currentTextHandler) {
1054 cvox.ChromeVoxEventWatcher.currentTextHandler.teardown();
1055 cvox.ChromeVoxEventWatcher.currentTextHandler = null;
1057 if (currentFocus == null) {
1060 if (currentFocus.constructor == HTMLInputElement &&
1061 cvox.DomUtil.isInputTypeText(currentFocus) &&
1062 cvox.ChromeVoxEventWatcher.shouldEchoKeys) {
1063 cvox.ChromeVoxEventWatcher.currentTextControl = currentFocus;
1064 cvox.ChromeVoxEventWatcher.currentTextHandler =
1065 new cvox.ChromeVoxEditableHTMLInput(currentFocus, cvox.ChromeVox.tts);
1066 } else if ((currentFocus.constructor == HTMLTextAreaElement) &&
1067 cvox.ChromeVoxEventWatcher.shouldEchoKeys) {
1068 cvox.ChromeVoxEventWatcher.currentTextControl = currentFocus;
1069 cvox.ChromeVoxEventWatcher.currentTextHandler =
1070 new cvox.ChromeVoxEditableTextArea(currentFocus, cvox.ChromeVox.tts);
1071 } else if (currentFocus.isContentEditable ||
1072 currentFocus.getAttribute('role') == 'textbox') {
1073 cvox.ChromeVoxEventWatcher.currentTextControl = currentFocus;
1074 cvox.ChromeVoxEventWatcher.currentTextHandler =
1075 new cvox.ChromeVoxEditableContentEditable(currentFocus,
1076 cvox.ChromeVox.tts);
1079 if (cvox.ChromeVoxEventWatcher.currentTextControl) {
1080 cvox.ChromeVoxEventWatcher.currentTextControl.addEventListener(
1081 'input', cvox.ChromeVoxEventWatcher.changeEventWatcher, false);
1082 cvox.ChromeVoxEventWatcher.currentTextControl.addEventListener(
1083 'click', cvox.ChromeVoxEventWatcher.changeEventWatcher, false);
1084 if (window.WebKitMutationObserver) {
1085 cvox.ChromeVoxEventWatcher.textMutationObserver_ =
1086 new window.WebKitMutationObserver(
1087 cvox.ChromeVoxEventWatcher.onTextMutation);
1088 cvox.ChromeVoxEventWatcher.textMutationObserver_.observe(
1089 cvox.ChromeVoxEventWatcher.currentTextControl,
1090 /** @type {!MutationObserverInit} */ ({
1094 attributeOldValue: false,
1095 characterDataOldValue: false
1098 if (!cvox.ChromeVoxEventSuspender.areEventsSuspended()) {
1099 cvox.ChromeVox.navigationManager.updateSel(
1100 cvox.CursorSelection.fromNode(
1101 cvox.ChromeVoxEventWatcher.currentTextControl));
1105 return (null != cvox.ChromeVoxEventWatcher.currentTextHandler);
1110 * Speaks updates to editable text controls as needed.
1112 * @param {boolean} isKeypress Was this change triggered by a keypress?
1113 * @return {boolean} True if an editable text control has focus.
1115 cvox.ChromeVoxEventWatcher.handleTextChanged = function(isKeypress) {
1116 if (cvox.ChromeVoxEventWatcher.currentTextHandler) {
1117 var handler = cvox.ChromeVoxEventWatcher.currentTextHandler;
1118 var shouldFlush = false;
1119 if (isKeypress && cvox.ChromeVoxEventWatcher.shouldFlushNextUtterance) {
1121 cvox.ChromeVoxEventWatcher.shouldFlushNextUtterance = false;
1123 handler.update(shouldFlush);
1124 cvox.ChromeVoxEventWatcher.shouldFlushNextUtterance = false;
1131 * Called when an editable text control has focus, because many changes
1132 * to a text box don't ever generate events - e.g. if the page's javascript
1133 * changes the contents of the text box after some delay, or if it's
1134 * contentEditable or a generic div with role="textbox".
1136 cvox.ChromeVoxEventWatcher.onTextMutation = function() {
1137 if (cvox.ChromeVoxEventWatcher.currentTextHandler) {
1138 window.setTimeout(function() {
1139 cvox.ChromeVoxEventWatcher.handleTextChanged(false);
1140 }, cvox.ChromeVoxEventWatcher.MAX_WAIT_TIME_MS_);
1145 * Speaks updates to other form controls as needed.
1146 * @param {Element} control The target control.
1148 cvox.ChromeVoxEventWatcher.handleControlChanged = function(control) {
1149 var newValue = cvox.DomUtil.getControlValueAndStateString(control);
1150 var parentControl = cvox.DomUtil.getSurroundingControl(control);
1151 var announceChange = false;
1153 if (control != cvox.ChromeVoxEventWatcher.lastFocusedNode &&
1154 (parentControl == null ||
1155 parentControl != cvox.ChromeVoxEventWatcher.lastFocusedNode)) {
1156 cvox.ChromeVoxEventWatcher.setLastFocusedNode_(control);
1157 } else if (newValue == cvox.ChromeVoxEventWatcher.lastFocusedNodeValue) {
1161 cvox.ChromeVoxEventWatcher.lastFocusedNodeValue = newValue;
1162 if (cvox.DomPredicates.checkboxPredicate([control]) ||
1163 cvox.DomPredicates.radioPredicate([control])) {
1164 // Always announce changes to checkboxes and radio buttons.
1165 announceChange = true;
1166 // Play earcons for checkboxes and radio buttons
1167 if (control.checked) {
1168 cvox.ChromeVox.earcons.playEarcon(cvox.AbstractEarcons.CHECK_ON);
1170 cvox.ChromeVox.earcons.playEarcon(cvox.AbstractEarcons.CHECK_OFF);
1174 if (control.tagName == 'SELECT') {
1175 announceChange = true;
1178 if (control.tagName == 'INPUT') {
1179 switch (control.type) {
1182 case 'datetime-local':
1184 announceChange = true;
1191 // Always announce changes for anything with an ARIA role.
1192 if (control.hasAttribute && control.hasAttribute('role')) {
1193 announceChange = true;
1196 if ((parentControl &&
1197 parentControl != control &&
1198 document.activeElement == control)) {
1199 // If focus has been set on a child of the parent control, we need to
1200 // sync to that node so that ChromeVox navigation will be in sync with
1201 // focus navigation.
1202 cvox.ApiImplementation.syncToNode(
1204 cvox.ChromeVoxEventWatcher.queueMode_());
1205 announceChange = false;
1206 } else if (cvox.AriaUtil.getActiveDescendant(control)) {
1207 cvox.ChromeVox.navigationManager.updateSelToArbitraryNode(
1208 cvox.AriaUtil.getActiveDescendant(control),
1211 announceChange = true;
1214 if (announceChange && !cvox.ChromeVoxEventSuspender.areEventsSuspended()) {
1215 cvox.ChromeVox.tts.speak(newValue,
1216 cvox.ChromeVoxEventWatcher.queueMode_(),
1218 cvox.NavBraille.fromText(newValue).write();
1223 * Handle actions on form controls triggered by key presses.
1224 * @param {Object} evt The event.
1225 * @return {boolean} True if this key event was handled.
1227 cvox.ChromeVoxEventWatcher.handleControlAction = function(evt) {
1228 // Ignore the control action if ChromeVox is not active.
1229 if (!cvox.ChromeVox.isActive) {
1232 var control = evt.target;
1234 if (control.tagName == 'SELECT' && (control.size <= 1) &&
1235 (evt.keyCode == 13 || evt.keyCode == 32)) { // Enter or Space
1236 // TODO (dmazzoni, clchen): Remove this workaround once accessibility
1237 // APIs make browser based popups accessible.
1239 // Do nothing, but eat this keystroke when the SELECT control
1240 // has a dropdown style since if we don't, it will generate
1241 // a browser popup menu which is not accessible.
1242 // List style SELECT controls are fine and don't need this workaround.
1243 evt.preventDefault();
1244 evt.stopPropagation();
1248 if (control.tagName == 'INPUT' && control.type == 'range') {
1249 var value = parseFloat(control.value);
1251 if (control.step && control.step > 0.0) {
1252 step = control.step;
1253 } else if (control.min && control.max) {
1254 var range = (control.max - control.min);
1255 if (range > 2 && range < 31) {
1258 step = (control.max - control.min) / 10;
1264 if (evt.keyCode == 37 || evt.keyCode == 38) { // left or up
1266 } else if (evt.keyCode == 39 || evt.keyCode == 40) { // right or down
1270 if (control.max && value > control.max) {
1271 value = control.max;
1273 if (control.min && value < control.min) {
1274 value = control.min;
1277 control.value = value;
1283 * When an element receives focus, see if we've entered or left a dialog
1284 * and return a string describing the event.
1286 * @param {Element} target The element that just received focus.
1287 * @return {boolean} True if an announcement was spoken.
1289 cvox.ChromeVoxEventWatcher.handleDialogFocus = function(target) {
1290 var dialog = target;
1293 if (dialog.hasAttribute) {
1294 role = dialog.getAttribute('role');
1295 if (role == 'dialog' || role == 'alertdialog') {
1299 dialog = dialog.parentElement;
1302 if (dialog == cvox.ChromeVox.navigationManager.currentDialog) {
1306 if (cvox.ChromeVox.navigationManager.currentDialog && !dialog) {
1307 if (!cvox.DomUtil.isDescendantOfNode(
1308 document.activeElement,
1309 cvox.ChromeVox.navigationManager.currentDialog)) {
1310 cvox.ChromeVox.navigationManager.currentDialog = null;
1312 cvox.ChromeVox.tts.speak(
1313 cvox.ChromeVox.msgs.getMsg('exiting_dialog'),
1314 cvox.ChromeVoxEventWatcher.queueMode_(),
1315 cvox.AbstractTts.PERSONALITY_ANNOTATION);
1320 cvox.ChromeVox.navigationManager.currentDialog = dialog;
1321 cvox.ChromeVox.tts.speak(
1322 cvox.ChromeVox.msgs.getMsg('entering_dialog'),
1323 cvox.ChromeVoxEventWatcher.queueMode_(),
1324 cvox.AbstractTts.PERSONALITY_ANNOTATION);
1325 if (role == 'alertdialog') {
1326 var dialogDescArray =
1327 cvox.DescriptionUtil.getFullDescriptionsFromChildren(null, dialog);
1328 var descSpeaker = new cvox.NavigationSpeaker();
1329 descSpeaker.speakDescriptionArray(dialogDescArray,
1330 cvox.AbstractTts.QUEUE_MODE_QUEUE,
1340 * Returns true if we should wait to process events.
1341 * @param {number} lastFocusTimestamp The timestamp of the last focus event.
1342 * @param {number} firstTimestamp The timestamp of the first event.
1343 * @param {number} currentTime The current timestamp.
1344 * @return {boolean} True if we should wait to process events.
1346 cvox.ChromeVoxEventWatcherUtil.shouldWaitToProcess = function(
1347 lastFocusTimestamp, firstTimestamp, currentTime) {
1348 var timeSinceFocusEvent = currentTime - lastFocusTimestamp;
1349 var timeSinceFirstEvent = currentTime - firstTimestamp;
1350 return timeSinceFocusEvent < cvox.ChromeVoxEventWatcher.WAIT_TIME_MS_ &&
1351 timeSinceFirstEvent < cvox.ChromeVoxEventWatcher.MAX_WAIT_TIME_MS_;
1356 * Returns the queue mode to use for the next utterance spoken as
1357 * a result of an event or navigation. The first utterance that's spoken
1358 * after an explicit user action like a key press will flush, and
1359 * subsequent events will return a category flush.
1360 * @return {number} Either QUEUE_MODE_FLUSH or QUEUE_MODE_QUEUE.
1363 cvox.ChromeVoxEventWatcher.queueMode_ = function() {
1364 if (cvox.ChromeVoxEventWatcher.shouldFlushNextUtterance) {
1365 cvox.ChromeVoxEventWatcher.shouldFlushNextUtterance = false;
1366 return cvox.AbstractTts.QUEUE_MODE_FLUSH;
1368 return cvox.AbstractTts.QUEUE_MODE_CATEGORY_FLUSH;
1373 * Processes the events queue.
1377 cvox.ChromeVoxEventWatcher.processQueue_ = function() {
1378 // Return now if there are no events in the queue.
1379 if (cvox.ChromeVoxEventWatcher.events_.length == 0) {
1383 // Look for the most recent focus event and delete any preceding event
1384 // that applied to whatever was focused previously.
1385 var events = cvox.ChromeVoxEventWatcher.events_;
1386 var lastFocusIndex = -1;
1387 var lastFocusTimestamp = 0;
1390 for (i = 0; evt = events[i]; i++) {
1391 if (evt.type == 'focus') {
1393 lastFocusTimestamp = evt.timeStamp;
1396 cvox.ChromeVoxEventWatcher.events_ = [];
1397 for (i = 0; evt = events[i]; i++) {
1398 var prevEvt = events[i - 1] || {};
1399 if ((i >= lastFocusIndex || evt.type == 'LiveRegion' ||
1400 evt.type == 'DOMSubtreeModified') &&
1401 (prevEvt.type != 'focus' || evt.type != 'change')) {
1402 cvox.ChromeVoxEventWatcher.events_.push(evt);
1406 cvox.ChromeVoxEventWatcher.events_.sort(function(a, b) {
1407 if (b.type != 'LiveRegion' && a.type == 'LiveRegion') {
1410 if (b.type != 'DOMSubtreeModified' && a.type == 'DOMSubtreeModified') {
1416 // If the most recent focus event was very recent, wait for things to
1417 // settle down before processing events, unless the max wait time has
1419 var currentTime = new Date().getTime();
1420 if (lastFocusIndex >= 0 &&
1421 cvox.ChromeVoxEventWatcherUtil.shouldWaitToProcess(
1423 cvox.ChromeVoxEventWatcher.firstUnprocessedEventTime,
1425 window.setTimeout(cvox.ChromeVoxEventWatcher.processQueue_,
1426 cvox.ChromeVoxEventWatcher.WAIT_TIME_MS_);
1430 // Process the remaining events in the queue, in order.
1431 for (i = 0; evt = cvox.ChromeVoxEventWatcher.events_[i]; i++) {
1432 cvox.ChromeVoxEventWatcher.handleEvent_(evt);
1434 cvox.ChromeVoxEventWatcher.events_ = new Array();
1435 cvox.ChromeVoxEventWatcher.firstUnprocessedEventTime = -1;
1436 cvox.ChromeVoxEventWatcher.queueProcessingScheduled_ = false;
1437 cvox.ChromeVoxEventWatcher.maybeCallReadyCallbacks_();
1441 * Handle events from the queue by routing them to their respective handlers.
1444 * @param {Event} evt The event to be handled.
1446 cvox.ChromeVoxEventWatcher.handleEvent_ = function(evt) {
1450 cvox.ChromeVoxEventWatcher.setUpTextHandler();
1451 if (cvox.ChromeVoxEventWatcher.currentTextControl) {
1452 cvox.ChromeVoxEventWatcher.handleTextChanged(true);
1454 var editableText = /** @type {cvox.ChromeVoxEditableTextBase} */
1455 (cvox.ChromeVoxEventWatcher.currentTextHandler);
1456 if (editableText && editableText.lastChangeDescribed) {
1460 // We're either not on a text control, or we are on a text control but no
1461 // text change was described. Let's try describing the state instead.
1462 cvox.ChromeVoxEventWatcher.handleControlChanged(document.activeElement);
1465 // Some controls change only after key up.
1466 cvox.ChromeVoxEventWatcher.handleControlChanged(document.activeElement);
1469 cvox.ChromeVoxEventWatcher.setUpTextHandler();
1472 cvox.ApiImplementation.syncToNode(/** @type {Node} */(evt.target), true);
1475 cvox.ChromeVoxEventWatcher.focusHandler(evt);
1478 cvox.ChromeVoxEventWatcher.setUpTextHandler();
1481 cvox.ChromeVoxEventWatcher.changeHandler(evt);
1484 cvox.ChromeVoxEventWatcher.setUpTextHandler();
1487 cvox.ChromeVoxEventWatcher.speakLiveRegion_(
1488 evt.assertive, evt.navDescriptions);
1490 case 'DOMSubtreeModified':
1491 cvox.ChromeVoxEventWatcher.subtreeModifiedHandler(evt);
1498 * Sets up the time handler.
1499 * @return {boolean} True if a time control has focus.
1502 cvox.ChromeVoxEventWatcher.setUpTimeHandler_ = function() {
1503 var currentFocus = document.activeElement;
1505 currentFocus.hasAttribute &&
1506 currentFocus.getAttribute('aria-hidden') == 'true' &&
1507 currentFocus.getAttribute('chromevoxignoreariahidden') != 'true') {
1508 currentFocus = null;
1510 if (currentFocus.constructor == HTMLInputElement &&
1511 currentFocus.type && (currentFocus.type == 'time')) {
1512 cvox.ChromeVoxEventWatcher.currentTimeHandler =
1513 new cvox.ChromeVoxHTMLTimeWidget(currentFocus, cvox.ChromeVox.tts);
1515 cvox.ChromeVoxEventWatcher.currentTimeHandler = null;
1517 return (null != cvox.ChromeVoxEventWatcher.currentTimeHandler);
1522 * Sets up the media (video/audio) handler.
1523 * @return {boolean} True if a media control has focus.
1526 cvox.ChromeVoxEventWatcher.setUpMediaHandler_ = function() {
1527 var currentFocus = document.activeElement;
1529 currentFocus.hasAttribute &&
1530 currentFocus.getAttribute('aria-hidden') == 'true' &&
1531 currentFocus.getAttribute('chromevoxignoreariahidden') != 'true') {
1532 currentFocus = null;
1534 if ((currentFocus.constructor == HTMLVideoElement) ||
1535 (currentFocus.constructor == HTMLAudioElement)) {
1536 cvox.ChromeVoxEventWatcher.currentMediaHandler =
1537 new cvox.ChromeVoxHTMLMediaWidget(currentFocus, cvox.ChromeVox.tts);
1539 cvox.ChromeVoxEventWatcher.currentMediaHandler = null;
1541 return (null != cvox.ChromeVoxEventWatcher.currentMediaHandler);
1545 * Sets up the date handler.
1546 * @return {boolean} True if a date control has focus.
1549 cvox.ChromeVoxEventWatcher.setUpDateHandler_ = function() {
1550 var currentFocus = document.activeElement;
1552 currentFocus.hasAttribute &&
1553 currentFocus.getAttribute('aria-hidden') == 'true' &&
1554 currentFocus.getAttribute('chromevoxignoreariahidden') != 'true') {
1555 currentFocus = null;
1557 if (currentFocus.constructor == HTMLInputElement &&
1558 currentFocus.type &&
1559 ((currentFocus.type == 'date') ||
1560 (currentFocus.type == 'month') ||
1561 (currentFocus.type == 'week'))) {
1562 cvox.ChromeVoxEventWatcher.currentDateHandler =
1563 new cvox.ChromeVoxHTMLDateWidget(currentFocus, cvox.ChromeVox.tts);
1565 cvox.ChromeVoxEventWatcher.currentDateHandler = null;
1567 return (null != cvox.ChromeVoxEventWatcher.currentDateHandler);