Skip to content

[pull] master from jitsi:master - #837

Merged
pull[bot] merged 1 commit into
code:masterfrom
jitsi:master
Aug 19, 2026
Merged

[pull] master from jitsi:master#837
pull[bot] merged 1 commit into
code:masterfrom
jitsi:master

Conversation

@pull

@pull pull Bot commented Aug 19, 2026

Copy link
Copy Markdown

See Commits and Changes for more details.


Created by pull[bot] (v2.0.0-alpha.4)

Can you help keep this open source service alive? 💖 Please sponsor : )

* feat(multi-screen): in-app triggers for the second screen

- add a "Show on second screen" entry to the local, remote and fake
  participant menus, and a hover icon on the screenshare and shared-video
  tiles, which have no menu of their own
- toggle the same trigger to take a source off its screen again, which is
  the only way to close an in-app second screen from the meeting window
- pick the target window by filling a free external screen first, then
  reusing the least recently targeted in-app window; a window the embedder
  opened through the external API is counted but never taken over
- cache the ScreenDetails so a send stays in the click's own task and keeps
  its user activation, and pre-load them on CONFERENCE_JOINED when the
  permission has already been granted, which never prompts on its own
- notify the user when a send fails; until now a blocked popup or a denied
  permission was reported only to the embedder, so an in-app click that
  failed did nothing observable
- resolve a screenshare named by its virtual screenshare participant, so
  the right one is shown when several are live at once

* fix(multi-screen): drop the unreachable second-screen load fast path

- remove the already-loaded check in awaitSecondScreenLoad: the caller always
  opens with the shell URL and navigating a reused named context loads it
  again, so the only document that could match is a stale outgoing one, which
  carries the marker too and would have the handle built on it just before it
  is replaced
- correct the in-flight guard comment, which claimed nothing is lost when a
  concurrent request is dropped; that holds on the success path only, since a
  failed open removes the entry the dropped request had written

* fix(multi-screen): address the review on the in-app triggers

- Derive which screens are occupied from where the second-screen windows
  actually are, not from the screenId their entry was opened with. That id only
  records intent at open time: the browser renumbers ScreenDetails.screens when
  a display comes or goes, a live window is never re-placed, and an entry can
  carry no id at all, so it drifted from reality three different ways and a send
  could open a window fullscreen on top of a live one. Occupancy now reads each
  window's own position, frees the screen of a window closed by hand, and only
  falls back to the stored id for an entry whose window has not opened yet.
- Keep the stored screenId when a setSecondScreen names no screen, so an
  embedder re-sourcing a window it placed explicitly no longer clobbers it.
- Open the window before obtaining the screen details rather than after. On a
  profile that has not granted window-management yet the prompt sat between the
  click and window.open, and Chromium expires transient user activation after
  5s, so a user who read the prompt before pressing Allow had their popup
  blocked. The window is now opened unplaced in the click's own task and moved
  onto its screen once the permission is answered.
- Close an in-app second screen when what it was sent has left the meeting: a
  screenshare that stops, a participant who leaves, a shared video that ends.
  Each trigger lives on the thing it sends, so the only toggle-off disappeared
  with it and the window was left showing an anonymous avatar on black that
  nobody in the meeting could close.
- Render the shared-video trigger's positioned wrapper only when the trigger
  itself renders, so a deployment with the feature off no longer lays an empty
  box over the corner of a tile that is itself the click target for pinning.

* fix(multi-screen): bound the wait on the window-management permission

An open that has to ask for the permission could stay pending forever. A call to
getScreenDetails() does not settle while its prompt is on screen, and a prompt
that is never answered never settles it at all, so the open produced no window,
no error and no event: the embedder is promised an outcome for every
setSecondScreen and got silence. The id also stayed in the in-flight set, so it
could never be opened again for the life of the page.

The prompt is only ever raised where the call carries transient user activation,
which is the in-app triggers. Without it Chromium rejects immediately with
NotAllowedError, which is why the external API path fails fast rather than
hanging, and why headless does too (verified on 151: the API is present, the
permission reports `prompt`, and the call rejects in ~2ms).

The request is now bounded, and the timeout rejects into the existing failure
path, so it is reported as window-management-unavailable exactly like a denial.
The caller cannot tell the two apart, which is correct: either way the window
cannot be placed on another screen.

* fix(multi-screen): address the second review on the in-app triggers

