All Downloads are FREE. Search and download functionalities are using the official Maven repository.

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