// Copyright 2014 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. /** * @fileoverview Implements the setFocus function. */ goog.provide('cvox.Focuser'); goog.require('cvox.ChromeVoxEventSuspender'); goog.require('cvox.DomUtil'); /** * Sets the browser focus to the targetNode or its closest ancestor that is * focusable. * * @param {Node} targetNode The node to move the browser focus to. * @param {boolean=} opt_focusDescendants Whether or not we check descendants * of the target node to see if they are focusable. If true, sets focus on the * first focusable descendant. If false, only sets focus on the targetNode or * its closest ancestor. Default is false. */ cvox.Focuser.setFocus = function(targetNode, opt_focusDescendants) { // Save the selection because Chrome will lose it if there's a focus or blur. var sel = window.getSelection(); var range; if (sel.rangeCount > 0) { range = sel.getRangeAt(0); } // Blur the currently-focused element if the target node is not a descendant. if (document.activeElement && !cvox.DomUtil.isDescendantOfNode(targetNode, document.activeElement)) { document.activeElement.blur(); } // Video elements should always be focusable. if (targetNode && (targetNode.constructor == HTMLVideoElement)) { if (!cvox.DomUtil.isFocusable(targetNode)) { targetNode.setAttribute('tabIndex', 0); } } if (opt_focusDescendants && !cvox.DomUtil.isFocusable(targetNode)) { var focusableDescendant = cvox.DomUtil.findFocusableDescendant(targetNode); if (focusableDescendant) { targetNode = focusableDescendant; } } else { // Search up the parent chain until a focusable node is found. while (targetNode && !cvox.DomUtil.isFocusable(targetNode)) { targetNode = targetNode.parentNode; } } // If we found something focusable, focus it - otherwise, blur it. if (cvox.DomUtil.isFocusable(targetNode)) { // Don't let the instance of ChromeVox in the parent focus iframe children // - instead, let the instance of ChromeVox in the iframe focus itself to // avoid getting trapped in iframes that have no ChromeVox in them. // This self focusing is performed by calling window.focus() in // cvox.NavigationManager.prototype.addInterframeListener_ if (targetNode.tagName != 'IFRAME') { // setTimeout must be used because there's a bug (in Chrome, I think) // with .focus() which causes the page to be redrawn incorrectly if // not in setTimeout. if (cvox.ChromeVoxEventSuspender.areEventsSuspended()) { if (cvox.Focuser.shouldEnterSuspendEvents_(targetNode)) { cvox.ChromeVoxEventSuspender.enterSuspendEvents(); } window.setTimeout(function() { targetNode.focus(); cvox.ChromeVoxEventSuspender.exitSuspendEvents(); }, 0); } else { window.setTimeout(function() { targetNode.focus(); }, 0); } } } else if (document.activeElement && document.activeElement.tagName != 'BODY') { document.activeElement.blur(); } // Restore the selection, unless the focused item is a text box. if (cvox.DomUtil.isInputTypeText(targetNode)) { targetNode.select(); } else if (range) { sel.removeAllRanges(); sel.addRange(range); } }; /** * Rules for whether or not enterSuspendEvents should be called. * In general, we should not enterSuspendEvents if the targetNode will get some * special handlers attached when a focus event is received for it; otherwise, * the special handlers will not get attached. * * @param {Node} targetNode The node that is being focused. * @return {boolean} True if enterSuspendEvents should be called. */ cvox.Focuser.shouldEnterSuspendEvents_ = function(targetNode){ if (targetNode.constructor && targetNode.constructor == HTMLVideoElement) { return false; } if (targetNode.hasAttribute) { switch (targetNode.getAttribute('type')) { case 'time': case 'date': case 'week': case 'month': return false; } } return true; };