diff --git a/blocking-elements.html b/blocking-elements.html index 609a297..caeffb1 100644 --- a/blocking-elements.html +++ b/blocking-elements.html @@ -20,7 +20,7 @@ - + @@ -51,7 +51,7 @@ - + diff --git a/blocking-elements.js b/blocking-elements.js index 4ef0582..696d88c 100644 --- a/blocking-elements.js +++ b/blocking-elements.js @@ -32,10 +32,9 @@ * the blocking elements */ destructor() { - for (let i = 0; i < this._blockingElements.length; i++) { - getPathToBody(this._blockingElements[i]).forEach(function(node) { - setInertToSiblingsOfNode(node, false); - }); + // Loop from the last to first to gradually update the tree up to body. + for (let i = this._blockingElements.length - 1; i >= 0; i--) { + topChanged(this._blockingElements[i - 1], this._blockingElements[i]); } delete this._blockingElements; } @@ -57,7 +56,7 @@ // TODO(valdrin) should this element be moved to the top if already in // the list? if (i !== -1) { - console.warn('node already added in `document.blockingElements`.'); + console.warn('node already added in document.blockingElements'); return; } var oldTop = this.top; @@ -98,48 +97,53 @@ } /** - * Sets `inert` to all document nodes except the top node, its parents, and its - * distributed content. + * Sets `inert` to all document nodes except the new top node, its parents, + * and its distributed content. Pass `oldTop` to limit node updates (will look + * for common parents and avoid setting them twice). * @param {Node=} newTop * @param {Node=} oldTop */ function topChanged(newTop, oldTop) { - // TODO(valdrin) optimize this as it sets values twice. - if (oldTop) { - getPathToBody(oldTop).forEach(function(node) { - setInertToSiblingsOfNode(node, false); - }); - } - if (newTop) { - let nodesToSkip = newTop.shadowRoot ? getDistributedChildren(newTop.shadowRoot) : null; - getPathToBody(newTop).forEach(function(node) { - setInertToSiblingsOfNode(node, true, nodesToSkip); - }); + let oldPath = oldTop ? getPathToBody(oldTop) : []; + let newPath = newTop ? getPathToBody(newTop) : []; + let nodesToSkip = newTop && newTop.shadowRoot ? getDistributedChildren(newTop.shadowRoot) : null; + // Loop from top to deepest elements, so we find the common parents and + // avoid setting them twice. + while (oldPath.length || newPath.length) { + let oldEl = oldPath.pop(); + let newEl = newPath.pop(); + if (oldEl !== newEl) { + // Same parent, set only these 2 children. + if (oldEl && newEl && oldEl.parentNode === newEl.parentNode) { + oldEl.inert = true; + newEl.inert = false; + } else { + oldEl && setInertToSiblingsOfNode(oldEl, false); + newEl && setInertToSiblingsOfNode(newEl, true, nodesToSkip); + } + } } } + /* Regex for elements that are not inertable. */ + const NOT_INERTABLE = /^(style|template|script)$/; + /** * Sets `inert` to the siblings of the node except the nodes to skip. - * @param {Node} node + * @param {!Node} node * @param {boolean} inert * @param {Set=} nodesToSkip */ function setInertToSiblingsOfNode(node, inert, nodesToSkip) { let sibling = node; while ((sibling = sibling.previousElementSibling)) { - if (sibling.localName === 'style' || sibling.localName === 'script') { - continue; - } - if (!nodesToSkip || !nodesToSkip.has(sibling)) { + if (!NOT_INERTABLE.test(sibling.localName) && (!nodesToSkip || !nodesToSkip.has(sibling))) { sibling.inert = inert; } } sibling = node; while ((sibling = sibling.nextElementSibling)) { - if (sibling.localName === 'style' || sibling.localName === 'script') { - continue; - } - if (!nodesToSkip || !nodesToSkip.has(sibling)) { + if (!NOT_INERTABLE.test(sibling.localName) && (!nodesToSkip || !nodesToSkip.has(sibling))) { sibling.inert = inert; } }