intersection-observer 0.5.1 → 0.12.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -6,12 +6,15 @@
6
6
  * https://www.w3.org/Consortium/Legal/2015/copyright-software-and-document
7
7
  *
8
8
  */
9
-
10
- (function(window, document) {
9
+ (function() {
11
10
  'use strict';
12
11
 
12
+ // Exit early if we're not running in a browser.
13
+ if (typeof window !== 'object') {
14
+ return;
15
+ }
13
16
 
14
- // Exits early if all IntersectionObserver and IntersectionObserverEntry
17
+ // Exit early if all IntersectionObserver and IntersectionObserverEntry
15
18
  // features are natively supported.
16
19
  if ('IntersectionObserver' in window &&
17
20
  'IntersectionObserverEntry' in window &&
@@ -30,6 +33,32 @@ if ('IntersectionObserver' in window &&
30
33
  return;
31
34
  }
32
35
 
36
+ /**
37
+ * Returns the embedding frame element, if any.
38
+ * @param {!Document} doc
39
+ * @return {!Element}
40
+ */
41
+ function 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
+ */
53
+ var 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);
33
62
 
34
63
  /**
35
64
  * An IntersectionObserver registry. This registry exists to hold a strong
@@ -39,6 +68,19 @@ if ('IntersectionObserver' in window &&
39
68
  */
40
69
  var registry = [];
41
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
+ */
76
+ var crossOriginUpdater = null;
77
+
78
+ /**
79
+ * The current cross-origin intersection. Only used in the cross-origin mode.
80
+ * @type {DOMRect|ClientRect}
81
+ */
82
+ var crossOriginRect = null;
83
+
42
84
 
43
85
  /**
44
86
  * Creates the global IntersectionObserverEntry constructor.
@@ -49,9 +91,9 @@ var registry = [];
49
91
  function IntersectionObserverEntry(entry) {
50
92
  this.time = entry.time;
51
93
  this.target = entry.target;
52
- this.rootBounds = entry.rootBounds;
53
- this.boundingClientRect = entry.boundingClientRect;
54
- this.intersectionRect = entry.intersectionRect || getEmptyRect();
94
+ this.rootBounds = ensureDOMRect(entry.rootBounds);
95
+ this.boundingClientRect = ensureDOMRect(entry.boundingClientRect);
96
+ this.intersectionRect = ensureDOMRect(entry.intersectionRect || getEmptyRect());
55
97
  this.isIntersecting = !!entry.intersectionRect;
56
98
 
57
99
  // Calculates the intersection ratio.
@@ -89,8 +131,12 @@ function IntersectionObserver(callback, opt_options) {
89
131
  throw new Error('callback must be a function');
90
132
  }
91
133
 
92
- if (options.root && options.root.nodeType != 1) {
93
- throw new Error('root must be an Element');
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');
94
140
  }
95
141
 
96
142
  // Binds and throttles `this._checkForIntersections`.
@@ -109,6 +155,11 @@ function IntersectionObserver(callback, opt_options) {
109
155
  this.rootMargin = this._rootMarginValues.map(function(margin) {
110
156
  return margin.value + margin.unit;
111
157
  }).join(' ');
158
+
159
+ /** @private @const {!Array<!Document>} */
160
+ this._monitoringDocuments = [];
161
+ /** @private @const {!Array<function()>} */
162
+ this._monitoringUnsubscribes = [];
112
163
  }
113
164
 
114
165
 
@@ -133,6 +184,45 @@ IntersectionObserver.prototype.POLL_INTERVAL = null;
133
184
  IntersectionObserver.prototype.USE_MUTATION_OBSERVER = true;
134
185
 
135
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
+ */
196
+ IntersectionObserver._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
+ */
220
+ IntersectionObserver._resetCrossOriginUpdater = function() {
221
+ crossOriginUpdater = null;
222
+ crossOriginRect = null;
223
+ };
224
+
225
+
136
226
  /**
137
227
  * Starts observing a target element for intersection changes based on
138
228
  * the thresholds values.
@@ -153,7 +243,7 @@ IntersectionObserver.prototype.observe = function(target) {
153
243
 
154
244
  this._registerInstance();
155
245
  this._observationTargets.push({element: target, entry: null});
156
- this._monitorIntersections();
246
+ this._monitorIntersections(target.ownerDocument);
157
247
  this._checkForIntersections();
158
248
  };
159
249
 
@@ -165,11 +255,10 @@ IntersectionObserver.prototype.observe = function(target) {
165
255
  IntersectionObserver.prototype.unobserve = function(target) {
166
256
  this._observationTargets =
167
257
  this._observationTargets.filter(function(item) {
168
-
169
- return item.element != target;
170
- });
171
- if (!this._observationTargets.length) {
172
- this._unmonitorIntersections();
258
+ return item.element != target;
259
+ });
260
+ this._unmonitorIntersections(target.ownerDocument);
261
+ if (this._observationTargets.length == 0) {
173
262
  this._unregisterInstance();
174
263
  }
175
264
  };
@@ -180,7 +269,7 @@ IntersectionObserver.prototype.unobserve = function(target) {
180
269
  */
181
270
  IntersectionObserver.prototype.disconnect = function() {
182
271
  this._observationTargets = [];
183
- this._unmonitorIntersections();
272
+ this._unmonitorAllIntersections();
184
273
  this._unregisterInstance();
185
274
  };
186
275
 
@@ -253,31 +342,69 @@ IntersectionObserver.prototype._parseRootMargin = function(opt_rootMargin) {
253
342
  /**
254
343
  * Starts polling for intersection changes if the polling is not already
255
344
  * happening, and if the page's visibility state is visible.
345
+ * @param {!Document} doc
256
346
  * @private
257
347
  */
258
- IntersectionObserver.prototype._monitorIntersections = function() {
259
- if (!this._monitoringIntersections) {
260
- this._monitoringIntersections = true;
348
+ IntersectionObserver.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
+ }
261
358
 
262
- // If a poll interval is set, use polling instead of listening to
263
- // resize and scroll events or DOM mutations.
264
- if (this.POLL_INTERVAL) {
265
- this._monitoringInterval = setInterval(
266
- this._checkForIntersections, this.POLL_INTERVAL);
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
+ });
267
379
  }
268
- else {
269
- addEvent(window, 'resize', this._checkForIntersections, true);
270
- addEvent(document, 'scroll', this._checkForIntersections, true);
271
-
272
- if (this.USE_MUTATION_OBSERVER && 'MutationObserver' in window) {
273
- this._domObserver = new MutationObserver(this._checkForIntersections);
274
- this._domObserver.observe(document, {
275
- attributes: true,
276
- childList: true,
277
- characterData: true,
278
- subtree: true
279
- });
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);
280
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);
281
408
  }
282
409
  }
283
410
  };
