From 2fa11559f03c02b965f6d15386851fe9a2fa5487 Mon Sep 17 00:00:00 2001 From: mEp3ii2 <74845700+mEp3ii2@users.noreply.github.com> Date: Fri, 18 Apr 2025 21:07:54 +0800 Subject: [PATCH 1/7] Fix proxy web issue --- web/src/toga_web/libs.py | 5 +++-- web/src/toga_web/widgets/passwordinput.py | 2 +- web/src/toga_web/window.py | 8 +++++--- 3 files changed, 9 insertions(+), 6 deletions(-) diff --git a/web/src/toga_web/libs.py b/web/src/toga_web/libs.py index 669230a680..1ecc40e7ba 100644 --- a/web/src/toga_web/libs.py +++ b/web/src/toga_web/libs.py @@ -7,14 +7,15 @@ try: - # Try to import pyodide from the PyScript namespace + # Try to import pyodide from the PyScript namespace and wrappers import pyodide + import pyodide.ffi.wrappers except ModuleNotFoundError: # To ensure the code can be imported, provide a pyodide symbol as a fallback pyodide = None -create_proxy = pyodide.ffi.create_proxy if pyodide else lambda f: f +create_proxy = pyodide.ffi.wrappers.add_event_listener if pyodide else lambda f: f def create_element( diff --git a/web/src/toga_web/widgets/passwordinput.py b/web/src/toga_web/widgets/passwordinput.py index 76b1064e38..57d5d5ac09 100644 --- a/web/src/toga_web/widgets/passwordinput.py +++ b/web/src/toga_web/widgets/passwordinput.py @@ -7,7 +7,7 @@ class PasswordInput(TextInput): def create(self): super().create() self.native.setAttribute("type", "password") - self.native.addEventListener("sl-change", create_proxy(self.dom_onchange)) + create_proxy(self.native,"sl-change",self.dom_onchange) def dom_onchange(self, event): self.interface.on_change(None) diff --git a/web/src/toga_web/window.py b/web/src/toga_web/window.py index 9eadb9edd1..69042e6ae9 100644 --- a/web/src/toga_web/window.py +++ b/web/src/toga_web/window.py @@ -21,9 +21,11 @@ def __init__(self, interface, title, position, size): app_placeholder = js.document.getElementById("app-placeholder") app_placeholder.appendChild(self.native) - js.document.body.onfocus = self.dom_on_gain_focus - js.document.body.onblur = self.dom_on_lose_focus - js.document.addEventListener("visibilitychange", self.dom_on_visibility_change) + create_proxy(js.document.body, "focus", self.dom_on_gain_focus) + create_proxy(js.document.body, "blur", self.dom_on_lose_focus) + create_proxy( + js.document.body, "visibilitychange", self.dom_on_visibility_change + ) self.set_title(title) From 7071df779531fc468fb1be504b738a0cb021e48a Mon Sep 17 00:00:00 2001 From: Mitchell Date: Fri, 18 Apr 2025 21:11:19 +0800 Subject: [PATCH 2/7] pre-commit fixes --- web/src/toga_web/widgets/passwordinput.py | 2 +- web/src/toga_web/window.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/web/src/toga_web/widgets/passwordinput.py b/web/src/toga_web/widgets/passwordinput.py index 57d5d5ac09..40acde96df 100644 --- a/web/src/toga_web/widgets/passwordinput.py +++ b/web/src/toga_web/widgets/passwordinput.py @@ -7,7 +7,7 @@ class PasswordInput(TextInput): def create(self): super().create() self.native.setAttribute("type", "password") - create_proxy(self.native,"sl-change",self.dom_onchange) + create_proxy(self.native, "sl-change", self.dom_onchange) def dom_onchange(self, event): self.interface.on_change(None) diff --git a/web/src/toga_web/window.py b/web/src/toga_web/window.py index 69042e6ae9..ca3ea845c5 100644 --- a/web/src/toga_web/window.py +++ b/web/src/toga_web/window.py @@ -1,7 +1,7 @@ from toga.command import Group, Separator from toga.constants import WindowState from toga.types import Position, Size -from toga_web.libs import create_element, js +from toga_web.libs import create_element, create_proxy, js from .screens import Screen as ScreenImpl From 6ea645390a8c1cb40decc53c0740bd8e019082ab Mon Sep 17 00:00:00 2001 From: Mitchell Date: Fri, 18 Apr 2025 21:36:15 +0800 Subject: [PATCH 3/7] Made changelog file --- changes/3345.bugfix.rst | 1 + 1 file changed, 1 insertion(+) create mode 100644 changes/3345.bugfix.rst diff --git a/changes/3345.bugfix.rst b/changes/3345.bugfix.rst new file mode 100644 index 0000000000..6537eb78b1 --- /dev/null +++ b/changes/3345.bugfix.rst @@ -0,0 +1 @@ +Improved web event listeners to prevent errors and improved stability \ No newline at end of file From f862a84839fdb9fd563d0d5135ee793a4b629b58 Mon Sep 17 00:00:00 2001 From: Mitchell Date: Fri, 18 Apr 2025 21:38:29 +0800 Subject: [PATCH 4/7] pre-commit fix --- changes/3345.bugfix.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/changes/3345.bugfix.rst b/changes/3345.bugfix.rst index 6537eb78b1..46d1fe8d03 100644 --- a/changes/3345.bugfix.rst +++ b/changes/3345.bugfix.rst @@ -1 +1 @@ -Improved web event listeners to prevent errors and improved stability \ No newline at end of file +Improved web event listeners to prevent errors and improved stability From 95cfd489d23759c5228f7b3bf4b54c944e286bcb Mon Sep 17 00:00:00 2001 From: Mitchell Date: Mon, 28 Apr 2025 16:34:54 +0800 Subject: [PATCH 5/7] Refactor event handling to use pyscript.web APIs -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 --- web/src/toga_web/libs.py | 28 +++++++++++++---------- web/src/toga_web/widgets/passwordinput.py | 4 ++-- web/src/toga_web/widgets/switch.py | 4 ++-- web/src/toga_web/window.py | 22 ++++++++---------- 4 files changed, 30 insertions(+), 28 deletions(-) diff --git a/web/src/toga_web/libs.py b/web/src/toga_web/libs.py index 1ecc40e7ba..c05d7d2c80 100644 --- a/web/src/toga_web/libs.py +++ b/web/src/toga_web/libs.py @@ -5,17 +5,7 @@ # To ensure the code can be imported, provide a js symbol as a fallback js = None - -try: - # Try to import pyodide from the PyScript namespace and wrappers - import pyodide - import pyodide.ffi.wrappers -except ModuleNotFoundError: - # To ensure the code can be imported, provide a pyodide symbol as a fallback - pyodide = None - - -create_proxy = pyodide.ffi.wrappers.add_event_listener if pyodide else lambda f: f +from pyscript.web import document # type: ignore def create_element( @@ -44,7 +34,7 @@ def create_element( events or methods. :returns: A newly created DOM element. """ - element = js.document.createElement(tag) + element = document.createElement(tag) if id: element.id = id @@ -70,3 +60,17 @@ def create_element( element.appendChild(child) return element + + +def add_event_listener(element_id, event_type, callback): + """ + Utility method to attach event listener to an element by ID + + :param element_id: The ID of the DOM element + :param event_type: The name of the event e.g. sl-change + :param callback: The function to be called when the event triggers + """ + + element = document.querySelector(f"#{element_id}") + if element: + element.addEventListener(event_type, callback) diff --git a/web/src/toga_web/widgets/passwordinput.py b/web/src/toga_web/widgets/passwordinput.py index 40acde96df..4a3010304c 100644 --- a/web/src/toga_web/widgets/passwordinput.py +++ b/web/src/toga_web/widgets/passwordinput.py @@ -1,4 +1,4 @@ -from toga_web.libs import create_proxy +from toga_web.libs import add_event_listener from .textinput import TextInput @@ -7,7 +7,7 @@ class PasswordInput(TextInput): def create(self): super().create() self.native.setAttribute("type", "password") - create_proxy(self.native, "sl-change", self.dom_onchange) + add_event_listener(self.native.id, "sl-change", self.dom_onchange) def dom_onchange(self, event): self.interface.on_change(None) diff --git a/web/src/toga_web/widgets/switch.py b/web/src/toga_web/widgets/switch.py index 558f837661..052d4e6afa 100644 --- a/web/src/toga_web/widgets/switch.py +++ b/web/src/toga_web/widgets/switch.py @@ -1,4 +1,4 @@ -from toga_web.libs import create_proxy +from toga_web.libs import add_event_listener from .base import Widget @@ -6,7 +6,7 @@ class Switch(Widget): 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) def dom_onchange(self, event): self.interface.on_change() diff --git a/web/src/toga_web/window.py b/web/src/toga_web/window.py index ca3ea845c5..2213b6eb33 100644 --- a/web/src/toga_web/window.py +++ b/web/src/toga_web/window.py @@ -1,7 +1,9 @@ +from pyscript.web import document, when + from toga.command import Group, Separator from toga.constants import WindowState from toga.types import Position, Size -from toga_web.libs import create_element, create_proxy, js +from toga_web.libs import create_element, js from .screens import Screen as ScreenImpl @@ -21,12 +23,6 @@ def __init__(self, interface, title, position, size): app_placeholder = js.document.getElementById("app-placeholder") app_placeholder.appendChild(self.native) - create_proxy(js.document.body, "focus", self.dom_on_gain_focus) - create_proxy(js.document.body, "blur", self.dom_on_lose_focus) - create_proxy( - js.document.body, "visibilitychange", self.dom_on_visibility_change - ) - self.set_title(title) ###################################################################### @@ -39,18 +35,20 @@ def on_close(self, *args): def on_size_allocate(self, widget, allocation): pass + @when("focus", selector="body") def dom_on_gain_focus(self, event): self.interface.on_gain_focus() + @when("onblur", selector="body") def dom_on_lose_focus(self, event): self.interface.on_lose_focus() + @when("visibilitychange", selector="document") def dom_on_visibility_change(self, event): - if hasattr(js.document, "hidden"): - if js.document.visibilityState == "visible": - self.interface.on_show() - else: - self.interface.on_hide() + if document.element.visibilityState == "visible": + self.interface.on_show() + else: + self.interface.on_hide() ###################################################################### # Window properties From ec1836ceb39a5dedb208b5b557f36d06489c7263 Mon Sep 17 00:00:00 2001 From: Mitchell Date: Fri, 2 May 2025 10:00:38 +0800 Subject: [PATCH 6/7] Revert back to the current implementation for create proxy, made changes so that the focus/blur and visbilitychange listeners in the window module are firing and proxy error is not occuring --- web/src/toga_web/libs.py | 20 ++++++-------------- web/src/toga_web/widgets/passwordinput.py | 4 ++-- web/src/toga_web/widgets/switch.py | 4 ++-- web/src/toga_web/window.py | 19 ++++++++++++------- 4 files changed, 22 insertions(+), 25 deletions(-) diff --git a/web/src/toga_web/libs.py b/web/src/toga_web/libs.py index c05d7d2c80..00c568e317 100644 --- a/web/src/toga_web/libs.py +++ b/web/src/toga_web/libs.py @@ -4,9 +4,15 @@ except ModuleNotFoundError: # To ensure the code can be imported, provide a js symbol as a fallback js = None +try: + import pyodide +except ModuleNotFoundError: + pyodide = None from pyscript.web import document # type: ignore +create_proxy = pyodide.ffi.create_proxy if pyodide else lambda f: f + def create_element( tag, @@ -60,17 +66,3 @@ def create_element( element.appendChild(child) return element - - -def add_event_listener(element_id, event_type, callback): - """ - Utility method to attach event listener to an element by ID - - :param element_id: The ID of the DOM element - :param event_type: The name of the event e.g. sl-change - :param callback: The function to be called when the event triggers - """ - - element = document.querySelector(f"#{element_id}") - if element: - element.addEventListener(event_type, callback) diff --git a/web/src/toga_web/widgets/passwordinput.py b/web/src/toga_web/widgets/passwordinput.py index 4a3010304c..76b1064e38 100644 --- a/web/src/toga_web/widgets/passwordinput.py +++ b/web/src/toga_web/widgets/passwordinput.py @@ -1,4 +1,4 @@ -from toga_web.libs import add_event_listener +from toga_web.libs import create_proxy from .textinput import TextInput @@ -7,7 +7,7 @@ class PasswordInput(TextInput): def create(self): super().create() self.native.setAttribute("type", "password") - add_event_listener(self.native.id, "sl-change", self.dom_onchange) + self.native.addEventListener("sl-change", create_proxy(self.dom_onchange)) def dom_onchange(self, event): self.interface.on_change(None) diff --git a/web/src/toga_web/widgets/switch.py b/web/src/toga_web/widgets/switch.py index 052d4e6afa..558f837661 100644 --- a/web/src/toga_web/widgets/switch.py +++ b/web/src/toga_web/widgets/switch.py @@ -1,4 +1,4 @@ -from toga_web.libs import add_event_listener +from toga_web.libs import create_proxy from .base import Widget @@ -6,7 +6,7 @@ class Switch(Widget): def create(self): self.native = self._create_native_widget("sl-switch") - add_event_listener(self.native.id, "sl-change", self.dom_onchange) + self.native.addEventListener("sl-change", create_proxy(self.dom_onchange)) def dom_onchange(self, event): self.interface.on_change() diff --git a/web/src/toga_web/window.py b/web/src/toga_web/window.py index 2213b6eb33..0ebdf897c8 100644 --- a/web/src/toga_web/window.py +++ b/web/src/toga_web/window.py @@ -1,9 +1,7 @@ -from pyscript.web import document, when - from toga.command import Group, Separator from toga.constants import WindowState from toga.types import Position, Size -from toga_web.libs import create_element, js +from toga_web.libs import create_element, create_proxy, js from .screens import Screen as ScreenImpl @@ -23,6 +21,16 @@ def __init__(self, interface, title, position, size): app_placeholder = js.document.getElementById("app-placeholder") app_placeholder.appendChild(self.native) + js.document.body.addEventListener( + "focus", create_proxy(self.dom_on_gain_focus), True + ) + js.document.body.addEventListener( + "blur", create_proxy(self.dom_on_lose_focus), True + ) + js.document.addEventListener( + "visibilitychange", create_proxy(self.dom_on_visibility_change) + ) + self.set_title(title) ###################################################################### @@ -35,17 +43,14 @@ def on_close(self, *args): def on_size_allocate(self, widget, allocation): pass - @when("focus", selector="body") def dom_on_gain_focus(self, event): self.interface.on_gain_focus() - @when("onblur", selector="body") def dom_on_lose_focus(self, event): self.interface.on_lose_focus() - @when("visibilitychange", selector="document") def dom_on_visibility_change(self, event): - if document.element.visibilityState == "visible": + if js.document.visibilityState == "visible": self.interface.on_show() else: self.interface.on_hide() From 294d57e446bc0911c07f741baeb57b3b2e547c49 Mon Sep 17 00:00:00 2001 From: Mitchell Date: Fri, 2 May 2025 10:19:59 +0800 Subject: [PATCH 7/7] update release note and cleanup of uneeded annotation --- changes/3345.bugfix.rst | 2 +- web/src/toga_web/libs.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/changes/3345.bugfix.rst b/changes/3345.bugfix.rst index 46d1fe8d03..667bb89314 100644 --- a/changes/3345.bugfix.rst +++ b/changes/3345.bugfix.rst @@ -1 +1 @@ -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 diff --git a/web/src/toga_web/libs.py b/web/src/toga_web/libs.py index 00c568e317..b98eb8abdc 100644 --- a/web/src/toga_web/libs.py +++ b/web/src/toga_web/libs.py @@ -9,7 +9,7 @@ except ModuleNotFoundError: pyodide = None -from pyscript.web import document # type: ignore +from pyscript.web import document create_proxy = pyodide.ffi.create_proxy if pyodide else lambda f: f