From 73e78d406bb47d49105616935327d4df7dffb527 Mon Sep 17 00:00:00 2001 From: Chris Joel Date: Mon, 27 Jul 2015 10:22:17 -0700 Subject: [PATCH] Fix scroll locking in ShadowDOM Scroll locking was broken in ShadowDOM, because the result of calling `element.contains` is different compared to ShadeyDOM (which lacks encapsulation). What we want in the scroll manager is to query the composed ShadowDOM tree when testing element hierarchy. So, a new method and supporting caching mechanism has been added to enable this in a (hopefully) performant manner. --- iron-dropdown-scroll-manager.html | 73 ++++++++++++++++++++++++++++++- 1 file changed, 71 insertions(+), 2 deletions(-) diff --git a/iron-dropdown-scroll-manager.html b/iron-dropdown-scroll-manager.html index 2dea219..d895dd6 100644 --- a/iron-dropdown-scroll-manager.html +++ b/iron-dropdown-scroll-manager.html @@ -31,6 +31,7 @@ return this._lockingElements[this._lockingElements.length - 1]; }, + /** * Returns true if the provided element is "scroll locked," which is to * say that it cannot be scrolled via pointer or keyboard interactions. @@ -40,10 +41,27 @@ */ elementIsScrollLocked: function(element) { var currentLockingElement = this.currentLockingElement; + var scrollLocked; + + if (this._hasCachedLockedElement(element)) { + return true; + } - return !!currentLockingElement && + if (this._hasCachedUnlockedElement(element)) { + return false; + } + + scrollLocked = !!currentLockingElement && currentLockingElement !== element && - !currentLockingElement.contains(element); + !this._composedTreeContains(currentLockingElement, element); + + if (scrollLocked) { + this._lockedElementCache.push(element); + } else { + this._unlockedElementCache.push(element); + } + + return scrollLocked; }, /** @@ -62,6 +80,9 @@ } this._lockingElements.push(element); + + this._lockedElementCache = []; + this._unlockedElementCache = []; }, /** @@ -82,6 +103,9 @@ this._lockingElements.splice(index, 1); + this._lockedElementCache = []; + this._unlockedElementCache = []; + if (this._lockingElements.length === 0) { this._unlockScrollInteractions(); } @@ -89,6 +113,10 @@ _lockingElements: [], + _lockedElementCache: null, + + _unlockedElementCache: null, + _originalBodyStyles: {}, _isScrollingKeypress: function(event) { @@ -96,6 +124,47 @@ event, 'pageup pagedown home end up left down right'); }, + _hasCachedLockedElement: function(element) { + return this._lockedElementCache.indexOf(element) > -1; + }, + + _hasCachedUnlockedElement: function(element) { + return this._unlockedElementCache.indexOf(element) > -1; + }, + + _composedTreeContains: function(element, child) { + // NOTE(cdata): This method iterates over content elements and their + // corresponding distributed nodes to implement a contains-like method + // that pierces through the composed tree of the ShadowDOM. Results of + // this operation are cached (elsewhere) on a per-scroll-lock basis, to + // guard against potentially expensive lookups happening repeatedly as + // a user scrolls / touchmoves. + var contentElements; + var distributedNodes; + var contentIndex; + var nodeIndex; + + if (element.contains(child)) { + return true; + } + + contentElements = Polymer.dom(element).querySelectorAll('content'); + + for (contentIndex = 0; contentIndex < contentElements.length; ++contentIndex) { + + distributedNodes = Polymer.dom(contentElements[contentIndex]).getDistributedNodes(); + + for (nodeIndex = 0; nodeIndex < distributedNodes.length; ++nodeIndex) { + + if (this._composedTreeContains(distributedNodes[nodeIndex], child)) { + return true; + } + } + } + + return false; + }, + _scrollInteractionHandler: function(event) { if (Polymer .IronDropdownScrollManager