Skip to content

Commit

Permalink
feat: canvas
Browse files Browse the repository at this point in the history
  • Loading branch information
adamdbradley committed Oct 21, 2021
1 parent aa23619 commit a534ff9
Show file tree
Hide file tree
Showing 4 changed files with 149 additions and 65 deletions.
52 changes: 24 additions & 28 deletions src/lib/sandbox/read-main-interfaces.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,5 @@
import { debug, getConstructorName, isValidMemberName, logMain, noop } from '../utils';
import {
InitWebWorkerData,
InterfaceInfo,
InterfaceType,
MainWindow,
MembersInterfaceTypeInfo,
} from '../types';
import { InitWebWorkerData, InterfaceType, MainWindow } from '../types';

export const readMainInterfaces = (win: MainWindow) => {
// web worker has requested data from the main thread
Expand All @@ -15,62 +9,64 @@ export const readMainInterfaces = (win: MainWindow) => {
const $url$ = win.location + '';

const docImpl = doc.implementation.createHTMLDocument();
const docHead = docImpl.head;
const inputElm = docImpl.createElement('input');

const implementations: MainImplementation[] = [
[InterfaceType.Window, win],
[InterfaceType.CSSStyleDeclaration, docHead.style],
[InterfaceType.CSSStyleDeclaration, inputElm.style],
[InterfaceType.Document, docImpl],
[InterfaceType.DocumentFragmentNode, docImpl.createDocumentFragment()],
[InterfaceType.DOMStringMap, docHead.dataset],
[InterfaceType.DOMTokenList, docHead.classList],
[InterfaceType.Element, docHead],
[InterfaceType.DOMStringMap, inputElm.dataset],
[InterfaceType.DOMTokenList, inputElm.classList],
[InterfaceType.Element, inputElm],
[InterfaceType.Element, docImpl.createElement('canvas')],
[InterfaceType.History, win.history],
[InterfaceType.Location, win.location],
[InterfaceType.MutationObserver, new MutationObserver(noop)],
[InterfaceType.NamedNodeMap, docHead.attributes],
[InterfaceType.NodeList, docHead.childNodes],
[InterfaceType.NamedNodeMap, inputElm.attributes],
[InterfaceType.NodeList, inputElm.childNodes],
[InterfaceType.Screen, win.screen],
[InterfaceType.Storage, win.localStorage],
[InterfaceType.TextNode, docImpl.createTextNode('')],
].map((i) => [...i, getConstructorName(i[1] as any)]) as any;

const $interfaces$ = implementations.map(([interfaceType, impl, cstrName]) => {
let members: MembersInterfaceTypeInfo = {};
const initWebWorkerData: InitWebWorkerData = {
$config$,
$htmlConstructors$: Object.getOwnPropertyNames(win).filter((c) => /^HT.*t$/i.test(c)),
$interfaces$: [],
$libPath$: new URL($libPath$, $url$) + '',
};

implementations.map(([interfaceType, impl, cstrName]) => {
let memberName: string;
let value: any;
let type: string;
let objCstrName: string;
let objImpl: MainImplementation | undefined;
let interfaceInfo = initWebWorkerData.$interfaces$.find((i) => i[0] === interfaceType);

if (!interfaceInfo) {
initWebWorkerData.$interfaces$.push((interfaceInfo = [interfaceType, cstrName, {}]));
}

for (memberName in impl) {
if (isValidMemberName(memberName)) {
value = impl[memberName];
type = typeof value;
if (type === 'function') {
members[memberName] = InterfaceType.Function;
interfaceInfo[2][memberName] = InterfaceType.Function;
} else if (type === 'object') {
objCstrName = getConstructorName(value);
objImpl = implementations.find((i) => i[2] === objCstrName);
if (objImpl) {
// this object's constructor is one of the interfaces we care about
members[memberName] = objImpl[0];
interfaceInfo[2][memberName] = objImpl[0];
}
}
}
}

const interfaceInfo: InterfaceInfo = [interfaceType, cstrName, members];
return interfaceInfo;
});

const initWebWorkerData: InitWebWorkerData = {
$config$,
$htmlConstructors$: Object.getOwnPropertyNames(win).filter((c) => /^HT.*t$/i.test(c)),
$interfaces$,
$libPath$: new URL($libPath$, $url$) + '',
};

if (debug) {
logMain(
`Read main window, interfaces: ${initWebWorkerData.$interfaces$.length}, HTML Constructors: ${initWebWorkerData.$htmlConstructors$.length}`
Expand Down
63 changes: 26 additions & 37 deletions src/lib/web-worker/worker-proxy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -175,40 +175,6 @@ export const applyBeforeSyncSetters = (instance: WorkerProxy) => {
}
};

const createComplexMember = (
interfaceType: InterfaceType,
instance: WorkerProxy,
memberPath: string[]
) => {
if (
interfaceType === InterfaceType.CommentNode ||
interfaceType === InterfaceType.DocumentTypeNode
) {
// have these nodes interfaces just use the same as a text node
interfaceType = InterfaceType.TextNode;
}

const interfaceInfo = webWorkerCtx.$interfaces$.find((i) => i[0] === interfaceType);
if (interfaceInfo) {
const memberTypeInfo = interfaceInfo[2];
const memberInfo = memberTypeInfo[memberPath[len(memberPath) - 1]];
if (memberInfo === InterfaceType.Function) {
return (...args: any[]) => callMethod(instance, memberPath, args);
} else if (memberInfo > InterfaceType.Window) {
return proxy(memberInfo, instance, [...memberPath]);
}
}

const stateValue = getInstanceStateValue<Function>(instance, memberPath[0]);
if (typeof stateValue === 'function') {
return (...args: any[]) => {
const rtnValue = stateValue.apply(instance, args);
logWorkerCall(instance, memberPath, args, rtnValue);
return rtnValue;
};
}
};

export const proxy = <T = any>(
interfaceType: InterfaceType,
target: T,
Expand Down Expand Up @@ -243,10 +209,33 @@ export const proxy = <T = any>(
return globalRtnValue;
}

if (
interfaceType === InterfaceType.CommentNode ||
interfaceType === InterfaceType.DocumentTypeNode
) {
// have these nodes interfaces just use the same as a text node
interfaceType = InterfaceType.TextNode;
}

const memberPath = [...initMemberPath, String(propKey)];
const complexProp = createComplexMember(interfaceType, target, memberPath);
if (complexProp) {
return complexProp;
const interfaceInfo = webWorkerCtx.$interfaces$.find((i) => i[0] === interfaceType);
if (interfaceInfo) {
const memberTypeInfo = interfaceInfo[2];
const memberInfo = memberTypeInfo[memberPath[len(memberPath) - 1]];
if (memberInfo === InterfaceType.Function) {
return (...args: any[]) => callMethod(target, memberPath, args);
} else if (memberInfo > InterfaceType.Window) {
return proxy(memberInfo, target, [...memberPath]);
}
}

const stateValue = getInstanceStateValue<Function>(target, memberPath[0]);
if (typeof stateValue === 'function') {
return (...args: any[]) => {
const rtnValue = stateValue.apply(target, args);
logWorkerCall(target, memberPath, args, rtnValue);
return rtnValue;
};
}

return getter(target, memberPath);
Expand Down
10 changes: 10 additions & 0 deletions tests/platform/canvas/canvas.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import { test, expect } from '@playwright/test';

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

await page.waitForSelector('.completed');

const testGetContext2d = page.locator('#testGetContext2d');
await expect(testGetContext2d).toHaveText('#000000');
});
89 changes: 89 additions & 0 deletions tests/platform/canvas/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
<!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>Canvas</title>
<link
rel="icon"
id="favicon"
href="data:image/svg+xml,<svg xmlns=%22http://www.w3.org/2000/svg%22 viewBox=%220 0 100 100%22><text y=%22.9em%22 font-size=%2290%22>🎉</text></svg>"
/>
<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>
</head>
<body>
<h1>Canvas</h1>
<ul>
<li>
<strong>getContext('2d') fillStyle</strong>
<code id="testGetContext2d"></code>
<script type="text/partytown">
(function () {
const canvas = document.createElement('canvas');
const ctx = canvas.getContext('2d');
const elm = document.getElementById('testGetContext2d');
elm.textContent = String(ctx.fillStyle);
})();
</script>
</li>

<script type="text/partytown">
(function () {
document.body.classList.add('completed');
})();
</script>
</ul>

<hr />
<p><a href="/">All Tests</a></p>

<script>
partytown = {
logCalls: true,
logGetters: true,
logSetters: true,
logStackTraces: false,
logScriptExecution: true,
};
</script>
<script src="/~partytown/debug/partytown.js" async defer></script>
</body>
</html>

1 comment on commit a534ff9

@vercel
Copy link

@vercel vercel bot commented on a534ff9 Oct 21, 2021

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.