Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
41 commits
Select commit Hold shift + click to select a range
8a893e7
fix events_engine (T1307313)
GoodDayForSurf Oct 10, 2025
3d07c63
Merge branch '25_1' into 25_1_ng_leak_click
GoodDayForSurf Oct 10, 2025
9db9b67
Merge branch '25_1' into 25_1_ng_leak_click
GoodDayForSurf Oct 10, 2025
68f8c19
fix events_engine (T1307313)
GoodDayForSurf Oct 13, 2025
1729cf9
Revert "fix events_engine (T1307313)"
GoodDayForSurf Dec 23, 2025
da02412
Revert "fix events_engine (T1307313)"
GoodDayForSurf Dec 23, 2025
5d77d74
Merge branch '25_1' of https://github.com/DevExpress/DevExtreme into …
GoodDayForSurf Dec 23, 2025
c80a207
fix click subscription in events_engine (T1307313)
GoodDayForSurf Dec 23, 2025
e4636e8
Merge branch '25_1' into 25_1_ng_leak_click
GoodDayForSurf Dec 23, 2025
0e2619c
Merge branch '25_1_ng_leak_click' of https://github.com/GoodDayForSur…
GoodDayForSurf Dec 23, 2025
a355bc0
add test
GoodDayForSurf Dec 26, 2025
5ca768b
add test
GoodDayForSurf Dec 26, 2025
c750384
fix test
GoodDayForSurf Dec 26, 2025
f5f3d6f
add qunit test
GoodDayForSurf Dec 26, 2025
314abed
add qunit test
GoodDayForSurf Dec 26, 2025
37bcfc5
add qunit test
GoodDayForSurf Dec 26, 2025
eaf8de3
add qunit test
GoodDayForSurf Dec 26, 2025
cfc4a23
WIP. changes for check test
GoodDayForSurf Dec 27, 2025
1c19108
WIP. changes for check test
GoodDayForSurf Dec 27, 2025
1b8b3e2
WIP. changes for check test
GoodDayForSurf Dec 27, 2025
2b439a5
WIP. changes for check test
GoodDayForSurf Dec 27, 2025
0c94bde
WIP. changes for check test
GoodDayForSurf Dec 29, 2025
32c7b1c
WIP. changes for check test
GoodDayForSurf Dec 29, 2025
c4ce5b0
WIP. changes for check test
GoodDayForSurf Dec 29, 2025
f342809
WIP. changes for check test
GoodDayForSurf Dec 29, 2025
6ad190f
WIP. changes for check test
GoodDayForSurf Dec 29, 2025
9b891ec
WIP. changes for check test
GoodDayForSurf Dec 29, 2025
3d32046
WIP. changes for check test
GoodDayForSurf Dec 29, 2025
79463ed
WIP. changes for check test
GoodDayForSurf Dec 29, 2025
04c7358
WIP. changes for check test
GoodDayForSurf Dec 29, 2025
149aa8d
WIP. changes for check test
GoodDayForSurf Dec 29, 2025
2c3ff7c
WIP. changes for check test
GoodDayForSurf Dec 30, 2025
97af45a
revert changes for check test
GoodDayForSurf Dec 30, 2025
bbdfede
fix click subscription in events_engine (T1307313)
GoodDayForSurf Dec 30, 2025
62dc878
fix test for click subscription in events_engine (T1307313)
GoodDayForSurf Dec 30, 2025
d1b4e52
Merge branch '25_1' into 25_1_ng_leak_click
GoodDayForSurf Dec 30, 2025
d3baa52
fix test for click subscription in events_engine (T1307313)
GoodDayForSurf Dec 30, 2025
4155af4
Merge remote-tracking branch 'my/25_1_ng_leak_click' into 25_1_ng_lea…
GoodDayForSurf Dec 30, 2025
f5c3654
Merge branch '25_1' into 25_1_ng_leak_click
GoodDayForSurf Dec 30, 2025
d967b9c
fix test for click subscription in events_engine (T1307313)
GoodDayForSurf Dec 30, 2025
77436a4
revert not used changes
GoodDayForSurf Jan 7, 2026
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
26 changes: 24 additions & 2 deletions packages/devextreme-angular/karma.conf.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,20 @@ module.exports = function (config) {

autoWatch: true,

browsers: ['ChromeHeadless'],
browsers: ['ChromeHeadlessWithGC'],

customLaunchers: {
ChromeHeadlessWithGC: {
base: 'ChromeHeadless',
flags: [
'--enable-features=MeasureMemory',
'--js-flags=--expose-gc',
'--no-sandbox',
'--disable-gpu',
'--enable-precise-memory-info',
],
},
},

reporters: [
'progress',
Expand All @@ -34,13 +47,22 @@ module.exports = function (config) {
junitReporter: {
outputFile: 'test-results.xml',
},

beforeMiddleware: ['customHeaders'],
// Karma plugins loaded
plugins: [
require('karma-jasmine'),
require('karma-chrome-launcher'),
require('karma-junit-reporter'),
require('karma-webpack'),
{
'middleware:customHeaders': ['factory', function () {
return function (req, res, next) {
res.setHeader('Cross-Origin-Opener-Policy', 'same-origin');
res.setHeader('Cross-Origin-Embedder-Policy', 'require-corp');
next();
};
}],
},
],

webpack: webpackConfig,
Expand Down
51 changes: 51 additions & 0 deletions packages/devextreme-angular/tests/src/ui/data-grid.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -559,3 +559,54 @@ describe('Nested DxDataGrid', () => {
}, 1000);
});
});

