Continue web refactor work.

This commit is contained in:
Storm Dragon
2026-07-26 17:36:43 -04:00
parent 243854b761
commit f92fd33089
6 changed files with 335 additions and 11 deletions
@@ -39,6 +39,7 @@ from cthulhu.ax_object import AXObject
from cthulhu.ax_utilities import AXUtilities
from cthulhu.scripts import default
from cthulhu.scripts import web
from cthulhu.scripts.web.event_router import RouteResult
from .braille_generator import BrailleGenerator
from .script_utilities import Utilities
from .speech_generator import SpeechGenerator
@@ -152,12 +153,20 @@ class Script(web.Script):
def onCheckedChanged(self, event):
"""Callback for object:state-changed:checked accessibility events."""
if super().onCheckedChanged(event):
return
def handle_shared_web(routed_event: object) -> RouteResult:
handled = web.Script.onCheckedChanged(self, routed_event)
return RouteResult.HANDLED if handled else RouteResult.CONTINUE
msg = "CHROMIUM: Passing along event to default script"
debug.printMessage(debug.LEVEL_INFO, msg, True)
default.Script.onCheckedChanged(self, event)
self._eventRouter.route_event(
event,
application_handler=None,
toolkit_handler=None,
web_handler=handle_shared_web,
default_handler=lambda routed_event: default.Script.onCheckedChanged(
self,
routed_event,
),
)
def onColumnReordered(self, event):
"""Callback for object:column-reordered accessibility events."""
+14 -5
View File
@@ -42,6 +42,7 @@ from cthulhu.ax_object import AXObject
from cthulhu.ax_utilities import AXUtilities
from cthulhu.scripts import default
from cthulhu.scripts import web
from cthulhu.scripts.web.event_router import RouteResult
from .script_utilities import Utilities
@@ -125,12 +126,20 @@ class Script(web.Script):
def onCheckedChanged(self, event):
"""Callback for object:state-changed:checked accessibility events."""
if super().onCheckedChanged(event):
return
def handle_shared_web(routed_event: object) -> RouteResult:
handled = web.Script.onCheckedChanged(self, routed_event)
return RouteResult.HANDLED if handled else RouteResult.CONTINUE
msg = "GECKO: Passing along event to default script"
debug.printMessage(debug.LEVEL_INFO, msg, True)
default.Script.onCheckedChanged(self, event)
self._eventRouter.route_event(
event,
application_handler=None,
toolkit_handler=None,
web_handler=handle_shared_web,
default_handler=lambda routed_event: default.Script.onCheckedChanged(
self,
routed_event,
),
)
def onColumnReordered(self, event):
"""Callback for object:column-reordered accessibility events."""
+121
View File
@@ -0,0 +1,121 @@
#!/usr/bin/env python3
#
# Copyright (c) 2026 Stormux
#
# This library is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
# License as published by the Free Software Foundation; either
# version 2.1 of the License, or (at your option) any later version.
#
# This library is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
# Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public
# License along with this library; if not, write to the
# Free Software Foundation, Inc., Franklin Street, Fifth Floor,
# Boston MA 02110-1301 USA.
"""Defines the ownership contract for routing web accessibility events.
Handlers run in application, toolkit, and shared-web order. A handler returns
``HANDLED`` to claim the event, ``CONTINUE`` to offer it to the next layer, or
``DEFER_DEFAULT`` to skip directly to the default script. Invalid results and
handler exceptions fail safely by reaching the default handler exactly once.
This module defines the Phase 2 routing boundary. Existing event families must
be migrated individually; creating a router does not activate parallel routing.
"""
from __future__ import annotations
import enum
from collections.abc import Callable
from cthulhu import debug
class RouteResult(enum.Enum):
"""A routing layer's explicit decision for one accessibility event."""
HANDLED = enum.auto()
CONTINUE = enum.auto()
DEFER_DEFAULT = enum.auto()
class RoutingLayer(enum.Enum):
"""The layer which ultimately owns an accessibility event."""
APPLICATION = enum.auto()
TOOLKIT = enum.auto()
SHARED_WEB = enum.auto()
DEFAULT = enum.auto()
RouteHandler = Callable[[object], RouteResult]
DefaultHandler = Callable[[object], object]
class WebEventRouter:
"""Routes one event to at most one owning presentation layer."""
def route_event(
self,
event: object,
*,
application_handler: RouteHandler | None,
toolkit_handler: RouteHandler | None,
web_handler: RouteHandler | None,
default_handler: DefaultHandler,
) -> RoutingLayer:
"""Route ``event`` in application-first order and return its owner."""
handlers = (
(RoutingLayer.APPLICATION, application_handler),
(RoutingLayer.TOOLKIT, toolkit_handler),
(RoutingLayer.SHARED_WEB, web_handler),
)
for layer, handler in handlers:
if handler is None:
continue
try:
result = handler(event)
except Exception as error: # pylint: disable=broad-except
message = (
f"WEB EVENT ROUTER: {layer.name} raised "
f"{type(error).__name__}; deferring to DEFAULT"
)
debug.printMessage(debug.LEVEL_WARNING, message, True)
return self._route_to_default(event, default_handler)
if not isinstance(result, RouteResult):
message = (
f"WEB EVENT ROUTER: {layer.name} returned invalid result "
f"{type(result).__name__}; deferring to DEFAULT"
)
debug.printMessage(debug.LEVEL_WARNING, message, True)
return self._route_to_default(event, default_handler)
message = f"WEB EVENT ROUTER: {layer.name} -> {result.name}"
debug.printMessage(debug.LEVEL_INFO, message, True)
if result is RouteResult.HANDLED:
return layer
if result is RouteResult.DEFER_DEFAULT:
return self._route_to_default(event, default_handler)
return self._route_to_default(event, default_handler)
@staticmethod
def _route_to_default(
event: object,
default_handler: DefaultHandler,
) -> RoutingLayer:
debug.printMessage(
debug.LEVEL_INFO,
"WEB EVENT ROUTER: DEFAULT -> HANDLED",
True,
)
default_handler(event)
return RoutingLayer.DEFAULT
+2 -1
View File
@@ -2,6 +2,7 @@ web_python_sources = files([
'__init__.py',
'bookmarks.py',
'braille_generator.py',
'event_router.py',
'script.py',
'script_utilities.py',
'sound_generator.py',
@@ -12,4 +13,4 @@ web_python_sources = files([
python3.install_sources(
web_python_sources,
subdir: 'cthulhu/scripts/web'
)
)
+3
View File
@@ -64,6 +64,7 @@ from cthulhu.ax_utilities_event import AXUtilitiesEvent, TextEventReason
from .bookmarks import Bookmarks
from .braille_generator import BrailleGenerator
from .event_router import WebEventRouter
from .sound_generator import SoundGenerator
from .speech_generator import SpeechGenerator
from .tutorial_generator import TutorialGenerator
@@ -80,6 +81,8 @@ def _log_tokens(tokens, reason=None, timestamp=True, stack=False):
class Script(default.Script):
_eventRouter = WebEventRouter()
def __init__(self, app):
super().__init__(app)