@@ -285,26 +412,71 @@ IntersectionObserver.prototype._monitorIntersections = function() {
285
412
 
286
413
  /**
287
414
  * Stops polling for intersection changes.
415
+ * @param {!Document} doc
288
416
  * @private
289
417
  */
290
- IntersectionObserver.prototype._unmonitorIntersections = function() {
291
- if (this._monitoringIntersections) {
292
- this._monitoringIntersections = false;
293
-
294
- clearInterval(this._monitoringInterval);
295
- this._monitoringInterval = null;
418
+ IntersectionObserver.prototype._unmonitorIntersections = function(doc) {
419
+ var index = this._monitoringDocuments.indexOf(doc);
420
+ if (index == -1) {
421
+ return;
422
+ }
296
423
 
297
- removeEvent(window, 'resize', this._checkForIntersections, true);
298
- removeEvent(document, 'scroll', this._checkForIntersections, true);
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
+ }
299
448
 
300
- if (this._domObserver) {
301
- this._domObserver.disconnect();
302
- this._domObserver = null;
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);
303
460
  }
304
461
  }
305
462
  };
306
463
 
307
464
 
465
+ /**
466
+ * Stops polling for intersection changes.
467
+ * @param {!Document} doc
468
+ * @private
469
+ */
470
+ IntersectionObserver.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
+
308
480
  /**
309
481
  * Scans each observation target for intersection changes and adds them
310
482
  * to the internal entries queue. If new entries are found, it
@@ -312,6 +484,11 @@ IntersectionObserver.prototype._unmonitorIntersections = function() {
312
484
  * @private
313
485
  */