describe('DxDataGrid slow tests', () => {
const originalTimeout = jasmine.DEFAULT_TIMEOUT_INTERVAL;

beforeAll(() => {
jasmine.DEFAULT_TIMEOUT_INTERVAL = 30000;

TestBed.configureTestingModule(
{
declarations: [TestContainerComponent],
imports: [DxDataGridModule],
},
);
});
afterAll(() => {
jasmine.DEFAULT_TIMEOUT_INTERVAL = originalTimeout;
});

it('should not memory leak after click if dx-data-grid is on page (T1307313)', async () => {
TestBed.overrideComponent(TestContainerComponent, {
set: {
template: '<dx-data-grid [dataSource]="[]"></dx-data-grid>',
},
});

const fixture = TestBed.createComponent(TestContainerComponent);

fixture.detectChanges();

for (let i = 0; i < 100; i++) {
document.body.click();
fixture.detectChanges();
}

globalThis.gc();

const memoryBefore = await (performance as any).measureUserAgentSpecificMemory();

for (let i = 0; i < 100; i++) {
document.body.click();
fixture.detectChanges();
}

globalThis.gc();

const memoryAfter = await (performance as any).measureUserAgentSpecificMemory();
const memoryDiff = Math.round((memoryAfter.bytes - memoryBefore.bytes) / 1024);

expect(memoryDiff).toBeLessThan(30);
});
});
15 changes: 13 additions & 2 deletions packages/devextreme/js/__internal/events/m_click.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ const misc = { requestAnimationFrame, cancelAnimationFrame };

let prevented: boolean | null = null;
let lastFiredEvent = null;
const subscriptions = new Map();

const onNodeRemove = () => {
lastFiredEvent = null;
Expand All @@ -32,9 +33,19 @@ const clickHandler = function (e) {
originalEvent.DXCLICK_FIRED = true;
}

unsubscribeNodesDisposing(lastFiredEvent, onNodeRemove);
if (subscriptions.has(lastFiredEvent)) {
const { nodes, callback } = subscriptions.get(lastFiredEvent);

unsubscribeNodesDisposing(lastFiredEvent, callback, nodes);

subscriptions.delete(lastFiredEvent);
}

lastFiredEvent = originalEvent;
subscribeNodesDisposing(lastFiredEvent, onNodeRemove);

const subscriptionData = subscribeNodesDisposing(lastFiredEvent, onNodeRemove);

subscriptions.set(lastFiredEvent, subscriptionData);

fireEvent({
type: CLICK_EVENT_NAME,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,28 @@ function nodesByEvent(event) {
event.delegateTarget,
event.relatedTarget,
event.currentTarget,
].filter((node) => !!node);
].reduce((res, node) => {
if (!!node && !res.includes(node)) {
res.push(node);
}

return res;
}, []);
}

export const subscribeNodesDisposing = (event, callback) => {
eventsEngine.one(nodesByEvent(event), removeEvent, callback);
const nodes = nodesByEvent(event);
const onceCallback = function (...args) {
eventsEngine.off(nodes, removeEvent, onceCallback);

return callback(...args);
};

eventsEngine.on(nodes, removeEvent, onceCallback);

return { onceCallback, nodes };
};

export const unsubscribeNodesDisposing = (event, callback) => {
eventsEngine.off(nodesByEvent(event), removeEvent, callback);
export const unsubscribeNodesDisposing = (event, callback, nodes) => {
eventsEngine.off(nodes || nodesByEvent(event), removeEvent, callback);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import eventsEngine from 'common/core/events/core/events_engine';
import { removeEvent } from 'common/core/events/remove';
import {
subscribeNodesDisposing,
unsubscribeNodesDisposing,
} from '__internal/events/utils/m_event_nodes_disposing';

QUnit.testStart(function() {
const markup = '<button id="test-element">Test</button>';
const fixture = document.getElementById('qunit-fixture');
if(fixture) {
fixture.innerHTML = markup;
}
});

QUnit.module('event nodes disposing');

QUnit.test('should clean elementDataMap when using subscribeNodesDisposing and unsubscribeNodesDisposing for click', function(assert) {
const testElement = document.getElementById('test-element');

const clickEvent = eventsEngine.Event('click', {
target: testElement,
currentTarget: document,
delegateTarget: document
});

const subscriptionData = subscribeNodesDisposing(clickEvent, function() {});

const afterSubscribeElementData = eventsEngine.elementDataMap.get(document);
const afterSubscribeHandleObjectsCount = afterSubscribeElementData && afterSubscribeElementData[removeEvent]
? afterSubscribeElementData[removeEvent].handleObjects.length
: 0;

unsubscribeNodesDisposing(clickEvent, subscriptionData.callback, subscriptionData.nodes);

const finalElementData = eventsEngine.elementDataMap.get(document);
const afterUnsubscribeHandleObjectsCount = finalElementData && finalElementData[removeEvent]
? finalElementData[removeEvent].handleObjects.length
: 0;

assert.ok(
afterSubscribeHandleObjectsCount <= 1,
`HandleObjects should be added for "${removeEvent}" event after subscribe. HandleObjects count: ${afterSubscribeHandleObjectsCount};`
);

assert.equal(
afterUnsubscribeHandleObjectsCount,
0,
`HandleObjects should be removed for "${removeEvent}" event after unsubscribe. HandleObjects count: ${afterUnsubscribeHandleObjectsCount};`
);
});
Loading