Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Bugfix - send action on destroy + memory leaks #138

Merged
merged 17 commits into from
Apr 6, 2018
Merged
Show file tree
Hide file tree
Changes from 13 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
51 changes: 30 additions & 21 deletions addon/mixins/in-viewport.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,18 @@ export default Mixin.create({
/**
* IntersectionObserverEntry
*
* https://developer.mozilla.org/en-US/docs/Web/API/IntersectionObserverEntry
*
* @property intersectionObserver
* @default null
*/
intersectionObserver: null,
/**
* @property _debouncedEventHandler
* @default null
*/
_debouncedEventHandler: null,

viewportExited: not('viewportEntered').readOnly(),

init() {
Expand Down Expand Up @@ -136,17 +144,19 @@ export default Mixin.create({
* @param {Array} - entries
*/
_onIntersection(entries) {
if (this.isDestroyed || this.isDestroying) {
return;
}

const entry = entries[0];
const isTearingDown = this.isDestroyed || this.isDestroying;
const [entry] = entries;
let { isIntersecting, intersectionRatio } = entry;

if (entry.isIntersecting) {
set(this, 'viewportEntered', true);
if (isIntersecting) {
if (!isTearingDown) {
set(this, 'viewportEntered', true);
}
this.trigger('didEnterViewport');
} else if (entry.intersectionRatio <= 0) { // exiting viewport
set(this, 'viewportEntered', false);
} else if (intersectionRatio <= 0) { // exiting viewport
if (!isTearingDown) {
set(this, 'viewportEntered', false);
}
this.trigger('didExitViewport');
}
},
Expand Down Expand Up @@ -209,8 +219,8 @@ export default Mixin.create({
});
},

_debouncedEventHandler(methodName, ...args) {
assert('You must pass a methodName to _debouncedEventHandler', methodName);
_debouncedEvent(methodName, ...args) {
assert('You must pass a methodName to _debouncedEvent', methodName);
assert('methodName must be a string', typeOf(methodName) === 'string');

debounce(this, () => this[methodName](...args), get(this, 'viewportRefreshRate'));
Expand All @@ -222,9 +232,8 @@ export default Mixin.create({
const contextEl = get(this, 'scrollableArea') || window;
const elem = findElem(contextEl);

elem.addEventListener('scroll', () => {
this._debouncedEventHandler('_triggerDidScrollDirection', elem, sensitivity);
});
this._debouncedEventHandler = this._debouncedEvent.bind(this, '_triggerDidScrollDirection', elem, sensitivity);
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Another way I guess would be =>

this._debouncedEventClosure = (e => this._debouncedEventHandler('_triggerDidScrollDirection', elem, sensitivity));

elem.addEventListener('scroll', this.debouncedEventHandler, false);
},

_unbindScrollDirectionListener() {
Expand All @@ -233,9 +242,7 @@ export default Mixin.create({
const elem = findElem(context);

if (elem) {
elem.removeEventListener('scroll', () => {
this._debouncedEventHandler('_triggerDidScrollDirection', elem, get(this, 'viewportScrollSensitivity'));
});
elem.removeEventListener('scroll', this._debouncedEventHandler, false);
delete lastPosition[elementId];
delete lastDirection[elementId];
}
Expand All @@ -248,36 +255,38 @@ export default Mixin.create({
let elem = findElem(context);

elem.addEventListener(event, () => {
this._debouncedEventHandler('_setViewportEntered');
this._debouncedEvent('_setViewportEntered');
});
},

_unbindListeners() {
// 1.
if (this.intersectionObserver) {
this.intersectionObserver.unobserve(this.element);
return;
}

// 2.
if (get(this, 'viewportUseRAF')) {
const elementId = get(this, 'elementId');

next(this, () => {
window.cancelAnimationFrame(rAFIDS[elementId]);
delete rAFIDS[elementId];
});
return;
}

// 3.
get(this, 'viewportListeners').forEach((listener) => {
let { context, event } = listener;
context = get(this, 'scrollableArea') || context;
let elem = findElem(context);

elem.removeEventListener(event, () => {
this._debouncedEventHandler('_setViewportEntered');
this._debouncedEvent('_setViewportEntered');
});
});

// 4.
this._unbindScrollDirectionListener();
},
});
1 change: 0 additions & 1 deletion tests/acceptance/infinity-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,6 @@ module('Acceptance | infinity-scrollable', function(hooks) {
await waitFor('.infinity-scrollable.inactive');

assert.equal(findAll('.infinity-svg').length, 20);
assert.equal(findAll('.infinity-scrollable.inactive').length, 1, 'component is inactive after fetching more data');
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this is a race condition. For one, this fails randomly on diff builds. Second, it wouldn't proceed from the await waitFor unless it didn't find it as inactive.

Passes consistently locally; however, not up on CI.

});

test('rAF Component fetches more data when scrolled into viewport', async function(assert) {
Expand Down
2 changes: 1 addition & 1 deletion tests/acceptance/integration-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ module('Acceptance | Intersection Observer', function(hooks) {
await visit('/');

assert.ok(find('.my-component.bottom.inactive'), 'component is inactive');
document.querySelector('.my-component.bottom').scrollIntoView(false);
document.querySelector('.my-component.bottom').scrollIntoView();

await waitFor('.my-component.bottom.active');

Expand Down
10 changes: 8 additions & 2 deletions tests/dummy/app/controllers/infinity-scrollable.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import Controller from '@ember/controller';
import { set, get } from '@ember/object';
import { later } from '@ember/runloop';

let rect = '<rect x="10" y="10" width="30" height="30" stroke="black" fill="transparent" stroke-width="5"/>';
let circle = '<circle cx="25" cy="75" r="20" stroke="red" fill="transparent" stroke-width="5"/>';
Expand All @@ -18,8 +19,13 @@ export default Controller.extend({
actions: {
infinityLoad() {
const newModels = [...Array(10).fill().map(() => `${images[(Math.random() * images.length) | 0]}`)];
get(this, 'model').push(...newModels);
set(this, 'model', Array.from(get(this, 'model')));
return new Promise((resolve) => {
later(() => {
get(this, 'model').push(...newModels);
set(this, 'model', Array.from(get(this, 'model')));
resolve();
}, 0);
});
}
}
});
10 changes: 8 additions & 2 deletions tests/dummy/app/controllers/infinity.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import Controller from '@ember/controller';
import { set, get } from '@ember/object';
import { later } from '@ember/runloop';

const images = ["jarjan", "aio___", "kushsolitary", "kolage", "idiot", "gt"];

Expand All @@ -17,8 +18,13 @@ export default Controller.extend({
actions: {
infinityLoad() {
const newModels = [...Array(10).fill().map(() => `https://s3.amazonaws.com/uifaces/faces/twitter/${images[(Math.random() * images.length) | 0]}/128.jpg`)];
get(this, 'models').push(...newModels);
set(this, 'models', Array.from(get(this, 'models')));
return new Promise((resolve) => {
later(() => {
get(this, 'models').push(...newModels);
set(this, 'models', Array.from(get(this, 'models')));
resolve();
}, 0);
});
}
}
});
8 changes: 7 additions & 1 deletion tests/dummy/app/routes/infinity-scrollable.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import Route from '@ember/routing/route';
import { later } from '@ember/runloop';

let rect = '<rect x="10" y="10" width="30" height="30" stroke="black" fill="transparent" stroke-width="5"/>';
let circle = '<circle cx="25" cy="75" r="20" stroke="red" fill="transparent" stroke-width="5"/>';
Expand All @@ -8,6 +9,11 @@ const images = [rect, circle, line];

export default Route.extend({
model() {
return [...Array(10).fill().map(() => `${images[(Math.random() * images.length) | 0]}`)];
let models = [...Array(10).fill().map(() => `${images[(Math.random() * images.length) | 0]}`)];
return new Promise((resolve) => {
later(() => {
resolve(models);
}, 0);
});
}
});