-
-
Notifications
You must be signed in to change notification settings - Fork 784
Create proxy fix #3345
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Create proxy fix #3345
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The fix you've provided here looks like it resolves the issue; however, I'm not completely convinced it's the right approach.
Taking the solution "as is" - I'm not wild about the name change. You've taken a method called addEventListener and rebound it to the name create_proxy - which is a method name that also exists in the pyscript.ffi namespace. That means create_proxy() is (a) no longer doing what the built-in create_proxy method does, and (b) doesn't accurately describe what it does do, either. If we were going down this path, I'd be inclined to make a utility add_event_listener() method (possibly as member method on the widget class).
However, I'm not completely convinced this is needed. The ffi methods you're using here are low level pyodide methods; and one of the goals of PyScript is to provide a higher-level API that abstracts those methods and details behind a more Pythonic API.
To that end - Pyscript even provides a wrapper around create_proxy - but immediately advises that it shouldn't ever be needed if you're using the pyscript API as intended.
In particiular, the pyscript.web.* APIs (additional user guide here) provide a way to create any DOM element, and presumably it also "does the right thing" when it comes to attaching handlers - that would seem to be the intention of the when() method/decorator.
So - can we avoid the need to call pyodide internal APIs entirely by using the pyscript API exclusively? Can we replace Widget._create_dom_element with a native Pyscript call?
Briefcase is currently using PyScript 2024.11.1; if we need to switch to a newer version of PyScript to get bug fixes and updates, then that's absolutely something we can and should do. This is also something that would fit into the bigger piece of work of making Briefcase indepdendent of the specific needs of Toga - that is, the Pyscript and Shoelace version dependencies are dependencies of Toga, and should be defined there, not defined as part of Briefcase.
One other detail - when you're proposing a change that is purely code, and doesn't contain a change to tests, it's worth flagging how you've tested this yourself. If the code has tests (or a pre-existing, but failing test), then the "yes, this works" criteria is obvious; but when it's just code change, it's not necessarily obvious how you've established that code is working.
In this case, my go-to was the passwordinput example in the examples folder... and it won't work because (a) it's currently missing a travertino dependency, and (b) the dom_onchange implementation on PasswordInput is currently passing in a None argument whenever it is fired. You might not have hit the former problem (addressed in #3346) if you rolled your own test case - but I'm not sure how you could have tested any change event handling on PasswordInput and not hit the second problem.
Grabbing changes from upstream
updating branch
How would you suggest going about this. I'm assuming it would require coordinating PRs across both Toga and Briefcase to achieve this. Just want to make sure that I've understood this correctly. Also any tips or good to know things before i start would be greatly appreciated |
When Toga uses Briefcase, it's always using the "development" version; so as soon as any changes are made upstream, those changes will be used by Toga. And since the testing story for the web backend is weak, there's no chicken-and-egg coordination problem - there's very few automated tests that need to pass before we can merge changes to the web template, so we don't need to worry about fixing Toga so that the template change will pass automated testing so that we can merge a PR so that Toga can use new features :-) The two possible places a change might be required are in Briefcase itself, and in https://github.com/beeware/briefcase-web-static-template - the template that is used to generate Web code. The minimalist fix will be to update (2) to point at the newly required version. Assuming there's no other large configuration changes, this may be all you need to do. If you want to test things out, you can clone the template repository, then add The intermediate fix would be to decouple the PyScript version from Briefcase entirely. To do this, you'd modify the web template to make the Pyscript version a template variable, and use that template variable in the template (with a value of The comprehensive fix would be to resurrect the parts of #1945 and beeware/briefcase#1285 that would allow Toga to specify the version of Pyscript (and any other dependencies, such as Shoelace) that it requires. Those PRs allow a wheel to specify "inserts" which will be processed by Briefcase, and inserted into In terms of a pragmatic approach, I'd be entirely comfortable with the minimalist approach right now, as that gets you unstuck on the immediate issue of updating Pyscript API usage. However, I'd like to think you and the team would be able to take a swing at the intermediate or comprehensive fix as part of the work that you're doing improving BeeWare's web tooling. |
I've looked into using the
So we can use This approach is fully compatible with the current version of Pyscript we are using, so no updates are needed at this stage. Happy to hear any further thoughts or suggestions on refining this. I've already prepared the updated code based on this approach and it's ready to push if there are no issues. (For testing I used the switch example) |
We may not be able to use it as
From a quick poke around, it looks like
I think we're definitely at the "show me the code" stage :-) I suspect both approaches ( |
-Added helper method for event handling in web libs module. -Updated current use of event handlers in widgets and window module. -Updated element creation from js.document.createElement to pyscript.web.document.createElement
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The general ideas here make sense, but I'm not sure all the pieces are connecting together.
In particular, you say you've tested this with the switch example - and while the example loads without error, none of the event handlers are firing. From what I can make out, this is because the querySelector call is returning None from every request - so no event handlers are being installed.
web/src/toga_web/widgets/switch.py
Outdated
| def create(self): | ||
| self.native = self._create_native_widget("sl-switch") | ||
| self.native.addEventListener("sl-change", create_proxy(self.dom_onchange)) | ||
| add_event_listener(self.native.id, "sl-change", self.dom_onchange) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
So... is this actually doing anything?
As of this PR, add_event_listener is a wrapper around a call to document.querySelector(), then calling add_event_listener() on that object. But self.native should be the same object as is returned by the query selector... at which point the call to self.native.addEventListener will be functionally equivalent to calling add_event_listener(), except without the proxy.
It seems to me that the actual fix here is the change to use document.createElement() rather than the js.document API.
web/src/toga_web/window.py
Outdated
| def on_size_allocate(self, widget, allocation): | ||
| pass | ||
|
|
||
| @when("focus", selector="body") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
From a consistency perspective, it would be preferable to stick to a single mechanism for binding events - and given @when will be processed when the class is parsed, it seems preferable to use the explicit invocation of addEventListener().
web/src/toga_web/libs.py
Outdated
|
|
||
|
|
||
| create_proxy = pyodide.ffi.create_proxy if pyodide else lambda f: f | ||
| from pyscript.web import document # type: ignore |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What is the type: ignore doing? While we have type annotations for documentation purposes, we're not enforcing them.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This question is still outstanding.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ahh sorry the yellow squiggle was bothering me
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Will remove
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah - ok; that's what the try: except ImportError: blocks on the other js/pyodide imports are doing; they're ensuring that there's a minimum of squiggles :-)
web/src/toga_web/libs.py
Outdated
| """ | ||
|
|
||
| element = document.querySelector(f"#{element_id}") | ||
| if element: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Are you sure this isn't masking other problems? Under what conditions will the element not exist? From my testing... this is returning None for every widget, presumably because the widget hasn't been added to the DOM immediately after creation.
…ges so that the focus/blur and visbilitychange listeners in the window module are firing and proxy error is not occuring
|
I've reverted to using the current create_proxy pyodide method to focus on fixing the issues in being raised in the window module. I've used the tutorial1 example to test and ensure that the focus/blur and visibility change events are firing and that no proxy error was being raised. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Seems to work well as an interim fix to get you and the team unstuck for now.
One question outstanding; plus a suggested improvement to the release note.
changes/3345.bugfix.rst
Outdated
| @@ -0,0 +1 @@ | |||
| Improved web event listeners to prevent errors and improved stability | |||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
| Improved web event listeners to prevent errors and improved stability | |
| Window visibility and focus events in the web backend no longer raise errors when the browser window loses focus. |
web/src/toga_web/libs.py
Outdated
|
|
||
|
|
||
| create_proxy = pyodide.ffi.create_proxy if pyodide else lambda f: f | ||
| from pyscript.web import document # type: ignore |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This question is still outstanding.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Awesome - thanks for this cleanup. I'm sure we'll revisit the specifics over time, but for now, this is a good incremental improvement.
Improve event binding across web back-end by replacing the old create_proxy() with the pyodide.ffi.wrappers.add_event_listener(), this has been done in the libs module.
This affects any web widgets using DOM events such as blur, focus, change etc
Changes has been made to the passwordinput widget as well as the window module to make use of this change
New usage is now
create_proxy(dom_element,"event-type",python_handler)e.g.
create_proxy(self.native,"sl-blur",self.dom_onblur)Resolves issues related to garbage-collected borrowed proxies that show in the console.
Background
Brought to my attention in my previous pull request
PR Checklist: