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