Box 5: one browser profile, controls read even when Chromium calls its window hidden, the launcher never waits on itself

Found on the server's box after the update to 4:
- Chromium called its whole window "not showing" (it took itself for minimized) while its page
  was on the screen; the reader (like XBot's) skipped anything not showing, so a page had no
  numbers. Now: visible with a place on the screen counts, containers are walked through, and
  only a page that isn't showing (a background tab) is left out.
- Two browser profiles: Chromium's own (~/.config/chromium, where a person logged in by hand)
  and AnyBot's (~/.config/anybot-browser); the launcher used AnyBot's and Threads showed as
  logged out. One profile now (anybot-profile → Chromium's own; a box with only AnyBot's keeps
  that); both folders stay as they were.
- The launcher's lock fd went to Chromium, which then held it for its whole life: the next start
  waited 30 s and failed. And its flags line had been lost in an edit.

box_numbers_are_exact passes twice on a box from this image; the launcher answers in 0.09 s when
the browser runs; box_screen_is_exact passes.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
王性驊 2026-11-06 01:03:59 +08:00
parent ac00aea265
commit 338c49b0ee
4 changed files with 31 additions and 12 deletions

View File

@ -27,6 +27,6 @@ RUN chmod +x /usr/local/bin/anybot-box-start /usr/local/bin/anybot-a11y /usr/loc
# Accessibility on for every app, including those started over `docker exec`. # Accessibility on for every app, including those started over `docker exec`.
ENV DISPLAY=:1 SCREEN_SIZE=1280x800x24 GTK_MODULES=atk-bridge GTK_A11Y=atspi GNOME_ACCESSIBILITY=1 NO_AT_BRIDGE=0 ENV DISPLAY=:1 SCREEN_SIZE=1280x800x24 GTK_MODULES=atk-bridge GTK_A11Y=atspi GNOME_ACCESSIBILITY=1 NO_AT_BRIDGE=0
EXPOSE 6080 EXPOSE 6080
LABEL anybot.box.version="4" LABEL anybot.box.version="5"
ENTRYPOINT ["/usr/bin/tini", "--"] ENTRYPOINT ["/usr/bin/tini", "--"]
CMD ["/usr/local/bin/anybot-box-start"] CMD ["/usr/local/bin/anybot-box-start"]

View File

@ -4,7 +4,8 @@
in screen pixels. Web pages are in it too (Chromium runs with renderer accessibility on). in screen pixels. Web pages are in it too (Chromium runs with renderer accessibility on).
AnyBot numbers them on the bot's picture; a click by number lands on the centre. AnyBot numbers them on the bot's picture; a click by number lands on the centre.
Adapted from XBot's box/box-a11y. Usage: a11y [--limit N] [--budget SECONDS] Adapted from XBot's box/box-a11y (which skips anything not "showing"; Chromium can say that of
its whole window). Usage: a11y [--limit N] [--budget SECONDS]
Exit 2 when AT-SPI itself is unreachable. Exit 2 when AT-SPI itself is unreachable.
""" """
import json import json
@ -92,10 +93,16 @@ def main():
except Exception: # noqa: BLE001 except Exception: # noqa: BLE001
return return
st = state(node) st = state(node)
if depth > 1 and (st is None or not (st.contains(Atspi.StateType.SHOWING) and st.contains(Atspi.StateType.VISIBLE))): showing = bool(st and st.contains(Atspi.StateType.SHOWING))
visible = bool(st and st.contains(Atspi.StateType.VISIBLE))
# A page that isn't showing is a tab in the background: none of it is on the screen.
if role in ("document web", "document frame") and not showing:
return return
# Otherwise "showing" isn't trusted: Chromium can call its whole window not showing (it
# took itself for minimized under this window manager) while its page is on the screen.
# Visible with a place on the screen is what counts; containers are walked through.
box = extents(node) box = extents(node)
if depth > 1 and (box is None or not onscreen(box)): if depth > 1 and (not visible or box is None or not onscreen(box)):
return return
try: try:
name = (node.get_name() or "").strip() name = (node.get_name() or "").strip()

View File

@ -11,8 +11,18 @@ if [[ -z "${DBUS_SESSION_BUS_ADDRESS:-}" && -r /tmp/anybot-dbus.env ]]; then
source /tmp/anybot-dbus.env source /tmp/anybot-dbus.env
export DBUS_SESSION_BUS_ADDRESS export DBUS_SESSION_BUS_ADDRESS
fi fi
PROFILE=/home/bot/.config/anybot-browser # One profile. The box used to have two: Chromium's own (~/.config/chromium, what its menu entry
mkdir -p "$PROFILE" # opened, where a person logged in by hand) and AnyBot's (~/.config/anybot-browser, what the
# browser tools opened), so a site could be logged in in one and not the other. Chromium's own
# is the one now (the other stays as it was); a box that only has AnyBot's keeps that.
PROFILE=/home/bot/.config/anybot-profile
if [[ ! -e "$PROFILE" ]]; then
if [[ -d /home/bot/.config/chromium/Default || ! -d /home/bot/.config/anybot-browser/Default ]]; then
mkdir -p /home/bot/.config/chromium && ln -s /home/bot/.config/chromium "$PROFILE"
else
ln -s /home/bot/.config/anybot-browser "$PROFILE"
fi
fi
FLAGS=(--no-sandbox --test-type --user-data-dir="$PROFILE" --remote-debugging-address=127.0.0.1 --remote-debugging-port=9222 FLAGS=(--no-sandbox --test-type --user-data-dir="$PROFILE" --remote-debugging-address=127.0.0.1 --remote-debugging-port=9222
--remote-allow-origins=* --force-renderer-accessibility --disable-blink-features=AutomationControlled --remote-allow-origins=* --force-renderer-accessibility --disable-blink-features=AutomationControlled
--disable-dev-shm-usage --disable-gpu --use-angle=swiftshader --enable-unsafe-swiftshader --password-store=basic --disable-dev-shm-usage --disable-gpu --use-angle=swiftshader --enable-unsafe-swiftshader --password-store=basic
@ -22,14 +32,16 @@ FLAGS=(--no-sandbox --test-type --user-data-dir="$PROFILE" --remote-debugging-ad
# over the profile ("in use"): one at a time until the first is listening. # over the profile ("in use"): one at a time until the first is listening.
exec 9>/tmp/.anybot-browser.lock exec 9>/tmp/.anybot-browser.lock
flock -w 30 9 || true flock -w 30 9 || true
if pgrep -u bot -f -- "--user-data-dir=$PROFILE" >/dev/null; then if pgrep -u bot -f -- "--user-data-dir=$PROFILE" >/dev/null || pgrep -u bot -f -- "--user-data-dir=$(readlink -f "$PROFILE")" >/dev/null; then
flock -u 9 flock -u 9
exec chromium "${FLAGS[@]}" "$@" exec chromium "${FLAGS[@]}" "$@" 9>&-
fi fi
# A profile from before this box (a new container) still names the old one in its lock. # A profile from before this box (a new container) still names the old one in its lock.
rm -f "$PROFILE/SingletonLock" "$PROFILE/SingletonSocket" "$PROFILE/SingletonCookie" rm -f "$PROFILE"/SingletonLock "$PROFILE"/SingletonSocket "$PROFILE"/SingletonCookie
setsid chromium "${FLAGS[@]}" "${@:-about:blank}" >/tmp/anybot-browser.log 2>&1 < /dev/null & # (The lock's fd is not handed to the browser: it would hold the lock for its whole life, and the
for _ in $(seq 1 60); do # next start would wait on it.)
setsid chromium "${FLAGS[@]}" "${@:-about:blank}" >/tmp/anybot-browser.log 2>&1 < /dev/null 9>&- &
for _ in $(seq 1 100); do
python3 -c "import urllib.request; urllib.request.urlopen('http://127.0.0.1:9222/json/version', timeout=1).close()" 2>/dev/null && break python3 -c "import urllib.request; urllib.request.urlopen('http://127.0.0.1:9222/json/version', timeout=1).close()" 2>/dev/null && break
sleep 0.25 sleep 0.25
done done

View File

@ -2003,7 +2003,7 @@ mod tests {
assert!(!in_schedule_window("03:00", t("02:59"))); assert!(!in_schedule_window("03:00", t("02:59")));
assert!(in_schedule_window("23:30", t("00:10"))); assert!(in_schedule_window("23:30", t("00:10")));
assert!(!in_schedule_window("bad", t("00:10"))); assert!(!in_schedule_window("bad", t("00:10")));
assert_eq!(wanted_version(), "4"); assert_eq!(wanted_version(), "5");
} }
#[test] #[test]