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