Fix #1531 - Remove overall need for globalThis pollution (#1543)

This commit is contained in:
Andrea Giammarchi
2023-06-19 18:05:45 +02:00
committed by GitHub
parent d6b1c393f6
commit 0a7e1ce0d7
20 changed files with 83 additions and 124 deletions

View File

@@ -27,6 +27,7 @@ repos:
rev: v0.0.257 rev: v0.0.257
hooks: hooks:
- id: ruff - id: ruff
exclude: pyscript\.core/test
args: [--fix] args: [--fix]
- repo: https://github.com/psf/black - repo: https://github.com/psf/black

View File

@@ -124,6 +124,8 @@ export const handleCustomType = (node) => {
afterRunAsync: codeAfterRunWorkerAsync, afterRunAsync: codeAfterRunWorkerAsync,
}); });
module.setGlobal(interpreter, "XWorker", XWorker);
const resolved = { const resolved = {
type, type,
interpreter, interpreter,

View File

@@ -7,22 +7,6 @@ export const run = (interpreter, code) => interpreter.runPython(clean(code));
export const runAsync = (interpreter, code) => export const runAsync = (interpreter, code) =>
interpreter.runPythonAsync(clean(code)); interpreter.runPythonAsync(clean(code));
export function runEvent(interpreter, code, key) {
code = `import js;event=js.__events.get(${key});${code}`;
return this.run(interpreter, code);
}
const worker = (method) =>
function (interpreter, code, xworker) {
code = `from js import xworker;${code}`;
globalThis.xworker = xworker;
return this[method](interpreter, code);
};
export const runWorker = worker("run");
export const runWorkerAsync = worker("runAsync");
export const writeFile = ({ FS }, path, buffer) => export const writeFile = ({ FS }, path, buffer) =>
writeFileUtil(FS, path, buffer); writeFileUtil(FS, path, buffer);
/* c8 ignore stop */ /* c8 ignore stop */

View File

@@ -1,12 +1,5 @@
import { fetchPaths, stdio } from "./_utils.js"; import { fetchPaths, stdio } from "./_utils.js";
import { import { run, runAsync, writeFile } from "./_python.js";
run,
runAsync,
runEvent,
runWorker,
runWorkerAsync,
writeFile,
} from "./_python.js";
const type = "micropython"; const type = "micropython";
@@ -23,11 +16,18 @@ export default {
if (config.fetch) await fetchPaths(this, runtime, config.fetch); if (config.fetch) await fetchPaths(this, runtime, config.fetch);
return runtime; return runtime;
}, },
setGlobal(interpreter, name, value) {
const id = `__pyscript_${this.type}_${name}`;
globalThis[id] = value;
this.run(interpreter, `from js import ${id};${name}=${id};`);
},
deleteGlobal(interpreter, name) {
const id = `__pyscript_${this.type}_${name}`;
this.run(interpreter, `del ${id};del ${name}`);
delete globalThis[id];
},
run, run,
runAsync, runAsync,
runEvent,
runWorker,
runWorkerAsync,
writeFile, writeFile,
}; };
/* c8 ignore stop */ /* c8 ignore stop */

View File

@@ -1,12 +1,5 @@
import { fetchPaths, stdio } from "./_utils.js"; import { fetchPaths, stdio } from "./_utils.js";
import { import { run, runAsync, writeFile } from "./_python.js";
run,
runAsync,
runEvent,
runWorker,
runWorkerAsync,
writeFile,
} from "./_python.js";
const type = "pyodide"; const type = "pyodide";
@@ -31,11 +24,14 @@ export default {
} }
return interpreter; return interpreter;
}, },
setGlobal(interpreter, name, value) {
interpreter.globals.set(name, value);
},
deleteGlobal(interpreter, name) {
interpreter.globals.delete(name);
},
run, run,
runAsync, runAsync,
runEvent,
runWorker,
runWorkerAsync,
writeFile, writeFile,
}; };
/* c8 ignore stop */ /* c8 ignore stop */

View File

