Allow nodes in shadow roots to be addressed via Element (#1454)

This commit is contained in:
Andrea Giammarchi
2023-05-09 17:42:09 +02:00
committed by GitHub
parent 82e5b64bad
commit d3bcd87cfa
8 changed files with 76 additions and 5 deletions

View File

@@ -8,7 +8,8 @@
"name": "pyscript",
"version": "0.0.1",
"dependencies": {
"basic-devtools": "^0.1.6"
"basic-devtools": "^0.1.6",
"not-so-weak": "^1.0.0"
},
"devDependencies": {
"@codemirror/commands": "^6.2.2",
@@ -4802,6 +4803,11 @@
"node": ">=0.10.0"
}
},
"node_modules/not-so-weak": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/not-so-weak/-/not-so-weak-1.0.0.tgz",
"integrity": "sha512-kgpM6y09QLyfQXfA0AAupX8ZUqKn4caDxQTMVNsyKK02IQ+9P9ylcL1ioQem4qciUCy3IitESXQfMBfnGuGNOQ=="
},
"node_modules/npm-run-path": {
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz",
@@ -9610,6 +9616,11 @@
"integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==",
"dev": true
},
"not-so-weak": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/not-so-weak/-/not-so-weak-1.0.0.tgz",
"integrity": "sha512-kgpM6y09QLyfQXfA0AAupX8ZUqKn4caDxQTMVNsyKK02IQ+9P9ylcL1ioQem4qciUCy3IitESXQfMBfnGuGNOQ=="
},
"npm-run-path": {
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz",

View File

@@ -40,6 +40,7 @@
"typescript": "5.0.4"
},
"dependencies": {
"basic-devtools": "^0.1.6"
"basic-devtools": "^0.1.6",
"not-so-weak": "^1.0.0"
}
}

View File

@@ -1,5 +1,6 @@
import { $$, $x } from 'basic-devtools';
import { shadowRoots } from '../shadow_roots';
import { ltrim, htmlDecode, ensureUniqueId, createDeprecationWarning } from '../utils';
import { getLogger } from '../logger';
import { pyExec, displayPyException } from '../pyexec';
@@ -174,7 +175,9 @@ export function make_PyScript(interpreter: InterpreterClient, app: PyScriptApp)
const { attachShadow } = Element.prototype;
Object.assign(Element.prototype, {
attachShadow(init: ShadowRootInit) {
return observe(attachShadow.call(this as Element, init));
const shadowRoot = observe(attachShadow.call(this as Element, init));
shadowRoots.add(shadowRoot);
return shadowRoot;
},
});

View File

@@ -2,6 +2,7 @@ import time
from textwrap import dedent
import js
from _pyscript_js import deepQuerySelector
from . import _internal
from ._mime import format_mime as _format_mime
@@ -55,7 +56,7 @@ class Element:
def element(self):
"""Return the dom element"""
if not self._element:
self._element = js.document.querySelector(f"#{self._id}")
self._element = deepQuerySelector(f"#{self._id}")
return self._element
@property

View File

@@ -9,6 +9,7 @@ import type { ProxyMarked } from 'synclink';
import * as Synclink from 'synclink';
import { showWarning } from './utils';
import { define_custom_element } from './plugin';
import { deepQuerySelector } from './shadow_roots';
import { python_package } from './python_package';
@@ -100,7 +101,7 @@ export class RemoteInterpreter extends Object {
*/
async loadInterpreter(config: AppConfig, stdio: Synclink.Remote<Stdio & ProxyMarked>): Promise<void> {
// TODO: move this to "main thread"!
const _pyscript_js_main = { define_custom_element, showWarning };
const _pyscript_js_main = { define_custom_element, showWarning, deepQuerySelector };
this.interface = Synclink.proxy(
await loadPyodide({

View File

@@ -0,0 +1,18 @@
import { $ } from 'basic-devtools';
import { WSet } from 'not-so-weak';
// weakly retain shadow root nodes in an iterable way
// so that it's possible to query these and find elements by ID
export const shadowRoots: WSet<ShadowRoot> = new WSet();
// returns an element by ID if present within any of the live shadow roots
const findInShadowRoots = (selector: string): Element | null => {
for (const shadowRoot of shadowRoots) {
const element = $(selector, shadowRoot);
if (element) return element;
}
return null;
};
// find an element by ID either via document or via any live shadow root
export const deepQuerySelector = (selector: string) => $(selector, document) || findInShadowRoots(selector);

View File

@@ -0,0 +1,30 @@
from .support import PyScriptTest, skip_worker
class TestShadowRoot(PyScriptTest):
@skip_worker("FIXME: js.document")
def test_reachable_shadow_root(self):
self.pyscript_run(
r"""
<script>
// reason to wait for py-script is that it's the entry point for
// all patches and the MutationObserver, otherwise being this a synchronous
// script the constructor gets instantly invoked at the node before
// py-script gets a chance to initialize itself.
customElements.whenDefined('py-script').then(() => {
customElements.define('s-r', class extends HTMLElement {
constructor() {
super().attachShadow({mode: 'closed'}).innerHTML =
'<div id="shadowed">OK</div>';
}
});
});
</script>
<s-r></s-r>
<py-script>
import js
js.console.log(Element("shadowed").innerHtml)
</py-script>
"""
)
assert self.console.log.lines[-1] == "OK"

View File

@@ -1,4 +1,10 @@
from unittest.mock import Mock
import js
showWarning = Mock()
define_custom_element = Mock()
def deepQuerySelector(selector):
return js.document.querySelector(selector)