314
486
  IntersectionObserver.prototype._checkForIntersections = function() {
487
+ if (!this.root && crossOriginUpdater && !crossOriginRect) {
488
+ // Cross origin monitoring, but no initial data available yet.
489
+ return;
490
+ }
491
+
315
492
  var rootIsInDom = this._rootIsInDom();
316
493
  var rootRect = rootIsInDom ? this._getRootRect() : getEmptyRect();
317
494
 
@@ -321,13 +498,20 @@ IntersectionObserver.prototype._checkForIntersections = function() {
321
498
  var rootContainsTarget = this._rootContainsTarget(target);
322
499
  var oldEntry = item.entry;
323
500
  var intersectionRect = rootIsInDom && rootContainsTarget &&
324
- this._computeTargetAndRootIntersection(target, rootRect);
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
+ }
325
509
 
326
510
  var newEntry = item.entry = new IntersectionObserverEntry({
327
511
  time: now(),
328
512
  target: target,
329
513
  boundingClientRect: targetRect,
330
- rootBounds: rootRect,
514
+ rootBounds: rootBounds,
331
515
  intersectionRect: intersectionRect
332
516
  });
333
517
 
@@ -361,6 +545,7 @@ IntersectionObserver.prototype._checkForIntersections = function() {
361
545
  * TODO(philipwalton): at this time clip-path is not considered.
362
546
  * https://w3c.github.io/IntersectionObserver/#calculate-intersection-rect-algo
363
547
  * @param {Element} target The target DOM element
548
+ * @param {Object} targetRect The bounding rect of the target.
364
549
  * @param {Object} rootRect The bounding rect of the root after being
365
550
  * expanded by the rootMargin value.
366
551
  * @return {?Object} The final intersection rect object or undefined if no
@@ -368,34 +553,61 @@ IntersectionObserver.prototype._checkForIntersections = function() {
368
553
  * @private
369
554
  */
370
555
  IntersectionObserver.prototype._computeTargetAndRootIntersection =
371
- function(target, rootRect) {
372
-
556
+ function(target, targetRect, rootRect) {
373
557
  // If the element isn't displayed, an intersection can't happen.
374
558
  if (window.getComputedStyle(target).display == 'none') return;
375
559
 
376
- var targetRect = getBoundingClientRect(target);
377
560
  var intersectionRect = targetRect;
378
561
  var parent = getParentNode(target);
379
562
  var atRoot = false;
380
563
 
381
- while (!atRoot) {
564
+ while (!atRoot && parent) {
382
565
  var parentRect = null;
383
566
  var parentComputedStyle = parent.nodeType == 1 ?
384
567
  window.getComputedStyle(parent) : {};
385
568
 
386
569
  // If the parent isn't displayed, an intersection can't happen.
387
- if (parentComputedStyle.display == 'none') return;
570
+ if (parentComputedStyle.display == 'none') return null;
388
571
 
389
- if (parent == this.root || parent == document) {
572
+ if (parent == this.root || parent.nodeType == /* DOCUMENT */ 9) {
390
573
  atRoot = true;
391
- parentRect = rootRect;
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
+ }
392
603
  } else {
393
604
  // If the element has a non-visible overflow, and it's not the <body>
394
605
  // or <html> element, update the intersection rect.
395
606
  // Note: <body> and <html> cannot be clipped to a rect that's not also
396
607
  // the document rect, so no need to compute a new intersection.
397
- if (parent != document.body &&
398
- parent != document.documentElement &&
608
+ var doc = parent.ownerDocument;
609
+ if (parent != doc.body &&
610
+ parent != doc.documentElement &&
399
611
  parentComputedStyle.overflow != 'visible') {
400
612
  parentRect = getBoundingClientRect(parent);
401
613
  }
@@ -405,10 +617,9 @@ IntersectionObserver.prototype._computeTargetAndRootIntersection =
405
617
  // calculate new intersection data.
406
618
  if (parentRect) {
407
619
  intersectionRect = computeRectIntersection(parentRect, intersectionRect);
408
-
409
- if (!intersectionRect) break;
410
620
  }
411
- parent = getParentNode(parent);
621
+ if (!intersectionRect) break;
622
+ parent = parent && getParentNode(parent);
412
623
  }
413
624
  return intersectionRect;
414
625
  };
@@ -416,17 +627,18 @@ IntersectionObserver.prototype._computeTargetAndRootIntersection =
416
627
 
417
628
  /**
418
629
  * Returns the root rect after being expanded by the rootMargin value.
419
- * @return {Object} The expanded root rect.
630
+ * @return {ClientRect} The expanded root rect.
420
631
  * @private
421
632
  */
422
633
  IntersectionObserver.prototype._getRootRect = function() {
423
634
  var rootRect;
424
- if (this.root) {
635
+ if (this.root && !isDoc(this.root)) {
425
636
  rootRect = getBoundingClientRect(this.root);
426
637
  } else {
427
638
  // Use <html>/<body> instead of window since scroll bars affect size.
428
- var html = document.documentElement;
429
- var body = document.body;
639
+ var doc = isDoc(this.root) ? this.root : document;
640
+ var html = doc.documentElement;
641
+ var body = doc.body;
430
642
  rootRect = {
431
643
  top: 0,
432
644
  left: 0,
@@ -442,8 +654,8 @@ IntersectionObserver.prototype._getRootRect = function() {
442
654
 
443
655
  /**
444
656
  * Accepts a rect and expands it by the rootMargin value.
445
- * @param {Object} rect The rect object to expand.
446
- * @return {Object} The expanded rect.
657
+ * @param {DOMRect|ClientRect} rect The rect object to expand.
658
+ * @return {ClientRect} The expanded rect.
447
659
  * @private
448
660
  */
449
661
  IntersectionObserver.prototype._expandRectByRootMargin = function(rect) {
@@ -517,7 +729,12 @@ IntersectionObserver.prototype._rootIsInDom = function() {
517
729
  * @private
518
730
  */
519
731
  IntersectionObserver.prototype._rootContainsTarget = function(target) {
520
- return containsDeep(this.root || document, 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
+ );
521
738
  };
522
739
 
523
740
 
@@ -604,8 +821,8 @@ function removeEvent(node, event, fn, opt_useCapture) {
604
821
  if (typeof node.removeEventListener == 'function') {
605
822
  node.removeEventListener(event, fn, opt_useCapture || false);
606
823
  }
607
- else if (typeof node.detatchEvent == 'function') {
608
- node.detatchEvent('on' + event, fn);
824
+ else if (typeof node.detachEvent == 'function') {
825
+ node.detachEvent('on' + event, fn);
609
826
  }
610
827
  }
611
828
 
@@ -614,8 +831,8 @@ function removeEvent(node, event, fn, opt_useCapture) {
614
831
  * Returns the intersection between two rect objects.
615
832
  * @param {Object} rect1 The first rect.
616
833
  * @param {Object} rect2 The second rect.
617
- * @return {?Object} The intersection rect or undefined if no intersection
618
- * is found.
834
+ * @return {?Object|?ClientRect} The intersection rect or undefined if no
835
+ * intersection is found.
619
836
  */
620
837
  function computeRectIntersection(rect1, rect2) {
621
838
  var top = Math.max(rect1.top, rect2.top);
@@ -632,14 +849,14 @@ function computeRectIntersection(rect1, rect2) {
632
849
  right: right,
633
850
  width: width,
634
851
  height: height
635
- };
852
+ } || null;
636
853
  }
637
854
 
638
855
 
639
856
  /**
640
857
  * Shims the native getBoundingClientRect for compatibility with older IE.
641
858
  * @param {Element} el The element whose bounding rect to get.
642
- * @return {Object} The (possibly shimmed) rect of the element.
859
+ * @return {DOMRect|ClientRect} The (possibly shimmed) rect of the element.
643
860
  */
644
861
  function getBoundingClientRect(el) {
645
862
  var rect;
@@ -671,7 +888,7 @@ function getBoundingClientRect(el) {
671
888
  /**
672
889
  * Returns an empty rect object. An empty rect is returned when an element
673
890
  * is not in the DOM.
674
- * @return {Object} The empty rect.
891
+ * @return {ClientRect} The empty rect.
675
892
  */
676
893
  function getEmptyRect() {
677
894
  return {
@@ -684,6 +901,57 @@ function getEmptyRect() {
684
901
  };
685
902
  }
686
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
+ */
912
+ function 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
+ */
941
+ function 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
+
687
955
  /**
688
956
  * Checks to see if a parent element contains a child element (including inside
689
957
  * shadow DOM).
@@ -711,16 +979,36 @@ function containsDeep(parent, child) {
711
979
  function getParentNode(node) {
712
980
  var parent = node.parentNode;
713
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
+
714
992
  if (parent && parent.nodeType == 11 && parent.host) {
715
993
  // If the parent is a shadow root, return the host element.
716
994
  return parent.host;
717
995
  }
996
+
718
997
  return parent;
719
998
  }
720
999
 
1000
+ /**
1001
+ * Returns true if `node` is a Document.
1002
+ * @param {!Node} node
1003
+ * @returns {boolean}
1004
+ */
1005
+ function isDoc(node) {
1006
+ return node && node.nodeType === 9;
1007
+ }
1008
+
721
1009
 
722
1010
  // Exposes the constructors globally.
723
1011
  window.IntersectionObserver = IntersectionObserver;
724
1012
  window.IntersectionObserverEntry = IntersectionObserverEntry;
725
1013
 
726
- }(window, document));
1014
+ }());