-
Notifications
You must be signed in to change notification settings - Fork 4
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
👍
@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
(function(document) { | ||
|
||
/* Symbols for blocking elements and already inert elements */ | ||
const BLOCKING_ELEMS = Symbol('blockingElements'); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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 |
* @type {Set<HTMLElement>} | ||
* @private | ||
*/ | ||
this[_blockingElements] = new Set(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Also, you wouldn't have to track _topElement
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It was an Array
, but then got converted to Set
, see #1 (comment)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actually, there are multiple conditions, so maybe my proposal would just be uglier..
Implementation of
document.blockingElements
stack polyfill.document.$blockingElements
manages 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.body
inert
to 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
inert
polyfill performance. The polyfill tries to invokeinert
only 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-event
css 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-focus
inside nestedx-b
(Chrome v52 stable for MacOs -> http://localhost:8080/components/blockingElements/demo/ce.html?ce=v0)