Skip to content

Commit

Permalink
fix: better formatting for sparse arrays (#20379)
Browse files Browse the repository at this point in the history
Right now arrays preview yields all array elements. In case
of a sparse array with a single element on index 10000000,
this results in a large string that OOM Node.js.

This patch changes pretty-printing. For example:

```ts
// Given this array
const a = [];
a[10] = 1;
// Before this patch, pretty printing will yield:
"[,,,,,,,,1]"
// With this patch, pretty printing yields:
"[empty x 9, 1]"
```

The new array pretty-printing is equal to what Chrome DevTools
do to render sparse arrays.

Fixes #20347
  • Loading branch information
aslushnikov authored Jan 27, 2023
1 parent 5b93c1d commit 9ca9b08
Show file tree
Hide file tree
Showing 5 changed files with 46 additions and 13 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -137,11 +137,7 @@ function renderPreview(object: Protocol.Runtime.RemoteObject): string | undefine
tokens.push(`${name}: ${value}`);
return `{${tokens.join(', ')}}`;
}
if (object.subtype === 'array' && object.preview) {
const result = [];
for (const { name, value } of object.preview.properties)
result[+name] = value;
return '[' + String(result) + ']';
}
if (object.subtype === 'array' && object.preview)
return js.sparseArrayToString(object.preview.properties);
return object.description;
}
24 changes: 24 additions & 0 deletions packages/playwright-core/src/server/javascript.ts
Original file line number Diff line number Diff line change
Expand Up @@ -328,3 +328,27 @@ export class JavaScriptErrorInEvaluate extends Error {
export function isJavaScriptErrorInEvaluate(error: Error) {
return error instanceof JavaScriptErrorInEvaluate;
}

export function sparseArrayToString(entries: { name: string, value?: any }[]): string {
const arrayEntries = [];
for (const { name, value } of entries) {
const index = +name;
if (isNaN(index) || index < 0)
continue;
arrayEntries.push({ index, value });
}
arrayEntries.sort((a, b) => a.index - b.index);
let lastIndex = -1;
const tokens = [];
for (const { index, value } of arrayEntries) {
const emptyItems = index - lastIndex - 1;
if (emptyItems === 1)
tokens.push(`empty`);
else if (emptyItems > 1)
tokens.push(`empty x ${emptyItems}`);
tokens.push(String(value));
lastIndex = index;
}

return '[' + tokens.join(', ') + ']';
}
Original file line number Diff line number Diff line change
Expand Up @@ -142,11 +142,7 @@ function renderPreview(object: Protocol.Runtime.RemoteObject): string | undefine
tokens.push(`${name}: ${value}`);
return `{${tokens.join(', ')}}`;
}
if (object.subtype === 'array' && object.preview) {
const result = [];
for (const { name, value } of object.preview.properties!)
result[+name] = value;
return '[' + String(result) + ']';
}
if (object.subtype === 'array' && object.preview)
return js.sparseArrayToString(object.preview.properties!);
return object.description;
}
17 changes: 17 additions & 0 deletions tests/page/jshandle-to-string.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,23 @@ it('should work for complicated objects', async ({ page, browserName }) => {
expect(aHandle.toString()).toBe('JSHandle@object');
});

it('should beautifully render sparse arrays', async ({ page, browserName }) => {
const [msg] = await Promise.all([
page.waitForEvent('console'),
page.evaluateHandle(() => {
const a = [];
a[1] = 1;
a[10] = 2;
a[100] = 3;
console.log(a);
}),
]);
if (browserName === 'firefox')
expect(msg.text()).toBe('Array');
else
expect(msg.text()).toBe('[empty, 1, empty x 8, 2, empty x 89, 3]');
});

it('should work for promises', async ({ page }) => {
// wrap the promise in an object, otherwise we will await.
const wrapperHandle = await page.evaluateHandle(() => ({ b: Promise.resolve(123) }));
Expand Down
2 changes: 1 addition & 1 deletion tests/page/page-event-console.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -183,7 +183,7 @@ it('should use object previews for arrays and objects', async ({ page, browserNa
await page.evaluate(() => console.log([1, 2, 3], { a: 1 }, window));

if (browserName !== 'firefox')
expect(text).toEqual('[1,2,3] {a: 1} Window');
expect(text).toEqual('[1, 2, 3] {a: 1} Window');
else
expect(text).toEqual('Array JSHandle@object JSHandle@object');
});
Expand Down

0 comments on commit 9ca9b08

Please sign in to comment.