summaryrefslogtreecommitdiffstats
path: root/remoting
diff options
context:
space:
mode:
authorjamiewalch@google.com <jamiewalch@google.com@0039d316-1c4b-4281-b951-d872f2087c98>2012-05-10 18:40:48 +0000
committerjamiewalch@google.com <jamiewalch@google.com@0039d316-1c4b-4281-b951-d872f2087c98>2012-05-10 18:40:48 +0000
commitac4cac20044f97f9f4d2b8801be120c40d0d3f0d (patch)
tree614aed8d2b6e530f39043792d8cd67a26d9c6cd1 /remoting
parentb6adedd93b1a72252cd84fd824f354130c4b694e (diff)
downloadchromium_src-ac4cac20044f97f9f4d2b8801be120c40d0d3f0d.zip
chromium_src-ac4cac20044f97f9f4d2b8801be120c40d0d3f0d.tar.gz
chromium_src-ac4cac20044f97f9f4d2b8801be120c40d0d3f0d.tar.bz2
Constrain keyboard navigation while a modal dialog is active.
BUG=126200 TEST=Open and close various modal dialogs (IT2Me client and host, Me2Me host config) and make sure that the control tab ordering is what you would expect. Review URL: https://chromiumcodereview.appspot.com/10384097 git-svn-id: svn://svn.chromium.org/chrome/trunk/src@136351 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'remoting')
-rw-r--r--remoting/webapp/jscompiler_hacks.js20
-rw-r--r--remoting/webapp/remoting.js4
-rw-r--r--remoting/webapp/ui_mode.js108
3 files changed, 117 insertions, 15 deletions
diff --git a/remoting/webapp/jscompiler_hacks.js b/remoting/webapp/jscompiler_hacks.js
index 007bbb3..ff6dddc 100644
--- a/remoting/webapp/jscompiler_hacks.js
+++ b/remoting/webapp/jscompiler_hacks.js
@@ -44,5 +44,25 @@ HTMLEmbedElement.prototype.height;
/** @type {number} */
HTMLEmbedElement.prototype.width;
+/** @constructor */
+var MutationRecord = function() {};
+
+/** @type {string} */
+MutationRecord.prototype.attributeName;
+
+/** @type {Element} */
+MutationRecord.prototype.target;
+
+/** @type {string} */
+MutationRecord.prototype.type;
+
+/** @constructor
+ @param {function(Array.<MutationRecord>):void} callback */
+var WebKitMutationObserver = function(callback) {};
+
+/** @param {Element} element
+ @param {Object} options */
+WebKitMutationObserver.prototype.observe = function(element, options) {};
+
// This string is replaced with the actual value in build-webapp.py.
var OAUTH2_USE_OFFICIAL_CLIENT_ID = false;
diff --git a/remoting/webapp/remoting.js b/remoting/webapp/remoting.js
index 62d1618..21649fb 100644
--- a/remoting/webapp/remoting.js
+++ b/remoting/webapp/remoting.js
@@ -59,6 +59,8 @@ remoting.init = function() {
window.addEventListener('paste', pluginGotPaste_, false);
window.addEventListener('copy', pluginGotCopy_, false);
+ remoting.initModalDialogs();
+
if (isHostModeSupported_()) {
var noShare = document.getElementById('chrome-os-no-share');
noShare.parentNode.removeChild(noShare);
@@ -293,4 +295,4 @@ function jsonParseSafe(jsonString) {
} catch (err) {
return undefined;
}
-} \ No newline at end of file
+}
diff --git a/remoting/webapp/ui_mode.js b/remoting/webapp/ui_mode.js
index 115d689..ea1862d 100644
--- a/remoting/webapp/ui_mode.js
+++ b/remoting/webapp/ui_mode.js
@@ -50,13 +50,18 @@ remoting.AppMode = {
/**
* @param {Element} element The element to check.
- * @param {string} attr The attribute on the element to check.
- * @param {string} mode The mode to check for.
- * @return {boolean} True if |mode| is found within the element's |attr|.
+ * @param {string} attrName The attribute on the element to check.
+ * @param {Array.<string>} modes The modes to check for.
+ * @return {boolean} True if any mode in |modes| is found within the attribute.
*/
-remoting.hasModeAttribute = function(element, attr, mode) {
- return element.getAttribute(attr).match(
- new RegExp('(\\s|^)' + mode + '(\\s|$)')) != null;
+remoting.hasModeAttribute = function(element, attrName, modes) {
+ var attr = element.getAttribute(attrName);
+ for (var i = 0; i < modes.length; ++i) {
+ if (attr.match(new RegExp('(\\s|^)' + modes[i] + '(\\s|$)')) != null) {
+ return true;
+ }
+ }
+ return false;
};
/**
@@ -71,17 +76,18 @@ remoting.updateModalUi = function(mode, attr) {
for (var i = 1; i < modes.length; ++i)
modes[i] = modes[i - 1] + '.' + modes[i];
var elements = document.querySelectorAll('[' + attr + ']');
+ // Hide elements first so that we don't end up trying to show two modal
+ // dialogs at once (which would break keyboard-navigation confinement).
for (var i = 0; i < elements.length; ++i) {
var element = /** @type {Element} */ elements[i];
- var hidden = true;
- for (var m = 0; m < modes.length; ++m) {
- if (remoting.hasModeAttribute(element, attr, modes[m])) {
- hidden = false;
- break;
- }
+ if (!remoting.hasModeAttribute(element, attr, modes)) {
+ element.hidden = true;
}
- element.hidden = hidden;
- if (!hidden) {
+ }
+ for (var i = 0; i < elements.length; ++i) {
+ var element = /** @type {Element} */ elements[i];
+ if (remoting.hasModeAttribute(element, attr, modes)) {
+ element.hidden = false;
var autofocusNode = element.querySelector('[autofocus]');
if (autofocusNode) {
autofocusNode.focus();
@@ -154,3 +160,77 @@ remoting.resetInfographics = function() {
remoting.showOrHideIt2MeUi();
remoting.showOrHideMe2MeUi();
}
+
+
+/**
+ * Initialize all modal dialogs (class kd-modaldialog), adding event handlers
+ * to confine keyboard navigation to child controls of the dialog when it is
+ * shown and restore keyboard navigation when it is hidden.
+ */
+remoting.initModalDialogs = function() {
+ var dialogs = document.querySelectorAll('.kd-modaldialog');
+ var observer = new WebKitMutationObserver(confineOrRestoreFocus_);
+ var options = {
+ subtree: false,
+ attributes: true
+ };
+ for (var i = 0; i < dialogs.length; ++i) {
+ observer.observe(dialogs[i], options);
+ }
+};
+
+/**
+ * @param {Array.<MutationRecord>} mutations The set of mutations affecting
+ * an observed node.
+ */
+function confineOrRestoreFocus_(mutations) {
+ // The list of mutations can include duplicates, so reduce it to a canonical
+ // show/hide list.
+ /** @type {Array.<Element>} */
+ var shown = [];
+ /** @type {Array.<Element>} */
+ var hidden = [];
+ for (var i = 0; i < mutations.length; ++i) {
+ var mutation = mutations[i];
+ if (mutation.type == 'attributes' &&
+ mutation.attributeName == 'hidden') {
+ var node = mutation.target;
+ if (node.hidden && hidden.indexOf(node) == -1) {
+ hidden.push(node);
+ } else if (!node.hidden && shown.indexOf(node) == -1) {
+ shown.push(node);
+ }
+ }
+ }
+ var kSavedAttributeName = 'data-saved-tab-index';
+ // If any dialogs have been dismissed, restore all the tabIndex attributes.
+ if (hidden.length != 0) {
+ var elements = document.querySelectorAll('[' + kSavedAttributeName + ']');
+ for (var i = 0 ; i < elements.length; ++i) {
+ var element = /** @type {Element} */ elements[i];
+ element.tabIndex = element.getAttribute(kSavedAttributeName);
+ element.removeAttribute(kSavedAttributeName);
+ }
+ }
+ // If any dialogs have been shown, confine keyboard navigation to the first
+ // one. We don't have nested modal dialogs, so this will suffice for now.
+ if (shown.length != 0) {
+ var selector = '[tabIndex],a,area,button,input,select,textarea';
+ var disable = document.querySelectorAll(selector);
+ var except = shown[0].querySelectorAll(selector);
+ for (var i = 0; i < disable.length; ++i) {
+ var element = /** @type {Element} */ disable[i];
+ var removeFromKeyboardNavigation = true;
+ for (var j = 0; j < except.length; ++j) { // No indexOf on NodeList
+ if (element == except[j]) {
+ removeFromKeyboardNavigation = false;
+ break;
+ }
+ }
+ if (removeFromKeyboardNavigation) {
+ element.setAttribute(kSavedAttributeName, element.tabIndex);
+ element.tabIndex = -1;
+ }
+ }
+ }
+}