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`.
ENV DISPLAY=:1 SCREEN_SIZE=1280x800x24 GTK_MODULES=atk-bridge GTK_A11Y=atspi GNOME_ACCESSIBILITY=1 NO_AT_BRIDGE=0
EXPOSE 6080
LABEL anybot.box.version="4"
LABEL anybot.box.version="5"
ENTRYPOINT ["/usr/bin/tini", "--"]
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).
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.
"""
import json
@ -92,10 +93,16 @@ def main():
except Exception: # noqa: BLE001
return
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
# 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)
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
try:
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
export DBUS_SESSION_BUS_ADDRESS
fi
PROFILE=/home/bot/.config/anybot-browser
mkdir -p "$PROFILE"
# One profile. The box used to have two: Chromium's own (~/.config/chromium, what its menu entry
# 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
--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
@ -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.
exec 9>/tmp/.anybot-browser.lock
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
exec chromium "${FLAGS[@]}" "$@"
exec chromium "${FLAGS[@]}" "$@" 9>&-
fi
# 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"
setsid chromium "${FLAGS[@]}" "${@:-about:blank}" >/tmp/anybot-browser.log 2>&1 < /dev/null &
for _ in $(seq 1 60); do
rm -f "$PROFILE"/SingletonLock "$PROFILE"/SingletonSocket "$PROFILE"/SingletonCookie
# (The lock's fd is not handed to the browser: it would hold the lock for its whole life, and the
# 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
sleep 0.25
done

View File

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