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.Memoize');
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.QueueMode');
33 goog.require('cvox.TextHandlerInterface');
34 goog.require('cvox.UserEventDetail');
39 cvox.ChromeVoxEventWatcher = function() {
43 * The maximum amount of time to wait before processing events.
44 * A max time is needed so that even if a page is constantly updating,
45 * events will still go through.
50 cvox.ChromeVoxEventWatcher.MAX_WAIT_TIME_MS_ = 50;
53 * As long as the MAX_WAIT_TIME_ has not been exceeded, the event processor
54 * will wait this long after the last event was received before starting to
60 cvox.ChromeVoxEventWatcher.WAIT_TIME_MS_ = 10;
63 * Maximum number of live regions that we will attempt to process.
68 cvox.ChromeVoxEventWatcher.MAX_LIVE_REGIONS_ = 5;
72 * Whether or not ChromeVox should echo keys.
73 * It is useful to turn this off in case the system is already echoing keys (for
74 * example, in Android).
78 cvox.ChromeVoxEventWatcher.shouldEchoKeys = true;
82 * Whether or not the next utterance should flush all previous speech.
83 * Immediately after a key down or user action, we make the next speech
84 * flush, but otherwise it's better to do a category flush, so if a single
85 * user action generates both a focus change and a live region change,
89 cvox.ChromeVoxEventWatcher.shouldFlushNextUtterance = false;
93 * Inits the event watcher and adds listeners.
94 * @param {!Document|!Window} doc The DOM document to add event listeners to.
96 cvox.ChromeVoxEventWatcher.init = function(doc) {
100 cvox.ChromeVoxEventWatcher.lastFocusedNode = null;
105 cvox.ChromeVoxEventWatcher.announcedMouseOverNode = null;
110 cvox.ChromeVoxEventWatcher.pendingMouseOverNode = null;
115 cvox.ChromeVoxEventWatcher.mouseOverTimeoutId = null;
120 cvox.ChromeVoxEventWatcher.lastFocusedNodeValue = null;
125 cvox.ChromeVoxEventWatcher.eventToEat = null;
130 cvox.ChromeVoxEventWatcher.currentTextControl = null;
133 * @type {cvox.ChromeVoxEditableTextBase}
135 cvox.ChromeVoxEventWatcher.currentTextHandler = null;
138 * Array of event listeners we've added so we can unregister them if needed.
142 cvox.ChromeVoxEventWatcher.listeners_ = [];
145 * The mutation observer we use to listen for live regions.
146 * @type {MutationObserver}
149 cvox.ChromeVoxEventWatcher.mutationObserver_ = null;
152 * Whether or not mouse hover events should trigger focusing.
155 cvox.ChromeVoxEventWatcher.focusFollowsMouse = false;
158 * The delay before a mouseover triggers focusing or announcing anything.
161 cvox.ChromeVoxEventWatcher.mouseoverDelayMs = 500;
164 * Array of events that need to be processed.
165 * @type {Array.<Event>}
168 cvox.ChromeVoxEventWatcher.events_ = new Array();
171 * The time when the last event was received.
174 cvox.ChromeVoxEventWatcher.lastEventTime = 0;
177 * The timestamp for the first unprocessed event.
180 cvox.ChromeVoxEventWatcher.firstUnprocessedEventTime = -1;
183 * Whether or not queue processing is scheduled to run.
187 cvox.ChromeVoxEventWatcher.queueProcessingScheduled_ = false;
190 * A list of callbacks to be called when the EventWatcher has
191 * completed processing all events in its queue.
192 * @type {Array.<function()>}
195 cvox.ChromeVoxEventWatcher.readyCallbacks_ = new Array();
199 * tracks whether we've received two or more key up's while pass through mode
204 cvox.ChromeVoxEventWatcher.secondPassThroughKeyUp_ = false;
207 * Whether or not the ChromeOS Search key (keyCode == 91) is being held.
209 * We must track this manually because on ChromeOS, the Search key being held
210 * down does not cause keyEvent.metaKey to be set.
212 * TODO (clchen, dmazzoni): Refactor this since there are edge cases
213 * where manually tracking key down and key up can fail (such as when
214 * the user switches tabs before letting go of the key being held).
218 cvox.ChromeVox.searchKeyHeld = false;
221 * The mutation observer that listens for chagnes to text controls
222 * that might not send other events.
223 * @type {MutationObserver}
226 cvox.ChromeVoxEventWatcher.textMutationObserver_ = null;
228 cvox.ChromeVoxEventWatcher.addEventListeners_(doc);
233 * Stores state variables in a provided object.
235 * @param {Object} store The object.
237 cvox.ChromeVoxEventWatcher.storeOn = function(store) {
238 store['searchKeyHeld'] = cvox.ChromeVox.searchKeyHeld;
242 * Updates the object with state variables from an earlier storeOn call.
244 * @param {Object} store The object.
246 cvox.ChromeVoxEventWatcher.readFrom = function(store) {
247 cvox.ChromeVox.searchKeyHeld = store['searchKeyHeld'];
251 * Adds an event to the events queue and updates the time when the last
252 * event was received.
254 * @param {Event} evt The event to be added to the events queue.
255 * @param {boolean=} opt_ignoreVisibility Whether to ignore visibility
256 * checking on the document. By default, this is set to false (so an
257 * invisible document would result in this event not being added).
259 cvox.ChromeVoxEventWatcher.addEvent = function(evt, opt_ignoreVisibility) {
260 // Don't add any events to the events queue if ChromeVox is inactive or the
261 // page is hidden unless specified to not do so.
262 if (!cvox.ChromeVox.isActive ||
263 (document.webkitHidden && !opt_ignoreVisibility)) {
266 cvox.ChromeVoxEventWatcher.events_.push(evt);
267 cvox.ChromeVoxEventWatcher.lastEventTime = new Date().getTime();
268 if (cvox.ChromeVoxEventWatcher.firstUnprocessedEventTime == -1) {
269 cvox.ChromeVoxEventWatcher.firstUnprocessedEventTime = new Date().getTime();
271 if (!cvox.ChromeVoxEventWatcher.queueProcessingScheduled_) {
272 cvox.ChromeVoxEventWatcher.queueProcessingScheduled_ = true;
273 window.setTimeout(cvox.ChromeVoxEventWatcher.processQueue_,
274 cvox.ChromeVoxEventWatcher.WAIT_TIME_MS_);
279 * Adds a callback to be called when the event watcher has finished
280 * processing all pending events.
281 * @param {Function} cb The callback.
283 cvox.ChromeVoxEventWatcher.addReadyCallback = function(cb) {
284 cvox.ChromeVoxEventWatcher.readyCallbacks_.push(cb);
285 cvox.ChromeVoxEventWatcher.maybeCallReadyCallbacks_();
289 * Returns whether or not there are pending events.
290 * @return {boolean} Whether or not there are pending events.
293 cvox.ChromeVoxEventWatcher.hasPendingEvents_ = function() {
294 return cvox.ChromeVoxEventWatcher.firstUnprocessedEventTime != -1 ||
295 cvox.ChromeVoxEventWatcher.queueProcessingScheduled_;
300 * A bit used to make sure only one ready callback is pending at a time.
303 cvox.ChromeVoxEventWatcher.readyCallbackRunning_ = false;
306 * Checks if the event watcher has pending events. If not, call the oldest
307 * readyCallback in a loop until exhausted or until there are pending events.
310 cvox.ChromeVoxEventWatcher.maybeCallReadyCallbacks_ = function() {
311 if (!cvox.ChromeVoxEventWatcher.readyCallbackRunning_) {
312 cvox.ChromeVoxEventWatcher.readyCallbackRunning_ = true;
313 window.setTimeout(function() {
314 cvox.ChromeVoxEventWatcher.readyCallbackRunning_ = false;
315 if (!cvox.ChromeVoxEventWatcher.hasPendingEvents_() &&
316 !cvox.ChromeVoxEventWatcher.queueProcessingScheduled_ &&
317 cvox.ChromeVoxEventWatcher.readyCallbacks_.length > 0) {
318 cvox.ChromeVoxEventWatcher.readyCallbacks_.shift()();
319 cvox.ChromeVoxEventWatcher.maybeCallReadyCallbacks_();
327 * Add all of our event listeners to the document.
328 * @param {!Document|!Window} doc The DOM document to add event listeners to.
331 cvox.ChromeVoxEventWatcher.addEventListeners_ = function(doc) {
332 // We always need key down listeners to intercept activate/deactivate.
333 cvox.ChromeVoxEventWatcher.addEventListener_(doc,
334 'keydown', cvox.ChromeVoxEventWatcher.keyDownEventWatcher, true);
336 // If ChromeVox isn't active, skip all other event listeners.
337 if (!cvox.ChromeVox.isActive || cvox.ChromeVox.entireDocumentIsHidden) {
340 cvox.ChromeVoxEventWatcher.addEventListener_(doc,
341 'keypress', cvox.ChromeVoxEventWatcher.keyPressEventWatcher, true);
342 cvox.ChromeVoxEventWatcher.addEventListener_(doc,
343 'keyup', cvox.ChromeVoxEventWatcher.keyUpEventWatcher, true);
344 // Listen for our own events to handle public user commands if the web app
345 // doesn't do it for us.
346 cvox.ChromeVoxEventWatcher.addEventListener_(doc,
347 cvox.UserEventDetail.Category.JUMP,
348 cvox.ChromeVoxUserCommands.handleChromeVoxUserEvent,
351 cvox.ChromeVoxEventWatcher.addEventListener_(doc,
352 'focus', cvox.ChromeVoxEventWatcher.focusEventWatcher, true);
353 cvox.ChromeVoxEventWatcher.addEventListener_(doc,
354 'blur', cvox.ChromeVoxEventWatcher.blurEventWatcher, true);
355 cvox.ChromeVoxEventWatcher.addEventListener_(doc,
356 'change', cvox.ChromeVoxEventWatcher.changeEventWatcher, true);
357 cvox.ChromeVoxEventWatcher.addEventListener_(doc,
358 'copy', cvox.ChromeVoxEventWatcher.clipboardEventWatcher, true);
359 cvox.ChromeVoxEventWatcher.addEventListener_(doc,
360 'cut', cvox.ChromeVoxEventWatcher.clipboardEventWatcher, true);
361 cvox.ChromeVoxEventWatcher.addEventListener_(doc,
362 'paste', cvox.ChromeVoxEventWatcher.clipboardEventWatcher, true);
363 cvox.ChromeVoxEventWatcher.addEventListener_(doc,
364 'select', cvox.ChromeVoxEventWatcher.selectEventWatcher, true);
366 // TODO(dtseng): Experimental, see:
367 // https://developers.google.com/chrome/whitepapers/pagevisibility
368 cvox.ChromeVoxEventWatcher.addEventListener_(doc, 'webkitvisibilitychange',
369 cvox.ChromeVoxEventWatcher.visibilityChangeWatcher, true);
370 cvox.ChromeVoxEventWatcher.events_ = new Array();
371 cvox.ChromeVoxEventWatcher.queueProcessingScheduled_ = false;
373 // Handle mouse events directly without going into the events queue.
374 cvox.ChromeVoxEventWatcher.addEventListener_(doc,
375 'mouseover', cvox.ChromeVoxEventWatcher.mouseOverEventWatcher, true);
376 cvox.ChromeVoxEventWatcher.addEventListener_(doc,
377 'mouseout', cvox.ChromeVoxEventWatcher.mouseOutEventWatcher, true);
379 // With the exception of non-Android, click events go through the event queue.
380 cvox.ChromeVoxEventWatcher.addEventListener_(doc,
381 'click', cvox.ChromeVoxEventWatcher.mouseClickEventWatcher, true);
383 cvox.ChromeVoxEventWatcher.mutationObserver_ =
384 new window.WebKitMutationObserver(
385 cvox.ChromeVoxEventWatcher.mutationHandler);
386 var observerTarget = null;
387 if (doc.documentElement) {
388 observerTarget = doc.documentElement;
389 } else if (doc.document && doc.document.documentElement) {
390 observerTarget = doc.document.documentElement;
392 if (observerTarget) {
393 cvox.ChromeVoxEventWatcher.mutationObserver_.observe(
395 /** @type {!MutationObserverInit} */ ({
400 attributeOldValue: true,
401 characterDataOldValue: true
408 * Remove all registered event watchers.
409 * @param {!Document|!Window} doc The DOM document to add event listeners to.
411 cvox.ChromeVoxEventWatcher.cleanup = function(doc) {
412 for (var i = 0; i < cvox.ChromeVoxEventWatcher.listeners_.length; i++) {
413 var listener = cvox.ChromeVoxEventWatcher.listeners_[i];
414 doc.removeEventListener(
415 listener.type, listener.listener, listener.useCapture);
417 cvox.ChromeVoxEventWatcher.listeners_ = [];
418 if (cvox.ChromeVoxEventWatcher.currentDateHandler) {
419 cvox.ChromeVoxEventWatcher.currentDateHandler.shutdown();
421 if (cvox.ChromeVoxEventWatcher.currentTimeHandler) {
422 cvox.ChromeVoxEventWatcher.currentTimeHandler.shutdown();
424 if (cvox.ChromeVoxEventWatcher.currentMediaHandler) {
425 cvox.ChromeVoxEventWatcher.currentMediaHandler.shutdown();
427 if (cvox.ChromeVoxEventWatcher.mutationObserver_) {
428 cvox.ChromeVoxEventWatcher.mutationObserver_.disconnect();
430 cvox.ChromeVoxEventWatcher.mutationObserver_ = null;
434 * Add one event listener and save the data so it can be removed later.
435 * @param {!Document|!Window} doc The DOM document to add event listeners to.
436 * @param {string} type The event type.
437 * @param {EventListener|function(Event):(boolean|undefined)} listener
438 * The function to be called when the event is fired.
439 * @param {boolean} useCapture Whether this listener should capture events
440 * before they're sent to targets beneath it in the DOM tree.
443 cvox.ChromeVoxEventWatcher.addEventListener_ = function(doc, type,
444 listener, useCapture) {
445 cvox.ChromeVoxEventWatcher.listeners_.push(
446 {'type': type, 'listener': listener, 'useCapture': useCapture});
447 doc.addEventListener(type, listener, useCapture);
451 * Return the last focused node.
452 * @return {Object} The last node that was focused.
454 cvox.ChromeVoxEventWatcher.getLastFocusedNode = function() {
455 return cvox.ChromeVoxEventWatcher.lastFocusedNode;
459 * Sets the last focused node.
460 * @param {Element} element The last focused element.
464 cvox.ChromeVoxEventWatcher.setLastFocusedNode_ = function(element) {
465 cvox.ChromeVoxEventWatcher.lastFocusedNode = element;
466 cvox.ChromeVoxEventWatcher.lastFocusedNodeValue = !element ? null :
467 cvox.DomUtil.getControlValueAndStateString(element);
471 * Called when there's any mutation of the document. We use this to
472 * handle live region updates.
473 * @param {Array.<MutationRecord>} mutations The mutations.
474 * @return {boolean} True if the default action should be performed.
476 cvox.ChromeVoxEventWatcher.mutationHandler = function(mutations) {
477 if (cvox.ChromeVoxEventSuspender.areEventsSuspended()) {
481 cvox.ChromeVox.navigationManager.updateIndicatorIfChanged();
483 cvox.LiveRegions.processMutations(
485 function(assertive, navDescriptions) {
486 var evt = new window.Event('LiveRegion');
487 evt.navDescriptions = navDescriptions;
488 evt.assertive = assertive;
489 cvox.ChromeVoxEventWatcher.addEvent(evt, true);
496 * Handles mouseclick events.
497 * Mouseclick events are only triggered if the user touches the mouse;
498 * we use it to determine whether or not we should bother trying to sync to a
500 * @param {Event} evt The mouseclick event to process.
501 * @return {boolean} True if the default action should be performed.
503 cvox.ChromeVoxEventWatcher.mouseClickEventWatcher = function(evt) {
508 if (cvox.ChromeVox.host.mustRedispatchClickEvent()) {
509 cvox.ChromeVoxUserCommands.wasMouseClicked = true;
510 evt.stopPropagation();
511 evt.preventDefault();
512 // Since the click event was caught and we are re-dispatching it, we also
513 // need to refocus the current node because the current node has already
514 // been blurred by the window getting the click event in the first place.
515 // Failing to restore focus before clicking can cause odd problems such as
516 // the soft IME not coming up in Android (it only shows up if the click
517 // happens in a focused text field).
518 cvox.Focuser.setFocus(cvox.ChromeVox.navigationManager.getCurrentNode());
519 cvox.ChromeVox.tts.speak(
520 cvox.ChromeVox.msgs.getMsg('element_clicked'),
521 cvox.ChromeVoxEventWatcher.queueMode_(),
522 cvox.AbstractTts.PERSONALITY_ANNOTATION);
523 var targetNode = cvox.ChromeVox.navigationManager.getCurrentNode();
524 // If the targetNode has a defined onclick function, just call it directly
525 // rather than try to generate a click event and dispatching it.
526 // While both work equally well on standalone Chrome, when dealing with
527 // embedded WebViews, generating a click event and sending it is not always
528 // reliable since the framework may swallow the event.
529 cvox.DomUtil.clickElem(targetNode, false, true);
532 cvox.ChromeVoxEventWatcher.addEvent(evt);
534 cvox.ChromeVoxUserCommands.wasMouseClicked = true;
539 * Handles mouseover events.
540 * Mouseover events are only triggered if the user touches the mouse, so
541 * for users who only use the keyboard, this will have no effect.
543 * @param {Event} evt The mouseover event to process.
544 * @return {boolean} True if the default action should be performed.
546 cvox.ChromeVoxEventWatcher.mouseOverEventWatcher = function(evt) {
547 // Chrome simulates the meta key for mouse events generated from
548 // touch exploration.
549 var isTouchEvent = (evt.metaKey);
551 var mouseoverDelayMs = cvox.ChromeVoxEventWatcher.mouseoverDelayMs;
553 mouseoverDelayMs = 0;
554 } else if (!cvox.ChromeVoxEventWatcher.focusFollowsMouse) {
558 if (cvox.DomUtil.isDescendantOfNode(
559 cvox.ChromeVoxEventWatcher.announcedMouseOverNode, evt.target)) {
563 if (evt.target == cvox.ChromeVoxEventWatcher.pendingMouseOverNode) {
567 cvox.ChromeVoxEventWatcher.pendingMouseOverNode = evt.target;
568 if (cvox.ChromeVoxEventWatcher.mouseOverTimeoutId) {
569 window.clearTimeout(cvox.ChromeVoxEventWatcher.mouseOverTimeoutId);
570 cvox.ChromeVoxEventWatcher.mouseOverTimeoutId = null;
573 if (evt.target.tagName && (evt.target.tagName == 'BODY')) {
574 cvox.ChromeVoxEventWatcher.pendingMouseOverNode = null;
575 cvox.ChromeVoxEventWatcher.announcedMouseOverNode = null;
579 // Only focus and announce if the mouse stays over the same target
580 // for longer than the given delay.
581 cvox.ChromeVoxEventWatcher.mouseOverTimeoutId = window.setTimeout(
583 cvox.ChromeVoxEventWatcher.mouseOverTimeoutId = null;
584 if (evt.target != cvox.ChromeVoxEventWatcher.pendingMouseOverNode) {
588 cvox.Memoize.scope(function() {
589 cvox.ChromeVoxEventWatcher.shouldFlushNextUtterance = true;
590 cvox.ChromeVox.navigationManager.stopReading(true);
591 var target = /** @type {Node} */(evt.target);
592 cvox.Focuser.setFocus(target);
593 cvox.ApiImplementation.syncToNode(
594 target, true, cvox.ChromeVoxEventWatcher.queueMode_());
595 cvox.ChromeVoxEventWatcher.announcedMouseOverNode = target;
597 }, mouseoverDelayMs);
603 * Handles mouseout events.
605 * @param {Event} evt The mouseout event to process.
606 * @return {boolean} True if the default action should be performed.
608 cvox.ChromeVoxEventWatcher.mouseOutEventWatcher = function(evt) {
609 if (evt.target == cvox.ChromeVoxEventWatcher.pendingMouseOverNode) {
610 cvox.ChromeVoxEventWatcher.pendingMouseOverNode = null;
611 if (cvox.ChromeVoxEventWatcher.mouseOverTimeoutId) {
612 window.clearTimeout(cvox.ChromeVoxEventWatcher.mouseOverTimeoutId);
613 cvox.ChromeVoxEventWatcher.mouseOverTimeoutId = null;
622 * Watches for focus events.
624 * @param {Event} evt The focus event to add to the queue.
625 * @return {boolean} True if the default action should be performed.
627 cvox.ChromeVoxEventWatcher.focusEventWatcher = function(evt) {
628 // First remove any dummy spans. We create dummy spans in UserCommands in
629 // order to sync the browser's default tab action with the user's current
630 // navigation position.
631 cvox.ChromeVoxUserCommands.removeTabDummySpan();
633 if (!cvox.ChromeVoxEventSuspender.areEventsSuspended()) {
634 cvox.ChromeVoxEventWatcher.addEvent(evt);
635 } else if (evt.target && evt.target.nodeType == Node.ELEMENT_NODE) {
636 cvox.ChromeVoxEventWatcher.setLastFocusedNode_(
637 /** @type {Element} */(evt.target));
643 * Handles for focus events passed to it from the events queue.
645 * @param {Event} evt The focus event to handle.
647 cvox.ChromeVoxEventWatcher.focusHandler = function(evt) {
649 evt.target.hasAttribute &&
650 evt.target.getAttribute('aria-hidden') == 'true' &&
651 evt.target.getAttribute('chromevoxignoreariahidden') != 'true') {
652 cvox.ChromeVoxEventWatcher.setLastFocusedNode_(null);
653 cvox.ChromeVoxEventWatcher.setUpTextHandler();
656 if (evt.target && evt.target != window) {
657 var target = /** @type {Element} */(evt.target);
658 var parentControl = cvox.DomUtil.getSurroundingControl(target);
660 parentControl == cvox.ChromeVoxEventWatcher.lastFocusedNode) {
661 cvox.ChromeVoxEventWatcher.handleControlChanged(target);
666 cvox.ChromeVoxEventWatcher.setLastFocusedNode_(
667 /** @type {Element} */(parentControl));
669 cvox.ChromeVoxEventWatcher.setLastFocusedNode_(target);
672 var queueMode = cvox.ChromeVoxEventWatcher.queueMode_();
674 if (cvox.ChromeVoxEventWatcher.getInitialVisibility() ||
675 cvox.ChromeVoxEventWatcher.handleDialogFocus(target)) {
676 queueMode = cvox.QueueMode.QUEUE;
679 if (cvox.ChromeVox.navigationManager.clearPageSel(true)) {
680 queueMode = cvox.QueueMode.QUEUE;
683 // Navigate to this control so that it will be the same for focus as for
684 // regular navigation.
685 cvox.ApiImplementation.syncToNode(
686 target, !document.webkitHidden, queueMode);
688 if ((evt.target.constructor == HTMLVideoElement) ||
689 (evt.target.constructor == HTMLAudioElement)) {
690 cvox.ChromeVoxEventWatcher.setUpMediaHandler_();
693 if (evt.target.hasAttribute) {
694 var inputType = evt.target.getAttribute('type');
697 cvox.ChromeVoxEventWatcher.setUpTimeHandler_();
702 cvox.ChromeVoxEventWatcher.setUpDateHandler_();
706 cvox.ChromeVoxEventWatcher.setUpTextHandler();
708 cvox.ChromeVoxEventWatcher.setLastFocusedNode_(null);
714 * Watches for blur events.
716 * @param {Event} evt The blur event to add to the queue.
717 * @return {boolean} True if the default action should be performed.
719 cvox.ChromeVoxEventWatcher.blurEventWatcher = function(evt) {
720 window.setTimeout(function() {
721 if (!document.activeElement) {
722 cvox.ChromeVoxEventWatcher.setLastFocusedNode_(null);
723 cvox.ChromeVoxEventWatcher.addEvent(evt);
730 * Watches for key down events.
732 * @param {Event} evt The keydown event to add to the queue.
733 * @return {boolean} True if the default action should be performed.
735 cvox.ChromeVoxEventWatcher.keyDownEventWatcher = function(evt) {
736 return /** @type {boolean} */ (cvox.Memoize.scope(
737 cvox.ChromeVoxEventWatcher.doKeyDownEventWatcher_.bind(this, evt)));
741 * Implementation of |keyDownEventWatcher|.
743 * @param {Event} evt The keydown event to add to the queue.
744 * @return {boolean} True if the default action should be performed.
747 cvox.ChromeVoxEventWatcher.doKeyDownEventWatcher_ = function(evt) {
748 cvox.ChromeVoxEventWatcher.shouldFlushNextUtterance = true;
750 if (cvox.ChromeVox.passThroughMode) {
754 if (cvox.ChromeVox.isChromeOS && evt.keyCode == 91) {
755 cvox.ChromeVox.searchKeyHeld = true;
758 // Store some extra ChromeVox-specific properties in the event.
760 cvox.ChromeVox.searchKeyHeld && cvox.ChromeVox.isActive;
761 evt.stickyMode = cvox.ChromeVox.isStickyModeOn() && cvox.ChromeVox.isActive;
762 evt.keyPrefix = cvox.ChromeVox.keyPrefixOn && cvox.ChromeVox.isActive;
764 cvox.ChromeVox.keyPrefixOn = false;
766 cvox.ChromeVoxEventWatcher.eventToEat = null;
767 if (!cvox.ChromeVoxKbHandler.basicKeyDownActionsListener(evt) ||
768 cvox.ChromeVoxEventWatcher.handleControlAction(evt)) {
769 // Swallow the event immediately to prevent the arrow keys
770 // from driving controls on the web page.
771 evt.preventDefault();
772 evt.stopPropagation();
773 // Also mark this as something to be swallowed when the followup
774 // keypress/keyup counterparts to this event show up later.
775 cvox.ChromeVoxEventWatcher.eventToEat = evt;
778 cvox.ChromeVoxEventWatcher.addEvent(evt);
783 * Watches for key up events.
785 * @param {Event} evt The event to add to the queue.
786 * @return {boolean} True if the default action should be performed.
787 * @this {cvox.ChromeVoxEventWatcher}
789 cvox.ChromeVoxEventWatcher.keyUpEventWatcher = function(evt) {
790 if (evt.keyCode == 91) {
791 cvox.ChromeVox.searchKeyHeld = false;
794 if (cvox.ChromeVox.passThroughMode) {
795 if (!evt.ctrlKey && !evt.altKey && !evt.metaKey && !evt.shiftKey &&
796 !cvox.ChromeVox.searchKeyHeld) {
797 // Only reset pass through on the second key up without modifiers since
798 // the first one is from the pass through shortcut itself.
799 if (this.secondPassThroughKeyUp_) {
800 this.secondPassThroughKeyUp_ = false;
801 cvox.ChromeVox.passThroughMode = false;
803 this.secondPassThroughKeyUp_ = true;
809 if (cvox.ChromeVoxEventWatcher.eventToEat &&
810 evt.keyCode == cvox.ChromeVoxEventWatcher.eventToEat.keyCode) {
811 evt.stopPropagation();
812 evt.preventDefault();
816 cvox.ChromeVoxEventWatcher.addEvent(evt);
822 * Watches for key press events.
824 * @param {Event} evt The event to add to the queue.
825 * @return {boolean} True if the default action should be performed.
827 cvox.ChromeVoxEventWatcher.keyPressEventWatcher = function(evt) {
828 var url = document.location.href;
829 // Use ChromeVox.typingEcho as default value.
830 var speakChar = cvox.TypingEcho.shouldSpeakChar(cvox.ChromeVox.typingEcho);
832 if (typeof cvox.ChromeVox.keyEcho[url] !== 'undefined') {
833 speakChar = cvox.ChromeVox.keyEcho[url];
836 // Directly handle typed characters here while key echo is on. This
837 // skips potentially costly computations (especially for content editable).
838 // This is done deliberately for the sake of responsiveness and in some cases
839 // (e.g. content editable), to have characters echoed properly.
840 if (cvox.ChromeVoxEditableTextBase.eventTypingEcho && (speakChar &&
841 cvox.DomPredicates.editTextPredicate([document.activeElement])) &&
842 document.activeElement.type !== 'password') {
843 cvox.ChromeVox.tts.speak(String.fromCharCode(evt.charCode),
844 cvox.QueueMode.FLUSH);
846 cvox.ChromeVoxEventWatcher.addEvent(evt);
847 if (cvox.ChromeVoxEventWatcher.eventToEat &&
848 evt.keyCode == cvox.ChromeVoxEventWatcher.eventToEat.keyCode) {
849 evt.preventDefault();
850 evt.stopPropagation();
857 * Watches for change events.
859 * @param {Event} evt The event to add to the queue.
860 * @return {boolean} True if the default action should be performed.
862 cvox.ChromeVoxEventWatcher.changeEventWatcher = function(evt) {
863 cvox.ChromeVoxEventWatcher.addEvent(evt);
867 // TODO(dtseng): ChromeVoxEditableText interrupts cut and paste announcements.
869 * Watches for cut, copy, and paste events.
871 * @param {Event} evt The event to process.
872 * @return {boolean} True if the default action should be performed.
874 cvox.ChromeVoxEventWatcher.clipboardEventWatcher = function(evt) {
875 cvox.ChromeVox.tts.speak(cvox.ChromeVox.msgs.getMsg(evt.type).toLowerCase(),
876 cvox.QueueMode.QUEUE);
880 text = evt.clipboardData.getData('text');
884 text = window.getSelection().toString();
887 cvox.ChromeVox.tts.speak(text, cvox.QueueMode.QUEUE);
888 cvox.ChromeVox.navigationManager.clearPageSel();
893 * Handles change events passed to it from the events queue.
895 * @param {Event} evt The event to handle.
897 cvox.ChromeVoxEventWatcher.changeHandler = function(evt) {
898 if (cvox.ChromeVoxEventWatcher.setUpTextHandler()) {
901 if (document.activeElement == evt.target) {
902 cvox.ChromeVoxEventWatcher.handleControlChanged(document.activeElement);
907 * Watches for select events.
909 * @param {Event} evt The event to add to the queue.
910 * @return {boolean} True if the default action should be performed.
912 cvox.ChromeVoxEventWatcher.selectEventWatcher = function(evt) {
913 cvox.ChromeVoxEventWatcher.addEvent(evt);
918 * Listens for WebKit visibility change events.
920 cvox.ChromeVoxEventWatcher.visibilityChangeWatcher = function() {
921 cvox.ChromeVoxEventWatcher.initialVisibility = !document.webkitHidden;
922 if (document.webkitHidden) {
923 cvox.ChromeVox.navigationManager.stopReading(true);
928 * Gets the initial visibility of the page.
929 * @return {boolean} True if the page is visible and this is the first request
930 * for visibility state.
932 cvox.ChromeVoxEventWatcher.getInitialVisibility = function() {
933 var ret = cvox.ChromeVoxEventWatcher.initialVisibility;
934 cvox.ChromeVoxEventWatcher.initialVisibility = false;
939 * Speaks the text of one live region.
940 * @param {boolean} assertive True if it's an assertive live region.
941 * @param {Array.<cvox.NavDescription>} messages An array of navDescriptions
942 * representing the description of the live region changes.
945 cvox.ChromeVoxEventWatcher.speakLiveRegion_ = function(
946 assertive, messages) {
947 var queueMode = cvox.ChromeVoxEventWatcher.queueMode_();
948 var descSpeaker = new cvox.NavigationSpeaker();
949 descSpeaker.speakDescriptionArray(messages, queueMode, null);
953 * Sets up the text handler.
954 * @return {boolean} True if an editable text control has focus.
956 cvox.ChromeVoxEventWatcher.setUpTextHandler = function() {
957 var currentFocus = document.activeElement;
959 currentFocus.hasAttribute &&
960 currentFocus.getAttribute('aria-hidden') == 'true' &&
961 currentFocus.getAttribute('chromevoxignoreariahidden') != 'true') {
965 if (currentFocus != cvox.ChromeVoxEventWatcher.currentTextControl) {
966 if (cvox.ChromeVoxEventWatcher.currentTextControl) {
967 cvox.ChromeVoxEventWatcher.currentTextControl.removeEventListener(
968 'input', cvox.ChromeVoxEventWatcher.changeEventWatcher, false);
969 cvox.ChromeVoxEventWatcher.currentTextControl.removeEventListener(
970 'click', cvox.ChromeVoxEventWatcher.changeEventWatcher, false);
971 if (cvox.ChromeVoxEventWatcher.textMutationObserver_) {
972 cvox.ChromeVoxEventWatcher.textMutationObserver_.disconnect();
973 cvox.ChromeVoxEventWatcher.textMutationObserver_ = null;
976 cvox.ChromeVoxEventWatcher.currentTextControl = null;
977 if (cvox.ChromeVoxEventWatcher.currentTextHandler) {
978 cvox.ChromeVoxEventWatcher.currentTextHandler.teardown();
979 cvox.ChromeVoxEventWatcher.currentTextHandler = null;
981 if (currentFocus == null) {
984 if (currentFocus.constructor == HTMLInputElement &&
985 cvox.DomUtil.isInputTypeText(currentFocus) &&
986 cvox.ChromeVoxEventWatcher.shouldEchoKeys) {
987 cvox.ChromeVoxEventWatcher.currentTextControl = currentFocus;
988 cvox.ChromeVoxEventWatcher.currentTextHandler =
989 new cvox.ChromeVoxEditableHTMLInput(currentFocus, cvox.ChromeVox.tts);
990 } else if ((currentFocus.constructor == HTMLTextAreaElement) &&
991 cvox.ChromeVoxEventWatcher.shouldEchoKeys) {
992 cvox.ChromeVoxEventWatcher.currentTextControl = currentFocus;
993 cvox.ChromeVoxEventWatcher.currentTextHandler =
994 new cvox.ChromeVoxEditableTextArea(currentFocus, cvox.ChromeVox.tts);
995 } else if (currentFocus.isContentEditable ||
996 currentFocus.getAttribute('role') == 'textbox') {
997 cvox.ChromeVoxEventWatcher.currentTextControl = currentFocus;
998 cvox.ChromeVoxEventWatcher.currentTextHandler =
999 new cvox.ChromeVoxEditableContentEditable(currentFocus,
1000 cvox.ChromeVox.tts);
1003 if (cvox.ChromeVoxEventWatcher.currentTextControl) {
1004 cvox.ChromeVoxEventWatcher.currentTextControl.addEventListener(
1005 'input', cvox.ChromeVoxEventWatcher.changeEventWatcher, false);
1006 cvox.ChromeVoxEventWatcher.currentTextControl.addEventListener(
1007 'click', cvox.ChromeVoxEventWatcher.changeEventWatcher, false);
1008 if (window.WebKitMutationObserver) {
1009 cvox.ChromeVoxEventWatcher.textMutationObserver_ =
1010 new window.WebKitMutationObserver(
1011 cvox.ChromeVoxEventWatcher.onTextMutation);
1012 cvox.ChromeVoxEventWatcher.textMutationObserver_.observe(
1013 cvox.ChromeVoxEventWatcher.currentTextControl,
1014 /** @type {!MutationObserverInit} */ ({
1018 attributeOldValue: false,
1019 characterDataOldValue: false
1022 if (!cvox.ChromeVoxEventSuspender.areEventsSuspended()) {
1023 cvox.ChromeVox.navigationManager.updateSel(
1024 cvox.CursorSelection.fromNode(
1025 cvox.ChromeVoxEventWatcher.currentTextControl));
1029 return (null != cvox.ChromeVoxEventWatcher.currentTextHandler);
1034 * Speaks updates to editable text controls as needed.
1036 * @param {boolean} isKeypress Was this change triggered by a keypress?
1037 * @return {boolean} True if an editable text control has focus.
1039 cvox.ChromeVoxEventWatcher.handleTextChanged = function(isKeypress) {
1040 if (cvox.ChromeVoxEventWatcher.currentTextHandler) {
1041 var handler = cvox.ChromeVoxEventWatcher.currentTextHandler;
1042 var shouldFlush = false;
1043 if (isKeypress && cvox.ChromeVoxEventWatcher.shouldFlushNextUtterance) {
1045 cvox.ChromeVoxEventWatcher.shouldFlushNextUtterance = false;
1047 handler.update(shouldFlush);
1048 cvox.ChromeVoxEventWatcher.shouldFlushNextUtterance = false;
1055 * Called when an editable text control has focus, because many changes
1056 * to a text box don't ever generate events - e.g. if the page's javascript
1057 * changes the contents of the text box after some delay, or if it's
1058 * contentEditable or a generic div with role="textbox".
1060 cvox.ChromeVoxEventWatcher.onTextMutation = function() {
1061 if (cvox.ChromeVoxEventWatcher.currentTextHandler) {
1062 window.setTimeout(function() {
1063 cvox.ChromeVoxEventWatcher.handleTextChanged(false);
1064 }, cvox.ChromeVoxEventWatcher.MAX_WAIT_TIME_MS_);
1069 * Speaks updates to other form controls as needed.
1070 * @param {Element} control The target control.
1072 cvox.ChromeVoxEventWatcher.handleControlChanged = function(control) {
1073 var newValue = cvox.DomUtil.getControlValueAndStateString(control);
1074 var parentControl = cvox.DomUtil.getSurroundingControl(control);
1075 var announceChange = false;
1077 if (control != cvox.ChromeVoxEventWatcher.lastFocusedNode &&
1078 (parentControl == null ||
1079 parentControl != cvox.ChromeVoxEventWatcher.lastFocusedNode)) {
1080 cvox.ChromeVoxEventWatcher.setLastFocusedNode_(control);
1081 } else if (newValue == cvox.ChromeVoxEventWatcher.lastFocusedNodeValue) {
1085 cvox.ChromeVoxEventWatcher.lastFocusedNodeValue = newValue;
1086 if (cvox.DomPredicates.checkboxPredicate([control]) ||
1087 cvox.DomPredicates.radioPredicate([control])) {
1088 // Always announce changes to checkboxes and radio buttons.
1089 announceChange = true;
1090 // Play earcons for checkboxes and radio buttons
1091 if (control.checked) {
1092 cvox.ChromeVox.earcons.playEarcon(cvox.AbstractEarcons.CHECK_ON);
1094 cvox.ChromeVox.earcons.playEarcon(cvox.AbstractEarcons.CHECK_OFF);
1098 if (control.tagName == 'SELECT') {
1099 announceChange = true;
1102 if (control.tagName == 'INPUT') {
1103 switch (control.type) {
1106 case 'datetime-local':
1108 announceChange = true;
1115 // Always announce changes for anything with an ARIA role.
1116 if (control.hasAttribute && control.hasAttribute('role')) {
1117 announceChange = true;
1120 if ((parentControl &&
1121 parentControl != control &&
1122 document.activeElement == control)) {
1123 // If focus has been set on a child of the parent control, we need to
1124 // sync to that node so that ChromeVox navigation will be in sync with
1125 // focus navigation.
1126 cvox.ApiImplementation.syncToNode(
1128 cvox.ChromeVoxEventWatcher.queueMode_());
1129 announceChange = false;
1130 } else if (cvox.AriaUtil.getActiveDescendant(control)) {
1131 cvox.ChromeVox.navigationManager.updateSelToArbitraryNode(
1132 cvox.AriaUtil.getActiveDescendant(control),
1135 announceChange = true;
1138 if (announceChange && !cvox.ChromeVoxEventSuspender.areEventsSuspended()) {
1139 cvox.ChromeVox.tts.speak(newValue,
1140 cvox.ChromeVoxEventWatcher.queueMode_(),
1142 cvox.NavBraille.fromText(newValue).write();
1147 * Handle actions on form controls triggered by key presses.
1148 * @param {Object} evt The event.
1149 * @return {boolean} True if this key event was handled.
1151 cvox.ChromeVoxEventWatcher.handleControlAction = function(evt) {
1152 // Ignore the control action if ChromeVox is not active.
1153 if (!cvox.ChromeVox.isActive) {
1156 var control = evt.target;
1158 if (control.tagName == 'SELECT' && (control.size <= 1) &&
1159 (evt.keyCode == 13 || evt.keyCode == 32)) { // Enter or Space
1160 // TODO (dmazzoni, clchen): Remove this workaround once accessibility
1161 // APIs make browser based popups accessible.
1163 // Do nothing, but eat this keystroke when the SELECT control
1164 // has a dropdown style since if we don't, it will generate
1165 // a browser popup menu which is not accessible.
1166 // List style SELECT controls are fine and don't need this workaround.
1167 evt.preventDefault();
1168 evt.stopPropagation();
1172 if (control.tagName == 'INPUT' && control.type == 'range') {
1173 var value = parseFloat(control.value);
1175 if (control.step && control.step > 0.0) {
1176 step = control.step;
1177 } else if (control.min && control.max) {
1178 var range = (control.max - control.min);
1179 if (range > 2 && range < 31) {
1182 step = (control.max - control.min) / 10;
1188 if (evt.keyCode == 37 || evt.keyCode == 38) { // left or up
1190 } else if (evt.keyCode == 39 || evt.keyCode == 40) { // right or down
1194 if (control.max && value > control.max) {
1195 value = control.max;
1197 if (control.min && value < control.min) {
1198 value = control.min;
1201 control.value = value;
1207 * When an element receives focus, see if we've entered or left a dialog
1208 * and return a string describing the event.
1210 * @param {Element} target The element that just received focus.
1211 * @return {boolean} True if an announcement was spoken.
1213 cvox.ChromeVoxEventWatcher.handleDialogFocus = function(target) {
1214 var dialog = target;
1217 if (dialog.hasAttribute) {
1218 role = dialog.getAttribute('role');
1219 if (role == 'dialog' || role == 'alertdialog') {
1223 dialog = dialog.parentElement;
1226 if (dialog == cvox.ChromeVox.navigationManager.currentDialog) {
1230 if (cvox.ChromeVox.navigationManager.currentDialog && !dialog) {
1231 if (!cvox.DomUtil.isDescendantOfNode(
1232 document.activeElement,
1233 cvox.ChromeVox.navigationManager.currentDialog)) {
1234 cvox.ChromeVox.navigationManager.currentDialog = null;
1236 cvox.ChromeVoxEventWatcher.speakAnnotationWithCategory_(
1237 cvox.ChromeVox.msgs.getMsg('exiting_dialog'),
1238 cvox.TtsCategory.NAV);
1243 cvox.ChromeVox.navigationManager.currentDialog = dialog;
1244 cvox.ChromeVoxEventWatcher.speakAnnotationWithCategory_(
1245 cvox.ChromeVox.msgs.getMsg('entering_dialog'),
1246 cvox.TtsCategory.NAV);
1248 if (role == 'alertdialog') {
1249 var dialogDescArray =
1250 cvox.DescriptionUtil.getFullDescriptionsFromChildren(null, dialog);
1251 var descSpeaker = new cvox.NavigationSpeaker();
1252 descSpeaker.speakDescriptionArray(dialogDescArray,
1253 cvox.QueueMode.QUEUE,
1263 * Speak the given text with the annotation personality and the given
1264 * speech queue utterance category.
1265 * @param {string} text The text to speak.
1266 * @param {string} category The category of text, used by the speech queue
1267 * when flushing all speech from the same category while leaving other
1268 * speech in the queue.
1271 cvox.ChromeVoxEventWatcher.speakAnnotationWithCategory_ = function(
1273 var properties = {};
1274 var src = cvox.AbstractTts.PERSONALITY_ANNOTATION;
1275 for (var key in src) {
1276 properties[key] = src[key];
1278 properties['category'] = category;
1279 cvox.ChromeVox.tts.speak(
1281 cvox.ChromeVoxEventWatcher.queueMode_(),
1286 * Returns true if we should wait to process events.
1287 * @param {number} lastFocusTimestamp The timestamp of the last focus event.
1288 * @param {number} firstTimestamp The timestamp of the first event.
1289 * @param {number} currentTime The current timestamp.
1290 * @return {boolean} True if we should wait to process events.
1292 cvox.ChromeVoxEventWatcherUtil.shouldWaitToProcess = function(
1293 lastFocusTimestamp, firstTimestamp, currentTime) {
1294 var timeSinceFocusEvent = currentTime - lastFocusTimestamp;
1295 var timeSinceFirstEvent = currentTime - firstTimestamp;
1296 return timeSinceFocusEvent < cvox.ChromeVoxEventWatcher.WAIT_TIME_MS_ &&
1297 timeSinceFirstEvent < cvox.ChromeVoxEventWatcher.MAX_WAIT_TIME_MS_;
1302 * Returns the queue mode to use for the next utterance spoken as
1303 * a result of an event or navigation. The first utterance that's spoken
1304 * after an explicit user action like a key press will flush, and
1305 * subsequent events will return a category flush.
1306 * @return {cvox.QueueMode} The queue mode.
1309 cvox.ChromeVoxEventWatcher.queueMode_ = function() {
1310 if (cvox.ChromeVoxEventWatcher.shouldFlushNextUtterance) {
1311 cvox.ChromeVoxEventWatcher.shouldFlushNextUtterance = false;
1312 return cvox.QueueMode.FLUSH;
1314 return cvox.QueueMode.CATEGORY_FLUSH;
1319 * Processes the events queue.
1323 cvox.ChromeVoxEventWatcher.processQueue_ = function() {
1324 cvox.Memoize.scope(cvox.ChromeVoxEventWatcher.doProcessQueue_);
1328 * Implementation of |processQueue_|.
1332 cvox.ChromeVoxEventWatcher.doProcessQueue_ = function() {
1333 // Return now if there are no events in the queue.
1334 if (cvox.ChromeVoxEventWatcher.events_.length == 0) {
1338 // Look for the most recent focus event and delete any preceding event
1339 // that applied to whatever was focused previously.
1340 var events = cvox.ChromeVoxEventWatcher.events_;
1341 var lastFocusIndex = -1;
1342 var lastFocusTimestamp = 0;
1345 for (i = 0; evt = events[i]; i++) {
1346 if (evt.type == 'focus') {
1348 lastFocusTimestamp = evt.timeStamp;
1351 cvox.ChromeVoxEventWatcher.events_ = [];
1352 for (i = 0; evt = events[i]; i++) {
1353 var prevEvt = events[i - 1] || {};
1354 if ((i >= lastFocusIndex || evt.type == 'LiveRegion') &&
1355 (prevEvt.type != 'focus' || evt.type != 'change')) {
1356 cvox.ChromeVoxEventWatcher.events_.push(evt);
1360 cvox.ChromeVoxEventWatcher.events_.sort(function(a, b) {
1361 if (b.type != 'LiveRegion' && a.type == 'LiveRegion') {
1367 // If the most recent focus event was very recent, wait for things to
1368 // settle down before processing events, unless the max wait time has
1370 var currentTime = new Date().getTime();
1371 if (lastFocusIndex >= 0 &&
1372 cvox.ChromeVoxEventWatcherUtil.shouldWaitToProcess(
1374 cvox.ChromeVoxEventWatcher.firstUnprocessedEventTime,
1376 window.setTimeout(cvox.ChromeVoxEventWatcher.processQueue_,
1377 cvox.ChromeVoxEventWatcher.WAIT_TIME_MS_);
1381 // Process the remaining events in the queue, in order.
1382 for (i = 0; evt = cvox.ChromeVoxEventWatcher.events_[i]; i++) {
1383 cvox.ChromeVoxEventWatcher.handleEvent_(evt);
1385 cvox.ChromeVoxEventWatcher.events_ = new Array();
1386 cvox.ChromeVoxEventWatcher.firstUnprocessedEventTime = -1;
1387 cvox.ChromeVoxEventWatcher.queueProcessingScheduled_ = false;
1388 cvox.ChromeVoxEventWatcher.maybeCallReadyCallbacks_();
1392 * Handle events from the queue by routing them to their respective handlers.
1395 * @param {Event} evt The event to be handled.
1397 cvox.ChromeVoxEventWatcher.handleEvent_ = function(evt) {
1401 cvox.ChromeVoxEventWatcher.setUpTextHandler();
1402 if (cvox.ChromeVoxEventWatcher.currentTextControl) {
1403 cvox.ChromeVoxEventWatcher.handleTextChanged(true);
1405 var editableText = /** @type {cvox.ChromeVoxEditableTextBase} */
1406 (cvox.ChromeVoxEventWatcher.currentTextHandler);
1407 if (editableText && editableText.lastChangeDescribed) {
1411 // We're either not on a text control, or we are on a text control but no
1412 // text change was described. Let's try describing the state instead.
1413 cvox.ChromeVoxEventWatcher.handleControlChanged(document.activeElement);
1416 // Some controls change only after key up.
1417 cvox.ChromeVoxEventWatcher.handleControlChanged(document.activeElement);
1420 cvox.ChromeVoxEventWatcher.setUpTextHandler();
1423 cvox.ApiImplementation.syncToNode(/** @type {Node} */(evt.target), true);
1426 cvox.ChromeVoxEventWatcher.focusHandler(evt);
1429 cvox.ChromeVoxEventWatcher.setUpTextHandler();
1432 cvox.ChromeVoxEventWatcher.changeHandler(evt);
1435 cvox.ChromeVoxEventWatcher.setUpTextHandler();
1438 cvox.ChromeVoxEventWatcher.speakLiveRegion_(
1439 evt.assertive, evt.navDescriptions);
1446 * Sets up the time handler.
1447 * @return {boolean} True if a time control has focus.
1450 cvox.ChromeVoxEventWatcher.setUpTimeHandler_ = function() {
1451 var currentFocus = document.activeElement;
1453 currentFocus.hasAttribute &&
1454 currentFocus.getAttribute('aria-hidden') == 'true' &&
1455 currentFocus.getAttribute('chromevoxignoreariahidden') != 'true') {
1456 currentFocus = null;
1458 if (currentFocus.constructor == HTMLInputElement &&
1459 currentFocus.type && (currentFocus.type == 'time')) {
1460 cvox.ChromeVoxEventWatcher.currentTimeHandler =
1461 new cvox.ChromeVoxHTMLTimeWidget(currentFocus, cvox.ChromeVox.tts);
1463 cvox.ChromeVoxEventWatcher.currentTimeHandler = null;
1465 return (null != cvox.ChromeVoxEventWatcher.currentTimeHandler);
1470 * Sets up the media (video/audio) handler.
1471 * @return {boolean} True if a media control has focus.
1474 cvox.ChromeVoxEventWatcher.setUpMediaHandler_ = function() {
1475 var currentFocus = document.activeElement;
1477 currentFocus.hasAttribute &&
1478 currentFocus.getAttribute('aria-hidden') == 'true' &&
1479 currentFocus.getAttribute('chromevoxignoreariahidden') != 'true') {
1480 currentFocus = null;
1482 if ((currentFocus.constructor == HTMLVideoElement) ||
1483 (currentFocus.constructor == HTMLAudioElement)) {
1484 cvox.ChromeVoxEventWatcher.currentMediaHandler =
1485 new cvox.ChromeVoxHTMLMediaWidget(currentFocus, cvox.ChromeVox.tts);
1487 cvox.ChromeVoxEventWatcher.currentMediaHandler = null;
1489 return (null != cvox.ChromeVoxEventWatcher.currentMediaHandler);
1493 * Sets up the date handler.
1494 * @return {boolean} True if a date control has focus.
1497 cvox.ChromeVoxEventWatcher.setUpDateHandler_ = function() {
1498 var currentFocus = document.activeElement;
1500 currentFocus.hasAttribute &&
1501 currentFocus.getAttribute('aria-hidden') == 'true' &&
1502 currentFocus.getAttribute('chromevoxignoreariahidden') != 'true') {
1503 currentFocus = null;
1505 if (currentFocus.constructor == HTMLInputElement &&
1506 currentFocus.type &&
1507 ((currentFocus.type == 'date') ||
1508 (currentFocus.type == 'month') ||
1509 (currentFocus.type == 'week'))) {
1510 cvox.ChromeVoxEventWatcher.currentDateHandler =
1511 new cvox.ChromeVoxHTMLDateWidget(currentFocus, cvox.ChromeVox.tts);
1513 cvox.ChromeVoxEventWatcher.currentDateHandler = null;
1515 return (null != cvox.ChromeVoxEventWatcher.currentDateHandler);