From 67723f226e424f29bcf1d12f190ecd79d819b12e Mon Sep 17 00:00:00 2001 From: Jake Fried Date: Thu, 12 Nov 2020 19:13:40 -0500 Subject: [PATCH 01/10] polyfill: support iframe viewport tracking --- polyfill/intersection-observer-test.js | 7 +++++-- polyfill/intersection-observer.js | 8 ++++++-- 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/polyfill/intersection-observer-test.js b/polyfill/intersection-observer-test.js index dfb87dae..87d34444 100644 --- a/polyfill/intersection-observer-test.js +++ b/polyfill/intersection-observer-test.js @@ -69,12 +69,15 @@ describe('IntersectionObserver', function() { io = new IntersectionObserver(noop); expect(io.root).to.be(null); + io = new IntersectionObserver(noop, {root: document}); + expect(io.root).to.be(document); + io = new IntersectionObserver(noop, {root: rootEl}); - expect(io.root).to.be(rootEl); + expect(io.root).to.be(rootEl); }); - it('throws when root is not an Element', function() { + it('throws when root is not a Document or Element', function() { expect(function() { io = new IntersectionObserver(noop, {root: 'foo'}); }).to.throwException(); diff --git a/polyfill/intersection-observer.js b/polyfill/intersection-observer.js index 9b619cb1..a491ef34 100644 --- a/polyfill/intersection-observer.js +++ b/polyfill/intersection-observer.js @@ -131,8 +131,12 @@ function IntersectionObserver(callback, opt_options) { throw new Error('callback must be a function'); } - if (options.root && options.root.nodeType != 1) { - throw new Error('root must be an Element'); + if ( + options.root && + options.root.nodeType != 1 && + options.root.nodeType !== 9 + ) { + throw new Error("root must be a Document or Element"); } // Binds and throttles `this._checkForIntersections`. From b1a4e50d378a8fdf10ac4ddebda0c6024d1529de Mon Sep 17 00:00:00 2001 From: Jake Fried Date: Fri, 13 Nov 2020 11:44:42 -0500 Subject: [PATCH 02/10] add unit tests --- polyfill/intersection-observer-test.js | 58 ++++++++++++++++++++++++-- polyfill/intersection-observer.js | 24 ++++++++--- 2 files changed, 72 insertions(+), 10 deletions(-) diff --git a/polyfill/intersection-observer-test.js b/polyfill/intersection-observer-test.js index 87d34444..475d56a8 100644 --- a/polyfill/intersection-observer-test.js +++ b/polyfill/intersection-observer-test.js @@ -1401,6 +1401,31 @@ describe('IntersectionObserver', function() { io.observe(iframeTargetEl2); }); + it('handles tracking iframe viewport', function(done) { + iframe.style.height = '100px'; + // {root:iframeDoc} means to track the iframe viewport irrespective of toplevel viewport + var io = new IntersectionObserver( + function (records) { + var subviewportWidth = + iframeDoc.documentElement.clientWidth || iframeDoc.body.clientWidth; + var subviewportHeight = + iframeDoc.documentElement.clientHeight || iframeDoc.body.clientHeight; + + expect(records.length).to.be(1); + expect(records[0].rootBounds.top).to.be(0); + expect(records[0].rootBounds.left).to.be(0); + expect(records[0].rootBounds.right).to.be(subviewportWidth); + expect(records[0].rootBounds.width).to.be(subviewportWidth); + expect(records[0].rootBounds.bottom).to.be(subviewportHeight); + expect(records[0].rootBounds.height).to.be(subviewportHeight); + done(); + }, + { root: iframeDoc } + ); + + io.observe(iframeTargetEl1); + }); + it('handles style changes', function(done) { var spy = sinon.spy(); @@ -1627,10 +1652,10 @@ describe('IntersectionObserver', function() { var ASYNC_TIMEOUT = 300; beforeEach(function(done) { - /* Uncomment these lines to force polyfill inside the iframe. + // /* Uncomment these lines to force polyfill inside the iframe. delete iframeWin.IntersectionObserver; delete iframeWin.IntersectionObserverEntry; - */ + // */ // Install polyfill right into the iframe. if (!iframeWin.IntersectionObserver) { @@ -2290,10 +2315,10 @@ describe('IntersectionObserver', function() { beforeEach(function(done) { Object.defineProperty(iframeWin, 'frameElement', {value: null}); - /* Uncomment these lines to force polyfill inside the iframe. + // /* Uncomment these lines to force polyfill inside the iframe. delete iframeWin.IntersectionObserver; delete iframeWin.IntersectionObserverEntry; - */ + // */ // Install polyfill right into the iframe. if (!iframeWin.IntersectionObserver) { @@ -3025,6 +3050,31 @@ describe('IntersectionObserver', function() { } ], done); }); + + it('handles tracking iframe viewport', function(done) { + iframe.style.height = '100px'; + // {root:iframeDoc} means to track the iframe viewport irrespective of toplevel viewport + var io = new IntersectionObserver( + function (records) { + var subviewportWidth = + iframeDoc.documentElement.clientWidth || iframeDoc.body.clientWidth; + var subviewportHeight = + iframeDoc.documentElement.clientHeight || iframeDoc.body.clientHeight; + + expect(records.length).to.be(1); + expect(records[0].rootBounds.top).to.be(0); + expect(records[0].rootBounds.left).to.be(0); + expect(records[0].rootBounds.right).to.be(subviewportWidth); + expect(records[0].rootBounds.width).to.be(subviewportWidth); + expect(records[0].rootBounds.bottom).to.be(subviewportHeight); + expect(records[0].rootBounds.height).to.be(subviewportHeight); + done(); + }, + { root: iframeDoc } + ); + + io.observe(iframeTargetEl1); + }); }); }); }); diff --git a/polyfill/intersection-observer.js b/polyfill/intersection-observer.js index a491ef34..60024d9f 100644 --- a/polyfill/intersection-observer.js +++ b/polyfill/intersection-observer.js @@ -399,7 +399,10 @@ IntersectionObserver.prototype._monitorIntersections = function(doc) { }); // Also monitor the parent. - if (doc != (this.root && this.root.ownerDocument || document)) { + if ( + doc != ((this.root && this.root.ownerDocument) || document) && + !isDoc(this.root) + ) { var frame = getFrameElement(doc); if (frame) { this._monitorIntersections(frame.ownerDocument); @@ -419,7 +422,9 @@ IntersectionObserver.prototype._unmonitorIntersections = function(doc) { return; } - var rootDoc = (this.root && this.root.ownerDocument || document); + var rootDoc = isDoc(this.root) + ? this.root + : (this.root && this.root.ownerDocument) || document; // Check if any dependent targets are still remaining. var hasDependentTargets = @@ -622,7 +627,7 @@ IntersectionObserver.prototype._computeTargetAndRootIntersection = */ IntersectionObserver.prototype._getRootRect = function() { var rootRect; - if (this.root) { + if (this.root && !isDoc(this.root)) { rootRect = getBoundingClientRect(this.root); } else { // Use / instead of window since scroll bars affect size. @@ -634,7 +639,7 @@ IntersectionObserver.prototype._getRootRect = function() { right: html.clientWidth || body.clientWidth, width: html.clientWidth || body.clientWidth, bottom: html.clientHeight || body.clientHeight, - height: html.clientHeight || body.clientHeight + height: html.clientHeight || body.clientHeight, }; } return this._expandRectByRootMargin(rootRect); @@ -718,8 +723,11 @@ IntersectionObserver.prototype._rootIsInDom = function() { * @private */ IntersectionObserver.prototype._rootContainsTarget = function(target) { - return containsDeep(this.root || document, target) && - (!this.root || this.root.ownerDocument == target.ownerDocument); + const rootDoc = isDoc(this.root) ? this.root : this.root.ownerDocument; + return ( + containsDeep(this.root || document, target) && + (!this.root || rootDoc == target.ownerDocument) + ); }; @@ -982,6 +990,10 @@ function getParentNode(node) { return parent; } +function isDoc(node) { + return node && node.nodeType === 9; +} + // Exposes the constructors globally. window.IntersectionObserver = IntersectionObserver; From 3d1147fba4025e81d1be5e16db4ef5ea162febb4 Mon Sep 17 00:00:00 2001 From: Jake Fried Date: Fri, 13 Nov 2020 11:58:55 -0500 Subject: [PATCH 03/10] recomment --- polyfill/intersection-observer-test.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/polyfill/intersection-observer-test.js b/polyfill/intersection-observer-test.js index 475d56a8..68028917 100644 --- a/polyfill/intersection-observer-test.js +++ b/polyfill/intersection-observer-test.js @@ -1652,10 +1652,10 @@ describe('IntersectionObserver', function() { var ASYNC_TIMEOUT = 300; beforeEach(function(done) { - // /* Uncomment these lines to force polyfill inside the iframe. + /* Uncomment these lines to force polyfill inside the iframe. delete iframeWin.IntersectionObserver; delete iframeWin.IntersectionObserverEntry; - // */ + */ // Install polyfill right into the iframe. if (!iframeWin.IntersectionObserver) { @@ -2315,10 +2315,10 @@ describe('IntersectionObserver', function() { beforeEach(function(done) { Object.defineProperty(iframeWin, 'frameElement', {value: null}); - // /* Uncomment these lines to force polyfill inside the iframe. + /* Uncomment these lines to force polyfill inside the iframe. delete iframeWin.IntersectionObserver; delete iframeWin.IntersectionObserverEntry; - // */ + */ // Install polyfill right into the iframe. if (!iframeWin.IntersectionObserver) { From b356b47c6c02e03fa914ae45a3a66cf62b13c7fb Mon Sep 17 00:00:00 2001 From: Jake Fried Date: Fri, 13 Nov 2020 15:12:25 -0500 Subject: [PATCH 04/10] address most comments from dvoytenko --- polyfill/intersection-observer-test.js | 24 ++---------------------- polyfill/intersection-observer.js | 26 +++++++++++++------------- 2 files changed, 15 insertions(+), 35 deletions(-) diff --git a/polyfill/intersection-observer-test.js b/polyfill/intersection-observer-test.js index 68028917..f38a58ff 100644 --- a/polyfill/intersection-observer-test.js +++ b/polyfill/intersection-observer-test.js @@ -1406,18 +1406,8 @@ describe('IntersectionObserver', function() { // {root:iframeDoc} means to track the iframe viewport irrespective of toplevel viewport var io = new IntersectionObserver( function (records) { - var subviewportWidth = - iframeDoc.documentElement.clientWidth || iframeDoc.body.clientWidth; - var subviewportHeight = - iframeDoc.documentElement.clientHeight || iframeDoc.body.clientHeight; - expect(records.length).to.be(1); - expect(records[0].rootBounds.top).to.be(0); - expect(records[0].rootBounds.left).to.be(0); - expect(records[0].rootBounds.right).to.be(subviewportWidth); - expect(records[0].rootBounds.width).to.be(subviewportWidth); - expect(records[0].rootBounds.bottom).to.be(subviewportHeight); - expect(records[0].rootBounds.height).to.be(subviewportHeight); + expect(rect(records[0].rootBounds)).to.eql(getRootRect(iframeDoc)); done(); }, { root: iframeDoc } @@ -3056,18 +3046,8 @@ describe('IntersectionObserver', function() { // {root:iframeDoc} means to track the iframe viewport irrespective of toplevel viewport var io = new IntersectionObserver( function (records) { - var subviewportWidth = - iframeDoc.documentElement.clientWidth || iframeDoc.body.clientWidth; - var subviewportHeight = - iframeDoc.documentElement.clientHeight || iframeDoc.body.clientHeight; - expect(records.length).to.be(1); - expect(records[0].rootBounds.top).to.be(0); - expect(records[0].rootBounds.left).to.be(0); - expect(records[0].rootBounds.right).to.be(subviewportWidth); - expect(records[0].rootBounds.width).to.be(subviewportWidth); - expect(records[0].rootBounds.bottom).to.be(subviewportHeight); - expect(records[0].rootBounds.height).to.be(subviewportHeight); + expect(rect(records[0].rootBounds)).to.eql(getRootRect(iframeDoc)); done(); }, { root: iframeDoc } diff --git a/polyfill/intersection-observer.js b/polyfill/intersection-observer.js index 60024d9f..63d4f921 100644 --- a/polyfill/intersection-observer.js +++ b/polyfill/intersection-observer.js @@ -134,9 +134,9 @@ function IntersectionObserver(callback, opt_options) { if ( options.root && options.root.nodeType != 1 && - options.root.nodeType !== 9 + options.root.nodeType != 9 ) { - throw new Error("root must be a Document or Element"); + throw new Error('root must be a Document or Element'); } // Binds and throttles `this._checkForIntersections`. @@ -399,10 +399,9 @@ IntersectionObserver.prototype._monitorIntersections = function(doc) { }); // Also monitor the parent. - if ( - doc != ((this.root && this.root.ownerDocument) || document) && - !isDoc(this.root) - ) { + var rootDoc = + (this.root && (this.root.ownerDocument || this.root)) || document; + if (doc != rootDoc) { var frame = getFrameElement(doc); if (frame) { this._monitorIntersections(frame.ownerDocument); @@ -422,9 +421,8 @@ IntersectionObserver.prototype._unmonitorIntersections = function(doc) { return; } - var rootDoc = isDoc(this.root) - ? this.root - : (this.root && this.root.ownerDocument) || document; + var rootDoc = + (this.root && (this.root.ownerDocument || this.root)) || document; // Check if any dependent targets are still remaining. var hasDependentTargets = @@ -631,8 +629,9 @@ IntersectionObserver.prototype._getRootRect = function() { rootRect = getBoundingClientRect(this.root); } else { // Use / instead of window since scroll bars affect size. - var html = document.documentElement; - var body = document.body; + var doc = isDoc(this.root) ? this.root : document; + var html = doc.documentElement; + var body = doc.body; rootRect = { top: 0, left: 0, @@ -723,9 +722,10 @@ IntersectionObserver.prototype._rootIsInDom = function() { * @private */ IntersectionObserver.prototype._rootContainsTarget = function(target) { - const rootDoc = isDoc(this.root) ? this.root : this.root.ownerDocument; + var rootDoc = + (this.root && (this.root.ownerDocument || this.root)) || document; return ( - containsDeep(this.root || document, target) && + containsDeep(this.root || rootDoc, target) && (!this.root || rootDoc == target.ownerDocument) ); }; From bf707a46aec1792812504647d47a75e3721621c1 Mon Sep 17 00:00:00 2001 From: Jake Fried Date: Mon, 16 Nov 2020 14:31:12 -0500 Subject: [PATCH 05/10] update with rect tests --- polyfill/intersection-observer-test.js | 21 ++++++++++++++++++++- polyfill/intersection-observer.js | 2 +- 2 files changed, 21 insertions(+), 2 deletions(-) diff --git a/polyfill/intersection-observer-test.js b/polyfill/intersection-observer-test.js index f38a58ff..1c065220 100644 --- a/polyfill/intersection-observer-test.js +++ b/polyfill/intersection-observer-test.js @@ -73,7 +73,7 @@ describe('IntersectionObserver', function() { expect(io.root).to.be(document); io = new IntersectionObserver(noop, {root: rootEl}); - expect(io.root).to.be(rootEl); + expect(io.root).to.be(rootEl); }); @@ -1403,11 +1403,21 @@ describe('IntersectionObserver', function() { it('handles tracking iframe viewport', function(done) { iframe.style.height = '100px'; + iframe.style.top = '100px'; + iframeWin.scrollTo(0, 110); // {root:iframeDoc} means to track the iframe viewport irrespective of toplevel viewport var io = new IntersectionObserver( function (records) { + + var intersectionRect = rect({ + top: 0, // if root=null, then this would be 100. + left: 0, + height: 90, + width: bodyWidth, + }) expect(records.length).to.be(1); expect(rect(records[0].rootBounds)).to.eql(getRootRect(iframeDoc)); + expect(rect(records[0].intersectionRect)).to.eql(intersectionRect); done(); }, { root: iframeDoc } @@ -3043,11 +3053,20 @@ describe('IntersectionObserver', function() { it('handles tracking iframe viewport', function(done) { iframe.style.height = '100px'; + iframe.style.top = '100px'; + iframeWin.scrollTo(0, 110); // {root:iframeDoc} means to track the iframe viewport irrespective of toplevel viewport var io = new IntersectionObserver( function (records) { + var intersectionRect = rect({ + top: 0, // if root=null, then this would be 100. + left: 0, + height: 90, + width: bodyWidth, + }) expect(records.length).to.be(1); expect(rect(records[0].rootBounds)).to.eql(getRootRect(iframeDoc)); + expect(rect(records[0].intersectionRect)).to.eql(intersectionRect); done(); }, { root: iframeDoc } diff --git a/polyfill/intersection-observer.js b/polyfill/intersection-observer.js index 63d4f921..b600677a 100644 --- a/polyfill/intersection-observer.js +++ b/polyfill/intersection-observer.js @@ -638,7 +638,7 @@ IntersectionObserver.prototype._getRootRect = function() { right: html.clientWidth || body.clientWidth, width: html.clientWidth || body.clientWidth, bottom: html.clientHeight || body.clientHeight, - height: html.clientHeight || body.clientHeight, + height: html.clientHeight || body.clientHeight }; } return this._expandRectByRootMargin(rootRect); From a591735602ab72cad1ed8b9acc8dfd1480d883d0 Mon Sep 17 00:00:00 2001 From: Jake Fried Date: Mon, 23 Nov 2020 15:25:10 -0500 Subject: [PATCH 06/10] support returning zeros rect rootbounds for cross-document --- polyfill/intersection-observer-test.js | 30 +++++++++++++++++++++++++- polyfill/intersection-observer.js | 13 +++++++++-- 2 files changed, 40 insertions(+), 3 deletions(-) diff --git a/polyfill/intersection-observer-test.js b/polyfill/intersection-observer-test.js index 1c065220..72cf9c33 100644 --- a/polyfill/intersection-observer-test.js +++ b/polyfill/intersection-observer-test.js @@ -1408,6 +1408,7 @@ describe('IntersectionObserver', function() { // {root:iframeDoc} means to track the iframe viewport irrespective of toplevel viewport var io = new IntersectionObserver( function (records) { + io.unobserve(iframeTargetEl1); var intersectionRect = rect({ top: 0, // if root=null, then this would be 100. @@ -1426,6 +1427,32 @@ describe('IntersectionObserver', function() { io.observe(iframeTargetEl1); }); + // Current spec indicates that cross-document tracking yields + // an essentially empty IntersectionObserverEntry. + // See: + it('Does not track cross-document elements', function(done) { + var io = new IntersectionObserver( + function (records) { + io.unobserve(iframeTargetEl1) + + expect(records.length).to.be(1); + const zeroesRect = rect({ + top: 0, + left: 0, + width: 0, + height: 0 + }); + expect(rect(records[0].rootBounds)).to.eql(zeroesRect); + expect(rect(records[0].intersectionRect)).to.eql(zeroesRect); + expect(records.isIntersecting).false; + done(); + }, + { root: document } + ); + + io.observe(iframeTargetEl1); + }); + it('handles style changes', function(done) { var spy = sinon.spy(); @@ -3056,8 +3083,9 @@ describe('IntersectionObserver', function() { iframe.style.top = '100px'; iframeWin.scrollTo(0, 110); // {root:iframeDoc} means to track the iframe viewport irrespective of toplevel viewport - var io = new IntersectionObserver( + var io = createObserver( function (records) { + io.unobserve(iframeTargetEl1); var intersectionRect = rect({ top: 0, // if root=null, then this would be 100. left: 0, diff --git a/polyfill/intersection-observer.js b/polyfill/intersection-observer.js index b600677a..4908f3b7 100644 --- a/polyfill/intersection-observer.js +++ b/polyfill/intersection-observer.js @@ -500,11 +500,18 @@ IntersectionObserver.prototype._checkForIntersections = function() { var intersectionRect = rootIsInDom && rootContainsTarget && this._computeTargetAndRootIntersection(target, targetRect, rootRect); + var rootBounds = null; + if (!this._rootContainsTarget(target)) { + rootBounds = getEmptyRect(); + } else if (!crossOriginUpdater || this.root) { + rootBounds = rootRect + } + var newEntry = item.entry = new IntersectionObserverEntry({ time: now(), target: target, boundingClientRect: targetRect, - rootBounds: crossOriginUpdater && !this.root ? null : rootRect, + rootBounds: rootBounds, intersectionRect: intersectionRect }); @@ -711,7 +718,9 @@ IntersectionObserver.prototype._hasCrossedThreshold = * @private */ IntersectionObserver.prototype._rootIsInDom = function() { - return !this.root || containsDeep(document, this.root); + var rootDoc = + (this.root && (this.root.ownerDocument || this.root)) || document; + return !this.root || containsDeep(rootDoc, this.root); }; From 8e9227f807a0e754d955d2346e97697e1ca128cf Mon Sep 17 00:00:00 2001 From: Jake Fried Date: Thu, 3 Dec 2020 15:14:58 -0500 Subject: [PATCH 07/10] two small fixes --- polyfill/intersection-observer-test.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/polyfill/intersection-observer-test.js b/polyfill/intersection-observer-test.js index 72cf9c33..771de4a7 100644 --- a/polyfill/intersection-observer-test.js +++ b/polyfill/intersection-observer-test.js @@ -1405,7 +1405,7 @@ describe('IntersectionObserver', function() { iframe.style.height = '100px'; iframe.style.top = '100px'; iframeWin.scrollTo(0, 110); - // {root:iframeDoc} means to track the iframe viewport irrespective of toplevel viewport + // {root:iframeDoc} means to track the iframe viewport. var io = new IntersectionObserver( function (records) { io.unobserve(iframeTargetEl1); @@ -1429,7 +1429,7 @@ describe('IntersectionObserver', function() { // Current spec indicates that cross-document tracking yields // an essentially empty IntersectionObserverEntry. - // See: + // See: https://github.com/w3c/IntersectionObserver/issues/87 it('Does not track cross-document elements', function(done) { var io = new IntersectionObserver( function (records) { @@ -3082,7 +3082,7 @@ describe('IntersectionObserver', function() { iframe.style.height = '100px'; iframe.style.top = '100px'; iframeWin.scrollTo(0, 110); - // {root:iframeDoc} means to track the iframe viewport irrespective of toplevel viewport + // {root:iframeDoc} means to track the iframe viewport. var io = createObserver( function (records) { io.unobserve(iframeTargetEl1); @@ -3090,7 +3090,7 @@ describe('IntersectionObserver', function() { top: 0, // if root=null, then this would be 100. left: 0, height: 90, - width: bodyWidth, + width: bodyWidth }) expect(records.length).to.be(1); expect(rect(records[0].rootBounds)).to.eql(getRootRect(iframeDoc)); From 629866389cd4f5c8ee87824de77b3db8adc99686 Mon Sep 17 00:00:00 2001 From: Jake Fried Date: Fri, 4 Dec 2020 13:14:22 -0500 Subject: [PATCH 08/10] Bump version vfom 0.11.0 --> 0.12.0 --- polyfill/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/polyfill/package.json b/polyfill/package.json index e8ce0699..0b841fbd 100644 --- a/polyfill/package.json +++ b/polyfill/package.json @@ -1,6 +1,6 @@ { "name": "intersection-observer", - "version": "0.11.0", + "version": "0.12.0", "description": "A polyfill for IntersectionObserver", "main": "intersection-observer", "repository": { From 000ca3758663c8c055641547d0ca2e1734a6fb37 Mon Sep 17 00:00:00 2001 From: Jake Fried Date: Fri, 4 Dec 2020 17:04:22 -0500 Subject: [PATCH 09/10] address dvoytenko comments --- polyfill/intersection-observer-test.js | 35 ++++++++++++++++++++++++-- polyfill/intersection-observer.js | 13 ++++++---- 2 files changed, 41 insertions(+), 7 deletions(-) diff --git a/polyfill/intersection-observer-test.js b/polyfill/intersection-observer-test.js index 771de4a7..aeab93f8 100644 --- a/polyfill/intersection-observer-test.js +++ b/polyfill/intersection-observer-test.js @@ -1430,7 +1430,7 @@ describe('IntersectionObserver', function() { // Current spec indicates that cross-document tracking yields // an essentially empty IntersectionObserverEntry. // See: https://github.com/w3c/IntersectionObserver/issues/87 - it('Does not track cross-document elements', function(done) { + it('does not track cross-document elements', function(done) { var io = new IntersectionObserver( function (records) { io.unobserve(iframeTargetEl1) @@ -3091,7 +3091,7 @@ describe('IntersectionObserver', function() { left: 0, height: 90, width: bodyWidth - }) + }); expect(records.length).to.be(1); expect(rect(records[0].rootBounds)).to.eql(getRootRect(iframeDoc)); expect(rect(records[0].intersectionRect)).to.eql(intersectionRect); @@ -3102,6 +3102,37 @@ describe('IntersectionObserver', function() { io.observe(iframeTargetEl1); }); + + it('handles tracking iframe viewport with rootMargin', function(done) { + iframe.style.height = '100px'; + + var io = createObserver( + function (records) { + io.unobserve(iframeTargetEl1); + var intersectionRect = rect({ + top: 0, // if root=null, then this would be 100. + left: 0, + height: 200, + width: bodyWidth + }); + + // rootMargin: 100% --> 3x width + 3x height. + var expectedRootBounds = rect({ + top: -100, + left: -bodyWidth, + width: bodyWidth * 3, + height: 100 * 3 + }); + expect(records.length).to.be(1); + expect(rect(records[0].rootBounds)).to.eql(expectedRootBounds); + expect(rect(records[0].intersectionRect)).to.eql(intersectionRect); + done(); + }, + { root: iframeDoc, rootMargin: '100%' } + ); + + io.observe(iframeTargetEl1); + }); }); }); }); diff --git a/polyfill/intersection-observer.js b/polyfill/intersection-observer.js index 4908f3b7..871140ba 100644 --- a/polyfill/intersection-observer.js +++ b/polyfill/intersection-observer.js @@ -504,7 +504,7 @@ IntersectionObserver.prototype._checkForIntersections = function() { if (!this._rootContainsTarget(target)) { rootBounds = getEmptyRect(); } else if (!crossOriginUpdater || this.root) { - rootBounds = rootRect + rootBounds = rootRect; } var newEntry = item.entry = new IntersectionObserverEntry({ @@ -718,9 +718,7 @@ IntersectionObserver.prototype._hasCrossedThreshold = * @private */ IntersectionObserver.prototype._rootIsInDom = function() { - var rootDoc = - (this.root && (this.root.ownerDocument || this.root)) || document; - return !this.root || containsDeep(rootDoc, this.root); + return !this.root || containsDeep(document, this.root); }; @@ -734,7 +732,7 @@ IntersectionObserver.prototype._rootContainsTarget = function(target) { var rootDoc = (this.root && (this.root.ownerDocument || this.root)) || document; return ( - containsDeep(this.root || rootDoc, target) && + containsDeep(rootDoc, target) && (!this.root || rootDoc == target.ownerDocument) ); }; @@ -999,6 +997,11 @@ function getParentNode(node) { return parent; } +/** + * Returns true if `node` is a Document. + * @param {!Node} node + * @returns {boolean} + */ function isDoc(node) { return node && node.nodeType === 9; } From 77d813a50e629da2527129d9c976e794bea12f19 Mon Sep 17 00:00:00 2001 From: Jake Fried Date: Fri, 4 Dec 2020 18:02:59 -0500 Subject: [PATCH 10/10] duplicate rootMargin test for same-origin --- polyfill/intersection-observer-test.js | 35 ++++++++++++++++++++++++-- 1 file changed, 33 insertions(+), 2 deletions(-) diff --git a/polyfill/intersection-observer-test.js b/polyfill/intersection-observer-test.js index aeab93f8..79666c18 100644 --- a/polyfill/intersection-observer-test.js +++ b/polyfill/intersection-observer-test.js @@ -1414,8 +1414,8 @@ describe('IntersectionObserver', function() { top: 0, // if root=null, then this would be 100. left: 0, height: 90, - width: bodyWidth, - }) + width: bodyWidth + }); expect(records.length).to.be(1); expect(rect(records[0].rootBounds)).to.eql(getRootRect(iframeDoc)); expect(rect(records[0].intersectionRect)).to.eql(intersectionRect); @@ -1427,6 +1427,37 @@ describe('IntersectionObserver', function() { io.observe(iframeTargetEl1); }); + it('handles tracking iframe viewport with rootMargin', function(done) { + iframe.style.height = '100px'; + + var io = new IntersectionObserver( + function (records) { + io.unobserve(iframeTargetEl1); + var intersectionRect = rect({ + top: 0, // if root=null, then this would be 100. + left: 0, + height: 200, + width: bodyWidth + }); + + // rootMargin: 100% --> 3x width + 3x height. + var expectedRootBounds = rect({ + top: -100, + left: -bodyWidth, + width: bodyWidth * 3, + height: 100 * 3 + }); + expect(records.length).to.be(1); + expect(rect(records[0].rootBounds)).to.eql(expectedRootBounds); + expect(rect(records[0].intersectionRect)).to.eql(intersectionRect); + done(); + }, + { root: iframeDoc, rootMargin: '100%' } + ); + + io.observe(iframeTargetEl1); + }); + // Current spec indicates that cross-document tracking yields // an essentially empty IntersectionObserverEntry. // See: https://github.com/w3c/IntersectionObserver/issues/87