vendor: 25,633 bytes, lines 1-824
1/** 2 * Copyright 2016 Google Inc. All Rights Reserved. 3 * 4 * Licensed under the W3C SOFTWARE AND DOCUMENT NOTICE AND LICENSE. 5 * 6 * https://www.w3.org/Consortium/Legal/2015/copyright-software-and-document 7 * 8 */ 9(function() { 10'use strict'; 11 12// Exit early if we're not running in a browser. 13if (typeof window !== 'object') { 14 return; 15} 16 17// Exit early if all IntersectionObserver and IntersectionObserverEntry 18// features are natively supported. 19if ('IntersectionObserver' in window && 20 'IntersectionObserverEntry' in window && 21 'intersectionRatio' in window.IntersectionObserverEntry.prototype) { 22 23 // Minimal polyfill for Edge 15's lack of `isIntersecting` 24 // See: https://github.com/w3c/IntersectionObserver/issues/211 25 if (!('isIntersecting' in window.IntersectionObserverEntry.prototype)) { 26 Object.defineProperty(window.IntersectionObserverEntry.prototype, 27 'isIntersecting', { 28 get: function () { 29 return this.intersectionRatio > 0; 30 } 31 }); 32 } 33 return; 34} 35 36/** 37 * Returns the embedding frame element, if any. 38 * @param {!Document} doc 39 * @return {!Element} 40 */ 41function getFrameElement(doc) { 42 try { 43 return doc.defaultView && doc.defaultView.frameElement || null; 44 } catch (e) { 45 // Ignore the error. 46 return null; 47 } 48} 49 50/** 51 * A local reference to the root document. 52 */ 53var document = (function(startDoc) { 54 var doc = startDoc; 55 var frame = getFrameElement(doc); 56 while (frame) { 57 doc = frame.ownerDocument; 58 frame = getFrameElement(doc); 59 } 60 return doc; 61})(window.document); 62 63/** 64 * An IntersectionObserver registry. This registry exists to hold a strong 65 * reference to IntersectionObserver instances currently observing a target 66 * element. Without this registry, instances without another reference may be 67 * garbage collected. 68 */ 69var registry = []; 70 71/** 72 * The signal updater for cross-origin intersection. When not null, it means 73 * that the polyfill is configured to work in a cross-origin mode. 74 * @type {function(DOMRect|ClientRect, DOMRect|ClientRect)} 75 */ 76var crossOriginUpdater = null; 77 78/** 79 * The current cross-origin intersection. Only used in the cross-origin mode. 80 * @type {DOMRect|ClientRect} 81 */ 82var crossOriginRect = null; 83 84 85/** 86 * Creates the global IntersectionObserverEntry constructor. 87 * https://w3c.github.io/IntersectionObserver/#intersection-observer-entry 88 * @param {Object} entry A dictionary of instance properties. 89 * @constructor 90 */ 91function IntersectionObserverEntry(entry) { 92 this.time = entry.time; 93 this.target = entry.target; 94 this.rootBounds = ensureDOMRect(entry.rootBounds); 95 this.boundingClientRect = ensureDOMRect(entry.boundingClientRect); 96 this.intersectionRect = ensureDOMRect(entry.intersectionRect || getEmptyRect()); 97 this.isIntersecting = !!entry.intersectionRect; 98 99 // Calculates the intersection ratio. 100 var targetRect = this.boundingClientRect; 101 var targetArea = targetRect.width * targetRect.height; 102 var intersectionRect = this.intersectionRect; 103 var intersectionArea = intersectionRect.width * intersectionRect.height; 104 105 // Sets intersection ratio. 106 if (targetArea) { 107 // Round the intersection ratio to avoid floating point math issues: 108 // https://github.com/w3c/IntersectionObserver/issues/324 109 this.intersectionRatio = Number((intersectionArea / targetArea).toFixed(4)); 110 } else { 111 // If area is zero and is intersecting, sets to 1, otherwise to 0 112 this.intersectionRatio = this.isIntersecting ? 1 : 0; 113 } 114} 115 116 117/** 118 * Creates the global IntersectionObserver constructor. 119 * https://w3c.github.io/IntersectionObserver/#intersection-observer-interface 120 * @param {Function} callback The function to be invoked after intersection 121 * changes have queued. The function is not invoked if the queue has 122 * been emptied by calling the `takeRecords` method. 123 * @param {Object=} opt_options Optional configuration options. 124 * @constructor 125 */ 126function IntersectionObserver(callback, opt_options) { 127 128 var options = opt_options || {}; 129 130 if (typeof callback != 'function') { 131 throw new Error('callback must be a function'); 132 } 133 134 if ( 135 options.root && 136 options.root.nodeType != 1 && 137 options.root.nodeType != 9 138 ) { 139 throw new Error('root must be a Document or Element'); 140 } 141 142 // Binds and throttles `this._checkForIntersections`. 143 this._checkForIntersections = throttle( 144 this._checkForIntersections.bind(this), this.THROTTLE_TIMEOUT); 145 146 // Private properties. 147 this._callback = callback; 148 this._observationTargets = []; 149 this._queuedEntries = []; 150 this._rootMarginValues = this._parseRootMargin(options.rootMargin); 151 152 // Public properties. 153 this.thresholds = this._initThresholds(options.threshold); 154 this.root = options.root || null; 155 this.rootMargin = this._rootMarginValues.map(function(margin) { 156 return margin.value + margin.unit; 157 }).join(' '); 158 159 /** @private @const {!Array<!Document>} */ 160 this._monitoringDocuments = []; 161 /** @private @const {!Array<function()>} */ 162 this._monitoringUnsubscribes = []; 163} 164 165 166/** 167 * The minimum interval within which the document will be checked for 168 * intersection changes. 169 */ 170IntersectionObserver.prototype.THROTTLE_TIMEOUT = 100; 171 172 173/** 174 * The frequency in which the polyfill polls for intersection changes. 175 * this can be updated on a per instance basis and must be set prior to 176 * calling `observe` on the first target. 177 */ 178IntersectionObserver.prototype.POLL_INTERVAL = null; 179 180/** 181 * Use a mutation observer on the root element 182 * to detect intersection changes. 183 */ 184IntersectionObserver.prototype.USE_MUTATION_OBSERVER = true; 185 186 187/** 188 * Sets up the polyfill in the cross-origin mode. The result is the 189 * updater function that accepts two arguments: `boundingClientRect` and 190 * `intersectionRect` - just as these fields would be available to the 191 * parent via `IntersectionObserverEntry`. This function should be called 192 * each time the iframe receives intersection information from the parent 193 * window, e.g. via messaging. 194 * @return {function(DOMRect|ClientRect, DOMRect|ClientRect)} 195 */ 196IntersectionObserver._setupCrossOriginUpdater = function() { 197 if (!crossOriginUpdater) { 198 /** 199 * @param {DOMRect|ClientRect} boundingClientRect 200 * @param {DOMRect|ClientRect} intersectionRect 201 */ 202 crossOriginUpdater = function(boundingClientRect, intersectionRect) { 203 if (!boundingClientRect || !intersectionRect) { 204 crossOriginRect = getEmptyRect(); 205 } else { 206 crossOriginRect = convertFromParentRect(boundingClientRect, intersectionRect); 207 } 208 registry.forEach(function(observer) { 209 observer._checkForIntersections(); 210 }); 211 }; 212 } 213 return crossOriginUpdater; 214}; 215 216 217/** 218 * Resets the cross-origin mode. 219 */ 220IntersectionObserver._resetCrossOriginUpdater = function() { 221 crossOriginUpdater = null; 222 crossOriginRect = null; 223}; 224 225 226/** 227 * Starts observing a target element for intersection changes based on 228 * the thresholds values. 229 * @param {Element} target The DOM element to observe. 230 */ 231IntersectionObserver.prototype.observe = function(target) { 232 var isTargetAlreadyObserved = this._observationTargets.some(function(item) { 233 return item.element == target; 234 }); 235 236 if (isTargetAlreadyObserved) { 237 return; 238 } 239 240 if (!(target && target.nodeType == 1)) { 241 throw new Error('target must be an Element'); 242 } 243 244 this._registerInstance(); 245 this._observationTargets.push({element: target, entry: null}); 246 this._monitorIntersections(target.ownerDocument); 247 this._checkForIntersections(); 248}; 249 250 251/** 252 * Stops observing a target element for intersection changes. 253 * @param {Element} target The DOM element to observe. 254 */ 255IntersectionObserver.prototype.unobserve = function(target) { 256 this._observationTargets = 257 this._observationTargets.filter(function(item) { 258 return item.element != target; 259 }); 260 this._unmonitorIntersections(target.ownerDocument); 261 if (this._observationTargets.length == 0) { 262 this._unregisterInstance(); 263 } 264}; 265 266 267/** 268 * Stops observing all target elements for intersection changes. 269 */ 270IntersectionObserver.prototype.disconnect = function() { 271 this._observationTargets = []; 272 this._unmonitorAllIntersections(); 273 this._unregisterInstance(); 274}; 275 276 277/** 278 * Returns any queue entries that have not yet been reported to the 279 * callback and clears the queue. This can be used in conjunction with the 280 * callback to obtain the absolute most up-to-date intersection information. 281 * @return {Array} The currently queued entries. 282 */ 283IntersectionObserver.prototype.takeRecords = function() { 284 var records = this._queuedEntries.slice(); 285 this._queuedEntries = []; 286 return records; 287}; 288 289 290/** 291 * Accepts the threshold value from the user configuration object and 292 * returns a sorted array of unique threshold values. If a value is not 293 * between 0 and 1 and error is thrown. 294 * @private 295 * @param {Array|number=} opt_threshold An optional threshold value or 296 * a list of threshold values, defaulting to [0]. 297 * @return {Array} A sorted list of unique and valid threshold values. 298 */ 299IntersectionObserver.prototype._initThresholds = function(opt_threshold) { 300 var threshold = opt_threshold || [0]; 301 if (!Array.isArray(threshold)) threshold = [threshold]; 302 303 return threshold.sort().filter(function(t, i, a) { 304 if (typeof t != 'number' || isNaN(t) || t < 0 || t > 1) { 305 throw new Error('threshold must be a number between 0 and 1 inclusively'); 306 } 307 return t !== a[i - 1]; 308 }); 309}; 310 311 312/** 313 * Accepts the rootMargin value from the user configuration object 314 * and returns an array of the four margin values as an object containing 315 * the value and unit properties. If any of the values are not properly 316 * formatted or use a unit other than px or %, and error is thrown. 317 * @private 318 * @param {string=} opt_rootMargin An optional rootMargin value, 319 * defaulting to '0px'. 320 * @return {Array<Object>} An array of margin objects with the keys 321 * value and unit. 322 */ 323IntersectionObserver.prototype._parseRootMargin = function(opt_rootMargin) { 324 var marginString = opt_rootMargin || '0px'; 325 var margins = marginString.split(/\s+/).map(function(margin) { 326 var parts = /^(-?\d*\.?\d+)(px|%)$/.exec(margin); 327 if (!parts) { 328 throw new Error('rootMargin must be specified in pixels or percent'); 329 } 330 return {value: parseFloat(parts[1]), unit: parts[2]}; 331 }); 332 333 // Handles shorthand. 334 margins[1] = margins[1] || margins[0]; 335 margins[2] = margins[2] || margins[0]; 336 margins[3] = margins[3] || margins[1]; 337 338 return margins; 339}; 340 341 342/** 343 * Starts polling for intersection changes if the polling is not already 344 * happening, and if the page's visibility state is visible. 345 * @param {!Document} doc 346 * @private 347 */ 348IntersectionObserver.prototype._monitorIntersections = function(doc) { 349 var win = doc.defaultView; 350 if (!win) { 351 // Already destroyed. 352 return; 353 } 354 if (this._monitoringDocuments.indexOf(doc) != -1) { 355 // Already monitoring. 356 return; 357 } 358 359 // Private state for monitoring. 360 var callback = this._checkForIntersections; 361 var monitoringInterval = null; 362 var domObserver = null; 363 364 // If a poll interval is set, use polling instead of listening to 365 // resize and scroll events or DOM mutations. 366 if (this.POLL_INTERVAL) { 367 monitoringInterval = win.setInterval(callback, this.POLL_INTERVAL); 368 } else { 369 addEvent(win, 'resize', callback, true); 370 addEvent(doc, 'scroll', callback, true); 371 if (this.USE_MUTATION_OBSERVER && 'MutationObserver' in win) { 372 domObserver = new win.MutationObserver(callback); 373 domObserver.observe(doc, { 374 attributes: true, 375 childList: true, 376 characterData: true, 377 subtree: true 378 }); 379 } 380 } 381 382 this._monitoringDocuments.push(doc); 383 this._monitoringUnsubscribes.push(function() { 384 // Get the window object again. When a friendly iframe is destroyed, it 385 // will be null. 386 var win = doc.defaultView; 387 388 if (win) { 389 if (monitoringInterval) { 390 win.clearInterval(monitoringInterval); 391 } 392 removeEvent(win, 'resize', callback, true); 393 } 394 395 removeEvent(doc, 'scroll', callback, true); 396 if (domObserver) { 397 domObserver.disconnect(); 398 } 399 }); 400 401 // Also monitor the parent. 402 var rootDoc = 403 (this.root && (this.root.ownerDocument || this.root)) || document; 404 if (doc != rootDoc) { 405 var frame = getFrameElement(doc); 406 if (frame) { 407 this._monitorIntersections(frame.ownerDocument); 408 } 409 } 410}; 411 412 413/** 414 * Stops polling for intersection changes. 415 * @param {!Document} doc 416 * @private 417 */ 418IntersectionObserver.prototype._unmonitorIntersections = function(doc) { 419 var index = this._monitoringDocuments.indexOf(doc); 420 if (index == -1) { 421 return; 422 } 423 424 var rootDoc = 425 (this.root && (this.root.ownerDocument || this.root)) || document; 426 427 // Check if any dependent targets are still remaining. 428 var hasDependentTargets = 429 this._observationTargets.some(function(item) { 430 var itemDoc = item.element.ownerDocument; 431 // Target is in this context. 432 if (itemDoc == doc) { 433 return true; 434 } 435 // Target is nested in this context. 436 while (itemDoc && itemDoc != rootDoc) { 437 var frame = getFrameElement(itemDoc); 438 itemDoc = frame && frame.ownerDocument; 439 if (itemDoc == doc) { 440 return true; 441 } 442 } 443 return false; 444 }); 445 if (hasDependentTargets) { 446 return; 447 } 448 449 // Unsubscribe. 450 var unsubscribe = this._monitoringUnsubscribes[index]; 451 this._monitoringDocuments.splice(index, 1); 452 this._monitoringUnsubscribes.splice(index, 1); 453 unsubscribe(); 454 455 // Also unmonitor the parent. 456 if (doc != rootDoc) { 457 var frame = getFrameElement(doc); 458 if (frame) { 459 this._unmonitorIntersections(frame.ownerDocument); 460 } 461 } 462}; 463 464 465/** 466 * Stops polling for intersection changes. 467 * @param {!Document} doc 468 * @private 469 */ 470IntersectionObserver.prototype._unmonitorAllIntersections = function() { 471 var unsubscribes = this._monitoringUnsubscribes.slice(0); 472 this._monitoringDocuments.length = 0; 473 this._monitoringUnsubscribes.length = 0; 474 for (var i = 0; i < unsubscribes.length; i++) { 475 unsubscribes[i](); 476 } 477}; 478 479 480/** 481 * Scans each observation target for intersection changes and adds them 482 * to the internal entries queue. If new entries are found, it 483 * schedules the callback to be invoked. 484 * @private 485 */ 486IntersectionObserver.prototype._checkForIntersections = function() { 487 if (!this.root && crossOriginUpdater && !crossOriginRect) { 488 // Cross origin monitoring, but no initial data available yet. 489 return; 490 } 491 492 var rootIsInDom = this._rootIsInDom(); 493 var rootRect = rootIsInDom ? this._getRootRect() : getEmptyRect(); 494 495 this._observationTargets.forEach(function(item) { 496 var target = item.element; 497 var targetRect = getBoundingClientRect(target); 498 var rootContainsTarget = this._rootContainsTarget(target); 499 var oldEntry = item.entry; 500 var intersectionRect = rootIsInDom && rootContainsTarget && 501 this._computeTargetAndRootIntersection(target, targetRect, rootRect); 502 503 var rootBounds = null; 504 if (!this._rootContainsTarget(target)) { 505 rootBounds = getEmptyRect(); 506 } else if (!crossOriginUpdater || this.root) { 507 rootBounds = rootRect; 508 } 509 510 var newEntry = item.entry = new IntersectionObserverEntry({ 511 time: now(), 512 target: target, 513 boundingClientRect: targetRect, 514 rootBounds: rootBounds, 515 intersectionRect: intersectionRect 516 }); 517 518 if (!oldEntry) { 519 this._queuedEntries.push(newEntry); 520 } else if (rootIsInDom && rootContainsTarget) { 521 // If the new entry intersection ratio has crossed any of the 522 // thresholds, add a new entry. 523 if (this._hasCrossedThreshold(oldEntry, newEntry)) { 524 this._queuedEntries.push(newEntry); 525 } 526 } else { 527 // If the root is not in the DOM or target is not contained within 528 // root but the previous entry for this target had an intersection, 529 // add a new record indicating removal. 530 if (oldEntry && oldEntry.isIntersecting) { 531 this._queuedEntries.push(newEntry); 532 } 533 } 534 }, this); 535 536 if (this._queuedEntries.length) { 537 this._callback(this.takeRecords(), this); 538 } 539}; 540 541 542/** 543 * Accepts a target and root rect computes the intersection between then 544 * following the algorithm in the spec. 545 * TODO(philipwalton): at this time clip-path is not considered. 546 * https://w3c.github.io/IntersectionObserver/#calculate-intersection-rect-algo 547 * @param {Element} target The target DOM element 548 * @param {Object} targetRect The bounding rect of the target. 549 * @param {Object} rootRect The bounding rect of the root after being 550 * expanded by the rootMargin value. 551 * @return {?Object} The final intersection rect object or undefined if no 552 * intersection is found. 553 * @private 554 */ 555IntersectionObserver.prototype._computeTargetAndRootIntersection = 556 function(target, targetRect, rootRect) { 557 // If the element isn't displayed, an intersection can't happen. 558 if (window.getComputedStyle(target).display == 'none') return; 559 560 var intersectionRect = targetRect; 561 var parent = getParentNode(target); 562 var atRoot = false; 563 564 while (!atRoot && parent) { 565 var parentRect = null; 566 var parentComputedStyle = parent.nodeType == 1 ? 567 window.getComputedStyle(parent) : {}; 568 569 // If the parent isn't displayed, an intersection can't happen. 570 if (parentComputedStyle.display == 'none') return null; 571 572 if (parent == this.root || parent.nodeType == /* DOCUMENT */ 9) { 573 atRoot = true; 574 if (parent == this.root || parent == document) { 575 if (crossOriginUpdater && !this.root) { 576 if (!crossOriginRect || 577 crossOriginRect.width == 0 && crossOriginRect.height == 0) { 578 // A 0-size cross-origin intersection means no-intersection. 579 parent = null; 580 parentRect = null; 581 intersectionRect = null; 582 } else { 583 parentRect = crossOriginRect; 584 } 585 } else { 586 parentRect = rootRect; 587 } 588 } else { 589 // Check if there's a frame that can be navigated to. 590 var frame = getParentNode(parent); 591 var frameRect = frame && getBoundingClientRect(frame); 592 var frameIntersect = 593 frame && 594 this._computeTargetAndRootIntersection(frame, frameRect, rootRect); 595 if (frameRect && frameIntersect) { 596 parent = frame; 597 parentRect = convertFromParentRect(frameRect, frameIntersect); 598 } else { 599 parent = null; 600 intersectionRect = null; 601 } 602 } 603 } else { 604 // If the element has a non-visible overflow, and it's not the <body> 605 // or <html> element, update the intersection rect. 606 // Note: <body> and <html> cannot be clipped to a rect that's not also 607 // the document rect, so no need to compute a new intersection. 608 var doc = parent.ownerDocument; 609 if (parent != doc.body && 610 parent != doc.documentElement && 611 parentComputedStyle.overflow != 'visible') { 612 parentRect = getBoundingClientRect(parent); 613 } 614 } 615 616 // If either of the above conditionals set a new parentRect, 617 // calculate new intersection data. 618 if (parentRect) { 619 intersectionRect = computeRectIntersection(parentRect, intersectionRect); 620 } 621 if (!intersectionRect) break; 622 parent = parent && getParentNode(parent); 623 } 624 return intersectionRect; 625}; 626 627 628/** 629 * Returns the root rect after being expanded by the rootMargin value. 630 * @return {ClientRect} The expanded root rect. 631 * @private 632 */ 633IntersectionObserver.prototype._getRootRect = function() { 634 var rootRect; 635 if (this.root && !isDoc(this.root)) { 636 rootRect = getBoundingClientRect(this.root); 637 } else { 638 // Use <html>/<body> instead of window since scroll bars affect size. 639 var doc = isDoc(this.root) ? this.root : document; 640 var html = doc.documentElement; 641 var body = doc.body; 642 rootRect = { 643 top: 0, 644 left: 0, 645 right: html.clientWidth || body.clientWidth, 646 width: html.clientWidth || body.clientWidth, 647 bottom: html.clientHeight || body.clientHeight, 648 height: html.clientHeight || body.clientHeight 649 }; 650 } 651 return this._expandRectByRootMargin(rootRect); 652}; 653 654 655/** 656 * Accepts a rect and expands it by the rootMargin value. 657 * @param {DOMRect|ClientRect} rect The rect object to expand. 658 * @return {ClientRect} The expanded rect. 659 * @private 660 */ 661IntersectionObserver.prototype._expandRectByRootMargin = function(rect) { 662 var margins = this._rootMarginValues.map(function(margin, i) { 663 return margin.unit == 'px' ? margin.value : 664 margin.value * (i % 2 ? rect.width : rect.height) / 100; 665 }); 666 var newRect = { 667 top: rect.top - margins[0], 668 right: rect.right + margins[1], 669 bottom: rect.bottom + margins[2], 670 left: rect.left - margins[3] 671 }; 672 newRect.width = newRect.right - newRect.left; 673 newRect.height = newRect.bottom - newRect.top; 674 675 return newRect; 676}; 677 678 679/** 680 * Accepts an old and new entry and returns true if at least one of the 681 * threshold values has been crossed. 682 * @param {?IntersectionObserverEntry} oldEntry The previous entry for a 683 * particular target element or null if no previous entry exists. 684 * @param {IntersectionObserverEntry} newEntry The current entry for a 685 * particular target element. 686 * @return {boolean} Returns true if a any threshold has been crossed. 687 * @private 688 */ 689IntersectionObserver.prototype._hasCrossedThreshold = 690 function(oldEntry, newEntry) { 691 692 // To make comparing easier, an entry that has a ratio of 0 693 // but does not actually intersect is given a value of -1 694 var oldRatio = oldEntry && oldEntry.isIntersecting ? 695 oldEntry.intersectionRatio || 0 : -1; 696 var newRatio = newEntry.isIntersecting ? 697 newEntry.intersectionRatio || 0 : -1; 698 699 // Ignore unchanged ratios 700 if (oldRatio === newRatio) return; 701 702 for (var i = 0; i < this.thresholds.length; i++) { 703 var threshold = this.thresholds[i]; 704 705 // Return true if an entry matches a threshold or if the new ratio 706 // and the old ratio are on the opposite sides of a threshold. 707 if (threshold == oldRatio || threshold == newRatio || 708 threshold < oldRatio !== threshold < newRatio) { 709 return true; 710 } 711 } 712}; 713 714 715/** 716 * Returns whether or not the root element is an element and is in the DOM. 717 * @return {boolean} True if the root element is an element and is in the DOM. 718 * @private 719 */ 720IntersectionObserver.prototype._rootIsInDom = function() { 721 return !this.root || containsDeep(document, this.root); 722}; 723 724 725/** 726 * Returns whether or not the target element is a child of root. 727 * @param {Element} target The target element to check. 728 * @return {boolean} True if the target element is a child of root. 729 * @private 730 */ 731IntersectionObserver.prototype._rootContainsTarget = function(target) { 732 var rootDoc = 733 (this.root && (this.root.ownerDocument || this.root)) || document; 734 return ( 735 containsDeep(rootDoc, target) && 736 (!this.root || rootDoc == target.ownerDocument) 737 ); 738}; 739 740 741/** 742 * Adds the instance to the global IntersectionObserver registry if it isn't 743 * already present. 744 * @private 745 */ 746IntersectionObserver.prototype._registerInstance = function() { 747 if (registry.indexOf(this) < 0) { 748 registry.push(this); 749 } 750}; 751 752 753/** 754 * Removes the instance from the global IntersectionObserver registry. 755 * @private 756 */ 757IntersectionObserver.prototype._unregisterInstance = function() { 758 var index = registry.indexOf(this); 759 if (index != -1) registry.splice(index, 1); 760}; 761 762 763/** 764 * Returns the result of the performance.now() method or null in browsers 765 * that don't support the API. 766 * @return {number} The elapsed time since the page was requested. 767 */ 768function now() { 769 return window.performance && performance.now && performance.now(); 770} 771 772 773/** 774 * Throttles a function and delays its execution, so it's only called at most 775 * once within a given time period. 776 * @param {Function} fn The function to throttle. 777 * @param {number} timeout The amount of time that must pass before the 778 * function can be called again. 779 * @return {Function} The throttled function. 780 */ 781function throttle(fn, timeout) { 782 var timer = null; 783 return function () { 784 if (!timer) { 785 timer = setTimeout(function() { 786 fn(); 787 timer = null; 788 }, timeout); 789 } 790 }; 791} 792 793 794/** 795 * Adds an event handler to a DOM node ensuring cross-browser compatibility. 796 * @param {Node} node The DOM node to add the event handler to. 797 * @param {string} event The event name. 798 * @param {Function} fn The event handler to add. 799 * @param {boolean} opt_useCapture Optionally adds the even to the capture 800 * phase. Note: this only works in modern browsers. 801 */ 802function addEvent(node, event, fn, opt_useCapture) { 803 if (typeof node.addEventListener == 'function') { 804 node.addEventListener(event, fn, opt_useCapture || false); 805 } 806 else if (typeof node.attachEvent == 'function') { 807 node.attachEvent('on' + event, fn); 808 } 809} 810 811 812/** 813 * Removes a previously added event handler from a DOM node. 814 * @param {Node} node The DOM node to remove the event handler from. 815 * @param {string} event The event name. 816 * @param {Function} fn The event handler to remove. 817 * @param {boolean} opt_useCapture If the event handler was added with this 818 * flag set to true, it should be set to true here in order to remove it. 819 */ 820function removeEvent(node, event, fn, opt_useCapture) { 821 if (typeof node.removeEventListener == 'function') { 822 node.removeEventListener(event, fn, opt_useCapture || false); 823 } 824 else if (typeof node.deta
824tchEvent == 'function') { 825 node.detatchEvent('on' + event, fn); 826 } 827} 828 829 830/** 831 * Returns the intersection between two rect objects. 832 * @param {Object} rect1 The first rect. 833 * @param {Object} rect2 The second rect. 834 * @return {?Object|?ClientRect} The intersection rect or undefined if no 835 * intersection is found. 836 */ 837function computeRectIntersection(rect1, rect2) { 838 var top = Math.max(rect1.top, rect2.top); 839 var bottom = Math.min(rect1.bottom, rect2.bottom); 840 var left = Math.max(rect1.left, rect2.left); 841 var right = Math.min(rect1.right, rect2.right); 842 var width = right - left; 843 var height = bottom - top; 844 845 return (width >= 0 && height >= 0) && { 846 top: top, 847 bottom: bottom, 848 left: left, 849 right: right, 850 width: width, 851 height: height 852 } || null; 853} 854 855 856/** 857 * Shims the native getBoundingClientRect for compatibility with older IE. 858 * @param {Element} el The element whose bounding rect to get. 859 * @return {DOMRect|ClientRect} The (possibly shimmed) rect of the element. 860 */ 861function getBoundingClientRect(el) { 862 var rect; 863 864 try { 865 rect = el.getBoundingClientRect(); 866 } catch (err) { 867 // Ignore Windows 7 IE11 "Unspecified error"
vendor: 3,752 bytes, lines 868-1014
868 // https://github.com/w3c/IntersectionObserver/pull/205 869 } 870 871 if (!rect) return getEmptyRect(); 872 873 // Older IE 874 if (!(rect.width && rect.height)) { 875 rect = { 876 top: rect.top, 877 right: rect.right, 878 bottom: rect.bottom, 879 left: rect.left, 880 width: rect.right - rect.left, 881 height: rect.bottom - rect.top 882 }; 883 } 884 return rect; 885} 886 887 888/** 889 * Returns an empty rect object. An empty rect is returned when an element 890 * is not in the DOM. 891 * @return {ClientRect} The empty rect. 892 */ 893function getEmptyRect() { 894 return { 895 top: 0, 896 bottom: 0, 897 left: 0, 898 right: 0, 899 width: 0, 900 height: 0 901 }; 902} 903 904 905/** 906 * Ensure that the result has all of the necessary fields of the DOMRect. 907 * Specifically this ensures that `x` and `y` fields are set. 908 * 909 * @param {?DOMRect|?ClientRect} rect 910 * @return {?DOMRect} 911 */ 912function ensureDOMRect(rect) { 913 // A `DOMRect` object has `x` and `y` fields. 914 if (!rect || 'x' in rect) { 915 return rect; 916 } 917 // A IE's `ClientRect` type does not have `x` and `y`. The same is the case 918 // for internally calculated Rect objects. For the purposes of 919 // `IntersectionObserver`, it's sufficient to simply mirror `left` and `top` 920 // for these fields. 921 return { 922 top: rect.top, 923 y: rect.top, 924 bottom: rect.bottom, 925 left: rect.left, 926 x: rect.left, 927 right: rect.right, 928 width: rect.width, 929 height: rect.height 930 }; 931} 932 933 934/** 935 * Inverts the intersection and bounding rect from the parent (frame) BCR to 936 * the local BCR space. 937 * @param {DOMRect|ClientRect} parentBoundingRect The parent's bound client rect. 938 * @param {DOMRect|ClientRect} parentIntersectionRect The parent's own intersection rect. 939 * @return {ClientRect} The local root bounding rect for the parent's children. 940 */ 941function convertFromParentRect(parentBoundingRect, parentIntersectionRect) { 942 var top = parentIntersectionRect.top - parentBoundingRect.top; 943 var left = parentIntersectionRect.left - parentBoundingRect.left; 944 return { 945 top: top, 946 left: left, 947 height: parentIntersectionRect.height, 948 width: parentIntersectionRect.width, 949 bottom: top + parentIntersectionRect.height, 950 right: left + parentIntersectionRect.width 951 }; 952} 953 954 955/** 956 * Checks to see if a parent element contains a child element (including inside 957 * shadow DOM). 958 * @param {Node} parent The parent element. 959 * @param {Node} child The child element. 960 * @return {boolean} True if the parent node contains the child node. 961 */ 962function containsDeep(parent, child) { 963 var node = child; 964 while (node) { 965 if (node == parent) return true; 966 967 node = getParentNode(node); 968 } 969 return false; 970} 971 972 973/** 974 * Gets the parent node of an element or its host element if the parent node 975 * is a shadow root. 976 * @param {Node} node The node whose parent to get. 977 * @return {Node|null} The parent node or null if no parent exists. 978 */ 979function getParentNode(node) { 980 var parent = node.parentNode; 981 982 if (node.nodeType == /* DOCUMENT */ 9 && node != document) { 983 // If this node is a document node, look for the embedding frame. 984 return getFrameElement(node); 985 } 986 987 // If the parent has element that is assigned through shadow root slot 988 if (parent && parent.assignedSlot) { 989 parent = parent.assignedSlot.parentNode 990 } 991 992 if (parent && parent.nodeType == 11 && parent.host) { 993 // If the parent is a shadow root, return the host element. 994 return parent.host; 995 } 996 997 return parent; 998} 999 1000/** 1001 * Returns true if `node` is a Document. 1002 * @param {!Node} node 1003 * @returns {boolean} 1004 */ 1005function isDoc(node) { 1006 return node && node.nodeType === 9; 1007} 1008 1009 1010// Exposes the constructors globally. 1011window.IntersectionObserver = IntersectionObserver; 1012window.IntersectionObserverEntry = IntersectionObserverEntry; 1013 1014}());
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.