PageSourceSearch

https://017music.com/static/js/intersection-observer.js

js 017music.com collected 2026-10-09 04:10:20 UTC 29,936 bytes, 985 lines download raw bytes

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

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.