Skip to content

Commit e3f13d1

Browse files
Merge pull request #79 from pyscript/create_proxy
[experiment] Automatically patch Pyodide callbacks on main
2 parents 0cecd3d + 68a7efa commit e3f13d1

File tree

10 files changed

+140
-13
lines changed

10 files changed

+140
-13
lines changed

docs/core.js

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

docs/core.js.map

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

esm/interpreter/pyodide.js

Lines changed: 79 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,91 @@
1+
import { create } from 'gc-hook';
2+
13
import { fetchFiles, fetchJSModules, fetchPaths, writeFile } from './_utils.js';
24
import { registerJSModule, run, runAsync, runEvent } from './_python.js';
35
import { stdio } from './_io.js';
46

7+
const RUNNING_ON_MAIN = !!globalThis.window;
8+
59
const type = 'pyodide';
610
const toJsOptions = { dict_converter: Object.fromEntries };
711

12+
/* c8 ignore start */
13+
let overrideFunction = false;
14+
const overrideMethod = method => (...args) => {
15+
try {
16+
overrideFunction = true;
17+
return method(...args);
18+
}
19+
finally {
20+
overrideFunction = false;
21+
}
22+
};
23+
24+
let overridden = false;
25+
const applyOverride = () => {
26+
if (overridden) return;
27+
overridden = true;
28+
29+
const proxies = new WeakMap;
30+
const onGC = value => value.destroy();
31+
const patchArgs = args => {
32+
for (let i = 0; i < args.length; i++) {
33+
const value = args[i];
34+
if (
35+
typeof value === 'function' &&
36+
'copy' in value
37+
) {
38+
// avoid seppuku / Harakiri + speed up
39+
overrideFunction = false;
40+
// reuse copied value if known already
41+
let proxy = proxies.get(value)?.deref();
42+
if (!proxy) {
43+
try {
44+
// observe the copy and return a Proxy reference
45+
proxy = create(value.copy(), onGC);
46+
proxies.set(value, new WeakRef(proxy));
47+
}
48+
catch (error) {
49+
console.error(error);
50+
}
51+
}
52+
if (proxy) args[i] = proxy;
53+
overrideFunction = true;
54+
}
55+
}
56+
};
57+
58+
// trap apply to make call possible after the patch
59+
const { call } = Function;
60+
const apply = call.bind(call, call.apply);
61+
// the patch
62+
Object.defineProperties(Function.prototype, {
63+
apply: {
64+
value(context, args) {
65+
if (overrideFunction) patchArgs(args);
66+
return apply(this, context, args);
67+
}
68+
},
69+
call: {
70+
value(context, ...args) {
71+
if (overrideFunction) patchArgs(args);
72+
return apply(this, context, args);
73+
}
74+
}
75+
});
76+
};
77+
/* c8 ignore stop */
78+
879
// REQUIRES INTEGRATION TEST
980
/* c8 ignore start */
1081
export default {
1182
type,
1283
module: (version = '0.25.0') =>
1384
`https://cdn.jsdelivr.net/pyodide/v${version}/full/pyodide.mjs`,
1485
async engine({ loadPyodide }, config, url) {
86+
// apply override ASAP then load foreign code
87+
if (RUNNING_ON_MAIN && config.experimental_create_proxy === 'auto')
88+
applyOverride();
1589
const { stderr, stdout, get } = stdio();
1690
const indexURL = url.slice(0, url.lastIndexOf('/'));
1791
const interpreter = await get(
@@ -29,11 +103,11 @@ export default {
29103
return interpreter;
30104
},
31105
registerJSModule,
32-
run,
33-
runAsync,
34-
runEvent,
35-
transform: (interpreter, value) => (
36-
value instanceof interpreter.ffi.PyProxy ?
106+
run: overrideMethod(run),
107+
runAsync: overrideMethod(runAsync),
108+
runEvent: overrideMethod(runEvent),
109+
transform: ({ ffi: { PyProxy } }, value) => (
110+
value instanceof PyProxy ?
37111
value.toJs(toJsOptions) :
38112
value
39113
),

package-lock.json

Lines changed: 9 additions & 3 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -78,12 +78,13 @@
7878
"basic-devtools": "^0.1.6",
7979
"codedent": "^0.1.2",
8080
"coincident": "^1.1.0",
81+
"gc-hook": "^0.3.0",
8182
"html-escaper": "^3.0.3",
8283
"proxy-target": "^3.0.1",
8384
"sticky-module": "^0.1.1",
8485
"to-json-callback": "^0.1.1"
8586
},
8687
"worker": {
87-
"blob": "sha256-PKBZfnQ7M77Aa/aLk8qwkNzs3H1FkHlcvvaXmBd0UQQ="
88+
"blob": "sha256-vXBVEcfN4nOSdjr+A0maM42l688AeU7Ob3jmoGcyBT0="
8889
}
8990
}

test/current-script.html

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
<!doctype html>
2+
<html lang="en">
3+
<head>
4+
<meta charset="UTF-8" />
5+
<meta name="viewport" content="width=device-width,initial-scale=1.0" />
6+
<title>python</title>
7+
<link rel="stylesheet" href="style.css" />
8+
<script type="module">
9+
import { define } from '/core.js';
10+
define('mpy', { interpreter: 'micropython' });
11+
</script>
12+
</head>
13+
<body>
14+
<script type="mpy">
15+
from polyscript import currentScript
16+
print("main", currentScript.outerHTML);
17+
</script>
18+
<hr />
19+
<script type="mpy" worker>
20+
from polyscript import currentScript
21+
print("worker", currentScript.outerHTML);
22+
</script>
23+
</html>

test/py-events-worker.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
from polyscript import xworker
2+
from js import Object
3+
4+
# needed in MicroPython
5+
options = Object()
6+
options.once = True
7+
8+
xworker.window.document.addEventListener(
9+
'click',
10+
lambda event: print("worker", event.type),
11+
options
12+
)

test/py-events.html

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
<script type="module" src="../core.js"></script>
1010
</head>
1111
<body>
12-
<script type="pyodide" src="./py-events.py"></script>
12+
<script type="pyodide" src="./py-events.py" config="./py-events.toml"></script>
1313
<button
1414
pyodide-pointerdown="print_version"
1515
pyodide-click="printer.version"
@@ -24,5 +24,8 @@
2424
>
2525
micropython version
2626
</button>
27+
28+
<script type="pyodide" src="./py-events-worker.py" worker></script>
29+
<script type="micropython" src="./py-events-worker.py" worker></script>
2730
</body>
2831
</html>

test/py-events.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,3 +11,10 @@ def version(self, event):
1111

1212

1313
printer = Printer()
14+
15+
from js import document
16+
17+
document.addEventListener(
18+
'click',
19+
lambda event: print("document", event.type)
20+
)

test/py-events.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
experimental_create_proxy = "auto"

0 commit comments

Comments
 (0)