@@ -9,15 +9,6 @@ const type = "ruby-wasm-wasi";
// REQUIRES INTEGRATION TEST // REQUIRES INTEGRATION TEST
/* c8 ignore start */ /* c8 ignore start */
const worker = (method) =>
function (interpreter, code, xworker) {
globalThis.xworker = xworker;
return this[method](
interpreter,
`require "js";xworker=JS::eval("return xworker");${code}`,
);
};
export default { export default {
type, type,
experimental: true, experimental: true,
@@ -32,16 +23,18 @@ export default {
if (config.fetch) await fetchPaths(this, interpreter, config.fetch); if (config.fetch) await fetchPaths(this, interpreter, config.fetch);
return interpreter; return interpreter;
}, },
setGlobal(interpreter, name, value) {
const id = `__pyscript_ruby_wasm_wasi_${name}`;
globalThis[id] = value;
this.run(interpreter, `require "js";$${name}=JS::eval("return ${id}")`);
},
deleteGlobal(interpreter, name) {
const id = `__pyscript_ruby_wasm_wasi_${name}`;
this.run(interpreter, `$${name}=nil`);
delete globalThis[id];
},
run: (interpreter, code) => interpreter.eval(clean(code)), run: (interpreter, code) => interpreter.eval(clean(code)),
runAsync: (interpreter, code) => interpreter.evalAsync(clean(code)), runAsync: (interpreter, code) => interpreter.evalAsync(clean(code)),
runEvent(interpreter, code, key) {
return this.run(
interpreter,
`require "js";event=JS::eval("return __events.get(${key})");${code}`,
);
},
runWorker: worker("run"),
runWorkerAsync: worker("runAsync"),
writeFile: () => { writeFile: () => {
throw new Error(`writeFile is not supported in ${type}`); throw new Error(`writeFile is not supported in ${type}`);
}, },

View File

@@ -4,12 +4,6 @@ const type = "wasmoon";
// REQUIRES INTEGRATION TEST // REQUIRES INTEGRATION TEST
/* c8 ignore start */ /* c8 ignore start */
const worker = (method) =>
function (interpreter, code, xworker) {
interpreter.global.set("xworker", xworker);
return this[method](interpreter, code);
};
export default { export default {
type, type,
module: (version = "1.15.0") => module: (version = "1.15.0") =>
@@ -24,14 +18,14 @@ export default {
if (config.fetch) await fetchPaths(this, interpreter, config.fetch); if (config.fetch) await fetchPaths(this, interpreter, config.fetch);
return interpreter; return interpreter;
}, },
setGlobal(interpreter, name, value) {
interpreter.global.set(name, value);
},
deleteGlobal(interpreter, name) {
interpreter.global.set(name, void 0);
},
run: (interpreter, code) => interpreter.doStringSync(clean(code)), run: (interpreter, code) => interpreter.doStringSync(clean(code)),
runAsync: (interpreter, code) => interpreter.doString(clean(code)), runAsync: (interpreter, code) => interpreter.doString(clean(code)),
runEvent(interpreter, code, key) {
interpreter.global.set("event", globalThis.__events.get(key));
return this.run(interpreter, code);
},
runWorker: worker("run"),
runWorkerAsync: worker("runAsync"),
writeFile: ( writeFile: (
{ {
cmodule: { cmodule: {

View File

@@ -13,9 +13,6 @@ defineProperty(globalThis, "pyscript", {
}, },
}); });
let index = 0;
globalThis.__events = new Map();
/* c8 ignore start */ // attributes are tested via integration / e2e /* c8 ignore start */ // attributes are tested via integration / e2e
// ensure both interpreter and its queue are awaited then returns the interpreter // ensure both interpreter and its queue are awaited then returns the interpreter
const awaitInterpreter = async (key) => { const awaitInterpreter = async (key) => {
@@ -43,12 +40,12 @@ export const listener = async (event) => {
const interpreter = await awaitInterpreter( const interpreter = await awaitInterpreter(
el.getAttribute(`${name}-env`) || name, el.getAttribute(`${name}-env`) || name,
); );
const i = index++; const handler = registry.get(name);
try { try {
globalThis.__events.set(i, event); handler.setGlobal(interpreter, "event", event);
registry.get(name).runEvent(interpreter, value, i); handler.run(interpreter, value);
} finally { } finally {
globalThis.__events.delete(i); handler.deleteGlobal(interpreter, "event");
} }
} }
}; };

View File

@@ -54,18 +54,15 @@ const execute = async (script, source, XWorker, isAsync) => {
try { try {
// temporarily override inherited document.currentScript in a non writable way // temporarily override inherited document.currentScript in a non writable way
// but it deletes it right after to preserve native behavior (as it's sync: no trouble) // but it deletes it right after to preserve native behavior (as it's sync: no trouble)
defineProperty(globalThis, "XWorker", {
configurable: true,
get: () => XWorker,
});
defineProperty(document, "currentScript", { defineProperty(document, "currentScript", {
configurable: true, configurable: true,
get: () => script, get: () => script,
}); });
module.setGlobal(interpreter, "XWorker", XWorker);
return module[isAsync ? "runAsync" : "run"](interpreter, content); return module[isAsync ? "runAsync" : "run"](interpreter, content);
} finally { } finally {
delete globalThis.XWorker;
delete document.currentScript; delete document.currentScript;
module.deleteGlobal(interpreter, "XWorker");
} }
}; };

View File

@@ -23,15 +23,15 @@ try {
); );
} }
let engine, run, interpreterEvent; let interpreter, run, interpreterEvent;
const add = (type, fn) => { const add = (type, fn) => {
addEventListener( addEventListener(
type, type,
fn || fn ||
(async (event) => { (async (event) => {
const interpreter = await engine; await interpreter;
interpreterEvent = event; interpreterEvent = event;
run(interpreter, `xworker.on${type}(xworker.event);`, xworker); run(`xworker.on${type}(xworker.event);`, xworker);
}), }),
!!fn && { once: true }, !!fn && { once: true },
); );
@@ -48,6 +48,8 @@ const xworker = {
// this getter exists so that arbitrarily access to xworker.event // this getter exists so that arbitrarily access to xworker.event
// would always fail once an event has been dispatched, as that's not // would always fail once an event has been dispatched, as that's not
// meant to be accessed in the wild, respecting the one-off event nature of JS. // meant to be accessed in the wild, respecting the one-off event nature of JS.
// because xworker is a unique well defined globally shared reference,
// there's also no need to bother setGlobal and deleteGlobal every single time.
get event() { get event() {
const event = interpreterEvent; const event = interpreterEvent;
if (!event) throw new Error("Unauthorized event access"); if (!event) throw new Error("Unauthorized event access");
@@ -57,11 +59,14 @@ const xworker = {
}; };
add("message", ({ data: { options, code, hooks } }) => { add("message", ({ data: { options, code, hooks } }) => {
engine = (async () => { interpreter = (async () => {
const { type, version, config, async: isAsync } = options; const { type, version, config, async: isAsync } = options;
const engine = await getRuntime(getRuntimeID(type, version), config); const interpreter = await getRuntime(
getRuntimeID(type, version),
config,
);
const details = create(registry.get(type)); const details = create(registry.get(type));
const name = `runWorker${isAsync ? "Async" : ""}`; const name = `run${isAsync ? "Async" : ""}`;
if (hooks) { if (hooks) {
// patch code if needed // patch code if needed
@@ -73,33 +78,25 @@ add("message", ({ data: { options, code, hooks } }) => {
// append code that should be executed *after* first // append code that should be executed *after* first
if (after) { if (after) {
const method = details[name]; const method = details[name].bind(details);
details[name] = function (interpreter, code, xworker) { details[name] = (interpreter, code) =>
return method.call( method(interpreter, `${code}\n${after}`);
this,
interpreter,
`${code}\n${after}`,
xworker,
);
};
} }
// prepend code that should be executed *before* (so that after is post-patched) // prepend code that should be executed *before* (so that after is post-patched)
if (before) { if (before) {
const method = details[name]; const method = details[name].bind(details);
details[name] = function (interpreter, code, xworker) { details[name] = (interpreter, code) =>
return method.call( method(interpreter, `${before}\n${code}`);
this,
interpreter,
`${before}\n${code}`,
xworker,
);
};
} }
} }
run = details[name].bind(details); // set the `xworker` global reference once
run(engine, code, xworker); details.setGlobal(interpreter, "xworker", xworker);
return engine; // simplify run calls after possible patches
run = details[name].bind(details, interpreter);
// execute the content of the worker file
run(code);
return interpreter;
})(); })();
add("error"); add("error");
add("message"); add("message");

View File

@@ -50,6 +50,6 @@
"coincident": "^0.4.1" "coincident": "^0.4.1"
}, },
"worker": { "worker": {
"blob": "sha256-XQCxBN0Tsy6N0TFG5kG5/CaigwJWLnA63q5ZnUchya4=" "blob": "sha256-obDch1OaxOZKhaCpSW9vEAHJk01N+9kYvSiGkNmeJRU="
} }
} }

View File

@@ -40,7 +40,7 @@ globalThis.XPathEvaluator =
} }
}; };
const { registerPlugin } = require("../cjs"); require("../cjs");
(async () => { (async () => {
// shared 3rd party mocks // shared 3rd party mocks

View File

@@ -10,8 +10,6 @@
</head> </head>
<body> <body>
<script type="micropython"> <script type="micropython">
from js import XWorker
def show_image(event): def show_image(event):
from js import document from js import document
img = document.createElement("img") img = document.createElement("img")

View File

@@ -1,7 +1,9 @@
export const python = { content: "", target: null }; export const python = { content: "", target: null };
export const loadMicroPython = () => ({ export const loadMicroPython = () => ({
runPython(content) { runPython(content) {
python.content = content; if (document.currentScript?.target) {
python.target = document.currentScript.target; python.content = content;
python.target = document.currentScript.target;
}
}, },
}); });

View File

@@ -23,6 +23,14 @@ export const loadPyodide = () => ({
} }
python.target = document.currentScript.target; python.target = document.currentScript.target;
}, },
globals: {
set(name, value) {
globalThis[name] = value;
},
delete(name) {
delete globalThis[name];
},
},
FS: { FS: {
mkdirTree() {}, mkdirTree() {},
writeFile() {}, writeFile() {},

View File

@@ -23,7 +23,7 @@
print "ruby #{ RUBY_VERSION }p#{ RUBY_PATCHLEVEL }" print "ruby #{ RUBY_VERSION }p#{ RUBY_PATCHLEVEL }"
end end
</script> </script>
<button ruby-wasm-wasi-click="print_version(event)"> <button ruby-wasm-wasi-click="print_version($event)">
ruby-wasm-wasi version ruby-wasm-wasi version
</button> </button>
</body> </body>

View File

@@ -30,8 +30,6 @@
<!-- XWorker - MicroPython to MicroPython --> <!-- XWorker - MicroPython to MicroPython -->
<script type="micropython"> <script type="micropython">
from js import XWorker
def handle_message(event): def handle_message(event):
print(event.data) print(event.data)
@@ -42,8 +40,6 @@
<!-- XWorker - MicroPython to Pyodide --> <!-- XWorker - MicroPython to Pyodide -->
<script type="micropython"> <script type="micropython">
from js import XWorker
def handle_message(event): def handle_message(event):
print(event.data) print(event.data)
@@ -54,8 +50,6 @@
<!-- XWorker - MicroPython to Lua --> <!-- XWorker - MicroPython to Lua -->
<script type="micropython"> <script type="micropython">
from js import XWorker
def handle_message(event): def handle_message(event):
print(event.data) print(event.data)

View File

@@ -20,7 +20,7 @@
</head> </head>
<body> <body>
<script type="micropython"> <script type="micropython">
from js import XWorker, Promise, document from js import Promise, document
deferred = Promise.withResolvers() deferred = Promise.withResolvers()

View File

@@ -1,4 +1,2 @@
from js import xworker
print("What is 2 + 3?") print("What is 2 + 3?")
print("Answer: " + xworker.sync.input("What is 2 + 3?")) print("Answer: " + xworker.sync.input("What is 2 + 3?"))

View File

@@ -1,7 +1,5 @@
import re import re
from js import xworker
def on_message(event): def on_message(event):
print(event.data) print(event.data)