[GH-ISSUE #302] bug: Managed Chrome tab-capture locks onto the controller's own tab, not the content tab (black rectangle / intermittent 409) #215

Closed
opened 2026-10-18 14:03:51 +00:00 by bsakaguchi · 2 comments
Owner

Originally created by @PiefkePaul on GitHub (Sep 22, 2026).
Original GitHub issue: https://github.com/open-webui/computer/issues/302

Summary

A Managed Chrome Browser-Tab (Settings → Websuche → Browsersteuerung → Browser-Tabs → Chrome, with browser.cdp_url left empty so cptr spawns its own managed browser) connects cleanly and reports the correct title, URL, and status: "playing" — but the content area renders as a solid black rectangle. No error is shown anywhere in the UI.

Environment

  • cptr version: 0.9.21
  • OS: Windows 11
  • Chrome: 153.0.8010.53
  • browser.cdp_url: "" (empty, i.e. Managed Chrome), browser.tab_default_mode: chrome

Steps to reproduce

  1. Leave browser.cdp_url empty (Managed Chrome, not Personal).
  2. Open a new Browser tab in cptr's UI (+ → Browser, or Cmd/Ctrl+Shift+B).
  3. Type a real URL (e.g. https://example.com) into the address bar and press Enter.
  4. The tab correctly reports the title/URL and status: "playing", but the content area is a solid black rectangle — confirmed this is a real, successfully-decoded video frame (sampled the frontend <canvas> pixel data: uniform, fully-opaque rgb(17,17,17)), not a stream that never connected.

Also intermittently reproducible as a 409 Conflict on POST /api/browser/sessions instead of the black screen (empty-string exception body — asyncio.TimeoutError on viewer.encoder_configured.wait(), which only gets 5s for a managed host vs. 90s for external/Personal) — see root cause below for why the two symptoms are the same underlying bug.

Root cause

ChromeViewerManager.start() in cptr/utils/browser/viewer.py creates the content ("target") tab like this:

marker = f"data:text/html,<title>{quote(CAPTURE_TITLE)}</title>"
target_id, target_cdp = await host.create_target(marker)

CAPTURE_TITLE is "Open WebUI Computer Browser". The managed Chrome host is launched with --auto-select-tab-capture-source-by-title="Open WebUI Computer Browser" specifically so tab-capture can auto-select this tab without a picker dialog.

Target.createTarget silently refuses to navigate a brand-new target directly to a data: URL on current Chrome — the tab is created and just stays at about:blank. Confirmed via a raw CDP probe on the live target: Page.getNavigationHistory showed a single about:blank entry, with no attempt at the marker URL at all.

The only tab that actually ends up carrying the literal title "Open WebUI Computer Browser" is the controller tab (browser-encoder.html), because that exact string is hard-coded in its own <title> tag:

<head><meta charset="utf-8"><title>Open WebUI Computer Browser</title></head>

So --auto-select-tab-capture-source-by-title ends up matching the controller's own tab instead of the real content tab — the encoder faithfully captures its own near-blank driver page. That's why the session still reports playing with the correct title/URL (those come from cptr's session metadata, not from the tab actually being captured) while the visible content is uniformly blank/dark.

The same defect explains the 409s too: once one attempt's target/controller tabs leak without being closed (a separate, lower-priority cleanup bug — ChromeHost.close_target() swallows errors via contextlib.suppress(Exception)), a second attempt creates another tab sharing the identical hard-coded title, and Chrome's title-based auto-select has no single unambiguous match to resolve — sometimes it still resolves fast enough (black-rectangle "success"), sometimes it doesn't resolve before the 5s encoder_configured timeout (409).

Fix (tested, see branch)

# before
marker = f"data:text/html,<title>{quote(CAPTURE_TITLE)}</title>"
target_id, target_cdp = await host.create_target(marker)
await target_cdp.send("Page.enable")
await target_cdp.send("Runtime.enable")

# after
marker = f"data:text/html,<title>{quote(CAPTURE_TITLE)}</title>"
target_id, target_cdp = await host.create_target("about:blank")
await target_cdp.send("Page.enable")
await target_cdp.send("Runtime.enable")
await target_cdp.send("Page.navigate", {"url": marker})

Page.navigate to a data: URL on an already-open tab works fine even though Target.createTarget with the same URL doesn't (verified via CDP probe). So the target tab now briefly and correctly carries CAPTURE_TITLE before the real requested URL loads, giving the auto-select flag exactly one unambiguous match.

Verified end-to-end through cptr's real UI (not just the raw API): opened a Browser tab, typed https://example.com, pressed Enter — the tab now renders the actual page content correctly. Before the fix, the identical steps produced the solid black rectangle. Also ran several consecutive fresh-tab creations after the fix with no encoder_configured timeout/409.

This isn't Chrome-version-specific flakiness — Chrome has silently redirected new-tab/new-window top-level data: URL navigations to about:blank (no error, no event fired) since Chrome 61 (2017): chromestatus.com feature 5669602927312896, blink-dev Intent-to-Deprecate thread. cptr's code just hasn't hit this particular code path tested against that behavior before.

#240 ("Managed Chrome startup failed:") hits the same generic exception-wrapper log line (any exception in _host()/start() produces that exact string, so it's not diagnostic by itself), but its own posted patched source shows the actual fix there adds an Xvfb/--no-sandbox fallback for a headless Linux server with no display at all — _host() never finding a working display. This bug reproduces on a normal Windows desktop with a real, working display and Chrome launching fine every time, so it's a different root cause than #240's. Cross-linking since the log line is identical and might otherwise look like a dupe.

Branch

Same situation as #301 (collaborators-only PR policy on this repo prevents opening an actual PR from a fork) — the tested diff is here if a maintainer wants to cherry-pick or merge it directly:

https://github.com/open-webui/computer/compare/main...PiefkePaul💻fix/managed-chrome-data-url-target-title

Branch: PiefkePaul/computer@fix/managed-chrome-data-url-target-title (1 file changed, 7 insertions, 1 deletion). Happy to be added as a collaborator to open it properly, or adjust the diff if you'd like it done differently.

Separate, not-yet-fixed cleanup issue (lower priority, not blocking the fix above)

The target tab isn't reliably closed on a failed start() attempt — about:blank tabs were observed accumulating in the managed Chrome host's /json/list across repeated failed attempts, while the controller tab was usually cleaned up. ChromeHost.close_target() wraps its CDP call in contextlib.suppress(Exception), so a failure there is silent. Didn't dig further since it doesn't block the fix above (Managed Chrome now succeeds reliably on the first try), but flagging it in case it's a quick related fix.

Originally created by @PiefkePaul on GitHub (Sep 22, 2026). Original GitHub issue: https://github.com/open-webui/computer/issues/302 ## Summary A Managed Chrome Browser-Tab (Settings → Websuche → Browsersteuerung → Browser-Tabs → Chrome, with `browser.cdp_url` left empty so cptr spawns its own managed browser) connects cleanly and reports the correct title, URL, and `status: "playing"` — but the content area renders as a solid black rectangle. No error is shown anywhere in the UI. ## Environment - cptr version: 0.9.21 - OS: Windows 11 - Chrome: 153.0.8010.53 - `browser.cdp_url`: `""` (empty, i.e. Managed Chrome), `browser.tab_default_mode`: `chrome` ## Steps to reproduce 1. Leave `browser.cdp_url` empty (Managed Chrome, not Personal). 2. Open a new Browser tab in cptr's UI (`+` → Browser, or Cmd/Ctrl+Shift+B). 3. Type a real URL (e.g. `https://example.com`) into the address bar and press Enter. 4. The tab correctly reports the title/URL and `status: "playing"`, but the content area is a solid black rectangle — confirmed this is a real, successfully-decoded video frame (sampled the frontend `<canvas>` pixel data: uniform, fully-opaque `rgb(17,17,17)`), not a stream that never connected. Also intermittently reproducible as a `409 Conflict` on `POST /api/browser/sessions` instead of the black screen (empty-string exception body — `asyncio.TimeoutError` on `viewer.encoder_configured.wait()`, which only gets 5s for a managed host vs. 90s for `external`/Personal) — see root cause below for why the two symptoms are the same underlying bug. ## Root cause `ChromeViewerManager.start()` in `cptr/utils/browser/viewer.py` creates the content ("target") tab like this: ```python marker = f"data:text/html,<title>{quote(CAPTURE_TITLE)}</title>" target_id, target_cdp = await host.create_target(marker) ``` `CAPTURE_TITLE` is `"Open WebUI Computer Browser"`. The managed Chrome host is launched with `--auto-select-tab-capture-source-by-title="Open WebUI Computer Browser"` specifically so tab-capture can auto-select this tab without a picker dialog. **`Target.createTarget` silently refuses to navigate a brand-new target directly to a `data:` URL on current Chrome** — the tab is created and just stays at `about:blank`. Confirmed via a raw CDP probe on the live target: `Page.getNavigationHistory` showed a single `about:blank` entry, with no attempt at the marker URL at all. The only tab that actually ends up carrying the literal title `"Open WebUI Computer Browser"` is the **controller** tab (`browser-encoder.html`), because that exact string is hard-coded in its own `<title>` tag: ```html <head><meta charset="utf-8"><title>Open WebUI Computer Browser</title></head> ``` So `--auto-select-tab-capture-source-by-title` ends up matching the **controller's own tab** instead of the real content tab — the encoder faithfully captures its own near-blank driver page. That's why the session still reports `playing` with the correct title/URL (those come from cptr's session metadata, not from the tab actually being captured) while the visible content is uniformly blank/dark. The same defect explains the 409s too: once one attempt's target/controller tabs leak without being closed (a separate, lower-priority cleanup bug — `ChromeHost.close_target()` swallows errors via `contextlib.suppress(Exception)`), a second attempt creates *another* tab sharing the identical hard-coded title, and Chrome's title-based auto-select has no single unambiguous match to resolve — sometimes it still resolves fast enough (black-rectangle "success"), sometimes it doesn't resolve before the 5s `encoder_configured` timeout (409). ## Fix (tested, see branch) ```python # before marker = f"data:text/html,<title>{quote(CAPTURE_TITLE)}</title>" target_id, target_cdp = await host.create_target(marker) await target_cdp.send("Page.enable") await target_cdp.send("Runtime.enable") # after marker = f"data:text/html,<title>{quote(CAPTURE_TITLE)}</title>" target_id, target_cdp = await host.create_target("about:blank") await target_cdp.send("Page.enable") await target_cdp.send("Runtime.enable") await target_cdp.send("Page.navigate", {"url": marker}) ``` `Page.navigate` to a `data:` URL on an *already-open* tab works fine even though `Target.createTarget` with the same URL doesn't (verified via CDP probe). So the target tab now briefly and correctly carries `CAPTURE_TITLE` before the real requested URL loads, giving the auto-select flag exactly one unambiguous match. **Verified end-to-end through cptr's real UI** (not just the raw API): opened a Browser tab, typed `https://example.com`, pressed Enter — the tab now renders the actual page content correctly. Before the fix, the identical steps produced the solid black rectangle. Also ran several consecutive fresh-tab creations after the fix with no `encoder_configured` timeout/409. This isn't Chrome-version-specific flakiness — Chrome has silently redirected new-tab/new-window top-level `data:` URL navigations to `about:blank` (no error, no event fired) since **Chrome 61 (2017)**: [chromestatus.com feature 5669602927312896](https://chromestatus.com/feature/5669602927312896), [blink-dev Intent-to-Deprecate thread](https://groups.google.com/a/chromium.org/g/blink-dev/c/GbVcuwg_QjM). `cptr`'s code just hasn't hit this particular code path tested against that behavior before. ## Related but distinct: #240 #240 ("Managed Chrome startup failed:") hits the same generic exception-wrapper log line (any exception in `_host()`/`start()` produces that exact string, so it's not diagnostic by itself), but its own posted patched source shows the actual fix there adds an Xvfb/`--no-sandbox` fallback for a headless Linux server with no display at all — `_host()` never finding a working display. This bug reproduces on a normal Windows desktop with a real, working display and Chrome launching fine every time, so it's a different root cause than #240's. Cross-linking since the log line is identical and might otherwise look like a dupe. ## Branch Same situation as #301 (collaborators-only PR policy on this repo prevents opening an actual PR from a fork) — the tested diff is here if a maintainer wants to cherry-pick or merge it directly: https://github.com/open-webui/computer/compare/main...PiefkePaul:computer:fix/managed-chrome-data-url-target-title Branch: `PiefkePaul/computer@fix/managed-chrome-data-url-target-title` (1 file changed, 7 insertions, 1 deletion). Happy to be added as a collaborator to open it properly, or adjust the diff if you'd like it done differently. ## Separate, not-yet-fixed cleanup issue (lower priority, not blocking the fix above) The target tab isn't reliably closed on a failed `start()` attempt — `about:blank` tabs were observed accumulating in the managed Chrome host's `/json/list` across repeated failed attempts, while the controller tab was usually cleaned up. `ChromeHost.close_target()` wraps its CDP call in `contextlib.suppress(Exception)`, so a failure there is silent. Didn't dig further since it doesn't block the fix above (Managed Chrome now succeeds reliably on the first try), but flagging it in case it's a quick related fix.
Author
Owner

@owui-terminator[bot] commented on GitHub (Sep 22, 2026):

🔍 Related Issues Found

I found some existing issues that might be related. Please check if any of these are duplicates or contain helpful solutions:

  1. 🟢 #301 bug: Personal Chrome connects and shares, but encoder never emits a keyframe — connect_personal times out after 90s with an empty error
    Related at the transport/session layer: it also ends in an empty HTTP 409 after waiting for the encoder, and both issues involve the browser tab/encoder flow. However, #301 is about Personal Chrome never producing a keyframe, so it is a different root cause than the managed-tab title/capture bug here.
    by PiefkePaul

  2. 🟢 #240 bug: Managed Chrome startup failed:
    Related only in the sense that it uses the same generic Managed Chrome startup failed: wrapper and the managed Chrome path, but the body shows a different failure mode (headless/Xvfb/sandbox startup on Linux). I’d cross-link it because users may confuse the logs, but it is not the same bug as the black tab-capture/auto-select issue.
    by pixel4696


💡 If your issue is a duplicate, please close it and add any additional details to the existing issue instead.

This comment was generated automatically. React with 👍 if helpful, 👎 if not.

<!-- gh-comment-id:5778753610 --> @owui-terminator[bot] commented on GitHub (Sep 22, 2026): <!-- terminator-bot:related-issues-reply --> 🔍 **Related Issues Found** I found some existing issues that might be related. Please check if any of these are duplicates or contain helpful solutions: 1. 🟢 [#301](https://github.com/open-webui/computer/issues/301) **bug: Personal Chrome connects and shares, but encoder never emits a keyframe — connect_personal times out after 90s with an empty error** *Related at the transport/session layer: it also ends in an empty `HTTP 409` after waiting for the encoder, and both issues involve the browser tab/encoder flow. However, #301 is about Personal Chrome never producing a keyframe, so it is a different root cause than the managed-tab title/capture bug here.* *by PiefkePaul* 2. 🟢 [#240](https://github.com/open-webui/computer/issues/240) **bug: Managed Chrome startup failed:** *Related only in the sense that it uses the same generic `Managed Chrome startup failed:` wrapper and the managed Chrome path, but the body shows a different failure mode (headless/Xvfb/sandbox startup on Linux). I’d cross-link it because users may confuse the logs, but it is not the same bug as the black tab-capture/auto-select issue.* *by pixel4696* --- 💡 If your issue is a duplicate, please close it and add any additional details to the existing issue instead. *This comment was generated automatically.* React with 👍 if helpful, 👎 if not.
Author
Owner

@PiefkePaul commented on GitHub (Sep 22, 2026):

Correction: closing this — the "black rectangle" symptom is not actually a bug.

I went back and ran the missing control test before assuming causation: reverted my own patch, ran the completely unmodified original viewer.py, opened a fresh Managed Chrome Browser-Tab, and typed a real URL into the address bar instead of leaving it on the placeholder. Result: it renders correctly, every time, on the stock code — no black rectangle. The black content area only ever appeared for a genuinely blank, not-yet-navigated tab (the address bar showing https://example.com was its placeholder attribute, not an actual loaded value — my mistake, and apparently the same mistake in the original report this built on). That's just what an unnavigated about:blank tab looks like in this viewer; it's not a defect.

The one technical observation underneath is still real and independently confirmed (Target.createTarget really does silently drop a data: URL and leave the tab at about:blank — verified via a raw CDP probe, and matches Chrome's documented behavior for new-tab/new-window top-level data: navigations since Chrome 61, chromestatus.com feature 5669602927312896). But it turns out to have no visible effect here: tab-capture invoked via getDisplayMedia from the controller page apparently can't select its own calling tab as a capture source regardless of title matching, so it resolves to the correct content tab either way. My causal chain from that real observation to the reported symptom was wrong — plausible-sounding, but not what's actually happening. Sorry for the noise, and thanks for the patience.

Since I can't delete this issue myself (no collaborator access), closing it here instead.

<!-- gh-comment-id:5779114149 --> @PiefkePaul commented on GitHub (Sep 22, 2026): Correction: closing this — the "black rectangle" symptom is **not actually a bug**. I went back and ran the missing control test before assuming causation: reverted my own patch, ran the completely unmodified original `viewer.py`, opened a fresh Managed Chrome Browser-Tab, and typed a real URL into the address bar instead of leaving it on the placeholder. Result: it renders correctly, every time, on the stock code — no black rectangle. The black content area only ever appeared for a genuinely blank, not-yet-navigated tab (the address bar showing `https://example.com` was its `placeholder` attribute, not an actual loaded value — my mistake, and apparently the same mistake in the original report this built on). That's just what an unnavigated `about:blank` tab looks like in this viewer; it's not a defect. The one technical observation underneath is still real and independently confirmed (`Target.createTarget` really does silently drop a `data:` URL and leave the tab at `about:blank` — verified via a raw CDP probe, and matches Chrome's documented behavior for new-tab/new-window top-level `data:` navigations since Chrome 61, [chromestatus.com feature 5669602927312896](https://chromestatus.com/feature/5669602927312896)). But it turns out to have **no visible effect** here: tab-capture invoked via `getDisplayMedia` from the controller page apparently can't select its own calling tab as a capture source regardless of title matching, so it resolves to the correct content tab either way. My causal chain from that real observation to the reported symptom was wrong — plausible-sounding, but not what's actually happening. Sorry for the noise, and thanks for the patience. Since I can't delete this issue myself (no collaborator access), closing it here instead.
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
open-webui/computer#215
No description provided.