1(function (global, factory) {
2 typeof exports === 'object' && typeof module !== 'undefined' ? factory() :
3 typeof define === 'function' && define.amd ? define('inert', factory) :
4 (factory());
5}(this, (function () { 'use strict';
6
7 var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }();
8
9 function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
10
11 /**
12 * This work is licensed under the W3C Software and Document License
13 * (http://www.w3.org/Consortium/Legal/2015/copyright-software-and-document).
14 */
15
16 (function () {
17 // Return early if we're not running inside of the browser.
18 if (typeof window === 'undefined' || typeof Element === 'undefined') {
19 return;
20 }
21
22 // Convenience function for converting NodeLists.
23 /** @type {typeof Array.prototype.slice} */
24 var slice = Array.prototype.slice;
25
26 /**
27 * IE has a non-standard name for "matches".
28 * @type {typeof Element.prototype.matches}
29 */
30 var matches = Element.prototype.matches || Element.prototype.msMatchesSelector;
31
32 /** @type {string} */
33 var _focusableElementsString = ['a[href]', 'area[href]', 'input:not([disabled])', 'select:not([disabled])', 'textarea:not([disabled])', 'button:not([disabled])', 'details', 'summary', 'iframe', 'object', 'embed', 'video', '[contenteditable]'].join(',');
34
35 /**
36 * `InertRoot` manages a single inert subtree, i.e. a DOM subtree whose root element has an `inert`
37 * attribute.
38 *
39 * Its main functions are:
40 *
41 * - to create and maintain a set of managed `InertNode`s, including when mutations occur in the
42 * subtree. The `makeSubtreeUnfocusable()` method handles collecting `InertNode`s via registering
43 * each focusable node in the subtree with the singleton `InertManager` which manages all known
44 * focusable nodes within inert subtrees. `InertManager` ensures that a single `InertNode`
45 * instance exists for each focusable node which has at least one inert root as an ancestor.
46 *
47 * - to notify all managed `InertNode`s when this subtree stops being inert (i.e. when the `inert`
48 * attribute is removed from the root node). This is handled in the destructor, which calls the
49 * `deregister` method on `InertManager` for each managed inert node.
50 */
51
52 var InertRoot = function () {
53 /**
54 * @param {!HTMLElement} rootElement The HTMLElement at the root of the inert subtree.
55 * @param {!InertManager} inertManager The global singleton InertManager object.
56 */
57 function InertRoot(rootElement, inertManager) {
58 _classCallCheck(this, InertRoot);
59
60 /** @type {!InertManager} */
61 this._inertManager = inertManager;
62
63 /** @type {!HTMLElement} */
64 this._rootElement = rootElement;
65
66 /**
67 * @type {!Set<!InertNode>}
68 * All managed focusable nodes in this InertRoot's subtree.
69 */
70 this._managedNodes = new Set();
71
72 // Make the subtree hidden from assistive technology
73 if (this._rootElement.hasAttribute('aria-hidden')) {
74 /** @type {?string} */
75 this._savedAriaHidden = this._rootElement.getAttribute('aria-hidden');
76 } else {
77 this._savedAriaHidden = null;
78 }
79 this._rootElement.setAttribute('aria-hidden', 'true');
80
81 // Make all focusable elements in the subtree unfocusable and add them to _managedNodes
82 this._makeSubtreeUnfocusable(this._rootElement);
83
84 // Watch for:
85 // - any additions in the subtree: make them unfocusable too
86 // - any removals from the subtree: remove them from this inert root's managed nodes
87 // - attribute changes: if `tabindex` is added, or removed from an intrinsically focusable
88 // element, make that node a managed node.
89 this._observer = new MutationObserver(this._onMutation.bind(this));
90 this._observer.observe(this._rootElement, { attributes: true, childList: true, subtree: true });
91 }
92
93 /**
94 * Call this whenever this object is about to become obsolete. This unwinds all of the state
95 * stored in this object and updates the state of all of the managed nodes.
96 */
97
98
99 _createClass(InertRoot, [{
100 key: 'destructor',
101 value: function destructor() {
102 this._observer.disconnect();
103
104 if (this._rootElement) {
105 if (this._savedAriaHidden !== null) {
106 this._rootElement.setAttribute('aria-hidden', this._savedAriaHidden);
107 } else {
108 this._rootElement.removeAttribute('aria-hidden');
109 }
110 }
111
112 this._managedNodes.forEach(function (inertNode) {
113 this._unmanageNode(inertNode.node);
114 }, this);
115
116 // Note we cast the nulls to the ANY type here because:
117 // 1) We want the class properties to be declared as non-null, or else we
118 // need even more casts throughout this code. All bets are off if an
119 // instance has been destroyed and a method is called.
120 // 2) We don't want to cast "this", because we want type-aware optimizations
121 // to know which properties we're setting.
122 this._observer = /** @type {?} */null;
123 this._rootElement = /** @type {?} */null;
124 this._managedNodes = /** @type {?} */null;
125 this._inertManager = /** @type {?} */null;
126 }
127
128 /**
129 * @return {!Set<!InertNode>} A copy of this InertRoot's managed nodes set.
130 */
131
132 }, {
133 key: '_makeSubtreeUnfocusable',
134
135
136 /**
137 * @param {!Node} startNode
138 */
139 value: function _makeSubtreeUnfocusable(startNode) {
140 var _this2 = this;
141
142 composedTreeWalk(startNode, function (node) {
143 return _this2._visitNode(node);
144 });
145
146 var activeElement = document.activeElement;
147
148 if (!document.body.contains(startNode)) {
149 // startNode may be in shadow DOM, so find its nearest shadowRoot to get the activeElement.
150 var node = startNode;
151 /** @type {!ShadowRoot|undefined} */
152 var root = undefined;
153 while (node) {
154 if (node.nodeType === Node.DOCUMENT_FRAGMENT_NODE) {
155 root = /** @type {!ShadowRoot} */node;
156 break;
157 }
158 node = node.parentNode;
159 }
160 if (root) {
161 activeElement = root.activeElement;
162 }
163 }
164 if (startNode.contains(activeElement)) {
165 activeElement.blur();
166 // In IE11, if an element is already focused, and then set to tabindex=-1
167 // calling blur() will not actually move the focus.
168 // To work around this we call focus() on the body instead.
169 if (activeElement === document.activeElement) {
170 document.body.focus();
171 }
172 }
173 }
174
175 /**
176 * @param {!Node} node
177 */
178
179 }, {
180 key: '_visitNode',
181 value: function _visitNode(node) {
182 if (node.nodeType !== Node.ELEMENT_NODE) {
183 return;
184 }
185 var element = /** @type {!HTMLElement} */node;
186
187 // If a descendant inert root becomes un-inert, its descendants will still be inert because of
188 // this inert root, so all of its managed nodes need to be adopted by this InertRoot.
189 if (element !== this._rootElement && element.hasAttribute('inert')) {
190 this._adoptInertRoot(element);
191 }
192
193 if (matches.call(element, _focusableElementsString) || element.hasAttribute('tabindex')) {
194 this._manageNode(element);
195 }
196 }
197
198 /**
199 * Register the given node with this InertRoot and with InertManager.
200 * @param {!Node} node
201 */
202
203 }, {
204 key: '_manageNode',
205 value: function _manageNode(node) {
206 var inertNode = this._inertManager.register(node, this);
207 this._managedNodes.add(inertNode);
208 }
209
210 /**
211 * Unregister the given node with this InertRoot and with InertManager.
212 * @param {!Node} node
213 */
214
215 }, {
216 key: '_unmanageNode',
217 value: function _unmanageNode(node) {
218 var inertNode = this._inertManager.deregister(node, this);
219 if (inertNode) {
220 this._managedNodes['delete'](inertNode);
221 }
222 }
223
224 /**
225 * Unregister the entire subtree starting at `startNode`.
226 * @param {!Node} startNode
227 */
228
229 }, {
230 key: '_unmanageSubtree',
231 value: function _unmanageSubtree(startNode) {
232 var _this3 = this;
233
234 composedTreeWalk(startNode, function (node) {
235 return _this3._unmanageNode(node);
236 });
237 }
238
239 /**
240 * If a descendant node is found with an `inert` attribute, adopt its managed nodes.
241 * @param {!HTMLElement} node
242 */
243
244 }, {
245 key: '_adoptInertRoot',
246 value: function _adoptInertRoot(node) {
247 var inertSubroot = this._inertManager.getInertRoot(node);
248
249 // During initialisation this inert root may not have been registered yet,
250 // so register it now if need be.
251 if (!inertSubroot) {
252 this._inertManager.setInert(node, true);
253 inertSubroot = this._inertManager.getInertRoot(node);
254 }
255
256 inertSubroot.managedNodes.forEach(function (savedInertNode) {
257 this._manageNode(savedInertNode.node);
258 }, this);
259 }
260
261 /**
262 * Callback used when mutation observer detects subtree additions, removals, or attribute changes.
263 * @param {!Array<!MutationRecord>} records
264 * @param {!MutationObserver} self
265 */
266
267 }, {
268 key: '_onMutation',
269 value: function _onMutation(records, self) {
270 records.forEach(function (record) {
271 var target = /** @type {!HTMLElement} */record.target;
272 if (record.type === 'childList') {
273 // Manage added nodes
274 slice.call(record.addedNodes).forEach(function (node) {
275 this._makeSubtreeUnfocusable(node);
276 }, this);
277
278 // Un-manage removed nodes
279 slice.call(record.removedNodes).forEach(function (node) {
280 this._unmanageSubtree(node);
281 }, this);
282 } else if (record.type === 'attributes') {
283 if (record.attributeName === 'tabindex') {
284 // Re-initialise inert node if tabindex changes
285 this._manageNode(target);
286 } else if (target !== this._rootElement && record.attributeName === 'inert' && target.hasAttribute('inert')) {
287 // If a new inert root is added, adopt its managed nodes and make sure it knows about the
288 // already managed nodes from this inert subroot.
289 this._adoptInertRoot(target);
290 var inertSubroot = this._inertManager.getInertRoot(target);
291 this._managedNodes.forEach(function (managedNode) {
292 if (target.contains(managedNode.node)) {
293 inertSubroot._manageNode(managedNode.node);
294 }
295 });
296 }
297 }
298 }, this);
299 }
300 }, {
301 key: 'managedNodes',
302 get: function get() {
303 return new Set(this._managedNodes);
304 }
305
306 /** @return {boolean} */
307
308 }, {
309 key: 'hasSavedAriaHidden',
310 get: function get() {
311 return this._savedAriaHidden !== null;
312 }
313
314 /** @param {?string} ariaHidden */
315
316 }, {
317 key: 'savedAriaHidden',
318 set: function set(ariaHidden) {
319 this._savedAriaHidden = ariaHidden;
320 }
321
322 /** @return {?string} */
323 ,
324 get: function get() {
325 return this._savedAriaHidden;
326 }
327 }]);
328
329 return InertRoot;
330 }();
331
332 /**
333 * `InertNode` initialises and manages a single inert node.
334 * A node is inert if it is a descendant of one or more inert root elements.
335 *
336 * On construction, `InertNode` saves the existing `tabindex` value for the node, if any, and
337 * either removes the `tabindex` attribute or sets it to `-1`, depending on whether the element
338 * is intrinsically focusable or not.
339 *
340 * `InertNode` maintains a set of `InertRoot`s which are descendants of this `InertNode`. When an
341 * `InertRoot` is destroyed, and calls `InertManager.deregister()`, the `InertManager` notifies the
342 * `InertNode` via `removeInertRoot()`, which in turn destroys the `InertNode` if no `InertRoot`s
343 * remain in the set. On destruction, `InertNode` reinstates the stored `tabindex` if one exists,
344 * or removes the `tabindex` attribute if the element is intrinsically focusable.
345 */
346
347
348 var InertNode = function () {
349 /**
350 * @param {!Node} node A focusable element to be made inert.
351 * @param {!InertRoot} inertRoot The inert root element associated with this inert node.
352 */
353 function InertNode(node, inertRoot) {
354 _classCallCheck(this, InertNode);
355
356 /** @type {!Node} */
357 this._node = node;
358
359 /** @type {boolean} */
360 this._overrodeFocusMethod = false;
361
362 /**
363 * @type {!Set<!InertRoot>} The set of descendant inert roots.
364 * If and only if this set becomes empty, this node is no longer inert.
365 */
366 this._inertRoots = new Set([inertRoot]);
367
368 /** @type {?number} */
369 this._savedTabIndex = null;
370
371 /** @type {boolean} */
372 this._destroyed = false;
373
374 // Save any prior tabindex info and make this node untabbable
375 this.ensureUntabbable();
376 }
377
378 /**
379 * Call this whenever this object is about to become obsolete.
380 * This makes the managed node focusable again and deletes all of the previously stored state.
381 */
382
383
384 _createClass(InertNode, [{
385 key: 'destructor',
386 value: function destructor() {
387 this._throwIfDestroyed();
388
389 if (this._node && this._node.nodeType === Node.ELEMENT_NODE) {
390 var element = /** @type {!HTMLElement} */this._node;
391 if (this._savedTabIndex !== null) {
392 element.setAttribute('tabindex', this._savedTabIndex);
393 } else {
394 element.removeAttribute('tabindex');
395 }
396
397 // Use `delete` to restore native focus method.
398 if (this._overrodeFocusMethod) {
399 delete element.focus;
400 }
401 }
402
403 // See note in InertRoot.destructor for why we cast these nulls to ANY.
404 this._node = /** @type {?} */null;
405 this._inertRoots = /** @type {?} */null;
406 this._destroyed = true;
407 }
408
409 /**
410 * @type {boolean} Whether this object is obsolete because the managed node is no longer inert.
411 * If the object has been destroyed, any attempt to access it will cause an exception.
412 */
413
414 }, {
415 key: '_throwIfDestroyed',
416
417
418 /**
419 * Throw if user tries to access destroyed InertNode.
420 */
421 value: function _throwIfDestroyed() {
422 if (this.destroyed) {
423 throw new Error('Trying to access destroyed InertNode');
424 }
425 }
426
427 /** @return {boolean} */
428
429 }, {
430 key: 'ensureUntabbable',
431
432
433 /** Save the existing tabindex value and make the node untabbable and unfocusable */
434 value: function ensureUntabbable() {
435 if (this.node.nodeType !== Node.ELEMENT_NODE) {
436 return;
437 }
438 var element = /** @type {!HTMLElement} */this.node;
439 if (matches.call(element, _focusableElementsString)) {
440 if ( /** @type {!HTMLElement} */element.tabIndex === -1 && this.hasSavedTabIndex) {
441 return;
442 }
443
444 if (element.hasAttribute('tabindex')) {
445 this._savedTabIndex = /** @type {!HTMLElement} */element.tabIndex;
446 }
447 element.setAttribute('tabindex', '-1');
448 if (element.nodeType === Node.ELEMENT_NODE) {
449 element.focus = function () {};
450 this._overrodeFocusMethod = true;
451 }
452 } else if (element.hasAttribute('tabindex')) {
453 this._savedTabIndex = /** @type {!HTMLElement} */element.tabIndex;
454 element.removeAttribute('tabindex');
455 }
456 }
457
458 /**
459 * Add another inert root to this inert node's set of managing inert roots.
460 * @param {!InertRoot} inertRoot
461 */
462
463 }, {
464 key: 'addInertRoot',
465 value: function addInertRoot(inertRoot) {
466 this._throwIfDestroyed();
467 this._inertRoots.add(inertRoot);
468 }
469
470 /**
471 * Remove the given inert root from this inert node's set of managing inert roots.
472 * If the set of managing inert roots becomes empty, this node is no longer inert,
473 * so the object should be destroyed.
474 * @param {!InertRoot} inertRoot
475 */
476
477 }, {
478 key: 'removeInertRoot',
479 value: function removeInertRoot(inertRoot) {
480 this._throwIfDestroyed();
481 this._inertRoots['delete'](inertRoot);
482 if (this._inertRoots.size === 0) {
483 this.destructor();
484 }
485 }
486 }, {
487 key: 'destroyed',
488 get: function get() {
489 return (/** @type {!InertNode} */this._destroyed
490 );
491 }
492 }, {
493 key: 'hasSavedTabIndex',
494 get: function get() {
495 return this._savedTabIndex !== null;
496 }
497
498 /** @return {!Node} */
499
500 }, {
501 key: 'node',
502 get: function get() {
503 this._throwIfDestroyed();
504 return this._node;
505 }
506
507 /** @param {?number} tabIndex */
508
509 }, {
510 key: 'savedTabIndex',
511 set: function set(tabIndex) {
512 this._throwIfDestroyed();
513 this._savedTabIndex = tabIndex;
514 }
515
516 /** @return {?number} */
517 ,
518 get: function get() {
519 this._throwIfDestroyed();
520 return this._savedTabIndex;
521 }
522 }]);
523
524 return InertNode;
525 }();
526
527 /**
528 * InertManager is a per-document singleton object which manages all inert roots and nodes.
529 *
530 * When an element becomes an inert root by having an `inert` attribute set and/or its `inert`
531 * property set to `true`, the `setInert` method creates an `InertRoot` object for the element.
532 * The `InertRoot` in turn registers itself as managing all of the element's focusable descendant
533 * nodes via the `register()` method. The `InertManager` ensures that a single `InertNode` instance
534 * is created for each such node, via the `_managedNodes` map.
535 */
536
537
538 var InertManager = function () {
539 /**
540 * @param {!Document} document
541 */
542 function InertManager(document) {
543 _classCallCheck(this, InertManager);
544
545 if (!document) {
546 throw new Error('Missing required argument; InertManager needs to wrap a document.');
547 }
548
549 /** @type {!Document} */
550 this._document = document;
551
552 /**
553 * All managed nodes known to this InertManager. In a map to allow looking up by Node.
554 * @type {!Map<!Node, !InertNode>}
555 */
556 this._managedNodes = new Map();
557
558 /**
559 * All inert roots known to this InertManager. In a map to allow looking up by Node.
560 * @type {!Map<!Node, !InertRoot>}
561 */
562 this._inertRoots = new Map();
563
564 /**
565 * Observer for mutations on `document.body`.
566 * @type {!MutationObserver}
567 */
568 this._observer = new MutationObserver(this._watchForInert.bind(this));
569
570 // Add inert style.
571 addInertStyle(document.head || document.body || document.documentElement);
572
573 // Wait for document to be loaded.
574 if (document.readyState === 'loading') {
575 document.addEventListener('DOMContentLoaded', this._onDocumentLoaded.bind(this));
576 } else {
577 this._onDocumentLoaded();
578 }
579 }
580
581 /**
582 * Set whether the given element should be an inert root or not.
583 * @param {!HTMLElement} root
584 * @param {boolean} inert
585 */
586
587
588 _createClass(InertManager, [{
589 key: 'setInert',
590 value: function setInert(root, inert) {
591 if (inert) {
592 if (this._inertRoots.has(root)) {
593 // element is already inert
594 return;
595 }
596
597 var inertRoot = new InertRoot(root, this);
598 root.setAttribute('inert', '');
599 this._inertRoots.set(root, inertRoot);
600 // If not contained in the document, it must be in a shadowRoot.
601 // Ensure inert styles are added there.
602 if (!this._document.body.contains(root)) {
603 var parent = root.parentNode;
604 while (parent) {
605 if (parent.nodeType === 11) {
606 addInertStyle(parent);
607 }
608 parent = parent.parentNode;
609 }
610 }
611 } else {
612 if (!this._inertRoots.has(root)) {
613 // element is already non-inert
614 return;
615 }
616
617 var _inertRoot = this._inertRoots.get(root);
618 _inertRoot.destructor();
619 this._inertRoots['delete'](root);
620 root.removeAttribute('inert');
621 }
622 }
623
624 /**
625 * Get the InertRoot object corresponding to the given inert root element, if any.
626 * @param {!Node} element
627 * @return {!InertRoot|undefined}
628 */
629
630 }, {
631 key: 'getInertRoot',
632 value: function getInertRoot(element) {
633 return this._inertRoots.get(element);
634 }
635
636 /**
637 * Register the given InertRoot as managing the given node.
638 * In the case where the node has a previously existing inert root, this inert root will
639 * be added to its set of inert roots.
640 * @param {!Node} node
641 * @param {!InertRoot} inertRoot
642 * @return {!InertNode} inertNode
643 */
644
645 }, {
646 key: 'register',
647 value: function register(node, inertRoot) {
648 var inertNode = this._managedNodes.get(node);
649 if (inertNode !== undefined) {
650 // node was already in an inert subtree
651 inertNode.addInertRoot(inertRoot);
652 } else {
653 inertNode = new InertNode(node, inertRoot);
654 }
655
656 this._managedNodes.set(node, inertNode);
657
658 return inertNode;
659 }
660
661 /**
662 * De-register the given InertRoot as managing the given inert node.
663 * Removes the inert root from the InertNode's set of managing inert roots, and remove the inert
664 * node from the InertManager's set of managed nodes if it is destroyed.
665 * If the node is not currently managed, this is essentially a no-op.
666 * @param {!Node} node
667 * @param {!InertRoot} inertRoot
668 * @return {?InertNode} The potentially destroyed InertNode associated with this node, if any.
669 */
670
671 }, {
672 key: 'deregister',
673 value: function deregister(node, inertRoot) {
674 var inertNode = this._managedNodes.get(node);
675 if (!inertNode) {
676 return null;
677 }
678
679 inertNode.removeInertRoot(inertRoot);
680 if (inertNode.destroyed) {
681 this._managedNodes['delete'](node);
682 }
683
684 return inertNode;
685 }
686
687 /**
688 * Callback used when document has finished loading.
689 */
690
691 }, {
692 key: '_onDocumentLoaded',
693 value: function _onDocumentLoaded() {
694 // Find all inert roots in document and make them actually inert.
695 var inertElements = slice.call(this._document.querySelectorAll('[inert]'));
696 inertElements.forEach(function (inertElement) {
697 this.setInert(inertElement, true);
698 }, this);
699
700 // Comment this out to use programmatic API only.
701 this._observer.observe(this._document.body || this._document.documentElement, { attributes: true, subtree: true, childList: true });
702 }
703
704 /**
705 * Callback used when mutation observer detects attribute changes.
706 * @param {!Array<!MutationRecord>} records
707 * @param {!MutationObserver} self
708 */
709
710 }, {
711 key: '_watchForInert',
712 value: function _watchForInert(records, self) {
713 var _this = this;
714 records.forEach(function (record) {
715 switch (record.type) {
716 case 'childList':
717 slice.call(record.addedNodes).forEach(function (node) {
718 if (node.nodeType !== Node.ELEMENT_NODE) {
719 return;
720 }
721 var inertElements = slice.call(node.querySelectorAll('[inert]'));
722 if (matches.call(node, '[inert]')) {
723 inertElements.unshift(node);
724 }
725 inertElements.forEach(function (inertElement) {
726 this.setInert(inertElement, true);
727 }, _this);
728 }, _this);
729 break;
730 case 'attributes':
731 if (record.attributeName !== 'inert') {
732 return;
733 }
734 var target = /** @type {!HTMLElement} */record.target;
735 var inert = target.hasAttribute('inert');
736 _this.setInert(target, inert);
737 break;
738 }
739 }, this);
740 }
741 }]);
742
743 return InertManager;
744 }();
745
746 /**
747 * Recursively walk the composed tree from |node|.
748 * @param {!Node} node
749 * @param {(function (!HTMLElement))=} callback Callback to be called for each element traversed,
750 * before descending into child nodes.
751 * @param {?ShadowRoot=} shadowRootAncestor The nearest ShadowRoot ancestor, if any.
752 */
753
754
755 function composedTreeWalk(node, callback, shadowRootAncestor) {
756 if (node.nodeType == Node.ELEMENT_NODE) {
757 var element = /** @type {!HTMLElement} */node;
758 if (callback) {
759 callback(element);
760 }
761
762 // Descend into node:
763 // If it has a ShadowRoot, ignore all child elements - these will be picked
764 // up by the <content> or <shadow> elements. Descend straight into the
765 // ShadowRoot.
766 var shadowRoot = /** @type {!HTMLElement} */element.shadowRoot;
767 if (shadowRoot) {
768 composedTreeWalk(shadowRoot, callback, shadowRoot);
769 return;
770 }
771
772 // If it is a <content> element, descend into distributed elements - these
773 // are elements from outside the shadow root which are rendered inside the
774 // shadow DOM.
775 if (element.localName == 'content') {
776 var content = /** @type {!HTMLContentElement} */element;
777 // Verifies if ShadowDom v0 is supported.
778 var distributedNodes = content.getDistributedNodes ? content.getDistributedNodes() : [];
779 for (var i = 0; i < distributedNodes.length; i++) {
780 composedTreeWalk(distributedNodes[i], callback, shadowRootAncestor);
781 }
782 return;
783 }
784
785 // If it is a <slot> element, descend into assigned nodes - these
786 // are elements from outside the shadow root which are rendered inside the
787 // shadow DOM.
788 if (element.localName == 'slot') {
789 var slot = /** @type {!HTMLSlotElement} */element;
790 // Verify if ShadowDom v1 is supported.
791 var _distributedNodes = slot.assignedNodes ? slot.assignedNodes({ flatten: true }) : [];
792 for (var _i = 0; _i < _distributedNodes.length; _i++) {
793 composedTreeWalk(_distributedNodes[_i], callback, shadowRootAncestor);
794 }
795 return;
796 }
797 }
798
799 // If it is neither the parent of a ShadowRoot, a <content> element, a <slot>
800 // element, nor a <shadow> element recurse normally.
801 var child = node.firstChild;
802 while (child != null) {
803 composedTreeWalk(child, callback, shadowRootAncestor);
804 child = child.nextSibling;
805 }
806 }
807
808 /**
809 * Adds a style element to the node containing the inert specific styles
810 * @param {!Node} node
811 */
812 function addInertStyle(node) {
813 if (node.querySelector('style#inert-style, link#inert-style')) {
814 return;
815 }
816 var style = document.createElement('style');
817 style.setAttribute('id', 'inert-style');
818 style.textContent = '\n' + '[inert] {\n' + ' pointer-events: none;\n' + ' cursor: default;\n' + '}\n' + '\n' + '[inert], [inert] * {\n' + ' -webkit-user-select: none;\n' + ' -moz-user-select: none;\n' + ' -ms-user-select: none;\n' + ' user-select: none;\n' + '}\n';
819 node.appendChild(style);
820 }
821
822 if (!HTMLElement.prototype.hasOwnProperty('inert')) {
823 /** @type {!InertManager} */
824 var inertManager = new InertManager(document);
825
826 Object.defineProperty(HTMLElement.prototype, 'inert', {
827 enumerable: true,
828 /** @this {!HTMLElement} */
829 get: function get() {
830 return this.hasAttribute('inert');
831 },
832 /** @this {!HTMLElement} */
833 set: function set(inert) {
834 inertManager.setInert(this, inert);
835 }
836 });
837 }
838 })();
839
840})));
841