Index: modules/overlay/overlay-parent.js
===================================================================
RCS file: /cvs/drupal/drupal/modules/overlay/overlay-parent.js,v
retrieving revision 1.55
diff -u -p -r1.55 overlay-parent.js
--- modules/overlay/overlay-parent.js	29 Sep 2010 00:50:46 -0000	1.55
+++ modules/overlay/overlay-parent.js	12 Oct 2010 14:54:06 -0000
@@ -7,11 +7,18 @@
  */
 Drupal.behaviors.overlayParent = {
   attach: function (context, settings) {
+    if (Drupal.overlay.isOpen) {
+      Drupal.overlay.applyARIAPresentation(context);
+    }
+
     if (this.processed) {
       return;
     }
     this.processed = true;
 
+    // Initiate the overlay ARIA buffer.
+    Drupal.overlay.updateARIABuffer();
+
     $(window)
       // When the hash (URL fragment) changes, open the overlay if needed.
       .bind('hashchange.drupal-overlay', $.proxy(Drupal.overlay, 'eventhandlerOperateByURLFragment'))
@@ -79,6 +86,7 @@ Drupal.overlay.open = function (url) {
   this.isOpening = false;
   this.isOpen = true;
   $(document.documentElement).addClass('overlay-open');
+  this.applyARIAPresentation();
 
   // Allow other scripts to respond to this event.
   $(document).trigger('drupalOverlayOpen');
@@ -121,8 +129,7 @@ Drupal.overlay.create = function () {
     .bind('drupalOverlayClose' + eventClass, $.proxy(this, 'eventhandlerRefreshPage'))
     .bind('drupalOverlayBeforeClose' + eventClass +
           ' drupalOverlayBeforeLoad' + eventClass +
-          ' drupalOverlayResize' + eventClass, $.proxy(this, 'eventhandlerDispatchEvent'))
-    .bind('keydown' + eventClass, $.proxy(this, 'eventhandlerRestrictKeyboardNavigation'));
+          ' drupalOverlayResize' + eventClass, $.proxy(this, 'eventhandlerDispatchEvent'));
 
   if ($('.overlay-displace-top, .overlay-displace-bottom').length) {
     $(document)
@@ -149,6 +156,7 @@ Drupal.overlay.load = function (url) {
   $(document).trigger('drupalOverlayBeforeLoad');
 
   $(document.documentElement).addClass('overlay-loading');
+  this.$container.attr('aria-busy', 'true');
 
   // The contentDocument property is not supported in IE until IE8.
   var iframeDocument = this.inactiveFrame[0].contentDocument || this.inactiveFrame[0].contentWindow.document;
@@ -158,8 +166,7 @@ Drupal.overlay.load = function (url) {
   // entry using URL fragments.
   iframeDocument.location.replace(url);
 
-  // Immediately move the focus to the iframe.
-  this.inactiveFrame.focus();
+  this.updateARIABuffer();
   return true;
 };
 
@@ -188,10 +195,17 @@ Drupal.overlay.close = function () {
   $(document.documentElement).removeClass('overlay-open');
   // Restore the original document title.
   document.title = this.originalTitle;
+  this.restoreARIAPresentation();
 
   // Allow other scripts to respond to this event.
   $(document).trigger('drupalOverlayClose');
 
+  // Move focus to the element that was active right before opening the overlay.
+   if (this.lastActiveElement) {
+     $(this.lastActiveElement).focus();
+     this.lastActiveElement = null;
+   }
+
   // When the iframe is still loading don't destroy it immediately but after
   // the content is loaded (see Drupal.overlay.loadChild).
   if (!this.isLoading) {
@@ -206,8 +220,10 @@ Drupal.overlay.close = function () {
  */
 Drupal.overlay.destroy = function () {
   $([document, window]).unbind('.drupal-overlay-open');
-  this.$iframeA.unbind('.drupal-overlay');
-  this.$iframeB.unbind('.drupal-overlay');
+  // Remove iframes first to make sure they are removed from the ARIA buffer;
+  // FF does not update the virtual buffer correctly.
+  this.$iframeA.unbind('.drupal-overlay').remove();
+  this.$iframeB.unbind('.drupal-overlay').remove();
   this.$container.remove();
 
   this.$container = null;
@@ -271,7 +287,8 @@ Drupal.overlay.loadChild = function (eve
 
   this.isLoading = false;
   $(document.documentElement).removeClass('overlay-loading');
-  event.data.sibling.removeClass('overlay-active');
+  event.data.sibling.removeClass('overlay-active').attr({ 'tabindex': -1, 'role': 'presentation' });
+  this.$container.attr('aria-busy', 'false');
 
   // Only continue when overlay is still open and not closing.
   if (this.isOpen && !this.isClosing) {
@@ -289,6 +306,10 @@ Drupal.overlay.loadChild = function (eve
       // Load an empty document into the inactive iframe.
       (this.inactiveFrame[0].contentDocument || this.inactiveFrame[0].contentWindow.document).location.replace('about:blank');
 
+      this.updateARIABuffer();
+      this.activeFrame.focus();
+      iframeWindow.jQuery('a:first').focus();
+ 
       // Allow other scripts to respond to this event.
       $(document).trigger('drupalOverlayLoad');
     }
@@ -491,6 +512,8 @@ Drupal.overlay.eventhandlerOverrideLink 
       // pressing the ALT, CTRL, META (Command key on the Macintosh keyboard)
       // or SHIFT key.
       if (event.button == 0 && !event.altKey && !event.ctrlKey && !event.metaKey && !event.shiftKey) {
+        // Store the clicked link as the last active element.
+        this.lastActiveElement = target;
         // Redirect to a fragmentized href. This will trigger a hashchange event.
         this.redirect(href);
         // Prevent default action and further propagation of the event.
@@ -625,37 +648,6 @@ Drupal.overlay.eventhandlerRefreshPage =
 };
 
 /**
- * Event handler: makes sure that when the overlay is open no elements (except
- * for elements inside any displaced elements) of the parent document are
- * reachable through keyboard (TAB) navigation.
- *
- * @param event
- *   Event being triggered, with the following restrictions:
- *   - event.type: keydown
- *   - event.currentTarget: document
- */
-Drupal.overlay.eventhandlerRestrictKeyboardNavigation = function (event) {
-  if (!this.$tabbables) {
-    this.$tabbables = $(':tabbable');
-  }
-
-  if (event.keyCode && event.keyCode == $.ui.keyCode.TAB) {
-    // Whenever the focus is not inside the overlay (or a displaced element)
-    // move the focus along until it is.
-    var direction = event.shiftKey ? -1 : 1;
-    var current = this.$tabbables.index(event.target);
-    var $allowedParent = '#overlay-container, .overlay-displace-top, .overlay-displace-bottom';
-    if (current != -1 && this.$tabbables[current + direction] && !this.$tabbables.eq(current + direction).closest($allowedParent).length) {
-      while (this.$tabbables[current + direction] && !this.$tabbables.eq(current + direction).closest($allowedParent).length) {
-        current = current + direction;
-      }
-      // Move focus.
-      this.$tabbables.eq(current).focus();
-    }
-  }
-};
-
-/**
  * Event handler: dispatches events to the overlay document.
  *
  * @param event
@@ -813,6 +805,75 @@ Drupal.overlay.getDisplacement = functio
 };
 
 /**
+ * Updates ARIA buffer.
+ *
+ * @see http://juicystudio.com/article/improving-ajax-applications-for-jaws-users.php
+ */
+Drupal.overlay.updateARIABuffer = function () {
+  if (!Drupal.overlay.$ariabuffer) {
+    Drupal.overlay.$ariabuffer = $('<input id="overlay-ariabuffer" name="overlay-ariabuffer" type="hidden" value="0"/>').appendTo(document.body);
+  }
+  Drupal.overlay.$ariabuffer.val(Drupal.overlay.$ariabuffer.val() === '1' ? '0' : '1');
+};
+
+/**
+ * Set the presentation ARIA role to all elements in context.
+ *
+ * @param context
+ *   A DOM Element or jQuery to use as context.
+ */
+Drupal.overlay.applyARIAPresentation = function (context) {
+  $('*', context || document.body).each(function () {
+    var el = $(this);
+    if (el.closest('#overlay-container, #overlay-ariabuffer, .overlay-displace-top, .overlay-displace-bottom').length !== 0) {
+      return;
+    }
+    if (el.is(':tabbable')) {
+      var tabindex = el.attr('tabIndex');
+      $.data(this, 'drupalOverlayOriginalTabIndex', tabindex == undefined ? 0 : tabindex);
+      el.attr('tabIndex', -1);
+    }
+    var role = el.attr('role');
+    $.data(this, 'drupalOverlayOriginalRole', role == undefined ? 0 : role);
+    el.attr('role', 'presentation');
+  });
+
+  this.updateARIABuffer();
+};
+
+/**
+ * Removes the presentation ARIA role from all elements in context and restores
+ * their original attribute value.
+ *
+ * @param context
+ *   A DOM Element or jQuery to use as context.
+ */
+Drupal.overlay.restoreARIAPresentation = function (context) {
+  $('*', context || document.body).each(function () {
+    var el = $(this);
+    if (el.closest('#overlay-container, .overlay-displace-top, .overlay-displace-bottom').length !== 0) {
+      return;
+    }
+    var tabindex = $.data(this, 'drupalOverlayOriginalTabIndex');
+    if (tabindex) {
+      el.attr('tabIndex', tabindex);
+    }
+    else {
+      el.removeAttr('tabIndex');
+    }
+    var role = $.data(this, 'drupalOverlayOriginalRole');
+    if (role) {
+      el.attr('role', role);
+    }
+    else {
+      el.removeAttr('role');
+    }
+  });
+
+  this.updateARIABuffer();
+};
+
+/**
  * Theme function to create the overlay iframe element.
  */
 Drupal.theme.prototype.overlayContainer = function () {
