You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

840 lines
30 KiB

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