wp/wp-includes/js/dist/vendor/wp-polyfill-inert.js
changeset 21 48c4eec2b7e6
child 22 8c2e4d02f4ef
equal deleted inserted replaced
20:7b1b88e27a20 21:48c4eec2b7e6
       
     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') {
       
    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', '[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 })));