summaryrefslogtreecommitdiffstats
path: root/dom/base/test/test_intersectionobservers.html
diff options
context:
space:
mode:
Diffstat (limited to 'dom/base/test/test_intersectionobservers.html')
-rw-r--r--dom/base/test/test_intersectionobservers.html1221
1 files changed, 1221 insertions, 0 deletions
diff --git a/dom/base/test/test_intersectionobservers.html b/dom/base/test/test_intersectionobservers.html
new file mode 100644
index 0000000000..d228dfc944
--- /dev/null
+++ b/dom/base/test/test_intersectionobservers.html
@@ -0,0 +1,1221 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1243846
+
+Some tests ported from IntersectionObserver/polyfill/intersection-observer-test.html
+
+Original license header:
+
+Copyright 2016 Google Inc. All Rights Reserved.
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+ http://www.apache.org/licenses/LICENSE-2.0
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 1243846</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body onload="onLoad()">
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1243846">Mozilla Bug 1243846</a>
+<p id="display"></p>
+<pre id="test">
+<script type="application/javascript">
+ /* eslint "no-shadow": ["error", {"allow": ["done", "next"]}] */
+ var tests = [];
+ var curDescribeMsg = '';
+ var curItMsg = '';
+
+ function beforeEach_fn() { };
+ function afterEach_fn() { };
+
+ function before(fn) {
+ fn();
+ }
+
+ function beforeEach(fn) {
+ beforeEach_fn = fn;
+ }
+
+ function afterEach(fn) {
+ afterEach_fn = fn;
+ }
+
+ function it(msg, fn) {
+ tests.push({
+ msg: `${msg} [${curDescribeMsg}]`,
+ fn
+ });
+ }
+
+ var callbacks = [];
+ function callDelayed(fn) {
+ callbacks.push(fn);
+ }
+
+ requestAnimationFrame(function tick() {
+ var i = callbacks.length;
+ while (i--) {
+ var cb = callbacks[i];
+ SimpleTest.executeSoon(function() { SimpleTest.executeSoon(cb) });
+ callbacks.splice(i, 1);
+ }
+ requestAnimationFrame(tick);
+ });
+
+ function expect(val) {
+ return {
+ to: {
+ throwException (regexp) {
+ try {
+ val();
+ ok(false, `${curItMsg} - an exception should have beeen thrown`);
+ } catch (e) {
+ ok(regexp.test(e), `${curItMsg} - supplied regexp should match thrown exception`);
+ }
+ },
+ get be() {
+ var fn = function (expected) {
+ is(val, expected, curItMsg);
+ };
+ fn.ok = function () {
+ ok(val, curItMsg);
+ };
+ fn.greaterThan = function (other) {
+ ok(val > other, `${curItMsg} - ${val} should be greater than ${other}`);
+ };
+ fn.lessThan = function (other) {
+ ok(val < other, `${curItMsg} - ${val} should be less than ${other}`);
+ };
+ return fn;
+ },
+ eql (expected) {
+ if (Array.isArray(expected)) {
+ if (!Array.isArray(val)) {
+ ok(false, curItMsg, `${curItMsg} - should be an array,`);
+ return;
+ }
+ is(val.length, expected.length, curItMsg, `${curItMsg} - arrays should be the same length`);
+ if (expected.length != val.length) {
+ return;
+ }
+ for (var i = 0; i < expected.length; i++) {
+ is(val[i], expected[i], `${curItMsg} - array elements at position ${i} should be equal`);
+ if (expected[i] != val[i]) {
+ return;
+ }
+ }
+ ok(true);
+ }
+ },
+ }
+ }
+ }
+
+ function describe(msg, fn) {
+ curDescribeMsg = msg;
+ fn();
+ curDescribeMsg = '';
+ }
+
+ function next() {
+ var test = tests.shift();
+ if (test) {
+ console.log(test.msg);
+ curItMsg = test.msg;
+ var fn = test.fn;
+ beforeEach_fn();
+ if (fn.length) {
+ fn(function () {
+ afterEach_fn();
+ next();
+ });
+ } else {
+ fn();
+ afterEach_fn();
+ next();
+ }
+ } else {
+ SimpleTest.finish();
+ }
+ }
+
+ var sinon = {
+ spy () {
+ var cbs = [];
+ var fn = function () {
+ fn.callCount++;
+ fn.lastCall = { args: arguments };
+ if (cbs.length) {
+ cbs.shift()();
+ }
+ };
+ fn.callCount = 0;
+ fn.lastCall = { args: [] };
+ fn.waitForNotification = (fn1) => {
+ cbs.push(fn1);
+ };
+ return fn;
+ }
+ };
+
+ var ASYNC_TIMEOUT = 300;
+
+
+ var io;
+ var noop = function() {};
+
+
+ // References to DOM elements, which are accessible to any test
+ // and reset prior to each test so state isn't shared.
+ var rootEl;
+ var grandParentEl;
+ var parentEl;
+ var targetEl1;
+ var targetEl2;
+ var targetEl3;
+ var targetEl4;
+ var targetEl5;
+
+
+ describe('IntersectionObserver', function() {
+
+ before(function() {
+
+ });
+
+
+ beforeEach(function() {
+ addStyles();
+ addFixtures();
+ });
+
+
+ afterEach(function() {
+ if (io && 'disconnect' in io) io.disconnect();
+ io = null;
+
+ window.onmessage = null;
+
+ removeStyles();
+ removeFixtures();
+ });
+
+
+ describe('constructor', function() {
+
+ it('throws when callback is not a function', function() {
+ expect(function() {
+ io = new IntersectionObserver(null);
+ }).to.throwException(/.*/i);
+ });
+
+
+ it('instantiates root correctly', function() {
+ io = new IntersectionObserver(noop);
+ expect(io.root).to.be(null);
+
+ io = new IntersectionObserver(noop, {root: rootEl});
+ expect(io.root).to.be(rootEl);
+ });
+
+
+ it('throws when root is not an Element', function() {
+ expect(function() {
+ io = new IntersectionObserver(noop, {root: 'foo'});
+ }).to.throwException(/.*/i);
+ });
+
+
+ it('instantiates rootMargin correctly', function() {
+ io = new IntersectionObserver(noop, {rootMargin: '10px'});
+ expect(io.rootMargin).to.be('10px 10px 10px 10px');
+
+ io = new IntersectionObserver(noop, {rootMargin: '10px -5%'});
+ expect(io.rootMargin).to.be('10px -5% 10px -5%');
+
+ io = new IntersectionObserver(noop, {rootMargin: '10px 20% 0px'});
+ expect(io.rootMargin).to.be('10px 20% 0px 20%');
+
+ io = new IntersectionObserver(noop, {rootMargin: '0px 0px -5% 5px'});
+ expect(io.rootMargin).to.be('0px 0px -5% 5px');
+ });
+
+
+ it('throws when rootMargin is not in pixels or percent', function() {
+ expect(function() {
+ io = new IntersectionObserver(noop, {rootMargin: 'auto'});
+ }).to.throwException(/pixels.*percent/i);
+ });
+
+
+ it('instantiates thresholds correctly', function() {
+ io = new IntersectionObserver(noop);
+ expect(io.thresholds).to.eql([0]);
+
+ io = new IntersectionObserver(noop, {threshold: 0.5});
+ expect(io.thresholds).to.eql([0.5]);
+
+ io = new IntersectionObserver(noop, {threshold: [0.25, 0.5, 0.75]});
+ expect(io.thresholds).to.eql([0.25, 0.5, 0.75]);
+
+ io = new IntersectionObserver(noop, {threshold: [1, .5, 0]});
+ expect(io.thresholds).to.eql([0, .5, 1]);
+ });
+
+ it('throws when a threshold value is not between 0 and 1', function() {
+ expect(function() {
+ io = new IntersectionObserver(noop, {threshold: [0, -1]});
+ }).to.throwException(/threshold/i);
+ });
+
+ it('throws when a threshold value is not a number', function() {
+ expect(function() {
+ io = new IntersectionObserver(noop, {threshold: "foo"});
+ }).to.throwException(/.*/i);
+ });
+
+ });
+
+
+ describe('observe', function() {
+
+ it('throws when target is not an Element', function() {
+ expect(function() {
+ io = new IntersectionObserver(noop);
+ io.observe(null);
+ }).to.throwException(/.*/i);
+ });
+
+
+ it('triggers if target intersects when observing begins', function(done) {
+ io = new IntersectionObserver(function(records) {
+ expect(records.length).to.be(1);
+ expect(records[0].intersectionRatio).to.be(1);
+ done();
+ }, {root: rootEl});
+ io.observe(targetEl1);
+ });
+
+
+ it('triggers with the correct arguments', function(done) {
+ io = new IntersectionObserver(function(records, observer) {
+ expect(records.length).to.be(1);
+ expect(records[0] instanceof IntersectionObserverEntry).to.be.ok();
+ expect(observer).to.be(io);
+ expect(this).to.be(io);
+ done();
+ }, {root: rootEl});
+ io.observe(targetEl1);
+ });
+
+
+ it('does trigger if target does not intersect when observing begins',
+ function(done) {
+
+ var spy = sinon.spy();
+ io = new IntersectionObserver(spy, {root: rootEl});
+
+ targetEl2.style.top = '-40px';
+ io.observe(targetEl2);
+ callDelayed(function() {
+ expect(spy.callCount).to.be(1);
+ done();
+ });
+ });
+
+
+ it('triggers if target or root becomes invisible',
+ function(done) {
+
+ var spy = sinon.spy();
+ io = new IntersectionObserver(spy, {root: rootEl});
+
+ runSequence([
+ function(done) {
+ io.observe(targetEl1);
+ spy.waitForNotification(function() {
+ expect(spy.callCount).to.be(1);
+ var records = sortRecords(spy.lastCall.args[0]);
+ expect(records.length).to.be(1);
+ expect(records[0].intersectionRatio).to.be(1);
+ done();
+ });
+ },
+ function(done) {
+ targetEl1.style.display = 'none';
+ spy.waitForNotification(function() {
+ expect(spy.callCount).to.be(2);
+ var records = sortRecords(spy.lastCall.args[0]);
+ expect(records.length).to.be(1);
+ expect(records[0].intersectionRatio).to.be(0);
+ done();
+ });
+ },
+ function(done) {
+ targetEl1.style.display = 'block';
+ spy.waitForNotification(function() {
+ expect(spy.callCount).to.be(3);
+ var records = sortRecords(spy.lastCall.args[0]);
+ expect(records.length).to.be(1);
+ expect(records[0].intersectionRatio).to.be(1);
+ done();
+ });
+ },
+ function(done) {
+ rootEl.style.display = 'none';
+ spy.waitForNotification(function() {
+ expect(spy.callCount).to.be(4);
+ var records = sortRecords(spy.lastCall.args[0]);
+ expect(records.length).to.be(1);
+ expect(records[0].intersectionRatio).to.be(0);
+ done();
+ });
+ },
+ function(done) {
+ rootEl.style.display = 'block';
+ spy.waitForNotification(function() {
+ expect(spy.callCount).to.be(5);
+ var records = sortRecords(spy.lastCall.args[0]);
+ expect(records.length).to.be(1);
+ expect(records[0].intersectionRatio).to.be(1);
+ done();
+ });
+ },
+ ], done);
+ });
+
+
+ it('handles container elements with non-visible overflow',
+ function(done) {
+
+ var spy = sinon.spy();
+ io = new IntersectionObserver(spy, {root: rootEl});
+
+ runSequence([
+ function(done) {
+ io.observe(targetEl1);
+ spy.waitForNotification(function() {
+ expect(spy.callCount).to.be(1);
+ var records = sortRecords(spy.lastCall.args[0]);
+ expect(records.length).to.be(1);
+ expect(records[0].intersectionRatio).to.be(1);
+ done();
+ });
+ },
+ function(done) {
+ targetEl1.style.left = '-40px';
+ spy.waitForNotification(function() {
+ expect(spy.callCount).to.be(2);
+ var records = sortRecords(spy.lastCall.args[0]);
+ expect(records.length).to.be(1);
+ expect(records[0].intersectionRatio).to.be(0);
+ done();
+ });
+ },
+ function(done) {
+ parentEl.style.overflow = 'visible';
+ spy.waitForNotification(function() {
+ expect(spy.callCount).to.be(3);
+ var records = sortRecords(spy.lastCall.args[0]);
+ expect(records.length).to.be(1);
+ expect(records[0].intersectionRatio).to.be(1);
+ done();
+ });
+ }
+ ], done);
+ });
+
+
+ it('observes one target at a single threshold correctly', function(done) {
+
+ var spy = sinon.spy();
+ io = new IntersectionObserver(spy, {root: rootEl, threshold: 0.5});
+
+ runSequence([
+ function(done) {
+ targetEl1.style.left = '-5px';
+ io.observe(targetEl1);
+ spy.waitForNotification(function() {
+ expect(spy.callCount).to.be(1);
+ var records = sortRecords(spy.lastCall.args[0]);
+ expect(records.length).to.be(1);
+ expect(records[0].intersectionRatio).to.be.greaterThan(0.5);
+ done();
+ });
+ },
+ function(done) {
+ targetEl1.style.left = '-15px';
+ spy.waitForNotification(function() {
+ expect(spy.callCount).to.be(2);
+ var records = sortRecords(spy.lastCall.args[0]);
+ expect(records.length).to.be(1);
+ expect(records[0].intersectionRatio).to.be.lessThan(0.5);
+ done();
+ });
+ },
+ function(done) {
+ targetEl1.style.left = '-25px';
+ callDelayed(function() {
+ expect(spy.callCount).to.be(2);
+ done();
+ });
+ },
+ function(done) {
+ targetEl1.style.left = '-10px';
+ spy.waitForNotification(function() {
+ expect(spy.callCount).to.be(3);
+ var records = sortRecords(spy.lastCall.args[0]);
+ expect(records.length).to.be(1);
+ expect(records[0].intersectionRatio).to.be(0.5);
+ done();
+ });
+ }
+ ], done);
+
+ });
+
+
+ it('observes multiple targets at multiple thresholds correctly',
+ function(done) {
+
+ var spy = sinon.spy();
+ io = new IntersectionObserver(spy, {
+ root: rootEl,
+ threshold: [1, 0.5, 0]
+ });
+
+ runSequence([
+ function(done) {
+ targetEl1.style.top = '0px';
+ targetEl1.style.left = '-15px';
+ targetEl2.style.top = '-5px';
+ targetEl2.style.left = '0px';
+ targetEl3.style.top = '0px';
+ targetEl3.style.left = '205px';
+ io.observe(targetEl1);
+ io.observe(targetEl2);
+ io.observe(targetEl3);
+ spy.waitForNotification(function() {
+ expect(spy.callCount).to.be(1);
+ var records = sortRecords(spy.lastCall.args[0]);
+ expect(records.length).to.be(3);
+ expect(records[0].target).to.be(targetEl1);
+ expect(records[0].intersectionRatio).to.be(0.25);
+ expect(records[1].target).to.be(targetEl2);
+ expect(records[1].intersectionRatio).to.be(0.75);
+ done();
+ });
+ },
+ function(done) {
+ targetEl1.style.top = '0px';
+ targetEl1.style.left = '-5px';
+ targetEl2.style.top = '-15px';
+ targetEl2.style.left = '0px';
+ targetEl3.style.top = '0px';
+ targetEl3.style.left = '195px';
+ spy.waitForNotification(function() {
+ expect(spy.callCount).to.be(2);
+ var records = sortRecords(spy.lastCall.args[0]);
+ expect(records.length).to.be(3);
+ expect(records[0].target).to.be(targetEl1);
+ expect(records[0].intersectionRatio).to.be(0.75);
+ expect(records[1].target).to.be(targetEl2);
+ expect(records[1].intersectionRatio).to.be(0.25);
+ expect(records[2].target).to.be(targetEl3);
+ expect(records[2].intersectionRatio).to.be(0.25);
+ done();
+ });
+ },
+ function(done) {
+ targetEl1.style.top = '0px';
+ targetEl1.style.left = '5px';
+ targetEl2.style.top = '-25px';
+ targetEl2.style.left = '0px';
+ targetEl3.style.top = '0px';
+ targetEl3.style.left = '185px';
+ spy.waitForNotification(function() {
+ expect(spy.callCount).to.be(3);
+ var records = sortRecords(spy.lastCall.args[0]);
+ expect(records.length).to.be(3);
+ expect(records[0].target).to.be(targetEl1);
+ expect(records[0].intersectionRatio).to.be(1);
+ expect(records[1].target).to.be(targetEl2);
+ expect(records[1].intersectionRatio).to.be(0);
+ expect(records[2].target).to.be(targetEl3);
+ expect(records[2].intersectionRatio).to.be(0.75);
+ done();
+ });
+ },
+ function(done) {
+ targetEl1.style.top = '0px';
+ targetEl1.style.left = '15px';
+ targetEl2.style.top = '-35px';
+ targetEl2.style.left = '0px';
+ targetEl3.style.top = '0px';
+ targetEl3.style.left = '175px';
+ spy.waitForNotification(function() {
+ expect(spy.callCount).to.be(4);
+ var records = sortRecords(spy.lastCall.args[0]);
+ expect(records.length).to.be(1);
+ expect(records[0].target).to.be(targetEl3);
+ expect(records[0].intersectionRatio).to.be(1);
+ done();
+ });
+ }
+ ], done);
+ });
+
+
+ it('handles rootMargin properly', function(done) {
+
+ parentEl.style.overflow = 'visible';
+ targetEl1.style.top = '0px';
+ targetEl1.style.left = '-20px';
+ targetEl2.style.top = '-20px';
+ targetEl2.style.left = '0px';
+ targetEl3.style.top = '0px';
+ targetEl3.style.left = '200px';
+ targetEl4.style.top = '180px';
+ targetEl4.style.left = '180px';
+
+ runSequence([
+ function(done) {
+ io = new IntersectionObserver(function(records) {
+ records = sortRecords(records);
+ expect(records.length).to.be(4);
+ expect(records[0].target).to.be(targetEl1);
+ expect(records[0].intersectionRatio).to.be(1);
+ expect(records[1].target).to.be(targetEl2);
+ expect(records[1].intersectionRatio).to.be(.5);
+ expect(records[2].target).to.be(targetEl3);
+ expect(records[2].intersectionRatio).to.be(.5);
+ expect(records[3].target).to.be(targetEl4);
+ expect(records[3].intersectionRatio).to.be(1);
+ io.disconnect();
+ done();
+ }, {root: rootEl, rootMargin: '10px'});
+
+ io.observe(targetEl1);
+ io.observe(targetEl2);
+ io.observe(targetEl3);
+ io.observe(targetEl4);
+ },
+ function(done) {
+ io = new IntersectionObserver(function(records) {
+ records = sortRecords(records);
+ expect(records.length).to.be(4);
+ expect(records[0].target).to.be(targetEl1);
+ expect(records[0].intersectionRatio).to.be(0.5);
+ expect(records[2].target).to.be(targetEl3);
+ expect(records[2].intersectionRatio).to.be(0.5);
+ expect(records[3].target).to.be(targetEl4);
+ expect(records[3].intersectionRatio).to.be(0.5);
+ io.disconnect();
+ done();
+ }, {root: rootEl, rootMargin: '-10px 10%'});
+
+ io.observe(targetEl1);
+ io.observe(targetEl2);
+ io.observe(targetEl3);
+ io.observe(targetEl4);
+ },
+ function(done) {
+ io = new IntersectionObserver(function(records) {
+ records = sortRecords(records);
+ expect(records.length).to.be(4);
+ expect(records[0].target).to.be(targetEl1);
+ expect(records[0].intersectionRatio).to.be(0.5);
+ expect(records[3].target).to.be(targetEl4);
+ expect(records[3].intersectionRatio).to.be(0.5);
+ io.disconnect();
+ done();
+ }, {root: rootEl, rootMargin: '-5% -2.5% 0px'});
+
+ io.observe(targetEl1);
+ io.observe(targetEl2);
+ io.observe(targetEl3);
+ io.observe(targetEl4);
+ },
+ function(done) {
+ io = new IntersectionObserver(function(records) {
+ records = sortRecords(records);
+ expect(records.length).to.be(4);
+ expect(records[0].target).to.be(targetEl1);
+ expect(records[0].intersectionRatio).to.be(0.5);
+ expect(records[1].target).to.be(targetEl2);
+ expect(records[1].intersectionRatio).to.be(0.5);
+ expect(records[3].target).to.be(targetEl4);
+ expect(records[3].intersectionRatio).to.be(0.25);
+ io.disconnect();
+ done();
+ }, {root: rootEl, rootMargin: '5% -2.5% -10px -190px'});
+
+ io.observe(targetEl1);
+ io.observe(targetEl2);
+ io.observe(targetEl3);
+ io.observe(targetEl4);
+ }
+ ], done);
+ });
+
+
+ it('handles targets on the boundary of root', function(done) {
+
+ var spy = sinon.spy();
+ io = new IntersectionObserver(spy, {root: rootEl});
+
+ runSequence([
+ function(done) {
+ targetEl1.style.top = '0px';
+ targetEl1.style.left = '-21px';
+ targetEl2.style.top = '-20px';
+ targetEl2.style.left = '0px';
+ io.observe(targetEl1);
+ io.observe(targetEl2);
+ spy.waitForNotification(function() {
+ expect(spy.callCount).to.be(1);
+ var records = sortRecords(spy.lastCall.args[0]);
+ expect(records.length).to.be(2);
+ expect(records[1].intersectionRatio).to.be(0);
+ expect(records[1].target).to.be(targetEl2);
+ done();
+ });
+ },
+ function(done) {
+ targetEl1.style.top = '0px';
+ targetEl1.style.left = '-20px';
+ targetEl2.style.top = '-21px';
+ targetEl2.style.left = '0px';
+ spy.waitForNotification(function() {
+ expect(spy.callCount).to.be(2);
+ var records = sortRecords(spy.lastCall.args[0]);
+ expect(records.length).to.be(2);
+ expect(records[0].intersectionRatio).to.be(0);
+ expect(records[0].isIntersecting).to.be.ok();
+ expect(records[0].target).to.be(targetEl1);
+ expect(records[1].intersectionRatio).to.be(0);
+ expect(records[1].target).to.be(targetEl2);
+ done();
+ });
+ },
+ function(done) {
+ targetEl1.style.top = '-20px';
+ targetEl1.style.left = '200px';
+ targetEl2.style.top = '200px';
+ targetEl2.style.left = '200px';
+ spy.waitForNotification(function() {
+ expect(spy.callCount).to.be(3);
+ var records = sortRecords(spy.lastCall.args[0]);
+ expect(records.length).to.be(1);
+ expect(records[0].intersectionRatio).to.be(0);
+ expect(records[0].target).to.be(targetEl2);
+ done();
+ });
+ },
+ function(done) {
+ targetEl3.style.top = '20px';
+ targetEl3.style.left = '-20px';
+ targetEl4.style.top = '-20px';
+ targetEl4.style.left = '20px';
+ io.observe(targetEl3);
+ io.observe(targetEl4);
+ spy.waitForNotification(function() {
+ expect(spy.callCount).to.be(4);
+ var records = sortRecords(spy.lastCall.args[0]);
+ expect(records.length).to.be(2);
+ expect(records[0].intersectionRatio).to.be(0);
+ expect(records[0].isIntersecting).to.be.ok();
+ expect(records[0].target).to.be(targetEl3);
+ expect(records[1].intersectionRatio).to.be(0);
+ expect(records[1].target).to.be(targetEl4);
+ done();
+ });
+ }
+ ], done);
+
+ });
+
+
+ it('handles zero-size targets within the root coordinate space',
+ function(done) {
+
+ var spy = sinon.spy();
+ io = new IntersectionObserver(spy, {root: rootEl});
+
+ runSequence([
+ function(done) {
+ targetEl1.style.top = '0px';
+ targetEl1.style.left = '0px';
+ targetEl1.style.width = '0px';
+ targetEl1.style.height = '0px';
+ io.observe(targetEl1);
+ spy.waitForNotification(function() {
+ var records = sortRecords(spy.lastCall.args[0]);
+ expect(records.length).to.be(1);
+ expect(records[0].intersectionRatio).to.be(1);
+ expect(records[0].isIntersecting).to.be.ok();
+ done();
+ });
+ },
+ function(done) {
+ targetEl1.style.top = '-1px';
+ spy.waitForNotification(function() {
+ var records = sortRecords(spy.lastCall.args[0]);
+ expect(records.length).to.be(1);
+ expect(records[0].intersectionRatio).to.be(0);
+ expect(records[0].isIntersecting).to.be(false);
+ done();
+ });
+ }
+ ], done);
+ });
+
+
+ it('handles root/target elements not yet in the DOM', function(done) {
+
+ rootEl.remove();
+ targetEl1.remove();
+
+ var spy = sinon.spy();
+ io = new IntersectionObserver(spy, {root: rootEl});
+
+ runSequence([
+ function(done) {
+ io.observe(targetEl1);
+ callDelayed(done);
+ },
+ function(done) {
+ document.getElementById('fixtures').appendChild(rootEl);
+ callDelayed(function() {
+ expect(spy.callCount).to.be(1);
+ done();
+ });
+ },
+ function(done) {
+ parentEl.insertBefore(targetEl1, targetEl2);
+ spy.waitForNotification(function() {
+ expect(spy.callCount).to.be(2);
+ var records = sortRecords(spy.lastCall.args[0]);
+ expect(records.length).to.be(1);
+ expect(records[0].intersectionRatio).to.be(1);
+ expect(records[0].target).to.be(targetEl1);
+ done();
+ });
+ },
+ function(done) {
+ grandParentEl.remove();
+ spy.waitForNotification(function() {
+ expect(spy.callCount).to.be(3);
+ var records = sortRecords(spy.lastCall.args[0]);
+ expect(records.length).to.be(1);
+ expect(records[0].intersectionRatio).to.be(0);
+ expect(records[0].target).to.be(targetEl1);
+ done();
+ });
+ },
+ function(done) {
+ rootEl.appendChild(targetEl1);
+ spy.waitForNotification(function() {
+ expect(spy.callCount).to.be(4);
+ var records = sortRecords(spy.lastCall.args[0]);
+ expect(records.length).to.be(1);
+ expect(records[0].intersectionRatio).to.be(1);
+ expect(records[0].target).to.be(targetEl1);
+ done();
+ });
+ },
+ function(done) {
+ rootEl.remove();
+ spy.waitForNotification(function() {
+ expect(spy.callCount).to.be(5);
+ var records = sortRecords(spy.lastCall.args[0]);
+ expect(records.length).to.be(1);
+ expect(records[0].intersectionRatio).to.be(0);
+ expect(records[0].target).to.be(targetEl1);
+ done();
+ });
+ }
+ ], done);
+ });
+
+
+ it('handles sub-root element scrolling', function(done) {
+ io = new IntersectionObserver(function(records) {
+ expect(records.length).to.be(1);
+ expect(records[0].intersectionRatio).to.be(1);
+ done();
+ }, {root: rootEl});
+
+ io.observe(targetEl3);
+ callDelayed(function() {
+ parentEl.scrollLeft = 40;
+ });
+ });
+
+
+ it('supports CSS transitions and transforms', function(done) {
+
+ targetEl1.style.top = '220px';
+ targetEl1.style.left = '220px';
+
+ var callCount = 0;
+
+ io = new IntersectionObserver(function(records) {
+ callCount++;
+ if (callCount <= 1) {
+ return;
+ }
+ expect(records.length).to.be(1);
+ expect(records[0].intersectionRatio).to.be(1);
+ done();
+ }, {root: rootEl, threshold: [1]});
+
+ io.observe(targetEl1);
+ callDelayed(function() {
+ targetEl1.style.transform = 'translateX(-40px) translateY(-40px)';
+ });
+ });
+
+
+ it('uses the viewport when no root is specified', function(done) {
+ window.onmessage = function (e) {
+ expect(e.data).to.be.ok();
+ win.close();
+ done();
+ };
+
+ var win = window.open("intersectionobserver_window.html");
+ });
+
+ it('triggers only once if observed multiple times (and does not crash when collected)', function(done) {
+ var spy = sinon.spy();
+ io = new IntersectionObserver(spy, {root: rootEl});
+ io.observe(targetEl1);
+ io.observe(targetEl1);
+ io.observe(targetEl1);
+
+ spy.waitForNotification(function() {
+ callDelayed(function () {
+ expect(spy.callCount).to.be(1);
+ done();
+ });
+ });
+ });
+
+ });
+
+ describe('observe subframe', function () {
+
+ it('boundingClientRect matches target.getBoundingClientRect() for an element inside an iframe',
+ function(done) {
+
+ io = new IntersectionObserver(function(records) {
+ expect(records.length).to.be(1);
+ expect(records[0].boundingClientRect.top, targetEl5.getBoundingClientRect().top);
+ expect(records[0].boundingClientRect.left, targetEl5.getBoundingClientRect().left);
+ expect(records[0].boundingClientRect.width, targetEl5.getBoundingClientRect().width);
+ expect(records[0].boundingClientRect.height, targetEl5.getBoundingClientRect().height);
+ done();
+ }, {threshold: [1]});
+
+ targetEl4.onload = function () {
+ targetEl5 = targetEl4.contentDocument.getElementById('target5');
+ io.observe(targetEl5);
+ }
+
+ targetEl4.src = "intersectionobserver_iframe.html";
+ });
+
+ it('rootBounds is set to null for cross-origin observations', function(done) {
+
+ window.onmessage = function (e) {
+ expect(e.data).to.be(true);
+ done();
+ };
+
+ targetEl4.src = "http://example.org/tests/dom/base/test/intersectionobserver_cross_domain_iframe.html";
+
+ });
+
+ });
+
+ describe('takeRecords', function() {
+
+ it('supports getting records before the callback is invoked', function(done) {
+
+ var lastestRecords = [];
+ io = new IntersectionObserver(function(records) {
+ lastestRecords = lastestRecords.concat(records);
+ }, {root: rootEl});
+ io.observe(targetEl1);
+
+ window.requestAnimationFrame && requestAnimationFrame(function wait() {
+ lastestRecords = lastestRecords.concat(io.takeRecords());
+ if (!lastestRecords.length) {
+ requestAnimationFrame(wait);
+ return;
+ }
+ callDelayed(function() {
+ expect(lastestRecords.length).to.be(1);
+ expect(lastestRecords[0].intersectionRatio).to.be(1);
+ done();
+ });
+ });
+
+ });
+
+ });
+
+ describe('unobserve', function() {
+
+ it('removes targets from the internal store', function(done) {
+
+ var spy = sinon.spy();
+ io = new IntersectionObserver(spy, {root: rootEl});
+
+ runSequence([
+ function(done) {
+ targetEl1.style.top = targetEl2.style.top = '0px';
+ targetEl1.style.left = targetEl2.style.left = '0px';
+ io.observe(targetEl1);
+ io.observe(targetEl2);
+ spy.waitForNotification(function() {
+ expect(spy.callCount).to.be(1);
+ var records = sortRecords(spy.lastCall.args[0]);
+ expect(records.length).to.be(2);
+ expect(records[0].target).to.be(targetEl1);
+ expect(records[0].intersectionRatio).to.be(1);
+ expect(records[1].target).to.be(targetEl2);
+ expect(records[1].intersectionRatio).to.be(1);
+ done();
+ });
+ },
+ function(done) {
+ io.unobserve(targetEl1);
+ targetEl1.style.top = targetEl2.style.top = '0px';
+ targetEl1.style.left = targetEl2.style.left = '-40px';
+ spy.waitForNotification(function() {
+ expect(spy.callCount).to.be(2);
+ var records = sortRecords(spy.lastCall.args[0]);
+ expect(records.length).to.be(1);
+ expect(records[0].target).to.be(targetEl2);
+ expect(records[0].intersectionRatio).to.be(0);
+ done();
+ });
+ },
+ function(done) {
+ io.unobserve(targetEl2);
+ targetEl1.style.top = targetEl2.style.top = '0px';
+ targetEl1.style.left = targetEl2.style.left = '0px';
+ callDelayed(function() {
+ expect(spy.callCount).to.be(2);
+ done();
+ });
+ }
+ ], done);
+
+ });
+
+ });
+
+ describe('disconnect', function() {
+
+ it('removes all targets and stops listening for changes', function(done) {
+
+ var spy = sinon.spy();
+ io = new IntersectionObserver(spy, {root: rootEl});
+
+ runSequence([
+ function(done) {
+ targetEl1.style.top = targetEl2.style.top = '0px';
+ targetEl1.style.left = targetEl2.style.left = '0px';
+ io.observe(targetEl1);
+ io.observe(targetEl2);
+ spy.waitForNotification(function() {
+ expect(spy.callCount).to.be(1);
+ var records = sortRecords(spy.lastCall.args[0]);
+ expect(records.length).to.be(2);
+ expect(records[0].target).to.be(targetEl1);
+ expect(records[0].intersectionRatio).to.be(1);
+ expect(records[1].target).to.be(targetEl2);
+ expect(records[1].intersectionRatio).to.be(1);
+ done();
+ });
+ },
+ function(done) {
+ io.disconnect();
+ targetEl1.style.top = targetEl2.style.top = '0px';
+ targetEl1.style.left = targetEl2.style.left = '-40px';
+ callDelayed(function() {
+ expect(spy.callCount).to.be(1);
+ done();
+ });
+ }
+ ], done);
+
+ });
+
+ });
+
+ });
+
+
+ /**
+ * Runs a sequence of function and when finished invokes the done callback.
+ * Each function in the sequence is invoked with its own done function and
+ * it should call that function once it's complete.
+ * @param {Array<Function>} functions An array of async functions.
+ * @param {Function} done A final callback to be invoked once all function
+ * have run.
+ */
+ function runSequence(functions, done) {
+ var next = functions.shift();
+ if (next) {
+ next(function() {
+ runSequence(functions, done);
+ });
+ } else {
+ done && done();
+ }
+ }
+
+
+ /**
+ * Sorts an array of records alphebetically by ascending ID. Since the current
+ * native implementation doesn't sort change entries by `observe` order, we do
+ * that ourselves for the non-polyfill case. Since all tests call observe
+ * on targets in sequential order, this should always match.
+ * https://crbug.com/613679
+ * @param {Array<IntersectionObserverEntry>} entries The entries to sort.
+ * @return {Array<IntersectionObserverEntry>} The sorted array.
+ */
+ function sortRecords(entries) {
+ entries = entries.sort(function(a, b) {
+ return a.target.id < b.target.id ? -1 : 1;
+ });
+ return entries;
+ }
+
+
+ /**
+ * Adds the common styles used by all tests to the page.
+ */
+ function addStyles() {
+ var styles = document.createElement('style');
+ styles.id = 'styles';
+ document.documentElement.appendChild(styles);
+
+ var cssText =
+ '#root {' +
+ ' position: relative;' +
+ ' width: 400px;' +
+ ' height: 200px;' +
+ ' background: #eee' +
+ '}' +
+ '#grand-parent {' +
+ ' position: relative;' +
+ ' width: 200px;' +
+ ' height: 200px;' +
+ '}' +
+ '#parent {' +
+ ' position: absolute;' +
+ ' top: 0px;' +
+ ' left: 200px;' +
+ ' overflow: hidden;' +
+ ' width: 200px;' +
+ ' height: 200px;' +
+ ' background: #ddd;' +
+ '}' +
+ '#target1, #target2, #target3, #target4 {' +
+ ' position: absolute;' +
+ ' top: 0px;' +
+ ' left: 0px;' +
+ ' width: 20px;' +
+ ' height: 20px;' +
+ ' transform: translateX(0px) translateY(0px);' +
+ ' transition: transform .5s;' +
+ ' background: #f00;' +
+ ' border: none;' +
+ '}';
+
+ styles.innerHTML = cssText;
+ }
+
+
+ /**
+ * Adds the DOM fixtures used by all tests to the page and assigns them to
+ * global variables so they can be referenced within the tests.
+ */
+ function addFixtures() {
+ var fixtures = document.createElement('div');
+ fixtures.id = 'fixtures';
+
+ fixtures.innerHTML =
+ '<div id="root">' +
+ ' <div id="grand-parent">' +
+ ' <div id="parent">' +
+ ' <div id="target1"></div>' +
+ ' <div id="target2"></div>' +
+ ' <div id="target3"></div>' +
+ ' <iframe id="target4"></iframe>' +
+ ' </div>' +
+ ' </div>' +
+ '</div>';
+
+ document.body.appendChild(fixtures);
+
+ rootEl = document.getElementById('root');
+ grandParentEl = document.getElementById('grand-parent');
+ parentEl = document.getElementById('parent');
+ targetEl1 = document.getElementById('target1');
+ targetEl2 = document.getElementById('target2');
+ targetEl3 = document.getElementById('target3');
+ targetEl4 = document.getElementById('target4');
+ }
+
+
+ /**
+ * Removes the common styles from the page.
+ */
+ function removeStyles() {
+ var styles = document.getElementById('styles');
+ styles.remove();
+ }
+
+
+ /**
+ * Removes the DOM fixtures from the page and resets the global references.
+ */
+ function removeFixtures() {
+ var fixtures = document.getElementById('fixtures');
+ fixtures.remove();
+
+ rootEl = null;
+ grandParentEl = null;
+ parentEl = null;
+ targetEl1 = null;
+ targetEl2 = null;
+ targetEl3 = null;
+ targetEl4 = null;
+ }
+
+ function onLoad() {
+ SpecialPowers.pushPrefEnv({"set": [["dom.IntersectionObserver.enabled", true]]}, next);
+ }
+
+ SimpleTest.waitForExplicitFinish();
+</script>
+</pre>
+<div id="log">
+</div>
+</body>
+</html>