Skip to content

fix: avoid browser crashes when serializing browser logs of complex objects #2944

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

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all 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
14 changes: 14 additions & 0 deletions .changeset/three-gifts-clap.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
---
'@web/browser-logs': patch
---

Avoid browser crashes when serializing browser logs of complex objects

If we are seeing too many object iterations, we know something is off. This can
happen when we are seeing e.g. large linked lists where every element recursively
has access to the full list again. This will not quickly yield circular early bail-outs,
but instead result in the stringification to take LOTS of processing time and results
in browser crashes. We should limit object iterations for this reason, and instead expect
people to inspect such logs in the browser directly.

Related #2798.
12 changes: 12 additions & 0 deletions packages/browser-logs/src/serialize.ts
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@ function serializeObject(value: any) {
function createReplacer() {
// maintain a stack of seen objects to handle circular references
var objectStack: any[] = [];
var objectIterations = 0;

return function replacer(this: any, key: string, value: unknown) {
if (this[KEY_WTR_TYPE]) {
Expand Down Expand Up @@ -113,6 +114,17 @@ function createReplacer() {
}
objectStack.unshift(value);

// If we are seeing too many object iterations, we know something is off. This can
// happen when we are seeing e.g. large linked lists where every element recursively
// has access to the full list again. This will not quickly yield circular early bail-outs,
// but instead result in the stringification to take LOTS of processing time and results
// in browser crashes. We should limit object iterations for this reason, and instead expect
// people to inspect such logs in the browser directly.
objectIterations++;
if (objectIterations > 10_000) {
return '[Serialization Limit]';
}

if (Array.isArray(value)) {
return value;
}
Expand Down
40 changes: 40 additions & 0 deletions packages/browser-logs/test/serialize-deserialize.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -336,6 +336,46 @@ describe('serialize deserialize', function () {
expect(deserialized.x).to.equal('[Circular]');
});

it('handles complex circular references', async () => {
const serialized = await page.evaluate(() => {
interface ChipNode {
idx: number;
next?: ChipNode;
prev?: ChipNode;
val: unknown;
}
class MatChip {
viewRef: { nodes: ChipNode[] } = { nodes: [] };

constructor() {
let prevNode: ChipNode | null = null;
for (let i = 0; i < 1000; i++) {
const newNode: ChipNode = {
idx: i,
val: this,
next: this.viewRef.nodes[0],
};

this.viewRef.nodes.push(newNode);

if (prevNode) {
prevNode.next = newNode;
newNode.prev = prevNode;
}

prevNode = newNode;
}
}
}
return (window as any)._serialize(new MatChip());
});

await deserialize(serialized);

// No assertion. Simply expect this to pass and not crash.
expect(true).to.eq(true);
});

it('handles errors', async () => {
const serialized = await page.evaluate(() => {
const c = () => new Error('my error msg');
Expand Down
Loading