polyfills.IntersectionObserver.meta.json Maven / Gradle / Ivy
The newest version!
{"aliases":[],"browsers":{"android":"4.4 - *","bb":"7 - 10","chrome":"<51","firefox":"<52","firefox_mob":"47 - *","ie":"7 - *","ie_mob":"11 - *","ios_saf":"7 - *","op_mini":"17 - *","opera":"*","safari":"6 - *","samsung_mob":"*"},"dependencies":["getComputedStyle","Array.isArray","Array.prototype.filter","Array.prototype.forEach","Array.prototype.indexOf","Array.prototype.map","Array.prototype.some","Event","Function.prototype.bind","perfomance.now"],"docs":"https://github.com/WICG/IntersectionObserver/blob/master/explainer.md","spec":"http://rawgit.com/WICG/IntersectionObserver/master/index.html","notes":[],"repo":"https://github.com/WICG/IntersectionObserver","install":{"module":"intersection-observer","paths":["intersection-observer"]},"detectSource":"\"IntersectionObserver\" in this","testSource":"","baseDir":"IntersectionObserver","hasTests":true,"testsSource":"/* eslint-env mocha, browser*/\n/* global proclaim, it */\n\nbefore(function(done) {\n var head = head = document.head || document.getElementsByTagName('head')[0];\n var scriptEl = document.createElement('script');\n var readywait = null;\n scriptEl.src = 'https://cdnjs.cloudflare.com/ajax/libs/sinon.js/1.15.4/sinon.min.js';\n scriptEl.onload = function() {\n clearTimeout(readywait);\n done();\n };\n readywait = setInterval(function() {\n if ('sinon' in window) {\n clearTimeout(readywait);\n done();\n }\n }, 500);\n head.appendChild(scriptEl);\n});\n\nthis.timeout(10000);\n\n/* The following copy-paste from https://raw.githubusercontent.com/philipwalton/IntersectionObserver/ddc47f358db7624ac52a524451ef9f2a3d5ce8f7/polyfill/intersection-observer-test.js */\n\n\n/**\n * Copyright 2016 Google Inc. All Rights Reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n\n// Sets the timeout to three times the poll interval to ensure all updates\n// happen (especially in slower browsers). Native implementations get the\n// standard 100ms timeout defined in the spec.\nvar ASYNC_TIMEOUT = IntersectionObserver.prototype.THROTTLE_TIMEOUT * 3 || 100;\n\n\nvar io;\nvar noop = function() {};\n\n\n// References to DOM elements, which are accessible to any test\n// and reset prior to each test so state isn't shared.\nvar rootEl;\nvar grandParentEl;\nvar parentEl;\nvar targetEl1;\nvar targetEl2;\nvar targetEl3;\nvar targetEl4;\n\n\ndescribe('IntersectionObserver', function() {\n\n before(function() {\n // If the browser running the tests doesn't support MutationObserver,\n // fall back to polling.\n if (!('MutationObserver' in window)) {\n IntersectionObserver.prototype.POLL_INTERVAL =\n IntersectionObserver.prototype.THROTTLE_TIMEOUT || 100;\n }\n });\n\n\n beforeEach(function() {\n addStyles();\n addFixtures();\n });\n\n\n afterEach(function() {\n if (io && 'disconnect' in io) io.disconnect();\n io = null;\n\n removeStyles();\n removeFixtures();\n });\n\n\n describe('constructor', function() {\n\n it('throws when callback is not a function', function() {\n proclaim.throws(function() {\n io = new IntersectionObserver(null);\n }, /function/i);\n });\n\n\n it('instantiates root correctly', function() {\n io = new IntersectionObserver(noop);\n proclaim.equal(io.root, null);\n\n io = new IntersectionObserver(noop, {root: rootEl});\n proclaim.equal(io.root, rootEl);\n });\n\n\n it('throws when root is not an Element', function() {\n proclaim.throws(function() {\n io = new IntersectionObserver(noop, {root: 'foo'});\n }, /element/i);\n });\n\n\n it('instantiates rootMargin correctly', function() {\n io = new IntersectionObserver(noop, {rootMargin: '10px'});\n proclaim.equal(io.rootMargin, '10px 10px 10px 10px');\n\n io = new IntersectionObserver(noop, {rootMargin: '10px -5%'});\n proclaim.equal(io.rootMargin, '10px -5% 10px -5%');\n\n io = new IntersectionObserver(noop, {rootMargin: '10px 20% 0px'});\n proclaim.equal(io.rootMargin, '10px 20% 0px 20%');\n\n io = new IntersectionObserver(noop, {rootMargin: '0px 0px -5% 5px'});\n proclaim.equal(io.rootMargin, '0px 0px -5% 5px');\n\n // TODO(philipwalton): the polyfill supports fractional pixel and\n // percentage values, but the native Chrome implementation does not,\n // at least not in what it reports `rootMargin` to be.\n if (!supportsNativeIntersectionObserver()) {\n io = new IntersectionObserver(noop, {rootMargin: '-2.5% -8.5px'});\n proclaim.equal(io.rootMargin, '-2.5% -8.5px -2.5% -8.5px');\n }\n });\n\n\n it('throws when rootMargin is not in pixels or pecernt', function() {\n proclaim.throws(function() {\n io = new IntersectionObserver(noop, {rootMargin: '0'});\n }, /pixels.*percent/i);\n });\n\n\n // Chrome's implementation in version 51 doesn't include the thresholds\n // property, but versions 52+ do.\n if ('thresholds' in IntersectionObserver.prototype) {\n it('instantiates thresholds correctly', function() {\n io = new IntersectionObserver(noop);\n proclaim.deepEqual(io.thresholds, [0]);\n\n io = new IntersectionObserver(noop, {threshold: 0.5});\n proclaim.deepEqual(io.thresholds, [0.5]);\n\n io = new IntersectionObserver(noop, {threshold: [0.25, 0.5, 0.75]});\n proclaim.deepEqual(io.thresholds, [0.25, 0.5, 0.75]);\n\n io = new IntersectionObserver(noop, {threshold: [1, .5, 0]});\n proclaim.deepEqual(io.thresholds, [0, .5, 1]);\n });\n }\n\n\n it('throws when a threshold value is not between 0 and 1', function() {\n proclaim.throws(function() {\n io = new IntersectionObserver(noop, {threshold: [0, -1]});\n }, /threshold/i);\n });\n\n });\n\n\n describe('observe', function() {\n\n it('throws when target is not an Element', function() {\n proclaim.throws(function() {\n io = new IntersectionObserver(noop);\n io.observe(null);\n }, /element/i);\n });\n\n\n it('triggers if target intersects when observing begins', function(done) {\n io = new IntersectionObserver(function(records) {\n proclaim.equal(records.length, 1);\n proclaim.equal(records[0].intersectionRatio, 1);\n done();\n }, {root: rootEl});\n io.observe(targetEl1);\n });\n\n\n it('triggers with the correct arguments', function(done) {\n io = new IntersectionObserver(function(records, observer) {\n proclaim.equal(records.length, 1);\n proclaim.isInstanceOf(records[0], IntersectionObserverEntry);\n proclaim.equal(observer, io);\n proclaim.equal(this, io);\n done();\n }, {root: rootEl});\n io.observe(targetEl1);\n });\n\n\n it('does not trigger if target does not intersect when observing begins',\n function(done) {\n\n var spy = sinon.spy();\n io = new IntersectionObserver(spy, {root: rootEl});\n\n targetEl2.style.top = '-40px';\n io.observe(targetEl2);\n setTimeout(function() {\n proclaim.equal(spy.callCount, 0);\n done();\n }, ASYNC_TIMEOUT);\n });\n\n\n it('handles container elements with non-visible overflow',\n function(done) {\n\n var spy = sinon.spy();\n io = new IntersectionObserver(spy, {root: rootEl});\n\n runSequence([\n function(done) {\n io.observe(targetEl1);\n setTimeout(function() {\n proclaim.equal(spy.callCount, 1);\n var records = sortRecords(spy.lastCall.args[0]);\n proclaim.equal(records.length, 1);\n proclaim.equal(records[0].intersectionRatio, 1);\n done();\n }, ASYNC_TIMEOUT);\n },\n function(done) {\n targetEl1.style.left = '-40px';\n setTimeout(function() {\n proclaim.equal(spy.callCount, 2);\n var records = sortRecords(spy.lastCall.args[0]);\n proclaim.equal(records.length, 1);\n proclaim.equal(records[0].intersectionRatio, 0);\n done();\n }, ASYNC_TIMEOUT);\n },\n function(done) {\n parentEl.style.overflow = 'visible';\n setTimeout(function() {\n proclaim.equal(spy.callCount, 3);\n var records = sortRecords(spy.lastCall.args[0]);\n proclaim.equal(records.length, 1);\n proclaim.equal(records[0].intersectionRatio, 1);\n done();\n }, ASYNC_TIMEOUT);\n }\n ], done);\n });\n\n\n it('observes one target at a single threshold correctly', function(done) {\n\n var spy = sinon.spy();\n io = new IntersectionObserver(spy, {root: rootEl, threshold: 0.5});\n\n runSequence([\n function(done) {\n targetEl1.style.left = '-5px';\n io.observe(targetEl1);\n setTimeout(function() {\n proclaim.equal(spy.callCount, 1);\n var records = sortRecords(spy.lastCall.args[0]);\n proclaim.equal(records.length, 1);\n proclaim.greaterThan(records[0].intersectionRatio, 0.5);\n done();\n }, ASYNC_TIMEOUT);\n },\n function(done) {\n targetEl1.style.left = '-15px';\n setTimeout(function() {\n proclaim.equal(spy.callCount, 2);\n var records = sortRecords(spy.lastCall.args[0]);\n proclaim.equal(records.length, 1);\n proclaim.lessThan(records[0].intersectionRatio, 0.5);\n done();\n }, ASYNC_TIMEOUT);\n },\n function(done) {\n targetEl1.style.left = '-25px';\n setTimeout(function() {\n proclaim.equal(spy.callCount, 2);\n done();\n }, ASYNC_TIMEOUT);\n },\n function(done) {\n targetEl1.style.left = '-10px';\n setTimeout(function() {\n proclaim.equal(spy.callCount, 3);\n var records = sortRecords(spy.lastCall.args[0]);\n proclaim.equal(records.length, 1);\n proclaim.equal(records[0].intersectionRatio, 0.5);\n done();\n }, ASYNC_TIMEOUT);\n }\n ], done);\n\n });\n\n\n it('observes multiple targets at multiple thresholds correctly',\n function(done) {\n\n var spy = sinon.spy();\n io = new IntersectionObserver(spy, {\n root: rootEl,\n threshold: [1, 0.5, 0]\n });\n\n runSequence([\n function(done) {\n targetEl1.style.top = '0px';\n targetEl1.style.left = '-15px';\n targetEl2.style.top = '-5px';\n targetEl2.style.left = '0px';\n targetEl3.style.top = '0px';\n targetEl3.style.left = '205px';\n io.observe(targetEl1);\n io.observe(targetEl2);\n io.observe(targetEl3);\n setTimeout(function() {\n proclaim.equal(spy.callCount, 1);\n var records = sortRecords(spy.lastCall.args[0]);\n proclaim.equal(records.length, 2);\n proclaim.equal(records[0].target, targetEl1);\n proclaim.equal(records[0].intersectionRatio, 0.25);\n proclaim.equal(records[1].target, targetEl2);\n proclaim.equal(records[1].intersectionRatio, 0.75);\n done();\n }, ASYNC_TIMEOUT);\n },\n function(done) {\n targetEl1.style.top = '0px';\n targetEl1.style.left = '-5px';\n targetEl2.style.top = '-15px';\n targetEl2.style.left = '0px';\n targetEl3.style.top = '0px';\n targetEl3.style.left = '195px';\n setTimeout(function() {\n proclaim.equal(spy.callCount, 2);\n var records = sortRecords(spy.lastCall.args[0]);\n proclaim.equal(records.length, 3);\n proclaim.equal(records[0].target, targetEl1);\n proclaim.equal(records[0].intersectionRatio, 0.75);\n proclaim.equal(records[1].target, targetEl2);\n proclaim.equal(records[1].intersectionRatio, 0.25);\n proclaim.equal(records[2].target, targetEl3);\n proclaim.equal(records[2].intersectionRatio, 0.25);\n done();\n }, ASYNC_TIMEOUT);\n },\n function(done) {\n targetEl1.style.top = '0px';\n targetEl1.style.left = '5px';\n targetEl2.style.top = '-25px';\n targetEl2.style.left = '0px';\n targetEl3.style.top = '0px';\n targetEl3.style.left = '185px';\n setTimeout(function() {\n proclaim.equal(spy.callCount, 3);\n var records = sortRecords(spy.lastCall.args[0]);\n proclaim.equal(records.length, 3);\n proclaim.equal(records[0].target, targetEl1);\n proclaim.equal(records[0].intersectionRatio, 1);\n proclaim.equal(records[1].target, targetEl2);\n proclaim.equal(records[1].intersectionRatio, 0);\n proclaim.equal(records[2].target, targetEl3);\n proclaim.equal(records[2].intersectionRatio, 0.75);\n done();\n }, ASYNC_TIMEOUT);\n },\n function(done) {\n targetEl1.style.top = '0px';\n targetEl1.style.left = '15px';\n targetEl2.style.top = '-35px';\n targetEl2.style.left = '0px';\n targetEl3.style.top = '0px';\n targetEl3.style.left = '175px';\n setTimeout(function() {\n proclaim.equal(spy.callCount, 4);\n var records = sortRecords(spy.lastCall.args[0]);\n proclaim.equal(records.length, 1);\n proclaim.equal(records[0].target, targetEl3);\n proclaim.equal(records[0].intersectionRatio, 1);\n done();\n }, ASYNC_TIMEOUT);\n }\n ], done);\n });\n\n\n it('handles rootMargin properly', function(done) {\n\n parentEl.style.overflow = 'visible';\n targetEl1.style.top = '0px';\n targetEl1.style.left = '-20px';\n targetEl2.style.top = '-20px';\n targetEl2.style.left = '0px';\n targetEl3.style.top = '0px';\n targetEl3.style.left = '200px';\n targetEl4.style.top = '180px';\n targetEl4.style.left = '180px';\n\n runSequence([\n function(done) {\n io = new IntersectionObserver(function(records) {\n records = sortRecords(records);\n proclaim.equal(records.length, 4);\n proclaim.equal(records[0].target, targetEl1);\n proclaim.equal(records[0].intersectionRatio, 1);\n proclaim.equal(records[1].target, targetEl2);\n proclaim.equal(records[1].intersectionRatio, .5);\n proclaim.equal(records[2].target, targetEl3);\n proclaim.equal(records[2].intersectionRatio, .5);\n proclaim.equal(records[3].target, targetEl4);\n proclaim.equal(records[3].intersectionRatio, 1);\n io.disconnect();\n done();\n }, {root: rootEl, rootMargin: '10px'});\n\n io.observe(targetEl1);\n io.observe(targetEl2);\n io.observe(targetEl3);\n io.observe(targetEl4);\n\n // Force a new frame to fix https://crbug.com/612323\n window.requestAnimationFrame && requestAnimationFrame(function(){});\n },\n function(done) {\n io = new IntersectionObserver(function(records) {\n records = sortRecords(records);\n proclaim.equal(records.length, 3);\n proclaim.equal(records[0].target, targetEl1);\n proclaim.equal(records[0].intersectionRatio, 0.5);\n proclaim.equal(records[1].target, targetEl3);\n proclaim.equal(records[1].intersectionRatio, 0.5);\n proclaim.equal(records[2].target, targetEl4);\n proclaim.equal(records[2].intersectionRatio, 0.5);\n io.disconnect();\n done();\n }, {root: rootEl, rootMargin: '-10px 10%'});\n\n io.observe(targetEl1);\n io.observe(targetEl2);\n io.observe(targetEl3);\n io.observe(targetEl4);\n\n // Force a new frame to fix https://crbug.com/612323\n window.requestAnimationFrame && requestAnimationFrame(function(){});\n },\n function(done) {\n io = new IntersectionObserver(function(records) {\n records = sortRecords(records);\n proclaim.equal(records.length, 2);\n proclaim.equal(records[0].target, targetEl1);\n proclaim.equal(records[0].intersectionRatio, 0.5);\n proclaim.equal(records[1].target, targetEl4);\n proclaim.equal(records[1].intersectionRatio, 0.5);\n io.disconnect();\n done();\n }, {root: rootEl, rootMargin: '-5% -2.5% 0px'});\n\n io.observe(targetEl1);\n io.observe(targetEl2);\n io.observe(targetEl3);\n io.observe(targetEl4);\n\n // Force a new frame to fix https://crbug.com/612323\n window.requestAnimationFrame && requestAnimationFrame(function(){});\n },\n function(done) {\n io = new IntersectionObserver(function(records) {\n records = sortRecords(records);\n proclaim.equal(records.length, 3);\n proclaim.equal(records[0].target, targetEl1);\n proclaim.equal(records[0].intersectionRatio, 0.5);\n proclaim.equal(records[1].target, targetEl2);\n proclaim.equal(records[1].intersectionRatio, 0.5);\n proclaim.equal(records[2].target, targetEl4);\n proclaim.equal(records[2].intersectionRatio, 0.25);\n io.disconnect();\n done();\n }, {root: rootEl, rootMargin: '5% -2.5% -10px -190px'});\n\n io.observe(targetEl1);\n io.observe(targetEl2);\n io.observe(targetEl3);\n io.observe(targetEl4);\n\n // Force a new frame to fix https://crbug.com/612323\n window.requestAnimationFrame && requestAnimationFrame(function(){});\n }\n ], done);\n });\n\n\n it('handles targets on the boundary of root', function(done) {\n\n var spy = sinon.spy();\n io = new IntersectionObserver(spy, {root: rootEl});\n\n runSequence([\n function(done) {\n targetEl1.style.top = '0px';\n targetEl1.style.left = '-21px';\n targetEl2.style.top = '-20px';\n targetEl2.style.left = '0px';\n io.observe(targetEl1);\n io.observe(targetEl2);\n setTimeout(function() {\n proclaim.equal(spy.callCount, 1);\n var records = sortRecords(spy.lastCall.args[0]);\n proclaim.equal(records.length, 1);\n proclaim.equal(records[0].intersectionRatio, 0);\n proclaim.equal(records[0].target, targetEl2);\n done();\n }, ASYNC_TIMEOUT);\n },\n function(done) {\n targetEl1.style.top = '0px';\n targetEl1.style.left = '-20px';\n targetEl2.style.top = '-21px';\n targetEl2.style.left = '0px';\n setTimeout(function() {\n proclaim.equal(spy.callCount, 2);\n var records = sortRecords(spy.lastCall.args[0]);\n proclaim.equal(records.length, 2);\n proclaim.equal(records[0].intersectionRatio, 0);\n proclaim.equal(records[0].target, targetEl1);\n proclaim.equal(records[1].intersectionRatio, 0);\n proclaim.equal(records[1].target, targetEl2);\n done();\n }, ASYNC_TIMEOUT);\n },\n function(done) {\n targetEl1.style.top = '-20px';\n targetEl1.style.left = '200px';\n targetEl2.style.top = '200px';\n targetEl2.style.left = '200px';\n\n setTimeout(function() {\n proclaim.equal(spy.callCount, 3);\n var records = sortRecords(spy.lastCall.args[0]);\n proclaim.equal(records.length, 1);\n proclaim.equal(records[0].intersectionRatio, 0);\n proclaim.equal(records[0].target, targetEl2);\n done();\n }, ASYNC_TIMEOUT);\n }\n ], done);\n\n });\n\n\n it('handles zero-size targets within the root coordinate space',\n function(done) {\n\n io = new IntersectionObserver(function(records) {\n proclaim.equal(records.length, 1);\n proclaim.equal(records[0].intersectionRatio, 0);\n done();\n }, {root: rootEl});\n\n targetEl1.style.top = '0px';\n targetEl1.style.left = '0px';\n targetEl1.style.width = '0px';\n targetEl1.style.height = '0px';\n io.observe(targetEl1);\n });\n\n\n it('handles root/target elements not yet in the DOM', function(done) {\n\n rootEl.parentNode.removeChild(rootEl);\n targetEl1.parentNode.removeChild(targetEl1);\n\n var spy = sinon.spy();\n io = new IntersectionObserver(spy, {root: rootEl});\n\n runSequence([\n function(done) {\n io.observe(targetEl1);\n setTimeout(done, 0);\n },\n function(done) {\n document.getElementById('fixtures').appendChild(rootEl);\n setTimeout(function() {\n proclaim.equal(spy.callCount, 0);\n done();\n }, ASYNC_TIMEOUT);\n },\n function(done) {\n parentEl.insertBefore(targetEl1, targetEl2);\n setTimeout(function() {\n proclaim.equal(spy.callCount, 1);\n var records = sortRecords(spy.lastCall.args[0]);\n proclaim.equal(records.length, 1);\n proclaim.equal(records[0].intersectionRatio, 1);\n proclaim.equal(records[0].target, targetEl1);\n done();\n }, ASYNC_TIMEOUT);\n },\n function(done) {\n grandParentEl.parentNode.removeChild(grandParentEl);\n setTimeout(function() {\n proclaim.equal(spy.callCount, 2);\n var records = sortRecords(spy.lastCall.args[0]);\n proclaim.equal(records.length, 1);\n proclaim.equal(records[0].intersectionRatio, 0);\n proclaim.equal(records[0].target, targetEl1);\n done();\n }, ASYNC_TIMEOUT);\n },\n function(done) {\n rootEl.appendChild(targetEl1);\n setTimeout(function() {\n proclaim.equal(spy.callCount, 3);\n var records = sortRecords(spy.lastCall.args[0]);\n proclaim.equal(records.length, 1);\n proclaim.equal(records[0].intersectionRatio, 1);\n proclaim.equal(records[0].target, targetEl1);\n done();\n }, ASYNC_TIMEOUT);\n },\n function(done) {\n rootEl.parentNode.removeChild(rootEl);\n setTimeout(function() {\n proclaim.equal(spy.callCount, 4);\n var records = sortRecords(spy.lastCall.args[0]);\n proclaim.equal(records.length, 1);\n proclaim.equal(records[0].intersectionRatio, 0);\n proclaim.equal(records[0].target, targetEl1);\n done();\n }, ASYNC_TIMEOUT);\n }\n ], done);\n });\n\n\n it('handles sub-root element scrolling', function(done) {\n io = new IntersectionObserver(function(records) {\n proclaim.equal(records.length, 1);\n proclaim.equal(records[0].intersectionRatio, 1);\n done();\n }, {root: rootEl});\n\n io.observe(targetEl3);\n setTimeout(function() {\n parentEl.scrollLeft = 40;\n }, 0);\n });\n\n\n // Only run this test in browsers that support CSS transitions.\n if ('transform' in document.documentElement.style &&\n 'transform' in document.documentElement.style) {\n\n it('supports CSS transitions and transforms', function(done) {\n\n targetEl1.style.top = '220px';\n targetEl1.style.left = '220px';\n\n io = new IntersectionObserver(function(records) {\n proclaim.equal(records.length, 1);\n // Chrome's native implementation sometimes incorrectly reports\n // the intersection ratio as a number > 1.\n proclaim.lessThanOrEqual(records[0].intersectionRatio, 1);\n done();\n }, {root: rootEl, threshold: [1]});\n\n // CSS transitions that are slower than the default throttle timeout\n // require polling to detect, which can be set on a per-instance basis.\n if (!supportsNativeIntersectionObserver()) {\n io.POLL_INTERVAL = 100;\n }\n\n io.observe(targetEl1);\n setTimeout(function() {\n targetEl1.style.transform = 'translateX(-40px) translateY(-40px)';\n }, 0);\n });\n }\n\n\n it('uses the viewport when no root is specified', function(done) {\n io = new IntersectionObserver(function(records) {\n var viewportWidth = document.documentElement.clientWidth || document.body.clientWidth;\n var viewportHeight = document.documentElement.clientHeight || document.body.clientHeight;\n\n proclaim.equal(records.length, 1);\n proclaim.equal(records[0].rootBounds.top, 0);\n proclaim.equal(records[0].rootBounds.left, 0);\n proclaim.equal(records[0].rootBounds.right, viewportWidth);\n proclaim.equal(records[0].rootBounds.width, viewportWidth);\n proclaim.equal(records[0].rootBounds.bottom, viewportHeight);\n proclaim.equal(records[0].rootBounds.height, viewportHeight);\n done();\n });\n\n // Ensures targetEl1 is visible in the viewport before observing.\n window.scrollTo(0, 0);\n rootEl.style.position = 'absolute';\n rootEl.style.top = '0px';\n rootEl.style.left = '0px';\n\n io.observe(targetEl1);\n });\n\n });\n\n\n describe('takeRecords', function() {\n\n it('supports getting records before the callback is invoked',\n function(done) {\n\n var lastestRecords = [];\n io = new IntersectionObserver(function(records) {\n lastestRecords = lastestRecords.concat(records);\n }, {root: rootEl});\n io.observe(targetEl1);\n\n window.requestAnimationFrame && requestAnimationFrame(function() {\n lastestRecords = lastestRecords.concat(io.takeRecords());\n });\n\n setTimeout(function() {\n proclaim.equal(lastestRecords.length, 1);\n proclaim.equal(lastestRecords[0].intersectionRatio, 1);\n done();\n }, ASYNC_TIMEOUT);\n });\n\n });\n\n\n describe('unobserve', function() {\n\n it('removes targets from the internal store', function(done) {\n\n var spy = sinon.spy();\n io = new IntersectionObserver(spy, {root: rootEl});\n\n runSequence([\n function(done) {\n targetEl1.style.top = targetEl2.style.top = '0px';\n targetEl1.style.left = targetEl2.style.left = '0px';\n io.observe(targetEl1);\n io.observe(targetEl2);\n setTimeout(function() {\n proclaim.equal(spy.callCount, 1);\n var records = sortRecords(spy.lastCall.args[0]);\n proclaim.equal(records.length, 2);\n proclaim.equal(records[0].target, targetEl1);\n proclaim.equal(records[0].intersectionRatio, 1);\n proclaim.equal(records[1].target, targetEl2);\n proclaim.equal(records[1].intersectionRatio, 1);\n done();\n }, ASYNC_TIMEOUT);\n },\n function(done) {\n io.unobserve(targetEl1);\n targetEl1.style.top = targetEl2.style.top = '0px';\n targetEl1.style.left = targetEl2.style.left = '-40px';\n setTimeout(function() {\n proclaim.equal(spy.callCount, 2);\n var records = sortRecords(spy.lastCall.args[0]);\n proclaim.equal(records.length, 1);\n proclaim.equal(records[0].target, targetEl2);\n proclaim.equal(records[0].intersectionRatio, 0);\n done();\n }, ASYNC_TIMEOUT);\n },\n function(done) {\n io.unobserve(targetEl2);\n targetEl1.style.top = targetEl2.style.top = '0px';\n targetEl1.style.left = targetEl2.style.left = '0px';\n setTimeout(function() {\n proclaim.equal(spy.callCount, 2);\n done();\n }, ASYNC_TIMEOUT);\n }\n ], done);\n\n });\n\n });\n\n describe('disconnect', function() {\n\n it('removes all targets and stops listening for changes', function(done) {\n\n var spy = sinon.spy();\n io = new IntersectionObserver(spy, {root: rootEl});\n\n runSequence([\n function(done) {\n targetEl1.style.top = targetEl2.style.top = '0px';\n targetEl1.style.left = targetEl2.style.left = '0px';\n io.observe(targetEl1);\n io.observe(targetEl2);\n setTimeout(function() {\n proclaim.equal(spy.callCount, 1);\n var records = sortRecords(spy.lastCall.args[0]);\n proclaim.equal(records.length, 2);\n proclaim.equal(records[0].target, targetEl1);\n proclaim.equal(records[0].intersectionRatio, 1);\n proclaim.equal(records[1].target, targetEl2);\n proclaim.equal(records[1].intersectionRatio, 1);\n done();\n }, ASYNC_TIMEOUT);\n },\n function(done) {\n io.disconnect();\n targetEl1.style.top = targetEl2.style.top = '0px';\n targetEl1.style.left = targetEl2.style.left = '-40px';\n setTimeout(function() {\n proclaim.equal(spy.callCount, 1);\n done();\n }, ASYNC_TIMEOUT);\n }\n ], done);\n\n });\n\n });\n\n});\n\n\n/**\n * Runs a sequence of function and when finished invokes the done callback.\n * Each function in the sequence is invoked with its own done function and\n * it should call that function once it's complete.\n * @param {Array} functions An array of async functions.\n * @param {Function} done A final callback to be invoked once all function\n * have run.\n */\nfunction runSequence(functions, done) {\n var next = functions.shift();\n if (next) {\n next(function() {\n runSequence(functions, done);\n });\n } else {\n done && done();\n }\n}\n\n\n/**\n * Returns whether or not the current browser has native support for\n * IntersectionObserver.\n * @return {boolean} True if native support is detected.\n */\nfunction supportsNativeIntersectionObserver() {\n return 'IntersectionObserver' in window &&\n window.IntersectionObserver.toString().indexOf('[native code]') > -1;\n}\n\n\n/**\n * Sorts an array of records alphebetically by ascending ID. Since the current\n * native implementation doesn't sort change entries by `observe` order, we do\n * that ourselves for the non-polyfill case. Since all tests call observe\n * on targets in sequential order, this should always match.\n * https://crbug.com/613679\n * @param {Array} entries The entries to sort.\n * @return {Array} The sorted array.\n */\nfunction sortRecords(entries) {\n if (supportsNativeIntersectionObserver()) {\n entries = entries.sort(function(a, b) {\n return a.target.id < b.target.id ? -1 : 1;\n });\n }\n return entries;\n}\n\n\n/**\n * Adds the common styles used by all tests to the page.\n */\nfunction addStyles() {\n var styles = document.createElement('style');\n styles.id = 'styles';\n document.documentElement.appendChild(styles);\n\n var cssText =\n '#root {' +\n ' position: relative;' +\n ' width: 400px;' +\n ' height: 200px;' +\n ' background: #eee' +\n '}' +\n '#grand-parent {' +\n ' position: relative;' +\n ' width: 200px;' +\n ' height: 200px;' +\n '}' +\n '#parent {' +\n ' position: absolute;' +\n ' top: 0px;' +\n ' left: 200px;' +\n ' overflow: hidden;' +\n ' width: 200px;' +\n ' height: 200px;' +\n ' background: #ddd;' +\n '}' +\n '#target1, #target2, #target3, #target4 {' +\n ' position: absolute;' +\n ' top: 0px;' +\n ' left: 0px;' +\n ' width: 20px;' +\n ' height: 20px;' +\n ' transform: translateX(0px) translateY(0px);' +\n ' transition: transform .5s;' +\n ' background: #f00;' +\n '}';\n\n // IE8 doesn't allow setting innerHTML on a