- Close an in-app second screen showing the whiteboard when the whiteboard is
  closed. Its source names no participant, so it is asked for by role like the
  shared video; it also needed a signature of its own, since a whiteboard
  resolves to neither a track nor a participant and signed as the constant
  `avatar:::`, which never changed and so never re-ran the check.

- Stop deriving the window id from a screen index. Occupancy is positional now,
  so an id that also encodes a screen is a second answer to the same question
  that goes stale as soon as a display is undocked: a "that screen is free"
  decision could hand back the id of a window that already exists and re-source
  it, leaving the screen empty. Ids are now minted unique.

- Only take over a window that is actually on an external screen. One the
  browser relocated onto the meeting's own display was still the oldest
  candidate, so a send could land on top of the meeting while the external
  screens kept what they had.

- Treat a window whose position cannot be read as occupying its screen rather
  than as absent, which is what its docstring already promised.

- Place the window off the permission answer instead of after the page load.
  moveTo/resizeTo need no document, and only the load listener has to be
  attached before the load event fires. A profile that has already denied the
  permission now closes the window at once rather than after a whole load, and a
  send cancelled while the prompt is up no longer flies to the other screen a
  moment before it is closed.

* fix(multi-screen): register the second-screen window at load, not at permission

Every finding in the last two review rounds lived in the gap between the window
being open and its handle being in state, because the open held both the entry
and the in-flight guard while it waited on the window-management permission. A
cancel in that gap found no handle for closeSecondScreenHandle to close, so it
reported nothing and left the trigger inverted for up to the full bound, where
master reports secondScreenClosed promptly. A repeat send returned silently at
the guard instead of re-sourcing.

The handle is now registered as soon as the shell page loads, which is the point
at which the window can actually be rendered into and closed. Placement becomes a
tail hanging off the permission answer, holding no guard, so:

- a cancel, a source leaving the meeting and a conference end all take the
  ordinary close path and report secondScreenClosed
- a repeat send finds a live handle and re-sources it
- the guard now spans the page load and nothing else

The tail re-checks ownership by handle identity before it touches anything, so a
window closed and reopened for the same id is neither placed nor torn down by the
open it replaced. A denial or an unanswered prompt still fails the open, closing
the window and reporting window-management-unavailable.

The trade is deliberate: the portal is now built before the permission is known,
so a refusal tears down a live window rather than an empty one, and the window
renders on the meeting's own screen while the prompt is up rather than sitting
blank. Fullscreen moves into the tail behind placement, or the window would fill
the meeting's own screen for as long as the prompt is up.

* fix(multi-screen): report a refused permission as an error alone

- failSecondScreenOpen now detaches the handle before it removes the
  entry, so the middleware's REMOVE handler finds none and the removal
  stays silent
- a denied window-management permission therefore reports
  secondScreenError on its own, as master does, instead of following it
  with a secondScreenClosed that an embedder reopening on that event
  would loop on
- the denial is the only failure that reaches there with a handle in
  state, since it is the one that now comes after registration, but the
  detach covers any later failure the same way
- detaching first also unmounts the portal while the window is still
  open, which is the ordering the REMOVE handler documents
- record why applySource stays at registration: the subscriber calls it
  for every entry with a handle, so deferring the first event behind
  placement would only make it intermittent, and would lose it where
  placement throws on a live window

* fix(multi-screen): give a departed source a grace period

- a source that names a participant is now given 5s to come back before
  its window is closed, since a presence flap or a client rejoining is
  indistinguishable from a departure at the moment the check runs
- closing is one-way, because the trigger left with the tile, so a false
  positive costs a window the user cannot get back, while being slow
  costs a few seconds of a window already showing an avatar
- the countdown is stamped with the send it belongs to, so a window
  closed and reopened, or re-sourced, under a reused in-app id is not
  torn down by the countdown of the send it replaced
- it is not restarted on every refresh, or another window's traffic
  would hold the deadline open indefinitely, and it is cancelled when
  the source comes back or the conference ends
- the whiteboard and the shared video still close at once: those only
  stop when someone stops them, so a delay there would just lag a
  deliberate action
@pull pull Bot locked and limited conversation to collaborators Aug 19, 2026
@pull pull Bot added the ⤵️ pull label Aug 19, 2026
@pull
pull Bot merged commit ec0efb6 into code:master Aug 19, 2026
9 of 10 checks passed
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant