Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 7 additions & 4 deletions docs/decisions.md
Original file line number Diff line number Diff line change
Expand Up @@ -5220,10 +5220,13 @@ F38 points 1, 5 and 6 as stated below; points 2–4 and 7 stand.
- The service listens on loopback only. An agent of the Environment already reaches
the browser's DevTools, so loopback adds no reach.
9. **A closed person's tab closes its remote tab** (0191 point 11) after a grace of 30
seconds without any viewer. This applies only to a tab the view itself opened, and
only when no agent-browser session is bound to it (`<socket dir>/<session>.target`,
as F38 point 3 reads it). A view that is opened again within the grace keeps the
tab. After a restart of the service the record is gone, and nothing is closed.
seconds without any viewer. This applies to a tab the view itself opened and to a
tab or pop-up a page opened, and only when no agent-browser session is bound to it
(`<socket dir>/<session>.target`, as F38 point 3 reads it). Such a tab that nobody
ever views closes after 10 minutes, so pages' new tabs do not pile up in the
Environment's memory. An agent's own window is never closed by the view. A view
that is opened again within the grace keeps the tab. After a restart of the
service the record is gone, and nothing is closed.
10. **The service replaces the dashboard unit.**
- `lazurio-browser-view.service` runs `<selector> browser serve --port <port>
--origin <origin>` as a long-running service (`Type=simple`,
Expand Down
14 changes: 8 additions & 6 deletions src/browser/people/hub.ts
Original file line number Diff line number Diff line change
Expand Up @@ -197,8 +197,10 @@ export class BrowserHub {
private reconnectTimer: ReturnType<typeof setTimeout> | null = null;
private readonly tabs = new Map<string, Tab>();
private readonly bySession = new Map<string, string>();
/** Tabs the view opened, or a page opened from one: the only tabs the
* view ever closes (F39 point 9). Lost on restart, on purpose. */
/** Tabs the view opened or a page opened: the only tabs the view ever
* closes, and only while no agent-browser session is bound to them (F39
* point 9). An agent's own window is never one of them. Lost on restart,
* on purpose. */
private readonly ownTabs = new Set<string>();
private readonly fileChoosers = new Map<string, FileChooser>();
/** Page targets discovery reported on the current connection. */
Expand Down Expand Up @@ -668,10 +670,10 @@ export class BrowserHub {
* address, so it is announced at its first address, or after 1.5 s. */
private opened(tab: Tab, openerId: string): void {
if (!this.tabs.has(openerId)) return;
if (this.ownTabs.has(openerId)) {
this.ownTabs.add(tab.targetId);
this.scheduleGrace(tab, this.unviewedGraceMs);
}
// A page's new tab or pop-up is nobody's working window unless an agent
// binds a session to it: it closes when nobody views it (F39 point 9).
this.ownTabs.add(tab.targetId);
this.scheduleGrace(tab, this.unviewedGraceMs);
if (tab.info.url !== "" && tab.info.url !== "about:blank") {
this.announce(tab, openerId);
return;
Expand Down
32 changes: 32 additions & 0 deletions tests/environment-browser-view.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -880,6 +880,38 @@ test("an own tab an agent bound itself to stays open; an own tab nobody opened c
).toBe(false);
});

test("a tab a page opened closes when nobody views it, unless an agent is bound to it; the agent's own tab stays", async () => {
// id(1) is an agent's window (not opened by the view); its page opens two
// tabs, and an agent-browser session is bound to the second.
const browser = new FakeBrowser([tab(1)]);
await serve(browser, { graceMs: 40, bound: [id(3)] });
browser.created(
tab(2, {
url: "https://example.com/a",
openerId: id(1),
canAccessOpener: false,
}),
);
browser.created(
tab(3, {
url: "https://example.com/b",
openerId: id(1),
canAccessOpener: false,
}),
);
await until(() =>
browser
.called("Target.closeTarget")
.some((c) => c.params.targetId === id(2)),
);
await settle(120);
const closed = browser
.called("Target.closeTarget")
.map((c) => c.params.targetId);
expect(closed).not.toContain(id(3));
expect(closed).not.toContain(id(1));
});

test("a tab that goes away tells its viewers", async () => {
const browser = new FakeBrowser([tab(1)]);
const { base } = await serve(browser);
Expand Down
Loading