From 57967c27259855908a3dd87d336aadf0ececec8f Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?"J.=20Rene=CC=81e=20Beach"?= <splendidnoise@gmail.com>
Date: Sun, 24 Mar 2013 15:20:27 -0400
Subject: [PATCH] Issue #1847084 by trawekp, tarekdj, jessebeach, nod_:
 Vertical toolbar doesn't work with overlay
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: J. Renée Beach <splendidnoise@gmail.com>
---
 core/misc/displace.js                          |  168 ++++++++++++++++++++++++
 core/misc/tableheader.js                       |   42 +++---
 core/modules/overlay/overlay-child-rtl.css     |   15 +--
 core/modules/overlay/overlay-child.css         |   25 ++--
 core/modules/overlay/overlay-child.js          |   15 ++-
 core/modules/overlay/overlay-parent.js         |  157 +++++++---------------
 core/modules/overlay/overlay.module            |    1 +
 core/modules/system/system.module              |   17 +++
 core/modules/toolbar/css/toolbar.base.css      |    2 +-
 core/modules/toolbar/css/toolbar.theme-rtl.css |    9 ++
 core/modules/toolbar/css/toolbar.theme.css     |    4 +-
 core/modules/toolbar/js/toolbar.js             |   66 ++++++----
 core/modules/toolbar/toolbar.module            |    7 +-
 core/themes/bartik/css/style.css               |    4 +-
 core/themes/seven/style.css                    |    4 +-
 15 files changed, 328 insertions(+), 208 deletions(-)
 create mode 100644 core/misc/displace.js

