Skip to content

Commit

Permalink
feat: serialize errors send to worker (#185)
Browse files Browse the repository at this point in the history
* feat: serialize errors send to worker

* test: error serializer

* test: handle webkit error messages

* test: handle chromium error messages
  • Loading branch information
slawekkolodziej authored May 25, 2022
1 parent ddc39d7 commit 24e5c84
Show file tree
Hide file tree
Showing 6 changed files with 216 additions and 1 deletion.
12 changes: 11 additions & 1 deletion src/lib/sandbox/main-serialization.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,13 @@ export const serializeForWorker = (
);
}
} else if (type === 'object') {
if ((cstrName = getConstructorName(value)) === '') {
if (serializedValueIsError(value)) {
return [SerializedType.Error, {
name: value.name,
message: value.message,
stack: value.stack
}];
} else if ((cstrName = getConstructorName(value)) === '') {
// error reading this object, probably "DOMException: Blocked from accessing a cross-origin frame."
return [SerializedType.Object, {}];
} else if (cstrName === 'Window') {
Expand Down Expand Up @@ -114,6 +120,10 @@ const serializeCssRuleForWorker = (cssRule: any) => {
return obj;
};

const serializedValueIsError = (value: any) => {
return value instanceof (window.top as any).Error;
}

export const deserializeFromWorker = (
worker: PartytownWebWorker,
serializedTransfer: SerializedTransfer | undefined,
Expand Down
7 changes: 7 additions & 0 deletions src/lib/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -283,6 +283,7 @@ export const enum SerializedType {
CSSRule,
CSSRuleList,
CSSStyleDeclaration,
Error,
}

export type SerializedArrayTransfer = [SerializedType.Array, (SerializedTransfer | undefined)[]];
Expand All @@ -302,6 +303,11 @@ export type SerializedCSSStyleDeclarationTransfer = [
{ [key: string]: SerializedTransfer | undefined }
];

export type SerializedErrorTransfer = [
SerializedType.Error,
Error
];

export type SerializedEventTransfer = [SerializedType.Event, SerializedObject];

export type SerializedFunctionTransfer = [SerializedType.Function];
Expand Down Expand Up @@ -350,6 +356,7 @@ export type SerializedTransfer =
| SerializedObjectTransfer
| SerializedPrimitiveTransfer
| SerializedRefTransfer
| SerializedErrorTransfer
| [];

export interface SerializedObject {
Expand Down
13 changes: 13 additions & 0 deletions src/lib/web-worker/worker-serialization.ts
Original file line number Diff line number Diff line change
Expand Up @@ -171,6 +171,10 @@ export const deserializeFromMain = (
);
}

if (serializedType === SerializedType.Error) {
return new CustomError(serializedValue);
}

obj = {};
for (key in serializedValue) {
obj[key] = deserializeFromMain(winId!, instanceId, [...applyPath, key], serializedValue[key]);
Expand Down Expand Up @@ -265,6 +269,15 @@ const deserializeRefFromMain = (
return webWorkerRefsByRefId[$refId$];
};

class CustomError extends Error {
constructor(errorObject: Error) {
super(errorObject.message);
this.name = errorObject.name;
this.message = errorObject.message;
this.stack = errorObject.stack;
}
}

const NodeList = class {
private _: WorkerNode[];

Expand Down
1 change: 1 addition & 0 deletions tests/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,7 @@ <h2>Platform Tests</h2>
<li><a href="/tests/platform/element-class/">Element Class</a></li>
<li><a href="/tests/platform/element-style/">Element Style</a></li>
<li><a href="/tests/platform/event/">Event</a></li>
<li><a href="/tests/platform/error/">Error</a></li>
<li><a href="/tests/platform/fetch/">Fetch/XHR</a></li>
<li><a href="/tests/platform/form/">Form</a></li>
<li><a href="/tests/platform/history/">History</a></li>
Expand Down
35 changes: 35 additions & 0 deletions tests/platform/error/error.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import { test, expect } from '@playwright/test';

test('errors', async ({ page }) => {
await page.goto('/tests/platform/error/');

// ReferenceError
await page.waitForSelector('.refErrorReady');
const attachRefErrorBtn = page.locator('#refErrorAttachHandler');
await attachRefErrorBtn.click();
const throwRefErrorBtn = page.locator('#refErrorThrowingButton');
await throwRefErrorBtn.click();

const refErrorName = await page.locator('#refErrorName');
await expect(refErrorName).toHaveText('ReferenceError')
const refErrorMessage = await page.locator('#refErrorMessage');
// Error messages differ between Chromium and Webkit
await expect(refErrorMessage).toHaveText(/(blahblah is not defined|Can't find variable: blahblah)/);
const refErrorStack = await page.locator('#refErrorStack');
// Error messages differ between Chromium and Webkit
await expect(refErrorStack).toContainText(
/(ReferenceError: blahblah is not defined\s+at HTMLButtonElement|@https?:\/\/.+\/tests\/platform\/error\/:\d+:\d+)/
);

// CustomError
await page.waitForSelector('.customErrorReady');
const attachCustomErrorBtn = page.locator('#customErrorAttachHandler');
await attachCustomErrorBtn.click();
const throwCustomErrorBtn = page.locator('#customErrorThrowingButton');
await throwCustomErrorBtn.click();

const customErrorName = await page.locator('#customErrorName');
await expect(customErrorName).toHaveText('FooError')
const customErrorMessage = await page.locator('#customErrorMessage');
await expect(customErrorMessage).toHaveText('custom error message')
});
149 changes: 149 additions & 0 deletions tests/platform/error/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,149 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="description" content="Partytown Test Page" />
<title>Error</title>
<style>
body {
font-family: -apple-system, BlinkMacSystemFont, Segoe UI, Helvetica, Arial, sans-serif,
Apple Color Emoji, Segoe UI Emoji;
font-size: 12px;
}
h1 {
margin: 0 0 15px 0;
}
ul {
list-style-type: none;
margin: 0;
padding: 0;
}
a {
display: block;
padding: 16px 8px;
}
a:link,
a:visited {
text-decoration: none;
color: blue;
}
a:hover {
background-color: #eee;
}
li {
display: flex;
margin: 15px 0;
}
li strong,
li code,
li button {
white-space: nowrap;
flex: 1;
margin: 0 5px;
}
</style>
<script>
partytown = {
logCalls: true,
logGetters: true,
logSetters: true,
logStackTraces: false,
logScriptExecution: true,
};
</script>
<script src="/~partytown/debug/partytown.js"></script>
</head>
<body>
<h1>Error</h1>
<ul>
<li>
<button id="refErrorAttachHandler">attach</button>
<button id="refErrorThrowingButton">throw ReferenceError</button>
<code id="refErrorName"></code>
<code id="refErrorMessage"></code>
<code id="refErrorStack"></code>
<script type="text/javascript">
(function () {
const refErrorThrowingButton = document.getElementById('refErrorThrowingButton');

refErrorThrowingButton.addEventListener('click', function () {
blahblah
});
}())
</script>
<script type="text/partytown">
(function () {
const attachErrorBtn = document.getElementById('refErrorAttachHandler');
const reporterNameEl = document.getElementById('refErrorName');
const reporterMessageEl = document.getElementById('refErrorMessage');
const reporterStackEl = document.getElementById('refErrorStack');

function errorHandler(message, source, lineno, colno, err) {
reporterNameEl.textContent = err.name;
reporterMessageEl.textContent = err.message;
reporterStackEl.textContent = err.stack;

window.onerror = null;
}

attachErrorBtn.addEventListener('click', () => {
window.onerror = errorHandler;
});

reporterNameEl.className = 'refErrorReady';
})();
</script>
</li>

<li>
<button id="customErrorAttachHandler">attach</button>
<button id="customErrorThrowingButton">throw CustomError</button>
<code id="customErrorName"></code>
<code id="customErrorMessage"></code>
<code id="customErrorStack"></code>
<script type="text/javascript">
(function () {
const customErrorThrowingButton = document.getElementById('customErrorThrowingButton');

class FooError extends Error {
constructor(msg) {
super(msg);
this.name = 'FooError';
}
}

customErrorThrowingButton.addEventListener('click', function () {
const err = new FooError('custom error message');
console.log(err);
throw err;
});
}())
</script>
<script type="text/partytown">
(function () {
const attachErrorBtn = document.getElementById('customErrorAttachHandler');
const reporterNameEl = document.getElementById('customErrorName');
const reporterMessageEl = document.getElementById('customErrorMessage');

function errorHandler(message, source, lineno, colno, err) {
reporterNameEl.textContent = err.name;
reporterMessageEl.textContent = err.message;

window.onerror = null;
}

attachErrorBtn.addEventListener('click', () => {
window.onerror = errorHandler;
console.log('attached handler');
});
reporterNameEl.className = 'customErrorReady';
})();
</script>
</li>
</ul>

<hr />
<p><a href="/tests/">All Tests</a></p>
</body>
</html>

1 comment on commit 24e5c84

@vercel
Copy link

@vercel vercel bot commented on 24e5c84 May 25, 2022

Choose a reason for hiding this comment

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

Please sign in to comment.