Compare commits

..

1 Commits

Author SHA1 Message Date
Robin
d33dedd038 Hide the browser's default placeholder image for videos
Source: https://stackoverflow.com/a/79058682
2026-07-15 17:26:02 +02:00
9 changed files with 69 additions and 62 deletions

View File

@@ -13,7 +13,7 @@ services:
- ./backend/playwright_homeserver-othersite.yaml:/data/cfg/homeserver.yaml:Z
element-web:
# Pin to a SHA so that upstream cannot break our tests. Renovate handles regular updates.
image: ghcr.io/element-hq/element-web:develop@sha256:d1841828a390d96da2ee64232949db641f11766fb506ec561374819d10533b9a
image: ghcr.io/element-hq/element-web:develop@sha256:365eca844f0782676f2406e5107e2f74fae2ecdb1725cc42060a91681aeee4c4
element-web-1:
# Pin to a SHA so that upstream cannot break our tests. Renovate handles regular updates.
image: ghcr.io/element-hq/element-web:develop@sha256:d1841828a390d96da2ee64232949db641f11766fb506ec561374819d10533b9a
image: ghcr.io/element-hq/element-web:develop@sha256:365eca844f0782676f2406e5107e2f74fae2ecdb1725cc42060a91681aeee4c4

View File

@@ -73,6 +73,9 @@ export class TestHelpers {
audioOnly: boolean = false,
isDM: boolean = false,
): Promise<void> {
// XXX This using the notification toast to join the room.
// Not the button in the header
await page.waitForTimeout(3000);
const label = isDM
? audioOnly
@@ -82,9 +85,7 @@ export class TestHelpers {
await expect(page.getByText(label)).toBeVisible({
timeout: 10000,
});
// XXX This using the notification toast to join the room.
// Not the buttons in the header or timeline
await page.getByRole("alert").getByRole("button", { name: "Join" }).click({
await page.getByRole("button", { name: "Join" }).click({
timeout: 5000,
});
}

View File

@@ -39,10 +39,7 @@ widgetTest(
).toBeVisible();
await expect(whistler.page.getByText("Incoming voice call")).toBeVisible();
await whistler.page
.getByRole("alert")
.getByRole("button", { name: "Join" })
.click();
await whistler.page.getByRole("button", { name: "Join" }).click();
await expect(
whistler.page.locator('iframe[title="Element Call"]'),
@@ -132,10 +129,7 @@ widgetTest(
).toBeVisible();
await expect(whistler.page.getByText("Incoming video call")).toBeVisible();
await whistler.page
.getByRole("alert")
.getByRole("button", { name: "Join" })
.click();
await whistler.page.getByRole("button", { name: "Join" }).click();
await expect(
whistler.page.locator('iframe[title="Element Call"]'),

71
pnpm-lock.yaml generated
View File

@@ -10,13 +10,11 @@ overrides:
tar: ^7.5.11
minimatch: ^10.2.3
glob: ^10.5.0
qs: ^6.15.2
js-yaml: ^4.2.0
qs: ^6.14.1
js-yaml: ^4.1.1
esbuild: ^0.28.0
undici: ^6.27.0
undici: ^6.24.0
flatted: ^3.4.2
ws: ^8.21.0
dompurify: ^3.4.11
importers:
@@ -4022,8 +4020,8 @@ packages:
resolution: {integrity: sha512-GrwoxYN+uWlzO8uhUXRl0P+kHE4GtVPfYzVLcUxPL7KNdHKj66vvlhiweIHqYYXWlw+T8iLMp42Lm67ghw4WMQ==}
engines: {node: '>= 4'}
dompurify@3.4.12:
resolution: {integrity: sha512-zQvGet8Z2sWbQhCmfFz/T5QWH2oBmjnqK3qvOjaqaNLrLEF912WamU+ohnTp0TCep/MFVHpdJuCZEdFOdTnEFg==}
dompurify@3.4.5:
resolution: {integrity: sha512-OrwIBKsdNSVEeubdJ1HBv/wNENRM9ytAVCv7YXt//A3vPdVMNuACRqK9mXCGCBW2ln7BT/A4X0jXHo2Gu89miA==}
domutils@2.8.0:
resolution: {integrity: sha512-w96Cjofp72M5IIhpjgobBimYEfoPjx1Vx0BSX9P30WBdZW2WIKU0T1Bd0kz2eNZ9ikjKgHbEyKx8BB6H1L3h3A==}
@@ -4645,6 +4643,10 @@ packages:
js-tokens@4.0.0:
resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==}
js-yaml@4.1.1:
resolution: {integrity: sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==}
hasBin: true
js-yaml@4.3.0:
resolution: {integrity: sha512-1td788aAnnZ5qs7V2QIRl1owjtYpbKt749Y3xauqQgwIIGF/xXWz1wMTEBx5O3LK3lXLVuqXPdPxj2BoFHaW9Q==}
hasBin: true
@@ -5421,8 +5423,8 @@ packages:
engines: {node: '>=10.13.0'}
hasBin: true
qs@6.15.3:
resolution: {integrity: sha512-O9gl3zCl5h5blw1KGUzQKhA5oUXSl8rwUIM5o0S3nCXMliSvy5Dzx7/DJcI+SwgICv+IneSZwhBh1oSyEHA71A==}
qs@6.15.1:
resolution: {integrity: sha512-6YHEFRL9mfgcAvql/XhwTvf5jKcOiiupt2FiJxHkiX1z4j7WL8J/jRHYLluORvc1XxB5rV20KoeK00gVJamspg==}
engines: {node: '>=0.6'}
query-selector-shadow-dom@1.0.1:
@@ -5732,8 +5734,8 @@ packages:
resolution: {integrity: sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==}
engines: {node: '>= 0.4'}
side-channel@1.1.1:
resolution: {integrity: sha512-6x6dK6zJdpTzF4sQeNYxwtvBzf6Eg4GtlesS94HOvTudUeyK2WXAaIfmDgsyslYrRBeFIlsi54AYsFGUuhmvrQ==}
side-channel@1.1.0:
resolution: {integrity: sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==}
engines: {node: '>= 0.4'}
siginfo@2.0.0:
@@ -5989,8 +5991,8 @@ packages:
undici-types@7.18.2:
resolution: {integrity: sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w==}
undici@6.27.0:
resolution: {integrity: sha512-YmfV3YnEDzXRC5lZ2jWtWWHKGUm1zIt8AhesR1tens+HTNv+YZlN/dp6G727LOvMJ8xjP9Be7Y2Sdr96LDm+pg==}
undici@6.25.0:
resolution: {integrity: sha512-ZgpWDC5gmNiuY9CnLVXEH8rl50xhRCuLNA97fAUnKi8RRuV4E6KG31pDTsLVUKnohJE0I3XDrTeEydAXRw47xg==}
engines: {node: '>=18.17'}
unhomoglyph@1.0.6:
@@ -6291,6 +6293,18 @@ packages:
wrappy@1.0.2:
resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==}
ws@8.20.0:
resolution: {integrity: sha512-sAt8BhgNbzCtgGbt2OxmpuryO63ZoDk/sqaB/znQm94T4fCEsy/yV+7CdC1kJhOU9lboAEU7R3kquuycDoibVA==}
engines: {node: '>=10.0.0'}
peerDependencies:
bufferutil: ^4.0.1
utf-8-validate: '>=5.0.2'
peerDependenciesMeta:
bufferutil:
optional: true
utf-8-validate:
optional: true
ws@8.21.0:
resolution: {integrity: sha512-Vsp28b7DRcimFQvrqu2Wek3z1iYxDCWqHYB8Qsnk/S4RfaCQzPGPyBNuVjJV3cd6UiKtUtp6sNM77gWvzcCH+g==}
engines: {node: '>=10.0.0'}
@@ -6375,12 +6389,12 @@ snapshots:
'@octokit/plugin-rest-endpoint-methods': 10.4.1(@octokit/core@5.2.2)
'@octokit/request': 8.4.1
'@octokit/request-error': 5.1.1
undici: 6.27.0
undici: 6.25.0
'@actions/http-client@2.2.3':
dependencies:
tunnel: 0.0.6
undici: 6.27.0
undici: 6.25.0
'@actions/io@1.1.3': {}
@@ -9528,7 +9542,7 @@ snapshots:
cosmiconfig@8.3.6(typescript@5.9.3):
dependencies:
import-fresh: 3.3.1
js-yaml: 4.3.0
js-yaml: 4.1.1
parse-json: 5.2.0
path-type: 4.0.0
optionalDependencies:
@@ -9699,7 +9713,7 @@ snapshots:
dependencies:
domelementtype: 2.3.0
dompurify@3.4.12:
dompurify@3.4.5:
optionalDependencies:
'@types/trusted-types': 2.0.7
@@ -10380,6 +10394,10 @@ snapshots:
js-tokens@4.0.0: {}
js-yaml@4.1.1:
dependencies:
argparse: 2.0.1
js-yaml@4.3.0:
dependencies:
argparse: 2.0.1
@@ -10404,7 +10422,7 @@ snapshots:
whatwg-encoding: 3.1.1
whatwg-mimetype: 4.0.0
whatwg-url: 14.2.0
ws: 8.21.0
ws: 8.20.0
xml-name-validator: 5.0.0
transitivePeerDependencies:
- bufferutil
@@ -11281,7 +11299,7 @@ snapshots:
'@posthog/core': 1.29.3
'@posthog/types': 1.374.0
core-js: 3.49.0
dompurify: 3.4.12
dompurify: 3.4.5
fflate: 0.4.8
preact: 10.29.1
query-selector-shadow-dom: 1.0.1
@@ -11346,10 +11364,9 @@ snapshots:
pngjs: 5.0.0
yargs: 15.4.1
qs@6.15.3:
qs@6.15.1:
dependencies:
es-define-property: 1.0.1
side-channel: 1.1.1
side-channel: 1.1.0
query-selector-shadow-dom@1.0.1: {}
@@ -11685,7 +11702,7 @@ snapshots:
object-inspect: 1.13.4
side-channel-map: 1.0.1
side-channel@1.1.1:
side-channel@1.1.0:
dependencies:
es-errors: 1.3.0
object-inspect: 1.13.4
@@ -11924,7 +11941,7 @@ snapshots:
undici-types@7.18.2: {}
undici@6.27.0: {}
undici@6.25.0: {}
unhomoglyph@1.0.6: {}
@@ -11968,7 +11985,7 @@ snapshots:
url@0.11.4:
dependencies:
punycode: 1.4.1
qs: 6.15.3
qs: 6.15.1
use-callback-ref@1.3.3(@types/react@19.2.17)(react@19.2.7):
dependencies:
@@ -12018,7 +12035,7 @@ snapshots:
vite-plugin-generate-file@0.3.1:
dependencies:
ejs: 3.1.10
js-yaml: 4.3.0
js-yaml: 4.1.1
mime-types: 2.1.35
picocolors: 1.1.1
@@ -12210,6 +12227,8 @@ snapshots:
wrappy@1.0.2: {}
ws@8.20.0: {}
ws@8.21.0: {}
wsl-utils@0.1.0:

View File

@@ -29,30 +29,12 @@ overrides:
# Security fixes:
# - https://github.com/element-hq/element-call/security/dependabot/110
# - https://github.com/element-hq/element-call/security/dependabot/122
# - https://github.com/element-hq/element-call/security/dependabot/179
"qs": "^6.15.2"
# Security fixes:
# - https://github.com/element-hq/element-call/security/dependabot/106
# - https://github.com/element-hq/element-call/security/dependabot/203
"js-yaml": "^4.2.0"
"qs": "^6.14.1"
# Security fix: https://github.com/element-hq/element-call/security/dependabot/106
"js-yaml": "^4.1.1"
# Storybook declares support for 0.27.0 only but empirically works fine with 0.28.0.
"esbuild": "^0.28.0"
# Security fixes:
# - https://github.com/nodejs/undici/releases/tag/v6.24.0
# - https://github.com/element-hq/element-call/security/dependabot/201
# - https://github.com/element-hq/element-call/security/dependabot/199
"undici": "^6.27.0"
# Multiple security fixes: https://github.com/nodejs/undici/releases/tag/v6.24.0
"undici": "^6.24.0"
# Security fix: https://github.com/advisories/GHSA-rf6f-7fwh-wjgh
"flatted": "^3.4.2"
# Security fix: https://github.com/element-hq/element-call/security/dependabot/184
"ws": "^8.21.0"
# Security fixes:
# - https://github.com/element-hq/element-call/security/dependabot/191
# - https://github.com/element-hq/element-call/security/dependabot/190
# - https://github.com/element-hq/element-call/security/dependabot/192
# - https://github.com/element-hq/element-call/security/dependabot/194
# - https://github.com/element-hq/element-call/security/dependabot/198
# - https://github.com/element-hq/element-call/security/dependabot/196
# - https://github.com/element-hq/element-call/security/dependabot/195
# - https://github.com/element-hq/element-call/security/dependabot/193
"dompurify": "^3.4.11"

Binary file not shown.

After

Width:  |  Height:  |  Size: 78 B

View File

@@ -14,6 +14,7 @@ import { useTranslation } from "react-i18next";
import { TileAvatar } from "../tile/TileAvatar";
import styles from "./VideoPreview.module.css";
import { type EncryptionSystem } from "../e2ee/sharedKeyManagement";
import videoPlaceholder from "../graphics/video-placeholder.gif";
export type MatrixInfo = {
userId: string;
@@ -74,6 +75,9 @@ export const VideoPreview: FC<Props> = ({
// There's no reason for this to be focusable
tabIndex={-1}
disablePictureInPicture
// Set the placeholder to a small transparent image. (On Android web
// views the default poster image is particularly ugly.)
poster={videoPlaceholder}
/>
{(!videoEnabled || cameraIsStarting) && (
<>

View File

@@ -49,6 +49,7 @@ exports[`LobbyView > renders with AppBar android 1`] = `
<video
disablepictureinpicture=""
playsinline=""
poster="/src/graphics/video-placeholder.gif"
tabindex="-1"
/>
<div
@@ -280,6 +281,7 @@ exports[`LobbyView > renders with AppBar ios 1`] = `
<video
disablepictureinpicture=""
playsinline=""
poster="/src/graphics/video-placeholder.gif"
tabindex="-1"
/>
<div
@@ -558,6 +560,7 @@ exports[`LobbyView > renders with header and participant count 1`] = `
<video
disablepictureinpicture=""
playsinline=""
poster="/src/graphics/video-placeholder.gif"
tabindex="-1"
/>
<div

View File

@@ -25,6 +25,7 @@ import {
import { type ReactionOption } from "../reactions";
import { ReactionIndicator } from "../reactions/ReactionIndicator";
import { RTCConnectionStats } from "../RTCConnectionStats";
import videoPlaceholder from "../graphics/video-placeholder.gif";
interface Props extends ComponentProps<typeof animated.div> {
className?: string;
@@ -154,6 +155,9 @@ export const MediaView: FC<Props> = ({
tabIndex={-1}
disablePictureInPicture
data-testid="video"
// Set the placeholder to a small transparent image. (On Android web
// views the default poster image is particularly ugly.)
poster={videoPlaceholder}
/>
)}
</div>