diff --git a/core/misc/displace.js b/core/misc/displace.js
new file mode 100644
index 0000000..a211137
--- /dev/null
+++ b/core/misc/displace.js
@@ -0,0 +1,168 @@
+/**
+ * Manages elements that can offset the size of the viewport.
+ */
+(function ($, Drupal) {
+
+  var offsets = {
+    top: 0,
+    right: 0,
+    bottom: 0,
+    left: 0
+  };
+
+  /**
+   * Registers a resize hanlder on the window.
+   */
+  Drupal.behaviors.drupalDisplace = {
+    attach: function (context, settings) {
+      // Do not process the window of the overlay.
+      if (parent.Drupal.overlay && parent.Drupal.overlay.iframeWindow === window) {
+        return;
+      }
+      // Mark this behavior as processed on the first pass.
+      if (this.displaceProcessed) {
+        return;
+      }
+      this.displaceProcessed = true;
+
+      $(window).on('resize.drupalDisplace', Drupal.debounce(function (event) {
+        displace(true);
+      }, 200));
+    }
+  };
+
+  /**
+   * Informs listeners of the current offset dimensions.
+   *
+   * @param boolean broadcast
+   *   (optional) When true or undefined, causes the recalculated offsets values to be
+   *   broadcast to listeners.
+   *
+   * @return object
+   *   An object whose keys are the for sides an element -- top, right, bottom
+   *   and left. The value of each key is the viewport displacement distance for
+   *   that edge.
+   */
+  function displace (broadcast) {
+    offsets = calculateOffsets();
+    if (typeof broadcast === 'undefined' || broadcast) {
+      $(document).trigger('drupalViewportOffsetChange', offsets);
+    }
+    return offsets;
+  }
+
+  /**
+   * Determines the viewport offsets.
+   *
+   * @return object
+   *   An object whose keys are the for sides an element -- top, right, bottom
+   *   and left. The value of each key is the viewport displacement distance for
+   *   that edge.
+   */
+  function calculateOffsets () {
+    return {
+      top: calculateOffset('top'),
+      right: calculateOffset('right'),
+      bottom: calculateOffset('bottom'),
+      left: calculateOffset('left')
+    };
+  }
+
+  /**
+   * Gets a specific edge's offset.
+   *
+   * Any element with the attribute data-offset-{edge} e.g. data-offset-top will
+   * be consider in the viewport offset calculations. If the attribute has a
+   * numeric value, that value will be used. If no value is provided, one will
+   * be calculated using the element's dimensions and placement.
+   *
+   * @param string edge
+   *   The name of the edge to calculate. Can be 'top', 'right',
+   *   'bottom' or 'left'.
+   *
+   * @return number
+   *   The viewport displacement distance for the requested edge.
+   */
+  function calculateOffset (edge) {
+    var edgeOffset = 0;
+    var $displacingElements = $('[data-offset-' + edge + ']');
+    for (var i = 0, n = $displacingElements.length; i < n; i++) {
+      var $el = $displacingElements.eq(i);
+      // If the element is not visble, do consider its dimensions.
+      if ($el.get(0).style.display === 'none') {
+        continue;
+      }
+      // If the offset data attribute contains a displacing value, use it.
+      var displacement = parseInt($el.attr('data-offset-' + edge), 10);
+      // If the element's offset data attribute exits
+      // but is not a valid number then get the displacement
+      // dimensions directly from the element.
+      if (isNaN(displacement)) {
+          displacement = getRawOffset($el, edge);
+      }
+      // If the displacement value is larger than the current value for this
+      // edge, use the displacement value.
+      edgeOffset = Math.max(edgeOffset, displacement);
+    }
+
+    return edgeOffset;
+  }
+
+  /**
+   * Calculates displacement for element based on its dimensions and placement.
+   *
+   * @param jQuery $el
+   *   The jQuery element whose dimensions and placement will be measured.
+   *
+   * @param string edge
+   *   The name of the edge of the viewport that the element is associated
+   *   with.
+   *
+   * @return number
+   *   The viewport displacement distance for the requested edge.
+   */
+  function getRawOffset ($el, edge) {
+    var documentElement = document.documentElement;
+    var displacement = 0;
+    var horizontal = (edge === 'left' || edge === 'right');
+    // Get the offset of the element itself.
+    var placement = $el.offset()[ horizontal ? 'left' : 'top'];
+    // Subtract scroll distance from placement to get the distance
+    // to the edge of the viewport.
+    placement = placement - (window['scroll' + (horizontal ? 'X' : 'Y')] || document.documentElement['scroll' + (horizontal) ? 'Left' : 'Top'] || 0);
+    // Find the displacement value according to the edge.
+    switch (edge) {
+      // Left and top elements displace as a sum of their own offset value
+      // plus their size.
+      case 'top':
+        // Total displacment is the sum of the elements placement and size.
+        displacement = placement + $el.outerHeight();
+        break;
+
+      case 'left':
+        // Total displacment is the sum of the elements placement and size.
+        displacement = placement + $el.outerWidth();
+        break;
+
+      // Right and bottom elements displace according to their left and
+      // top offset. Their size isn't important.
+      case 'bottom':
+        displacement = documentElement.clientHeight - placement;
+        break;
+
+      case 'right':
+        displacement = documentElement.clientWidth - placement;
+        break;
+
+      default:
+        displacement = 0;
+    }
+    return displacement;
+  }
+
+  /**
+   * Assign the displace function to a property of the Drupal global object.
+   */
+  Drupal.displace = displace;
+
+})(jQuery, Drupal);
diff --git a/core/misc/tableheader.js b/core/misc/tableheader.js
index 0d4f7cd..84a3c28 100644
--- a/core/misc/tableheader.js
+++ b/core/misc/tableheader.js
@@ -17,6 +17,7 @@ function scrollValue(position) {
 
 // Select and initilize sticky table headers.
 function tableHeaderInitHandler(e) {
+  TableHeader.displacements = window.parent.Drupal.displace(false);
   var $tables = $(e.data.context).find('table.sticky-enabled').once('tableheader');
   for (var i = 0, il = $tables.length; i < il; i++) {
     TableHeader.tables.push(new TableHeader($tables[i]));
@@ -39,16 +40,16 @@ function tableHeaderOnScrollHandler(e) {
   forTables('onScroll');
 }
 
-function tableHeaderOffsetChangeHandler(e) {
+function tableHeaderOffsetChangeHandler(e, offsets) {
   // Compute the new offset value.
-  TableHeader.computeOffsetTop();
-  forTables('stickyPosition', TableHeader.offsetTop);
+  TableHeader.displacements = offsets;
+  forTables('stickyPosition', offsets.top);
 }
 
 // Bind event that need to change all tables.
 $(window).on({
   /**
-   * When resizing table width and offset top can change, recalculate everything.
+   * When resizing table width can change, recalculate everything.
    */
   'resize.TableHeader': tableHeaderResizeHandler,
 
@@ -66,9 +67,9 @@ $(document).on({
   'columnschange.TableHeader': tableHeaderResizeHandler,
 
   /**
-   * Offset value vas changed by a third party script.
+   * Recalculate TableHeader.topOffset when viewport is resized
    */
-  'offsettopchange.TableHeader': tableHeaderOffsetChangeHandler
+  'drupalViewportOffsetChange.TableHeader': tableHeaderOffsetChangeHandler
 });
 
 /**
@@ -77,9 +78,6 @@ $(document).on({
  * TableHeader will make the current table header stick to the top of the page
  * if the table is very long.
  *
- * Fire a custom "topoffsetchange" event to make TableHeader compute the
- * new offset value from the "data-offset-top" attributes of relevant elements.
- *
  * @param table
  *   DOM object for the table to add a sticky header to.
  *
@@ -122,24 +120,15 @@ $.extend(TableHeader, {
   tables: [],
 
   /**
-   * Cache of computed offset value.
+   * Cache of computed viewport displacement values.
    *
    * @type {Number}
    */
-  offsetTop: 0,
-
-  /**
-   * Sum all [data-offset-top] values and cache it.
-   */
-  computeOffsetTop: function () {
-    var $offsets = $('[data-offset-top]');
-    var value, sum = 0;
-    for (var i = 0, il = $offsets.length; i < il; i++) {
-      value = parseInt($offsets[i].getAttribute('data-offset-top'), 10);
-      sum += !isNaN(value) ? value : 0;
-    }
-    this.offsetTop = sum;
-    return sum;
+  displacements: {
+    top: 0,
+    right: 0,
+    bottom: 0,
+    left: 0
   }
 });
 
@@ -211,7 +200,7 @@ $.extend(TableHeader.prototype, {
    */
   checkStickyVisible: function () {
     var scrollTop = scrollValue('scrollTop');
-    var tableTop = this.tableOffset.top - TableHeader.offsetTop;
+    var tableTop = this.tableOffset.top - TableHeader.displacements.top;
     var tableBottom = tableTop + this.tableHeight;
     var visible = false;
 
@@ -248,9 +237,8 @@ $.extend(TableHeader.prototype, {
     this.tableHeight = this.$originalTable[0].clientHeight;
 
     // Update offset top.
-    TableHeader.computeOffsetTop();
     this.tableOffset = this.$originalTable.offset();
-    this.stickyPosition(TableHeader.offsetTop);
+    this.stickyPosition(TableHeader.displacements.top, scrollValue('scrollLeft'));
 
     // Update columns width.
     var $that = null;
diff --git a/core/modules/overlay/overlay-child-rtl.css b/core/modules/overlay/overlay-child-rtl.css
index 4195530..9f0c363 100644
--- a/core/modules/overlay/overlay-child-rtl.css
+++ b/core/modules/overlay/overlay-child-rtl.css
@@ -12,26 +12,13 @@ html {
   float: right;
   left: auto;
 }
-#overlay {
-  padding: 0.2em;
-  padding-left: 26px;
-}
 #overlay-close-wrapper {
   left: 0;
   right: auto;
 }
 #overlay-close,
 #overlay-close:hover {
-  background: transparent url(images/close.png) no-repeat;
-  border-top-right-radius: 0;
-
-  -webkit-border-top-left-radius: 12px;
-  -webkit-border-bottom-left-radius: 12px;
-  -moz-border-radius-topleft: 12px;
-  -moz-border-radius-bottomleft: 12px;
-  border-top-left-radius: 12px;
-  border-bottom-left-radius: 12px;
-  background-color: #ffffff;
+  border-radius: 12px 0 0 12px;
 }
 
 /**
diff --git a/core/modules/overlay/overlay-child.css b/core/modules/overlay/overlay-child.css
index ecfa4cb..3bbf906 100644
--- a/core/modules/overlay/overlay-child.css
+++ b/core/modules/overlay/overlay-child.css
@@ -1,4 +1,3 @@
-
 /**
  * @file
  * Basic styling for the Overlay child pages.
@@ -16,15 +15,15 @@
 }
 
 #overlay {
-  display: table;
+  box-sizing: border-box;
+  display: block;
   margin: 0 auto;
+  max-width: 80em;
+  max-width: 80rem;
   min-height: 100px;
-  min-width: 700px;
   position: relative;
-  padding: .2em;
-  padding-bottom: 2em;
-  padding-right: 26px; /* LTR */
-  width: 88%;
+  padding: 2em 40px;
+  width: 100%;
 }
 #overlay-titlebar {
   padding: 0 20px;
@@ -68,8 +67,8 @@
 }
 #overlay-close,
 #overlay-close:hover {
-  background: transparent url(images/close.png) no-repeat; /* LTR */
-  border-top-left-radius: 0; /* LTR */
+  background: #ffffff url(images/close.png) no-repeat;
+  border-radius: 0 12px 12px 0; /* LTR */
   display: block;
   height: 26px;
   margin: 0;
@@ -77,14 +76,6 @@
   /* Replace with position:fixed to get a scrolling close button. */
   position: absolute;
   width: 26px;
-
-  -webkit-border-top-right-radius: 12px;
-  -webkit-border-bottom-right-radius: 12px;
-  -moz-border-radius-topright: 12px;
-  -moz-border-radius-bottomright: 12px;
-  border-top-right-radius: 12px;
-  border-bottom-right-radius: 12px;
-  background-color: #ffffff;
 }
 
 /**
diff --git a/core/modules/overlay/overlay-child.js b/core/modules/overlay/overlay-child.js
index 80a8a2f..6044852 100644
--- a/core/modules/overlay/overlay-child.js
+++ b/core/modules/overlay/overlay-child.js
@@ -179,11 +179,14 @@ Drupal.overlayChild.behaviors.shortcutAddLink = function (context, settings) {
   });
 };
 
-// Workaround because of the way jQuery events works.
-// jQuery from the parent frame needs to be used to catch this event.
-parent.jQuery(document).bind('offsettopchange', function () {
-  // Fires an event that the child iframe can listen to.
-  $(document).trigger('offsettopchange');
-});
+Drupal.overlayChild.behaviors.bindDrupalViewportOffsetChangeEvent = function (context, settings) {
+  // Workaround because of the way jQuery events works.
+  // jQuery from the parent frame needs to be used to catch this event.
+  parent.jQuery(parent.document).bind('drupalViewportOffsetChange', function (event, offsets) {
+    // Fires an event that the child iframe can listen to.
+    $(document).trigger('drupalViewportOffsetChange', offsets);
+    $(document).trigger('resize');
+  });
+};
 
 })(jQuery);
diff --git a/core/modules/overlay/overlay-parent.js b/core/modules/overlay/overlay-parent.js
index caf9336..e7c1162 100644
--- a/core/modules/overlay/overlay-parent.js
+++ b/core/modules/overlay/overlay-parent.js
@@ -106,6 +106,11 @@ Drupal.overlay.open = function (url) {
  * Create the underlying markup and behaviors for the overlay.
  */
 Drupal.overlay.create = function () {
+  // The overlay's positioning can be affected by other elements on the page.
+  // Get the current viewpor offset values.
+  this.viewportOffsets = Drupal.displace();
+
+  // Build the overlay container.
   this.$container = $(Drupal.theme('overlayContainer'))
     .appendTo(document.body);
 
@@ -131,6 +136,7 @@ Drupal.overlay.create = function () {
   $(window)
     .bind('resize' + eventClass, $.proxy(this, 'eventhandlerOuterResize'));
   $(document)
+    .bind('drupalViewportOffsetChange' + eventClass, $.proxy(this, 'eventhandlerViewportOffsetChange'))
     .bind('drupalOverlayLoad' + eventClass, $.proxy(this, 'eventhandlerOuterResize'))
     .bind('drupalOverlayReady' + eventClass +
           ' drupalOverlayClose' + eventClass, $.proxy(this, 'eventhandlerSyncURLFragment'))
@@ -139,11 +145,9 @@ Drupal.overlay.create = function () {
           ' drupalOverlayBeforeLoad' + eventClass +
           ' drupalOverlayResize' + eventClass, $.proxy(this, 'eventhandlerDispatchEvent'));
 
-  if ($('.overlay-displace-top, .overlay-displace-bottom').length) {
-    $(document)
-      .bind('drupalOverlayResize' + eventClass, $.proxy(this, 'eventhandlerAlterDisplacedElements'))
-      .bind('drupalOverlayClose' + eventClass, $.proxy(this, 'eventhandlerRestoreDisplacedElements'));
-  }
+  $(document)
+    .bind('drupalOverlayResize' + eventClass, $.proxy(this, 'eventhandlerAlterDisplacedElements'))
+    .bind('drupalOverlayClose' + eventClass, $.proxy(this, 'eventhandlerRestoreDisplacedElements'));
 };
 
 /**
@@ -398,6 +402,23 @@ Drupal.overlay.isExternalLink = function (url) {
 };
 
 /**
+ * Responds to the drupalViewportOffsetChange event.
+ *
+ * @param object event
+ *   A jQuery event object.
+ *
+ * @param object offsets
+ *   An object whose keys are the for sides an element -- top, right, bottom
+ *   and left. The value of each key is the viewport displacement distance for
+ *   that edge.
+ */
+Drupal.overlay.eventhandlerViewportOffsetChange = function (event, offsets) {
+  Drupal.overlay.viewportOffsets = offsets;
+  // Allow other scripts to respond to this event.
+  $(document).trigger('drupalOverlayResize');
+}
+
+/**
  * Event handler: resizes overlay according to the size of the parent window.
  *
  * @param event
@@ -435,78 +456,29 @@ Drupal.overlay.eventhandlerAlterDisplacedElements = function (event) {
     return;
   }
 
-  $(this.iframeWindow.document.body).css({
-    marginTop: Drupal.overlay.getDisplacement('top'),
-    marginBottom: Drupal.overlay.getDisplacement('bottom')
-  }).attr('data-offset-top', Drupal.overlay.getDisplacement('top'));
-
-  $(document).bind('offsettopchange', function () {
-    var iframeDocument = Drupal.overlay.iframeWindow.document;
-    $(iframeDocument.body).attr('data-offset-top', Drupal.overlay.getDisplacement('top'));
-    $(iframeDocument).trigger('offsettopchange');
-  });
-
-  var documentHeight = this.iframeWindow.document.body.clientHeight;
-  var documentWidth = this.iframeWindow.document.body.clientWidth;
-  // IE6 doesn't support maxWidth, use width instead.
-  var maxWidthName = 'maxWidth';
-
-  if (Drupal.overlay.leftSidedScrollbarOffset === undefined && $(document.documentElement).attr('dir') === 'rtl') {
-    // We can't use element.clientLeft to detect whether scrollbars are placed
-    // on the left side of the element when direction is set to "rtl" as most
-    // browsers dont't support it correctly.
-    // http://www.gtalbot.org/BugzillaSection/DocumentAllDHTMLproperties.html
-    // There seems to be absolutely no way to detect whether the scrollbar
-    // is on the left side in Opera; always expect scrollbar to be on the left.
-    if ($.browser.opera) {
-      Drupal.overlay.leftSidedScrollbarOffset = document.documentElement.clientWidth - this.iframeWindow.document.documentElement.clientWidth + this.iframeWindow.document.documentElement.clientLeft;
-    }
-    else if (this.iframeWindow.document.documentElement.clientLeft) {
-      Drupal.overlay.leftSidedScrollbarOffset = this.iframeWindow.document.documentElement.clientLeft;
-    }
-    else {
-      var el1 = $('<div style="direction: rtl; overflow: scroll;"></div>').appendTo(document.body);
-      var el2 = $('<div></div>').appendTo(el1);
-      Drupal.overlay.leftSidedScrollbarOffset = parseInt(el2[0].offsetLeft - el1[0].offsetLeft, 10);
-      el1.remove();
-    }
+  var offsets = Drupal.overlay.viewportOffsets;
+
+  // Move the body of the iframe contentDocument inward a sufficient distance
+  // to prevent it from appearing underneath displacing elements like the
+  // toolbar.
+  var iframeBody = this.iframeWindow.document.body;
+  $(iframeBody).css({
+    'padding-top': offsets.top,
+    'padding-right': offsets.right,
+    'padding-bottom': offsets.bottom,
+    'padding-left': offsets.left
+  })
+  // Trigger a repaint.
+  iframeBody.style.display='none';
+  iframeBody.offsetHeight;
+  iframeBody.style.display='block';
+
+  // Constrain the width of offsetting top and bottom elements, such as the
+  // toolbar, so that a scroll in the overlay iframe won't be occluded.
+  var iframeBodyWidth = iframeBody.clientWidth;
+  if (iframeBodyWidth > 0 && iframeBodyWidth < document.documentElement.clientWidth) {
+    $('[data-offset-top], [data-offset-bottom]').css('max-width', iframeBodyWidth);
   }
-
-  // Consider any element that should be visible above the overlay (such as
-  // a toolbar).
-  $('.overlay-displace-top, .overlay-displace-bottom').each(function () {
-    var data = $(this).data();
-    var maxWidth = documentWidth;
-    // In IE, Shadow filter makes element to overlap the scrollbar with 1px.
-    if (this.filters && this.filters.length && this.filters.item('DXImageTransform.Microsoft.Shadow')) {
-      maxWidth -= 1;
-    }
-
-    if (Drupal.overlay.leftSidedScrollbarOffset) {
-      $(this).css('left', Drupal.overlay.leftSidedScrollbarOffset);
-    }
-
-    // Prevent displaced elements overlapping window's scrollbar.
-    var currentMaxWidth = parseInt($(this).css(maxWidthName), 10);
-    if ((data.drupalOverlay && data.drupalOverlay.maxWidth) || isNaN(currentMaxWidth) || currentMaxWidth > maxWidth || currentMaxWidth <= 0) {
-      $(this).css(maxWidthName, maxWidth);
-      (data.drupalOverlay = data.drupalOverlay || {}).maxWidth = true;
-    }
-
-    // Use a more rigorous approach if the displaced element still overlaps
-    // window's scrollbar; clip the element on the right.
-    var offset = $(this).offset();
-    var offsetRight = offset.left + $(this).outerWidth();
-    if ((data.drupalOverlay && data.drupalOverlay.clip) || offsetRight > maxWidth) {
-      if (Drupal.overlay.leftSidedScrollbarOffset) {
-        $(this).css('clip', 'rect(auto, auto, ' + (documentHeight - offset.top) + 'px, ' + (Drupal.overlay.leftSidedScrollbarOffset + 2) + 'px)');
-      }
-      else {
-        $(this).css('clip', 'rect(auto, ' + (maxWidth - offset.left) + 'px, ' + (documentHeight - offset.top) + 'px, auto)');
-      }
-      (data.drupalOverlay = data.drupalOverlay || {}).clip = true;
-    }
-  });
 };
 
 /**
@@ -519,16 +491,7 @@ Drupal.overlay.eventhandlerAlterDisplacedElements = function (event) {
  *   - event.currentTarget: any
  */
 Drupal.overlay.eventhandlerRestoreDisplacedElements = function (event) {
-  var $displacedElements = $('.overlay-displace-top, .overlay-displace-bottom');
-  try {
-    $displacedElements.css({ maxWidth: '', clip: '' });
-  }
-  // IE bug that doesn't allow unsetting style.clip (http://dev.jquery.com/ticket/6512).
-  catch (err) {
-    $displacedElements.attr('style', function (index, attr) {
-      return attr.replace(/clip\s*:\s*rect\([^)]+\);?/i, '');
-    });
-  }
+  $('[data-offset-top], [data-offset-bottom]').css('max-width', 'none');
 };
 
 /**
@@ -828,7 +791,7 @@ Drupal.overlay.resetActiveClass = function(activePath) {
   var self = this;
   var windowDomain = window.location.protocol + window.location.hostname;
 
-  $('.overlay-displace-top, .overlay-displace-bottom')
+  $('#toolbar-administration')
   .find('a[href]')
   // Remove active class from all links in displaced elements.
   .removeClass('active')
@@ -871,24 +834,6 @@ Drupal.overlay.getPath = function (link) {
 };
 
 /**
- * Get the total displacement of given region.
- *
- * @param region
- *   Region name. Either "top" or "bottom".
- *
- * @return
- *   The total displacement of given region in pixels.
- */
-Drupal.overlay.getDisplacement = function (region) {
-  var displacement = 0;
-  var lastDisplaced = $('[data-offset-' + region + ']');
-  if (lastDisplaced.length) {
-    displacement = parseInt(lastDisplaced.attr('data-offset-' + region));
-  }
-  return displacement;
-};
-
-/**
  * Makes elements outside the overlay unreachable via the tab key.
  *
  * @param context
@@ -916,7 +861,7 @@ Drupal.overlay.makeDocumentUntabbable = function (context) {
   // If another element (like a div) has a tabindex, it's also tabbable.
   $tabbable = $tabbable.add($hasTabindex);
   // Leave links inside the overlay and toolbars alone.
-  $overlay = $('.overlay-element, #overlay-container, .overlay-displace-top, .overlay-displace-bottom').find('*');
+  $overlay = $('.overlay-element, #overlay-container, #toolbar-administration').find('*');
   $tabbable = $tabbable.not($overlay);
   // We now have a list of everything in the underlying document that could
   // possibly be reachable via the tab key. Make it all unreachable.
diff --git a/core/modules/overlay/overlay.module b/core/modules/overlay/overlay.module
index cc29255..197d006 100644
--- a/core/modules/overlay/overlay.module
+++ b/core/modules/overlay/overlay.module
@@ -225,6 +225,7 @@ function overlay_library_info() {
       array('system', 'jquery'),
       array('system', 'drupal'),
       array('system', 'drupalSettings'),
+      array('system', 'drupal.displace'),
       array('system', 'jquery.ui.core'),
       array('system', 'jquery.bbq'),
     ),
diff --git a/core/modules/system/system.module b/core/modules/system/system.module
index 4fc06f7..d0aed29 100644
--- a/core/modules/system/system.module
+++ b/core/modules/system/system.module
@@ -1399,6 +1399,22 @@ function system_library_info() {
     ),
   );
 
+  // A utility that measures and reports viewport offset dimensions from
+  // elements like the toolbar that can potentially displace the positioning of
+  // elements like the overlay.
+  $libraries['drupal.displace'] = array(
+    'title' => 'Drupal displace',
+    'version' => VERSION,
+    'js' => array(
+      'core/misc/displace.js' => array('group' => JS_LIBRARY),
+    ),
+    'dependencies' => array(
+      array('system', 'jquery'),
+      array('system', 'drupal'),
+      array('system', 'drupal.debounce'),
+    ),
+  );
+
   // A utility function to limit calls to a function with a given time.
   $libraries['drupal.debounce'] = array(
     'title' => 'Drupal debounce',
@@ -2095,6 +2111,7 @@ function system_library_info() {
       array('system', 'drupal'),
       array('system', 'drupalSettings'),
       array('system', 'jquery.once'),
+      array('system', 'drupal.displace'),
     ),
   );
   $libraries['drupal.timezone'] = array(
diff --git a/core/modules/toolbar/css/toolbar.base.css b/core/modules/toolbar/css/toolbar.base.css
index 58a2c2b..64e6468 100644
--- a/core/modules/toolbar/css/toolbar.base.css
+++ b/core/modules/toolbar/css/toolbar.base.css
@@ -91,7 +91,7 @@ html.js .toolbar {
   position: absolute;
 }
 .toolbar .tray {
-  z-index: 250;
+  z-index: 1200;
 }
 .toolbar .horizontal {
   width: 100%;
diff --git a/core/modules/toolbar/css/toolbar.theme-rtl.css b/core/modules/toolbar/css/toolbar.theme-rtl.css
index fa5a1df..031bc60 100644
--- a/core/modules/toolbar/css/toolbar.theme-rtl.css
+++ b/core/modules/toolbar/css/toolbar.theme-rtl.css
@@ -3,8 +3,17 @@
  */
 
 /**
+ * Toolbar bar.
+ */
+.toolbar .bar {
+  box-shadow: 1px 0 3px 1px rgba(0, 0, 0, 0.3333);
+}
+/**
  * Toolbar tray.
  */
+ .toolbar .horizontal {
+  box-shadow: 2px 1px 3px 1px rgba(0, 0, 0, 0.3333);
+}
 .toolbar .horizontal > .lining {
   padding-right: 0;
   padding-left: 5em;
diff --git a/core/modules/toolbar/css/toolbar.theme.css b/core/modules/toolbar/css/toolbar.theme.css
index c1317e5..4120135 100644
--- a/core/modules/toolbar/css/toolbar.theme.css
+++ b/core/modules/toolbar/css/toolbar.theme.css
@@ -28,7 +28,7 @@
  */
 .toolbar .bar {
   background-color: #0f0f0f;
-  box-shadow: 0 0 3px 1px rgba(0, 0, 0, 0.3333);
+  box-shadow: -1px 0 3px 1px rgba(0, 0, 0, 0.3333); /* LTR */
   color: #dddddd;
 }
 .toolbar .bar a {
@@ -66,7 +66,7 @@
 }
 .toolbar .horizontal {
   border-bottom: 1px solid #aaaaaa;
-  box-shadow: 0 1px 3px 1px rgba(0, 0, 0, 0.3333);
+  box-shadow: -2px 1px 3px 1px rgba(0, 0, 0, 0.3333); /* LTR */
 }
 .toolbar .horizontal .tray {
   background-color: #f5f5f5;
diff --git a/core/modules/toolbar/js/toolbar.js b/core/modules/toolbar/js/toolbar.js
index 45e91ad..446f1e6 100644
--- a/core/modules/toolbar/js/toolbar.js
+++ b/core/modules/toolbar/js/toolbar.js
@@ -81,12 +81,9 @@ Drupal.behaviors.toolbar = {
       changeOrientation((locked) ? 'vertical' : ((mql.wide.matches) ? 'horizontal' : 'vertical'), locked);
       // Render the main menu as a nested, collapsible accordion.
       $toolbar.find('.toolbar-menu-administration > .menu').toolbarMenu();
-      // Call setHeight on screen resize. Wrap it in debounce to prevent
-      // setHeight from being called too frequently.
-      var setHeight = Drupal.debounce(Drupal.toolbar.setHeight, 200);
-      // Attach behavior to the window.
-      $(window)
-        .on('resize.toolbar', setHeight);
+      // Attach behaviors to the document.
+      $(document)
+        .on('drupalViewportOffsetChange.toolbar', Drupal.toolbar.adjustPlacement);
       // Attach behaviors to the toolbar.
       $toolbar
         .on('click.toolbar', '.bar a', Drupal.toolbar.toggleTray)
@@ -99,6 +96,8 @@ Drupal.behaviors.toolbar = {
         // Update the page and toolbar dimension indicators.
         updatePeripherals();
       }
+      // Call displace to get the initial placement of offset elements.
+      Drupal.displace();
     }
   },
   // Default options.
@@ -158,18 +157,22 @@ Drupal.toolbar.toggleTray = function (event) {
       // Set aria-pressed to false.
       .attr('aria-pressed', 'false');
     $toolbar.find('.tray').not($activateTray).removeClass('active');
-    // Update the page and toolbar dimension indicators.
-    updatePeripherals();
   }
+  // Update the page and toolbar dimension indicators.
+  updatePeripherals();
 };
 
 /**
- * The height of the toolbar offsets the top of the page content.
+ * Repositions trays and sets body padding according to the height of the bar.
+ *
+ * @param {Event} event
+ *   - jQuery Event object.
  *
- * Page components can register with the offsettopchange event to know when
- * the height of the toolbar changes.
+ * @param {Object} offsets
+ *   - Contains for keys -- top, right, bottom and left -- that indicate the
+ *   viewport offset distances calculated by Drupal.displace().
  */
-Drupal.toolbar.setHeight = function () {
+Drupal.toolbar.adjustPlacement = function (event, offsets) {
   // Set the top of the all the trays to the height of the bar.
   var barHeight = $toolbar.find('.bar').outerHeight();
   var height = barHeight;
@@ -181,20 +184,27 @@ Drupal.toolbar.setHeight = function () {
       tray.style.top = bhpx;
     }
   }
-  /**
-   * Get the height of the active tray and include it in the total
-   * height of the toolbar.
-   */
-  height += $trays.filter('.active.horizontal').outerHeight() || 0;
-  // Indicate the height of the toolbar in the attribute data-offset-top.
-  var offset = parseInt($toolbar.attr('data-offset-top'), 10);
-  if (offset !== height) {
-    $toolbar.attr('data-offset-top', height);
-    // Alter the padding on the top of the body element.
-    $('body').css('padding-top', height);
-    $(document).trigger('offsettopchange', height);
-    $(window).trigger('resize');
-  }
+  // Alter the padding on the top of the body element.
+  $('body').css('padding-top', offsets.top);
+};
+
+/**
+ * Sets the width of a vertical tray in a data attribute.
+ *
+ * If the width of the tray changed, Drupal.displace is called so that elements
+ * can adjust to the placement of the tray.
+ */
+Drupal.toolbar.setTrayWidth = function () {
+  var dir = document.documentElement.dir;
+  var edge = (dir === 'rtl') ? 'right' : 'left';
+  // Remove the left offset from the trays.
+  $toolbar.find('.tray').removeAttr('data-offset-' + edge + ' data-offset-top');
+  // If an active vertical tray exists, mark it as an offset element.
+  $toolbar.find('.tray.vertical.active').attr('data-offset-' + edge, '');
+  // If an active horizontal tray exists, mark it as an offset element.
+  $toolbar.find('.tray.horizontal.active').attr('data-offset-top', '');
+  // Trigger a recalculation of viewport displacing elements.
+  Drupal.displace();
 };
 
 /**
@@ -294,8 +304,8 @@ function toggleOrientationToggle (orientation) {
 function updatePeripherals () {
   // Adjust the body to accommodate trays.
   setBodyState();
-  // Adjust the height of the toolbar.
-  Drupal.toolbar.setHeight();
+  // Adjust the tray width for vertical trays.
+  Drupal.toolbar.setTrayWidth();
 }
 
 /**
diff --git a/core/modules/toolbar/toolbar.module b/core/modules/toolbar/toolbar.module
index f0f1837..2880a5d 100644
--- a/core/modules/toolbar/toolbar.module
+++ b/core/modules/toolbar/toolbar.module
@@ -93,7 +93,7 @@ function toolbar_element_info() {
       'id' => 'toolbar-administration',
       // The 'overlay-displace-top' class pushes the overlay down, so it appears
       // below the toolbar.
-      'class' => array('toolbar', 'overlay-displace-top'),
+      'class' => array('toolbar',),
       'role' => 'navigation',
     ),
     // Metadata for the administration bar.
@@ -101,7 +101,8 @@ function toolbar_element_info() {
       '#heading' => t('Toolbar items'),
       '#attributes' => array(
         'id' => 'toolbar-bar',
-        'class' => array('bar', 'overlay-displace-top', 'clearfix'),
+        'class' => array('bar', 'clearfix',),
+        'data-offset-top' => array(),
       ),
     ),
   );
@@ -315,7 +316,6 @@ function toolbar_pre_render_item($element) {
     }
     $element['tray']['#wrapper_attributes'] += $attributes;
     $element['tray']['#wrapper_attributes']['class'][] = 'tray';
-    $element['tray']['#wrapper_attributes']['class'][] = 'overlay-displace-top';
 
     if (!isset($element['tray']['#theme_wrappers'])) {
       $element['tray']['#theme_wrappers'] = array();
@@ -631,6 +631,7 @@ function toolbar_library_info() {
       array('system', 'matchmedia'),
       array('system', 'jquery.once'),
       array('system', 'drupal.debounce'),
+      array('system', 'drupal.displace'),
       array('toolbar', 'toolbar.menu'),
     ),
   );
diff --git a/core/themes/bartik/css/style.css b/core/themes/bartik/css/style.css
index 26534c7..27e2e0a 100644
--- a/core/themes/bartik/css/style.css
+++ b/core/themes/bartik/css/style.css
@@ -1622,7 +1622,7 @@ div.admin-panel .description {
 /**
  * Responsive tables.
  */
-@media screen and (max-width:28.125em) { /* 450px */
+@media screen and (max-width: 37.5em) { /* 600px */
   th.priority-low,
   td.priority-low,
   th.priority-medium,
@@ -1630,7 +1630,7 @@ div.admin-panel .description {
     display: none;
   }
 }
-@media screen and (max-width:45em) { /* 720px */
+@media screen and (max-width: 60em) { /* 920px */
   th.priority-low,
   td.priority-low {
     display: none;
diff --git a/core/themes/seven/style.css b/core/themes/seven/style.css
index 72ce5e6..6440294 100644
--- a/core/themes/seven/style.css
+++ b/core/themes/seven/style.css
@@ -522,7 +522,7 @@ table.system-status-report tr.error {
 /**
  * Responsive tables.
  */
-@media screen and (max-width:28.125em) { /* 450px */
+@media screen and (max-width: 37.5em) { /* 600px */
   th.priority-low,
   td.priority-low,
   th.priority-medium,
@@ -530,7 +530,7 @@ table.system-status-report tr.error {
     display: none;
   }
 }
-@media screen and (max-width:45em) { /* 720px */
+@media screen and (max-width: 60em) { /* 920px */
   th.priority-low,
   td.priority-low {
     display: none;
-- 
1.7.10.4

