Skip to content

Commit ad06ae2

Browse files
committed
Fix render frame lag issue
1 parent b05d0c2 commit ad06ae2

File tree

8 files changed

+216
-60
lines changed

8 files changed

+216
-60
lines changed

benchmarks/benchmark.mjs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -308,7 +308,7 @@ async function runBenchmarks() {
308308
await cdpSession.send("Performance.enable");
309309

310310
// Build URL with configuration
311-
const url = `http://localhost:8081/benchmarks/benchmark.html?rows=${CONFIG.rows}&columns=${CONFIG.columns}&headerDepth=${CONFIG.headerDepth}`;
311+
const url = `http://localhost:8080/benchmarks/benchmark.html?rows=${CONFIG.rows}&columns=${CONFIG.columns}&headerDepth=${CONFIG.headerDepth}`;
312312

313313
console.log(`\nLoading benchmark page: ${url}`);
314314
await page.goto(url);

playwright.config.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ import { defineConfig, devices } from "@playwright/test";
1313

1414
export default defineConfig({
1515
testDir: "./tests",
16-
timeout: 3000,
16+
timeout: 30_000,
1717
fullyParallel: true,
1818
forbidOnly: !!process.env.CI,
1919
retries: process.env.CI ? 2 : 0,

src/ts/utils.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -51,17 +51,17 @@ export async function flush_tag(
5151
tag: any,
5252
): Promise<PromiseWithResolvers<undefined> | undefined> {
5353
await new Promise(requestAnimationFrame);
54-
return await TAGS.get(tag);
54+
return await TAGS.get(tag)?.promise;
5555
}
5656

5757
export async function throttle_tag<T>(
5858
tag: any,
5959
f: () => Promise<T>,
6060
): Promise<T | undefined> {
6161
if (TAGS.has(tag)) {
62-
await TAGS.get(tag);
62+
await TAGS.get(tag)?.promise;
6363
if (TAGS.has(tag)) {
64-
await TAGS.get(tag);
64+
await TAGS.get(tag)?.promise;
6565
return;
6666
}
6767
}

tests/2_row_2_column_headers.html

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,14 @@
3434
const range = (x0, x1, f) =>
3535
Array.from(Array(x1 - x0).keys()).map((x) => f(x + x0));
3636

37-
function dataListener(x0, y0, x1, y1) {
37+
async function dataListener(x0, y0, x1, y1) {
38+
// Simulate lag
39+
if (Math.random() > 0.3) {
40+
await new Promise((x) =>
41+
setTimeout(x, Math.round(Math.random() * 10)),
42+
);
43+
}
44+
3845
return {
3946
num_rows: NUM_ROWS,
4047
num_columns: NUM_COLUMNS,

tests/api.html

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,14 @@
3434
const range = (x0, x1, f) =>
3535
Array.from(Array(x1 - x0).keys()).map((x) => f(x + x0));
3636

37-
function dataListener(x0, y0, x1, y1) {
37+
async function dataListener(x0, y0, x1, y1) {
38+
// Simulate lag
39+
if (Math.random() > 0.3) {
40+
await new Promise((x) =>
41+
setTimeout(x, Math.round(Math.random() * 10)),
42+
);
43+
}
44+
3845
return {
3946
num_rows: NUM_ROWS,
4047
num_columns: NUM_COLUMNS,

tests/large_data.html

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
<!--
2+
-- ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░
3+
-- ░░░░░░░░░░▄▀░█▀▄░█▀▀░█▀▀░█░█░█░░░█▀█░█▀▄░░░░░▀█▀░█▀█░█▀▄░█░░░█▀▀░▀▄░░░░░░░░░░
4+
-- ░░░░░░░░░▀▄░░█▀▄░█▀▀░█░█░█░█░█░░░█▀█░█▀▄░▀▀▀░░█░░█▀█░█▀▄░█░░░█▀▀░░▄▀░░░░░░░░░
5+
-- ░░░░░░░░░░░▀░▀░▀░▀▀▀░▀▀▀░▀▀▀░▀▀▀░▀░▀░▀░▀░░░░░░▀░░▀░▀░▀▀░░▀▀▀░▀▀▀░▀░░░░░░░░░░░
6+
-- ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░
7+
-- ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
8+
-- ┃ * Copyright (c) 2020, the Regular Table Authors. This file is part * ┃
9+
-- ┃ * of the Regular Table library, distributed under the terms of the * ┃
10+
-- ┃ * [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0). * ┃
11+
-- ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
12+
-->
13+
14+
<!doctype html>
15+
<html>
16+
<head>
17+
<meta
18+
name="viewport"
19+
content="width=device-width, initial-scale=1, maximum-scale=1, minimum-scale=1, user-scalable=no"
20+
/>
21+
<link rel="stylesheet" href="/dist/css/material.css" />
22+
</head>
23+
<body>
24+
<regular-table></regular-table>
25+
26+
<script type="module">
27+
import "/dist/esm/regular-table.js";
28+
29+
// Large data model for scroll traffic testing
30+
const NUM_ROWS = 100000;
31+
const NUM_COLUMNS = 100000;
32+
33+
const clamp = (x, y) => Math.floor(x / y) * y;
34+
const range = (x0, x1, f) =>
35+
Array.from(Array(x1 - x0).keys()).map((x) => f(x + x0));
36+
37+
async function dataListener(x0, y0, x1, y1) {
38+
// Simulate lag
39+
if (Math.random() > 0.3) {
40+
await new Promise((x) =>
41+
setTimeout(x, Math.round(Math.random() * 10)),
42+
);
43+
}
44+
45+
return {
46+
num_rows: NUM_ROWS,
47+
num_columns: NUM_COLUMNS,
48+
row_headers: range(y0, y1, (i) => [
49+
`Group ${clamp(i, 10)}`,
50+
`Row ${i}`,
51+
]),
52+
column_headers: range(x0, x1, (i) => [
53+
`Group ${clamp(i, 10)}`,
54+
`Column ${i}`,
55+
]),
56+
data: range(x0, x1, (x) =>
57+
range(y0, y1, (y) => String(x + y)),
58+
),
59+
};
60+
}
61+
62+
window.dataListener = dataListener;
63+
64+
const table = document.getElementsByTagName("regular-table")[0];
65+
table.setDataListener(dataListener);
66+
table.draw();
67+
</script>
68+
</body>
69+
</html>

tests/scrollFlush.spec.js

Lines changed: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,126 @@
1+
// ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░
2+
// ░░░░░░░░░░▄▀░█▀▄░█▀▀░█▀▀░█░█░█░░░█▀█░█▀▄░░░░░▀█▀░█▀█░█▀▄░█░░░█▀▀░▀▄░░░░░░░░░░
3+
// ░░░░░░░░░▀▄░░█▀▄░█▀▀░█░█░█░█░█░░░█▀█░█▀▄░▀▀▀░░█░░█▀█░█▀▄░█░░░█▀▀░░▄▀░░░░░░░░░
4+
// ░░░░░░░░░░░▀░▀░▀░▀▀▀░▀▀▀░▀▀▀░▀▀▀░▀░▀░▀░▀░░░░░░▀░░▀░▀░▀▀░░▀▀▀░▀▀▀░▀░░░░░░░░░░░
5+
// ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░
6+
// ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
7+
// ┃ * Copyright (c) 2020, the Regular Table Authors. This file is part * ┃
8+
// ┃ * of the Regular Table library, distributed under the terms of the * ┃
9+
// ┃ * [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0). * ┃
10+
// ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
11+
12+
import { test, expect } from "@playwright/test";
13+
14+
test.describe("Scroll traffic and flush to origin", () => {
15+
test.beforeEach(async ({ page }) => {
16+
await page.setViewportSize({ width: 800, height: 600 });
17+
await page.goto("/tests/large_data.html");
18+
await page.waitForSelector("regular-table table tbody tr td");
19+
});
20+
21+
test("renders correct DOM at origin after heavy scroll traffic and flush", async ({
22+
page,
23+
}) => {
24+
const table = page.locator("regular-table");
25+
26+
// Generate heavy scroll traffic with many rapid scroll operations
27+
await table.evaluate(async (el) => {
28+
let scrollPositions = [];
29+
for (let i = 0; i < 100; i++) {
30+
scrollPositions.push({
31+
top: Math.round(Math.random() * 50) * 1000,
32+
left: Math.round(Math.random() * 50) * 1000,
33+
});
34+
}
35+
36+
for (const pos of scrollPositions) {
37+
el.scrollTop = pos.top;
38+
el.scrollLeft = pos.left;
39+
await el.flush();
40+
}
41+
});
42+
43+
// Scroll back to origin and use flush() to wait for rendering
44+
await table.evaluate(async (el) => {
45+
el.scrollTop = 0;
46+
el.scrollLeft = 0;
47+
await el.flush();
48+
});
49+
50+
// Validate DOM structure
51+
const hasTableTag = await page.locator("regular-table table").count();
52+
const hasThead = await page.locator("regular-table thead").count();
53+
const hasTbody = await page.locator("regular-table tbody").count();
54+
55+
expect(hasTableTag).toBe(1);
56+
expect(hasThead).toBe(1);
57+
expect(hasTbody).toBe(1);
58+
59+
// Validate first cell content is at origin (0,0 should produce "0")
60+
const firstCell = await page
61+
.locator("regular-table tbody tr:first-child td:first-of-type")
62+
.textContent();
63+
expect(firstCell).toBe("0");
64+
65+
// Validate row header shows Row 0
66+
const firstRowHeader = await page
67+
.locator("regular-table tbody tr:first-child th:nth-child(2)")
68+
.textContent();
69+
expect(firstRowHeader).toBe("Row 0");
70+
71+
// Validate column header shows Column 0
72+
const firstColHeader = await page
73+
.locator("regular-table thead tr:nth-child(2) th:nth-child(3)")
74+
.textContent();
75+
expect(firstColHeader).toBe("Column 0");
76+
});
77+
78+
test("validates all visible cells at origin after scroll traffic", async ({
79+
page,
80+
}) => {
81+
const table = page.locator("regular-table");
82+
83+
await table.evaluate(async (el) => {
84+
for (let i = 0; i < 100; i++) {
85+
el.scrollTop = (i * 997) % 20000;
86+
el.scrollLeft = (i * 1009) % 20000;
87+
await el.flush();
88+
}
89+
});
90+
91+
// Flush to origin
92+
await table.evaluate(async (el) => {
93+
el.scrollTop = 0;
94+
el.scrollLeft = 0;
95+
await el.flush();
96+
});
97+
98+
// Validate first row cell values (should be column indices: 0, 1, 2, ...)
99+
const firstRowCells = await page
100+
.locator("regular-table tbody tr:first-child")
101+
.evaluate((el) =>
102+
Array.from(el.querySelectorAll("td")).map(
103+
(td) => td.textContent,
104+
),
105+
);
106+
107+
firstRowCells.forEach((cell, idx) => {
108+
expect(cell).toBe(String(idx));
109+
});
110+
111+
const allRowsFirstCells = await page
112+
.locator("regular-table tbody tr")
113+
.evaluateAll((rows) =>
114+
rows.map((row) => {
115+
const td = row.querySelector("td");
116+
return td ? td.textContent : null;
117+
}),
118+
);
119+
120+
allRowsFirstCells.forEach((cell, rowIdx) => {
121+
if (cell !== null) {
122+
expect(cell).toBe(String(rowIdx));
123+
}
124+
});
125+
});
126+
});

tests/setDataListener.spec.js

Lines changed: 0 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -52,59 +52,6 @@ test.describe("setDataListener()", () => {
5252
});
5353
});
5454

55-
test.describe("with preserve_state option", () => {
56-
test("preserves column sizes when set to true", async ({ page }) => {
57-
const table = page.locator("regular-table");
58-
const meta = await table.evaluate(async (el) => {
59-
await el.draw();
60-
el.saveColumnSizes()[0] = 200;
61-
await el.draw();
62-
el.setDataListener(window.dataListener, {
63-
preserve_state: true,
64-
});
65-
66-
await el.draw();
67-
return JSON.stringify(el.getMeta(document.querySelector("td")));
68-
});
69-
70-
expect(JSON.parse(meta)).toEqual({
71-
column_header: ["Group 0", "Column 0"],
72-
row_header: ["Group 0", "Row 0"],
73-
dx: 0,
74-
dy: 0,
75-
size_key: 2,
76-
virtual_x: 2,
77-
value: "0",
78-
x: 0,
79-
x0: 0,
80-
x1: 22,
81-
y: 0,
82-
y0: 0,
83-
y1: 36,
84-
});
85-
});
86-
87-
test("resets column sizes when preserve_state is false", async ({
88-
page,
89-
}) => {
90-
const table = page.locator("regular-table");
91-
const meta = await table.evaluate(async (el) => {
92-
await el.draw();
93-
el.saveColumnSizes()[0] = 200;
94-
await el.draw();
95-
el.setDataListener(window.dataListener, {
96-
preserve_state: false,
97-
});
98-
99-
await el.draw();
100-
return JSON.stringify(el.getMeta(document.querySelector("td")));
101-
});
102-
103-
const parsed = JSON.parse(meta);
104-
expect(parsed.x1).toBeLessThan(23);
105-
});
106-
});
107-
10855
test.describe("data listener callback", () => {
10956
test("receives correct viewport coordinates", async ({ page }) => {
11057
const table = page.locator("regular-table");

0 commit comments

Comments
 (0)