document.blockingElements polyfill with inert#1
Conversation
f4ba650 to
5e171b8
Compare
677934d to
17282ec
Compare
| // avoid setting them twice. | ||
| while (oldElParents.length || newElParents.length) { | ||
| const oldElParent = oldElParents.pop(); | ||
| const newElParent = newElParents.pop(); |
There was a problem hiding this comment.
The tools team is leaning in the direction of only using const on top-level things (modules and symbols, for example), and preferring let for most things that have a relatively confined scope. What do you think of potentially standardizing on this style?
At the risk of starting a 🔥 /cc @justinfagnani @rictic
There was a problem hiding this comment.
Leaning, pending final disposition of a gentlemanly duel of ideas between the tools team :)
I personally prefer let for local variables.
There was a problem hiding this comment.
synced offline, and the agreement is to use const when using a variable that won't be reassigned, and let for variables that will be reused within the scope.
|
@cdata @justinfagnani feedback incorporated, PTAL. Everyone, I've updated the description of the PR with more info about performance & other options considered, PTAL :) |
4771127 to
5928123
Compare
5928123 to
9e267e4
Compare
blocking-elements.js
Outdated
| (function(document) { | ||
|
|
||
| /* Symbols for blocking elements and already inert elements */ | ||
| const BLOCKING_ELEMS = Symbol('blockingElements'); |
There was a problem hiding this comment.
FYI and in case you care, I'm going to advocate in tools that for Symbols we don't use SCREAMING_CASE, but _privateCamelCase names. I think it'll read better in the class bodies, and I also in general disagree with all caps for const, especially if const is the default.
compare:
BlockingElements[TOP_CHANGED_FN](element, oldTop, this[ALREADY_INERT_ELEMS]);and
BlockingElements[_topChangedFn](element, oldTop, this[_alreadyInertElements]);There was a problem hiding this comment.
You're right, it's less of a punch in the eye! Will update :)
|
@justinfagnani updated the Symbol names to be lower case, much more readable indeed. |
e4da079 to
4262c5f
Compare
4262c5f to
1a33482
Compare
|
@bicknellr FYI |
blocking-elements.js
Outdated
| * @type {Set<HTMLElement>} | ||
| * @private | ||
| */ | ||
| this[_blockingElements] = new Set(); |
There was a problem hiding this comment.
An array might be nicer here; they're already stacks (push, pop, includes, splice(i, 1)) so you wouldn't have to do stuff like line 113.
There was a problem hiding this comment.
Also, you wouldn't have to track _topElement.
There was a problem hiding this comment.
It was an Array, but then got converted to Set, see #1 (comment)
There was a problem hiding this comment.
@cdata @justinfagnani So the collective decision on how to make a stack was to not use the built-in stack? 😆
There was a problem hiding this comment.
ergo: it is painfully slow to compute the last element :x http://jsbin.com/yukizak/3/edit?html,js,output
this needs to be changed
| const parents = []; | ||
| let current = element; | ||
| // Stop to body. | ||
| while (current && current !== document.body) { |
There was a problem hiding this comment.
The syntax for this loop might be able to be tightened up a bit?
let current = element;
do {
// ...
} while (current = current.parentNode || current.host)There was a problem hiding this comment.
Actually, there are multiple conditions, so maybe my proposal would just be uglier..
Implementation of
document.blockingElementsstack polyfill.document.$blockingElementsmanages a stack of elements that inert the interaction outside them.push(elem), remove(elem), pop(elem)document.$blockingElements.top) is the interactive part of the documenthas(elem)returns if the element is a blocking elementThis implementation uses the WICG/inert polyfill to disable interactions on the rest of the document. The algorithm will:
document.bodyinertto all the siblings of each parent, skipping the parents and the element's distributed content (if any)Why not listening to events that trigger focus change?
Another approach could be to listen for events that trigger focus change (e.g.
focus, blur, keydown) and prevent those if focus moves out of the blocking element (it's the idea behind GoogleChrome/inert-polyfill and iron-overlay-behavior).Wrapping the focus requires to find all the focusable nodes within the top blocking element, eventually sort by tabindex, in order to find first and last focusable node (e.g. see PR PolymerElements/iron-overlay-behavior#200 which works for ShadowDom v0).
This approach doesn't allow the focus to move outside the window (e.g. to the browser's url bar, dev console if opened, etc.), and is less robust when used with assistive technology (e.g. android talkback allows to move focus with swipe on screen, Apple Voiceover allows to move focus with special keyboard combinations).
Performance
Performance is dependent on the
inertpolyfill performance. The polyfill tries to invokeinertonly if strictly needed (e.g. avoid setting it twice when updating the top blocking element).At each toggle, scripting + rendering + painting totals to ~50ms (first toggle), ~35ms (next toggles)
The heaviest parts are:
cursor-eventcss property (see issue) => once fixed we should gain ~20-25msinert, should be a gain of at least ~5ms (cost of adding a node)The results have been obtained by toggling the deepest

x-trap-focusinside nestedx-b(Chrome v52 stable for MacOs -> http://localhost:8080/components/blockingElements/demo/ce.html?ce=v0)