git: 30709f1635dd - main - net-im/signal-desktop: Add new port

From: Mikael Urankar <mikael_at_FreeBSD.org>
Date: Thu, 23 Jun 2022 11:38:24 UTC
The branch main has been updated by mikael:

URL: https://cgit.FreeBSD.org/ports/commit/?id=30709f1635ddd14aa26e18d7ed91bc61d68aa388

commit 30709f1635ddd14aa26e18d7ed91bc61d68aa388
Author:     Mikael Urankar <mikael@FreeBSD.org>
AuthorDate: 2022-06-23 11:22:35 +0000
Commit:     Mikael Urankar <mikael@FreeBSD.org>
CommitDate: 2022-06-23 11:38:17 +0000

    net-im/signal-desktop: Add new port
    
    Signal is a cross-platform centralized encrypted messaging service developed
    by the Signal Technology Foundation and Signal Messenger LLC.
    It uses the Internet to send one-to-one and group messages, which can include
    files, voice notes, images and videos. It can also be used to make one-to-one
    and group voice and video calls,[16][17] and the Android version can optionally
    function as an SMS app
    
    WWW: https://signal.org/
    
    Special thanks to: tagattie@ for maintaining the electron ports
---
 net-im/Makefile                                    |   1 +
 net-im/signal-desktop/Makefile                     | 183 +++++
 net-im/signal-desktop/distinfo                     |  11 +
 net-im/signal-desktop/files/patch-packages.json    |  20 +
 .../files/patch-patches_better-sqlite3+7.5.0.patch |  20 +
 .../patch-patches_electron-builder+23.0.1.patch    |  16 +
 net-im/signal-desktop/files/patch-signal-desktop   | 183 +++++
 net-im/signal-desktop/files/patch-yarn.lock        |  17 +
 net-im/signal-desktop/files/playwrigth-registry.js | 753 +++++++++++++++++++++
 net-im/signal-desktop/files/signal-desktop.desktop |  12 +
 net-im/signal-desktop/get_deps.sh                  |  14 +
 net-im/signal-desktop/pkg-descr                    |   8 +
 net-im/signal-desktop/pkg-plist                    |  96 +++
 13 files changed, 1334 insertions(+)

diff --git a/net-im/Makefile b/net-im/Makefile
index 3341d6d8b47d..3fcd03cb6340 100644
--- a/net-im/Makefile
+++ b/net-im/Makefile
@@ -147,6 +147,7 @@
     SUBDIR += scudcloud
     SUBDIR += sendxmpp
     SUBDIR += signal-cli
+    SUBDIR += signal-desktop
     SUBDIR += signald
     SUBDIR += slack-term
     SUBDIR += spectral
diff --git a/net-im/signal-desktop/Makefile b/net-im/signal-desktop/Makefile
new file mode 100644
index 000000000000..75f02ca82770
--- /dev/null
+++ b/net-im/signal-desktop/Makefile
@@ -0,0 +1,183 @@
+PORTNAME=	signal-desktop
+DISTVERSIONPREFIX=	v
+DISTVERSION=	5.46.0
+CATEGORIES=	net-im
+MASTER_SITES=	LOCAL/mikael/signal-desktop/:yarn \
+		LOCAL/mikael/signal-desktop/:electron_gyp \
+		LOCAL/mikael/signal-desktop/:npm \
+		LOCAL/mikael/signal-desktop/:sqlite
+DISTFILES=	signal-desktop-${DISTVERSION}-yarn-cache.tar.gz:yarn \
+		signal-desktop-${DISTVERSION}-electron-gyp-cache.tar.gz:electron_gyp \
+		signal-desktop-${DISTVERSION}-npm-cache.tar.gz:npm \
+		sqlcipher.tar.gz:sqlite
+
+MAINTAINER=	mikael@FreeBSD.org
+COMMENT=	Cross-platform centralized encrypted messaging service
+
+LICENSE=	AGPLv3
+LICENSE_FILE=	${WRKSRC}/LICENSE
+
+BUILD_DEPENDS=	${LOCALBASE}/lib/libringrtc.so:multimedia/ringrtc \
+		${LOCALBASE}/lib/libsignal_node.so:net-im/libsignal-node \
+		app-builder>0:devel/app-builder \
+		electron18:devel/electron18 \
+		npm:www/npm-node16 \
+		openssl>0:security/openssl \
+		vips>0:graphics/vips \
+		yarn:www/yarn-node16
+LIB_DEPENDS=	libasound.so:audio/alsa-lib \
+		libatk-bridge-2.0.so:accessibility/at-spi2-atk \
+		libatspi.so:accessibility/at-spi2-core \
+		libcups.so:print/cups \
+		libdbus-1.so:devel/dbus \
+		libdrm.so:graphics/libdrm \
+		libexpat.so:textproc/expat2 \
+		libFLAC.so:audio/flac \
+		libfontconfig.so:x11-fonts/fontconfig \
+		libharfbuzz-subset.so:print/harfbuzz \
+		libharfbuzz.so:print/harfbuzz \
+		libnspr4.so:devel/nspr \
+		libnss3.so:security/nss \
+		libnssutil3.so:security/nss \
+		libopenh264.so:multimedia/openh264 \
+		libopus.so:audio/opus \
+		libpci.so:devel/libpci \
+		libplc4.so:devel/nspr \
+		libplds4.so:devel/nspr \
+		libpng16.so:graphics/png \
+		libsmime3.so:security/nss \
+		libsnappy.so:archivers/snappy \
+		libwebp.so:graphics/webp \
+		libwebpdemux.so:graphics/webp \
+		libwebpmux.so:graphics/webp \
+		libxkbcommon.so:x11/libxkbcommon \
+		libxshmfence.so:x11/libxshmfence
+
+USES=		desktop-file-utils gettext-runtime gl gnome jpeg xorg
+USE_GITHUB=	yes
+GH_ACCOUNT=	signalapp
+GH_PROJECT=	Signal-Desktop
+
+USE_GL=		gbm gl
+USE_GNOME=	atk cairo gdkpixbuf2 glib20 gtk30 libxml2 libxslt pango
+USE_XORG=	x11 xcb xcomposite xdamage xext xfixes xi xrandr xrender xtst
+
+PACKAGE_ENV=	SIGNAL_ENV=production
+MAKE_ENV+=	ELECTRON_OVERRIDE_DIST_PATH=${LOCALBASE}/share/electron18 \
+		HOME=${WRKDIR} \
+		PLAYWRIGHT_BROWSERS_PATH=${WRKDIR}/.cache \
+		PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD=true \
+		PLAYWRIGHT_SKIP_VALIDATE_HOST_REQUIREMENTS=true \
+		USE_SYSTEM_APP_BUILDER=true
+
+# Don't download electron binary distribution on electron node_modules installation
+MAKE_ENV+=	ELECTRON_SKIP_BINARY_DOWNLOAD=1
+
+# Don't create __pycache__ directory when executing node-gyp
+# This is a workaround to avoid filesystem violations during poudriere build
+MAKE_ENV+=	PYTHONDONTWRITEBYTECODE=1
+
+_BUILD_DATE=	$$(date +'%s')
+
+NPM_SIGNAL_DIR=		npm-@signalapp-libsignal-client-0.16.0-7acba54b7ba05f513cdcf7f555efa1ccc6ce0145-integrity
+NPM_RINGRTC_DIR=	npm-ringrtc-2.20.8-ebd88d19b7a520f5887e5bed529db9fa5631d07e
+NPM_SQLITE3_DIR=	npm-better-sqlite3-7.5.0-3c4a7eebba3d5f5d8cb88fe83be1c01b8c0dea7d
+NPM_PLAYWRITE_DIR=	npm-playwright-core-1.17.1-a16e0f89284a0ed8ae6d77e1c905c84b8a2ba022-integrity
+NPM_ESBUILD_VERS=	0.14.28
+NPM_ESBUILD_DIR=	npm-esbuild-${NPM_ESBUILD_VERS}-7738635d2ea19e446bd319d83a1802545e6aebb8-integrity
+
+post-patch:
+	${REINPLACE_CMD} "s#%%EPOCH%%#${_BUILD_DATE}#" \
+		${WRKSRC}/ts/scripts/get-expire-time.ts
+
+do-build:
+	${ECHO} 'yarn-offline-mirror "../yarn-cache"' > ${WRKSRC}/.yarnrc
+
+# electron-builder is stupid and tries to open ${LOCALBASE}/share/electron18/electron with WRITE priv
+# copy ${LOCALBASE}/share/electron18 in WRKDIR
+# it's needed for the do-install phase, it's here because of this issue
+# https://github.com/MikaelUrankar/signal-desktop/issues/6 (I don't know why it fails though)
+	@${CP} -pR ${LOCALBASE}/share/electron18 ${WRKDIR}/electron18
+	${CHMOD} -R a+w ${WRKDIR}/electron18
+
+	# Install libsignal_node.so and libringrtc.so binaries in the yarn cache
+	${MKDIR} ${WRKDIR}/.cache/yarn/v6/${NPM_SIGNAL_DIR}/node_modules/@signalapp/libsignal-client/prebuilds/freebsd-x64 \
+		 ${WRKDIR}/.cache/yarn/v6/${NPM_RINGRTC_DIR}/node_modules/ringrtc/build/freebsd
+	${CP} ${LOCALBASE}/lib/libsignal_node.so \
+		${WRKDIR}/.cache/yarn/v6/${NPM_SIGNAL_DIR}/node_modules/@signalapp/libsignal-client/prebuilds/freebsd-x64/node.napi.node
+	${CP} ${LOCALBASE}/lib/libringrtc.so \
+		${WRKDIR}/.cache/yarn/v6/${NPM_RINGRTC_DIR}/node_modules/ringrtc/build/freebsd/libringrtc-x64.node
+
+	# sqlcipher.tar.gz requires git-lfs to fetch, just copy a previously downloaded one (I've had to use ubuntu for that,
+	# as I didn't manage to do it with FreeBSD, see https://github.com/signalapp/Signal-Desktop/blob/development/CONTRIBUTING.md for instructions)
+	${MKDIR} ${WRKDIR}/.cache/yarn/v6/${NPM_SQLITE3_DIR}/node_modules/better-sqlite3/deps
+	${CP} ${DISTDIR}/sqlcipher.tar.gz \
+		${WRKDIR}/.cache/yarn/v6/${NPM_SQLITE3_DIR}/node_modules/better-sqlite3/deps/sqlcipher.tar.gz
+
+	# patch-package can't patch playwright-core, patch the yarn cache instead
+	${CP} ${FILESDIR}/playwrigth-registry.js \
+	 	${WRKDIR}/.cache/yarn/v6/${NPM_PLAYWRITE_DIR}/node_modules/playwright-core/lib/utils/registry.js
+
+	${MKDIR} ${WRKDIR}/esbuild-freebsd-64-${NPM_ESBUILD_VERS}
+	${TAR} -xf ${WRKDIR}/yarn-cache/esbuild-freebsd-64-${NPM_ESBUILD_VERS}.tgz -C ${WRKDIR}/esbuild-freebsd-64-${NPM_ESBUILD_VERS}
+	${CP} ${WRKDIR}/esbuild-freebsd-64-${NPM_ESBUILD_VERS}/package/bin/esbuild \
+		${WRKDIR}/.cache/yarn/v6/${NPM_ESBUILD_DIR}/node_modules/esbuild/lib/downloaded-esbuild-freebsd-64-esbuild
+
+# 	# For online build
+#	${RM} ${WRKDIR}/.npmrc
+#	cd ${WRKSRC} && \
+#		${SETENV} ${MAKE_ENV} yarn install --frozen-lockfile --ignore-optional
+#	cd ${WRKSRC} && \
+#		${SETENV} ${MAKE_ENV} yarn generate
+#	cd ${WRKSRC} && \
+#		${SETENV} ${MAKE_ENV} yarn build:webpack
+
+ 	# For offline build
+	${ECHO} offline=true > ${WRKDIR}/.npmrc
+	cd ${WRKSRC} && \
+		${SETENV} ${MAKE_ENV} yarn install --frozen-lockfile --ignore-optional --offline
+	cd ${WRKSRC} && \
+		${SETENV} ${MAKE_ENV} yarn --offline generate
+	cd ${WRKSRC} && \
+		${SETENV} ${MAKE_ENV} yarn --offline build:webpack
+
+do-install:
+# taken from https://github.com/tagattie/FreeBSD-Electron/blob/master/Mk/Uses/electron.mk#L387
+# and editors/vscode
+	cd ${WRKSRC} && \
+		${SETENV} ${MAKE_ENV} ${PACKAGE_ENV} yarn run electron-builder \
+			--linux --dir --config.npmRebuild=false \
+			--config.electronVersion=18 \
+			--config.electronDist=${WRKDIR}/electron18
+
+	${MKDIR} ${STAGEDIR}${DATADIR}
+	cd ${BUILD_WRKSRC}/dist/linux-unpacked && \
+		${COPYTREE_SHARE} . ${STAGEDIR}${DATADIR}
+
+	${MKDIR} ${STAGEDIR}${PREFIX}/share/pixmaps
+	${INSTALL_DATA} ${WRKSRC}/images/signal-logo-desktop-linux.png \
+		${STAGEDIR}${PREFIX}/share/pixmaps/signal-desktop.png
+	${INSTALL_DATA} ${FILESDIR}/${PORTNAME}.desktop \
+		${STAGEDIR}${PREFIX}/share/applications
+
+.for f in chromedriver mksnapshot v8_context_snapshot_generator
+	${RM} ${STAGEDIR}${DATADIR}/${f}
+.endfor
+	${RM} -r ${STAGEDIR}${DATADIR}/gen
+	${RM} -r ${STAGEDIR}${DATADIR}/node_headers
+	${RM} -r ${STAGEDIR}${DATADIR}/resources/completions
+	${RLN} ${STAGEDIR}${DATADIR}/signal-desktop ${STAGEDIR}${PREFIX}/bin
+
+create-caches-tarball:
+	# do some cleanup first
+	${RM} -r  ${WRKDIR}/.npm/_logs ${WRKDIR}/.npm/_update-notifier-last-checked ${WRKDIR}/.cache/yarn/v6/.tmp
+	${FIND} ${WRKDIR}/.cache -type f -perm 755 -exec file {} \; | ${EGREP} "ELF|PE32+|Mach-O" | ${AWK} -F ':' '{print $$1}' | ${XARGS} ${RM}
+
+	cd ${WRKDIR} && \
+		${TAR} czf signal-desktop-${DISTVERSION}-yarn-cache.tar.gz .cache yarn-cache
+	cd ${WRKDIR} && \
+		${TAR} czf signal-desktop-${DISTVERSION}-electron-gyp-cache.tar.gz .electron-gyp
+	cd ${WRKDIR} && \
+		${TAR} czf signal-desktop-${DISTVERSION}-npm-cache.tar.gz .npm
+
+.include <bsd.port.mk>
diff --git a/net-im/signal-desktop/distinfo b/net-im/signal-desktop/distinfo
new file mode 100644
index 000000000000..67562e800d3b
--- /dev/null
+++ b/net-im/signal-desktop/distinfo
@@ -0,0 +1,11 @@
+TIMESTAMP = 1655970160
+SHA256 (signal-desktop-5.46.0-yarn-cache.tar.gz) = 5d0de21f40a0b0c176078b6de8495eaca20af9f679931a013a170825ee6df000
+SIZE (signal-desktop-5.46.0-yarn-cache.tar.gz) = 935434061
+SHA256 (signal-desktop-5.46.0-electron-gyp-cache.tar.gz) = 9594b6dd908a8778d59fa421a970c0456f5bfd9582d60ab48b2a30fd860a39dc
+SIZE (signal-desktop-5.46.0-electron-gyp-cache.tar.gz) = 260284
+SHA256 (signal-desktop-5.46.0-npm-cache.tar.gz) = bc17b1f363140931361d49a47cd0c38123eb86bf8d5cfa270b6fac7df19d68fe
+SIZE (signal-desktop-5.46.0-npm-cache.tar.gz) = 8184368
+SHA256 (sqlcipher.tar.gz) = fe8bdc5e2f182970fb63a71ec4c519c8192453800bf142f755d7ed99e79fff84
+SIZE (sqlcipher.tar.gz) = 25833894
+SHA256 (signalapp-Signal-Desktop-v5.46.0_GH0.tar.gz) = f9ffce286fcf90b14a3b9f90cab3383c7a666383b5b012e27e995695322ad04b
+SIZE (signalapp-Signal-Desktop-v5.46.0_GH0.tar.gz) = 37763735
diff --git a/net-im/signal-desktop/files/patch-packages.json b/net-im/signal-desktop/files/patch-packages.json
new file mode 100644
index 000000000000..98a60920159d
--- /dev/null
+++ b/net-im/signal-desktop/files/patch-packages.json
@@ -0,0 +1,20 @@
+--- package.json.orig	2022-06-16 19:08:59 UTC
++++ package.json
+@@ -264,7 +264,7 @@
+     "cross-env": "5.2.0",
+     "css-loader": "3.2.0",
+     "debug": "4.3.3",
+-    "electron": "18.3.2",
++    "electron": "18.3.3",
+     "electron-builder": "23.0.8",
+     "electron-mocha": "11.0.2",
+     "electron-notarize": "1.2.1",
+@@ -309,7 +309,7 @@
+     "sharp/color/color-string": "1.7.4"
+   },
+   "engines": {
+-    "node": "16.13.2"
++    "node": "16.15.1"
+   },
+   "build": {
+     "appId": "org.whispersystems.signal-desktop",
diff --git a/net-im/signal-desktop/files/patch-patches_better-sqlite3+7.5.0.patch b/net-im/signal-desktop/files/patch-patches_better-sqlite3+7.5.0.patch
new file mode 100644
index 000000000000..3b02e7399c4b
--- /dev/null
+++ b/net-im/signal-desktop/files/patch-patches_better-sqlite3+7.5.0.patch
@@ -0,0 +1,20 @@
+--- patches/better-sqlite3+7.5.0.patch.orig	2021-05-16 17:14:17 UTC
++++ patches/better-sqlite3+7.5.0.patch
+@@ -0,0 +1,17 @@
++--- a/node_modules/better-sqlite3/deps/sqlite3.gyp.orig
+++++ b/node_modules/better-sqlite3/deps/sqlite3.gyp
++@@ -95,6 +95,14 @@
++             ]
++           }
++         },
+++        'OS == "freebsd"', {
+++          'link_settings': {
+++            'libraries': [
+++              # This statically links libcrypto, whereas -lcrypto would dynamically link it
+++              '/usr/local/lib/libcrypto.a'
+++            ]
+++          }
+++        },
++         { # Linux
++           'link_settings': {
++             'libraries': [
diff --git a/net-im/signal-desktop/files/patch-patches_electron-builder+23.0.1.patch b/net-im/signal-desktop/files/patch-patches_electron-builder+23.0.1.patch
new file mode 100644
index 000000000000..d0de11b7c8c7
--- /dev/null
+++ b/net-im/signal-desktop/files/patch-patches_electron-builder+23.0.1.patch
@@ -0,0 +1,16 @@
+--- patches/electron-builder+23.0.1.patch.orig	2022-02-24 13:03:47 UTC
++++ patches/electron-builder+23.0.1.patch
+@@ -0,0 +1,13 @@
++diff --git a/node_modules/electron-builder/out/cli/install-app-deps.js b/node_modules/electron-builder/out/cli/install-app-deps.js
++index 0c58564..2b6f9a5 100644
++--- a/node_modules/electron-builder/out/cli/install-app-deps.js
+++++ b/node_modules/electron-builder/out/cli/install-app-deps.js
++@@ -23,7 +23,7 @@ function configureInstallAppDepsCommand(yargs) {
++         "camel-case-expansion": false,
++     })
++         .option("platform", {
++-        choices: ["linux", "darwin", "win32"],
+++        choices: ["freebsd", "linux", "darwin", "win32"],
++         default: process.platform,
++         description: "The target platform",
++     })
diff --git a/net-im/signal-desktop/files/patch-signal-desktop b/net-im/signal-desktop/files/patch-signal-desktop
new file mode 100644
index 000000000000..1e8b0ab804ff
--- /dev/null
+++ b/net-im/signal-desktop/files/patch-signal-desktop
@@ -0,0 +1,183 @@
+--- ts/models/messages.ts	2022-02-12 02:48:01 UTC
++++ ts/models/messages.ts
+@@ -722,7 +722,7 @@ export class MessageModel extends window.Backbone.Mode
+ 
+     // Linux emoji support is mixed, so we disable it. (Note that this doesn't touch
+     //   the `text`, which can contain emoji.)
+-    const shouldIncludeEmoji = Boolean(emoji) && !window.Signal.OS.isLinux();
++    const shouldIncludeEmoji = Boolean(emoji) && !(window.Signal.OS.isLinux() || window.Signal.OS.isFreeBSD());
+     if (shouldIncludeEmoji) {
+       return window.i18n('message--getNotificationText--text-with-emoji', {
+         text: modifiedText,
+--- ts/OS.ts	2022-02-12 02:48:01.000000000 +0100
++++ ts/OS.ts	2022-02-18 20:38:13.220898000 +0100
+@@ -7,6 +7,7 @@ import semver from 'semver';
+ 
+ export const isMacOS = (): boolean => process.platform === 'darwin';
+ export const isLinux = (): boolean => process.platform === 'linux';
++export const isFreeBSD = (): boolean => process.platform === 'freebsd';
+ export const isWindows = (minVersion?: string): boolean => {
+   const osRelease = os.release();
+ 
+--- ts/services/notifications.ts	2022-02-12 02:48:01.000000000 +0100
++++ ts/services/notifications.ts	2022-02-18 20:39:11.725928000 +0100
+@@ -143,7 +143,7 @@ class NotificationService extends EventEmitter {
+     const audioNotificationSupport = getAudioNotificationSupport();
+ 
+     const notification = new window.Notification(title, {
+-      body: OS.isLinux() ? filterNotificationText(message) : message,
++      body: (OS.isFreeBSD() || OS.isLinux()) ? filterNotificationText(message) : message,
+       icon,
+       silent:
+         silent || audioNotificationSupport !== AudioNotificationSupport.Native,
+--- ts/set_os_class.ts	2022-02-12 02:48:01.000000000 +0100
++++ ts/set_os_class.ts	2022-02-18 20:39:35.451014000 +0100
+@@ -9,6 +9,8 @@ $(document).ready(() => {
+     className = 'os-macos';
+   } else if (window.Signal.OS.isLinux()) {
+     className = 'os-linux';
++  } else if (window.Signal.OS.isFreeBSD()) {
++    className = 'os-freebsd';
+   } else {
+     throw new Error('Unexpected operating system; not applying ');
+   }
+--- ts/test-node/types/Settings_test.ts	2022-02-12 02:48:01.000000000 +0100
++++ ts/test-node/types/Settings_test.ts	2022-02-18 20:41:29.688721000 +0100
+@@ -27,6 +27,15 @@ describe('Settings', () => {
+       );
+     });
+ 
++    it('returns custom support on FreeBSD', () => {
++      sandbox.stub(process, 'platform').value('freebsd');
++      assert.strictEqual(
++        Settings.getAudioNotificationSupport(),
++        Settings.AudioNotificationSupport.Custom
++      );
++    });
++
++
+     it('returns no support on Windows 7', () => {
+       sandbox.stub(process, 'platform').value('win32');
+       sandbox.stub(os, 'release').returns('7.0.0');
+@@ -60,6 +69,11 @@ describe('Settings', () => {
+       assert.isTrue(Settings.isAudioNotificationSupported());
+     });
+ 
++    it('returns true on FreeBSD', () => {
++      sandbox.stub(process, 'platform').value('freebsd');
++      assert.isTrue(Settings.isAudioNotificationSupported());
++    });
++
+     it('returns false on Windows 7', () => {
+       sandbox.stub(process, 'platform').value('win32');
+       sandbox.stub(os, 'release').returns('7.0.0');
+@@ -84,6 +98,11 @@ describe('Settings', () => {
+       assert.isTrue(Settings.isNotificationGroupingSupported());
+     });
+ 
++    it('returns true on FreeBSD', () => {
++      sandbox.stub(process, 'platform').value('freebsd');
++      assert.isTrue(Settings.isNotificationGroupingSupported());
++    });
++
+     it('returns true on Windows 7', () => {
+       sandbox.stub(process, 'platform').value('win32');
+       sandbox.stub(os, 'release').returns('7.0.0');
+@@ -126,6 +145,11 @@ describe('Settings', () => {
+       assert.isFalse(Settings.isHideMenuBarSupported());
+     });
+ 
++    it('returns true on FreeBSD', () => {
++      sandbox.stub(process, 'platform').value('freebsd');
++      assert.isTrue(Settings.isHideMenuBarSupported());
++    });
++
+     it('returns true on Windows 7', () => {
+       sandbox.stub(process, 'platform').value('win32');
+       sandbox.stub(os, 'release').returns('7.0.0');
+@@ -148,6 +172,11 @@ describe('Settings', () => {
+     it('returns false on macOS', () => {
+       sandbox.stub(process, 'platform').value('darwin');
+       assert.isFalse(Settings.isDrawAttentionSupported());
++    });
++
++    it('returns true on FreeBSD', () => {
++      sandbox.stub(process, 'platform').value('freebsd');
++      assert.isTrue(Settings.isDrawAttentionSupported());
+     });
+ 
+     it('returns true on Windows 7', () => {
+--- ts/test-node/util/getUserAgent_test.ts	2022-02-12 02:48:01.000000000 +0100
++++ ts/test-node/util/getUserAgent_test.ts	2022-02-18 20:42:25.165838000 +0100
+@@ -30,8 +30,8 @@ describe('getUserAgent', () => {
+     assert.strictEqual(getUserAgent('1.2.3'), 'Signal-Desktop/1.2.3 Linux');
+   });
+ 
+-  it('omits the platform on unsupported platforms', function test() {
++  it('returns the right User-Agent on Linux', function test() {
+     this.sandbox.stub(process, 'platform').get(() => 'freebsd');
+-    assert.strictEqual(getUserAgent('1.2.3'), 'Signal-Desktop/1.2.3');
++    assert.strictEqual(getUserAgent('1.2.3'), 'Signal-Desktop/1.2.3 Linux');
+   });
+ });
+--- ts/util/getUserAgent.ts	2022-02-12 02:48:01.000000000 +0100
++++ ts/util/getUserAgent.ts	2022-02-18 20:43:07.232944000 +0100
+@@ -7,6 +7,7 @@ const PLATFORM_STRINGS: { [platform: string]: string }
+   win32: 'Windows',
+   darwin: 'macOS',
+   linux: 'Linux',
++  freebsd: 'FreeBSD',
+ };
+ 
+ export function getUserAgent(appVersion: string): string {
+--- ts/types/Settings.ts.orig	2022-02-16 16:11:39.000000000 +0100
++++ ts/types/Settings.ts	2022-02-19 22:18:16.945135000 +0100
+@@ -19,7 +19,7 @@ export function getAudioNotificationSupport(): AudioNo
+   if (OS.isWindows(MIN_WINDOWS_VERSION) || OS.isMacOS()) {
+     return AudioNotificationSupport.Native;
+   }
+-  if (OS.isLinux()) {
++  if (OS.isLinux() || OS.isFreeBSD) {
+     return AudioNotificationSupport.Custom;
+   }
+   return AudioNotificationSupport.None;
+@@ -60,7 +60,7 @@ export const getTitleBarVisibility = (): TitleBarVisib
+  */
+ export const isSystemTraySupported = (appVersion: string): boolean =>
+   // We eventually want to support Linux in production.
+-  OS.isWindows() || (OS.isLinux() && !isProduction(appVersion));
++  OS.isWindows() || (OS.isLinux() && !isProduction(appVersion)) || (OS.isFreeBSD() && !isProduction(appVersion));
+ 
+ export const isAutoDownloadUpdatesSupported = (): boolean =>
+   OS.isWindows() || OS.isMacOS();
+--- app/main.ts.orig	2022-02-24 15:35:11.986213000 +0100
++++ app/main.ts	2022-02-24 15:34:26.610207000 +0100
+@@ -451,7 +451,7 @@ let windowIcon: string;
+ 
+ if (OS.isWindows()) {
+   windowIcon = join(__dirname, '../build/icons/win/icon.ico');
+-} else if (OS.isLinux()) {
++} else if (OS.isLinux() || OS.isFreeBSD()) {
+   windowIcon = join(__dirname, '../images/signal-logo-desktop-linux.png');
+ } else {
+   windowIcon = join(__dirname, '../build/icons/png/512x512.png');
+--- ts/scripts/get-expire-time.ts.orig	2022-02-16 15:11:39.000000000 +0000
++++ ts/scripts/get-expire-time.ts	2022-02-25 12:31:18.650062000 +0000
+@@ -2,15 +2,12 @@
+ // SPDX-License-Identifier: AGPL-3.0-only
+ 
+ import { join } from 'path';
+-import { execSync } from 'child_process';
+ import { writeFileSync } from 'fs';
+ 
+ import { DAY } from '../util/durations';
+ 
+-const unixTimestamp = parseInt(
+-  execSync('git show -s --format=%ct').toString('utf8'),
+-  10
+-);
++const unixTimestamp = %%EPOCH%%;
++
+ const buildCreation = unixTimestamp * 1000;
+ 
+ const buildExpiration = buildCreation + DAY * 90;
diff --git a/net-im/signal-desktop/files/patch-yarn.lock b/net-im/signal-desktop/files/patch-yarn.lock
new file mode 100644
index 000000000000..a3e3dcaef46c
--- /dev/null
+++ b/net-im/signal-desktop/files/patch-yarn.lock
@@ -0,0 +1,17 @@
+--- yarn.lock.orig	2022-06-16 19:08:59 UTC
++++ yarn.lock
+@@ -6220,10 +6220,10 @@ electron-window@^0.8.0:
+   dependencies:
+     is-electron-renderer "^2.0.0"
+ 
+-electron@18.3.2:
+-  version "18.3.2"
+-  resolved "https://registry.yarnpkg.com/electron/-/electron-18.3.2.tgz#015a8f4c92c62855d7f33206f2166d3e33b053b7"
+-  integrity sha512-Q1ciZ1M90L71WvyLbkD8Iwaq4YCwo8NUpBiLQUsd6M4E7i5vrzsA4g5Ylfzyela8DgRCNVknDVDfj6s+7YVWpA==
++electron@18.3.3:
++  version "18.3.3"
++  resolved "https://registry.yarnpkg.com/electron/-/electron-18.3.3.tgz#1c48273c1ad1522b8c18f19575e862c7ccd9f409"
++  integrity "sha512-LYxf3uCDc/r0klu7LL0eZLxkseoGIY/vrCfS0Qj4YTU3M7LLjOaIqrajI7icKwaI2dgxiuJJH3n4eqALFpJAFg=="
+   dependencies:
+     "@electron/get" "^1.13.0"
+     "@types/node" "^16.11.26"
diff --git a/net-im/signal-desktop/files/playwrigth-registry.js b/net-im/signal-desktop/files/playwrigth-registry.js
new file mode 100644
index 000000000000..6ad12151f76b
--- /dev/null
+++ b/net-im/signal-desktop/files/playwrigth-registry.js
@@ -0,0 +1,753 @@
+"use strict";
+
+Object.defineProperty(exports, "__esModule", {
+  value: true
+});
+exports.buildPlaywrightCLICommand = buildPlaywrightCLICommand;
+exports.installDefaultBrowsersForNpmInstall = installDefaultBrowsersForNpmInstall;
+exports.installBrowsersForNpmInstall = installBrowsersForNpmInstall;
+exports.findChromiumChannel = findChromiumChannel;
+exports.registry = exports.Registry = exports.registryDirectory = void 0;
+
+var os = _interopRequireWildcard(require("os"));
+
+var _path = _interopRequireDefault(require("path"));
+
+var util = _interopRequireWildcard(require("util"));
+
+var fs = _interopRequireWildcard(require("fs"));
+
+var _properLockfile = _interopRequireDefault(require("proper-lockfile"));
+
+var _ubuntuVersion = require("./ubuntuVersion");
+
+var _utils = require("./utils");
+
+var _dependencies = require("./dependencies");
+
+var _browserFetcher = require("./browserFetcher");
+
+function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
+
+function _getRequireWildcardCache(nodeInterop) { if (typeof WeakMap !== "function") return null; var cacheBabelInterop = new WeakMap(); var cacheNodeInterop = new WeakMap(); return (_getRequireWildcardCache = function (nodeInterop) { return nodeInterop ? cacheNodeInterop : cacheBabelInterop; })(nodeInterop); }
+
+function _interopRequireWildcard(obj, nodeInterop) { if (!nodeInterop && obj && obj.__esModule) { return obj; } if (obj === null || typeof obj !== "object" && typeof obj !== "function") { return { default: obj }; } var cache = _getRequireWildcardCache(nodeInterop); if (cache && cache.has(obj)) { return cache.get(obj); } var newObj = {}; var hasPropertyDescriptor = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var key in obj) { if (key !== "default" && Object.prototype.hasOwnProperty.call(obj, key)) { var desc = hasPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : null; if (desc && (desc.get || desc.set)) { Object.defineProperty(newObj, key, desc); } else { newObj[key] = obj[key]; } } } newObj.default = obj; if (cache) { cache.set(obj, newObj); } return newObj; }
+
+/**
+ * Copyright 2017 Google Inc. All rights reserved.
+ * Modifications copyright (c) Microsoft Corporation.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+const PACKAGE_PATH = _path.default.join(__dirname, '..', '..');
+
+const BIN_PATH = _path.default.join(__dirname, '..', '..', 'bin');
+
+const EXECUTABLE_PATHS = {
+  'chromium': {
+    'freebsd': ['chrome', 'chrome'],
+    'ubuntu18.04': ['chrome-linux', 'chrome'],
+    'ubuntu20.04': ['chrome-linux', 'chrome'],
+    'ubuntu18.04-arm64': ['chrome-linux', 'chrome'],
+    'ubuntu20.04-arm64': ['chrome-linux', 'chrome'],
+    'mac10.13': ['chrome-mac', 'Chromium.app', 'Contents', 'MacOS', 'Chromium'],
+    'mac10.14': ['chrome-mac', 'Chromium.app', 'Contents', 'MacOS', 'Chromium'],
+    'mac10.15': ['chrome-mac', 'Chromium.app', 'Contents', 'MacOS', 'Chromium'],
+    'mac11': ['chrome-mac', 'Chromium.app', 'Contents', 'MacOS', 'Chromium'],
+    'mac11-arm64': ['chrome-mac', 'Chromium.app', 'Contents', 'MacOS', 'Chromium'],
+    'win64': ['chrome-win', 'chrome.exe']
+  },
+  'firefox': {
+    'freebsd': ['firefox', 'firefox'],
+    'ubuntu18.04': ['firefox', 'firefox'],
+    'ubuntu20.04': ['firefox', 'firefox'],
+    'ubuntu18.04-arm64': undefined,
+    'ubuntu20.04-arm64': ['firefox', 'firefox'],
+    'mac10.13': ['firefox', 'Nightly.app', 'Contents', 'MacOS', 'firefox'],
+    'mac10.14': ['firefox', 'Nightly.app', 'Contents', 'MacOS', 'firefox'],
+    'mac10.15': ['firefox', 'Nightly.app', 'Contents', 'MacOS', 'firefox'],
+    'mac11': ['firefox', 'Nightly.app', 'Contents', 'MacOS', 'firefox'],
+    'mac11-arm64': ['firefox', 'Nightly.app', 'Contents', 'MacOS', 'firefox'],
+    'win64': ['firefox', 'firefox.exe']
+  },
+  'webkit': {
+    'ubuntu18.04': ['pw_run.sh'],
+    'ubuntu20.04': ['pw_run.sh'],
+    'ubuntu18.04-arm64': undefined,
+    'ubuntu20.04-arm64': ['pw_run.sh'],
+    'mac10.13': undefined,
+    'mac10.14': ['pw_run.sh'],
+    'mac10.15': ['pw_run.sh'],
+    'mac11': ['pw_run.sh'],
+    'mac11-arm64': ['pw_run.sh'],
+    'win64': ['Playwright.exe']
+  },
+  'ffmpeg': {
+    'ubuntu18.04': ['ffmpeg-linux'],
+    'ubuntu20.04': ['ffmpeg-linux'],
+    'ubuntu18.04-arm64': ['ffmpeg-linux-arm64'],
+    'ubuntu20.04-arm64': ['ffmpeg-linux-arm64'],
+    'mac10.13': ['ffmpeg-mac'],
+    'mac10.14': ['ffmpeg-mac'],
+    'mac10.15': ['ffmpeg-mac'],
+    'mac11': ['ffmpeg-mac'],
+    'mac11-arm64': ['ffmpeg-mac'],
+    'win64': ['ffmpeg-win64.exe']
+  }
+};
+const DOWNLOAD_URLS = {
+  'chromium': {
+    'freebsd': '%s/builds/chromium/%s/chromium-linux.zip',
+    'ubuntu18.04': '%s/builds/chromium/%s/chromium-linux.zip',
+    'ubuntu20.04': '%s/builds/chromium/%s/chromium-linux.zip',
+    'ubuntu18.04-arm64': '%s/builds/chromium/%s/chromium-linux-arm64.zip',
+    'ubuntu20.04-arm64': '%s/builds/chromium/%s/chromium-linux-arm64.zip',
+    'mac10.13': '%s/builds/chromium/%s/chromium-mac.zip',
+    'mac10.14': '%s/builds/chromium/%s/chromium-mac.zip',
+    'mac10.15': '%s/builds/chromium/%s/chromium-mac.zip',
+    'mac11': '%s/builds/chromium/%s/chromium-mac.zip',
+    'mac11-arm64': '%s/builds/chromium/%s/chromium-mac-arm64.zip',
+    'win64': '%s/builds/chromium/%s/chromium-win64.zip'
+  },
+  'chromium-with-symbols': {
+    'ubuntu18.04': '%s/builds/chromium/%s/chromium-with-symbols-linux.zip',
+    'ubuntu20.04': '%s/builds/chromium/%s/chromium-with-symbols-linux.zip',
+    'ubuntu18.04-arm64': '%s/builds/chromium/%s/chromium-with-symbols-linux-arm64.zip',
+    'ubuntu20.04-arm64': '%s/builds/chromium/%s/chromium-with-symbols-linux-arm64.zip',
+    'mac10.13': '%s/builds/chromium/%s/chromium-with-symbols-mac.zip',
+    'mac10.14': '%s/builds/chromium/%s/chromium-with-symbols-mac.zip',
+    'mac10.15': '%s/builds/chromium/%s/chromium-with-symbols-mac.zip',
+    'mac11': '%s/builds/chromium/%s/chromium-with-symbols-mac.zip',
+    'mac11-arm64': '%s/builds/chromium/%s/chromium-with-symbols-mac-arm64.zip',
+    'win64': '%s/builds/chromium/%s/chromium-with-symbols-win64.zip'
+  },
+  'firefox': {
+    'freebsd': '%s/builds/firefox/%s/firefox-ubuntu-18.04.zip',
+    'ubuntu18.04': '%s/builds/firefox/%s/firefox-ubuntu-18.04.zip',
+    'ubuntu20.04': '%s/builds/firefox/%s/firefox-ubuntu-20.04.zip',
+    'ubuntu18.04-arm64': undefined,
+    'ubuntu20.04-arm64': '%s/builds/firefox/%s/firefox-ubuntu-20.04-arm64.zip',
+    'mac10.13': '%s/builds/firefox/%s/firefox-mac-11.zip',
+    'mac10.14': '%s/builds/firefox/%s/firefox-mac-11.zip',
+    'mac10.15': '%s/builds/firefox/%s/firefox-mac-11.zip',
+    'mac11': '%s/builds/firefox/%s/firefox-mac-11.zip',
+    'mac11-arm64': '%s/builds/firefox/%s/firefox-mac-11-arm64.zip',
+    'win64': '%s/builds/firefox/%s/firefox-win64.zip'
+  },
+  'firefox-beta': {
+    'ubuntu18.04': '%s/builds/firefox-beta/%s/firefox-beta-ubuntu-18.04.zip',
+    'ubuntu20.04': '%s/builds/firefox-beta/%s/firefox-beta-ubuntu-20.04.zip',
+    'ubuntu18.04-arm64': undefined,
+    'ubuntu20.04-arm64': undefined,
+    'mac10.13': '%s/builds/firefox-beta/%s/firefox-beta-mac-11.zip',
+    'mac10.14': '%s/builds/firefox-beta/%s/firefox-beta-mac-11.zip',
+    'mac10.15': '%s/builds/firefox-beta/%s/firefox-beta-mac-11.zip',
+    'mac11': '%s/builds/firefox-beta/%s/firefox-beta-mac-11.zip',
+    'mac11-arm64': '%s/builds/firefox-beta/%s/firefox-beta-mac-11-arm64.zip',
+    'win64': '%s/builds/firefox-beta/%s/firefox-beta-win64.zip'
+  },
+  'webkit': {
+    'ubuntu18.04': '%s/builds/webkit/%s/webkit-ubuntu-18.04.zip',
+    'ubuntu20.04': '%s/builds/webkit/%s/webkit-ubuntu-20.04.zip',
+    'ubuntu18.04-arm64': undefined,
+    'ubuntu20.04-arm64': '%s/builds/webkit/%s/webkit-ubuntu-20.04-arm64.zip',
+    'mac10.13': undefined,
+    'mac10.14': '%s/builds/deprecated-webkit-mac-10.14/%s/deprecated-webkit-mac-10.14.zip',
+    'mac10.15': '%s/builds/webkit/%s/webkit-mac-10.15.zip',
+    'mac11': '%s/builds/webkit/%s/webkit-mac-10.15.zip',
+    'mac11-arm64': '%s/builds/webkit/%s/webkit-mac-11-arm64.zip',
+    'win64': '%s/builds/webkit/%s/webkit-win64.zip'
+  },
+  'ffmpeg': {
+    'ubuntu18.04': '%s/builds/ffmpeg/%s/ffmpeg-linux.zip',
+    'ubuntu20.04': '%s/builds/ffmpeg/%s/ffmpeg-linux.zip',
+    'ubuntu18.04-arm64': '%s/builds/ffmpeg/%s/ffmpeg-linux-arm64.zip',
+    'ubuntu20.04-arm64': '%s/builds/ffmpeg/%s/ffmpeg-linux-arm64.zip',
+    'mac10.13': '%s/builds/ffmpeg/%s/ffmpeg-mac.zip',
+    'mac10.14': '%s/builds/ffmpeg/%s/ffmpeg-mac.zip',
+    'mac10.15': '%s/builds/ffmpeg/%s/ffmpeg-mac.zip',
+    'mac11': '%s/builds/ffmpeg/%s/ffmpeg-mac.zip',
+    'mac11-arm64': '%s/builds/ffmpeg/%s/ffmpeg-mac.zip',
+    'win64': '%s/builds/ffmpeg/%s/ffmpeg-win64.zip'
+  }
+};
+
+const registryDirectory = (() => {
+  let result;
+  const envDefined = (0, _utils.getFromENV)('PLAYWRIGHT_BROWSERS_PATH');
+
+  if (envDefined === '0') {
+    result = _path.default.join(__dirname, '..', '..', '.local-browsers');
+  } else if (envDefined) {
+    result = envDefined;
+  } else {
+    let cacheDirectory;
+    if (process.platform === 'linux') cacheDirectory = process.env.XDG_CACHE_HOME || _path.default.join(os.homedir(), '.cache');else if (process.platform === 'darwin') cacheDirectory = _path.default.join(os.homedir(), 'Library', 'Caches');else if (process.platform === 'win32') cacheDirectory = process.env.LOCALAPPDATA || _path.default.join(os.homedir(), 'AppData', 'Local');else throw new Error('Unsupported platform: ' + process.platform);
+    result = _path.default.join(cacheDirectory, 'ms-playwright');
+  }
+
+  if (!_path.default.isAbsolute(result)) {
+    // It is important to resolve to the absolute path:
+    //   - for unzipping to work correctly;
+    //   - so that registry directory matches between installation and execution.
+    // INIT_CWD points to the root of `npm/yarn install` and is probably what
+    // the user meant when typing the relative path.
+    result = _path.default.resolve((0, _utils.getFromENV)('INIT_CWD') || process.cwd(), result);
+  }
+
+  return result;
+})();
+
+exports.registryDirectory = registryDirectory;
+
+function isBrowserDirectory(browserDirectory) {
+  const baseName = _path.default.basename(browserDirectory);
+
+  for (const browserName of allDownloadable) {
+    if (baseName.startsWith(browserName + '-')) return true;
+  }
+
+  return false;
+}
+
+function readDescriptors(browsersJSON) {
+  return browsersJSON['browsers'].map(obj => {
+    const name = obj.name;
+    const revisionOverride = (obj.revisionOverrides || {})[_utils.hostPlatform];
+    const revision = revisionOverride || obj.revision;
+    const browserDirectoryPrefix = revisionOverride ? `${name}_${_utils.hostPlatform}_special` : `${name}`;
+    const descriptor = {
+      name,
+      revision,
+      installByDefault: !!obj.installByDefault,
+      // Method `isBrowserDirectory` determines directory to be browser iff
+      // it starts with some browser name followed by '-'. Some browser names
+      // are prefixes of others, e.g. 'webkit' is a prefix of `webkit-technology-preview`.
+      // To avoid older registries erroneously removing 'webkit-technology-preview', we have to
+      // ensure that browser folders to never include dashes inside.
+      dir: _path.default.join(registryDirectory, browserDirectoryPrefix.replace(/-/g, '_') + '-' + revision)
+    };
+    return descriptor;
+  });
+}
+
+const allDownloadable = ['chromium', 'firefox', 'webkit', 'ffmpeg', 'firefox-beta', 'chromium-with-symbols'];
+
+class Registry {
+  constructor(browsersJSON) {
+    this._executables = void 0;
+    const descriptors = readDescriptors(browsersJSON);
+
+    const findExecutablePath = (dir, name) => {
+      const tokens = EXECUTABLE_PATHS[name][_utils.hostPlatform];
+      return tokens ? _path.default.join(dir, ...tokens) : undefined;
+    };
+
+    const executablePathOrDie = (name, e, installByDefault, sdkLanguage) => {
+      if (!e) throw new Error(`${name} is not supported on ${_utils.hostPlatform}`);
+      const installCommand = buildPlaywrightCLICommand(sdkLanguage, `install${installByDefault ? '' : ' ' + name}`);
+
+      if (!(0, _utils.canAccessFile)(e)) {
+        const prettyMessage = [`Looks like Playwright Test or Playwright was just installed or updated.`, `Please run the following command to download new browser${installByDefault ? 's' : ''}:`, ``, `    ${installCommand}`, ``, `<3 Playwright Team`].join('\n');
+        throw new Error(`Executable doesn't exist at ${e}\n${(0, _utils.wrapInASCIIBox)(prettyMessage, 1)}`);
+      }
+
+      return e;
+    };
+
+    this._executables = [];
+    const chromium = descriptors.find(d => d.name === 'chromium');
+    const chromiumExecutable = findExecutablePath(chromium.dir, 'chromium');
+
+    this._executables.push({
+      type: 'browser',
+      name: 'chromium',
+      browserName: 'chromium',
+      directory: chromium.dir,
+      executablePath: () => chromiumExecutable,
+      executablePathOrDie: sdkLanguage => executablePathOrDie('chromium', chromiumExecutable, chromium.installByDefault, sdkLanguage),
+      installType: chromium.installByDefault ? 'download-by-default' : 'download-on-demand',
+      validateHostRequirements: sdkLanguage => this._validateHostRequirements(sdkLanguage, 'chromium', chromium.dir, ['chrome-linux'], [], ['chrome-win']),
+      _install: () => this._downloadExecutable(chromium, chromiumExecutable, DOWNLOAD_URLS['chromium'][_utils.hostPlatform], 'PLAYWRIGHT_CHROMIUM_DOWNLOAD_HOST'),
+      _dependencyGroup: 'chromium'
+    });
+
+    const chromiumWithSymbols = descriptors.find(d => d.name === 'chromium-with-symbols');
+    const chromiumWithSymbolsExecutable = findExecutablePath(chromiumWithSymbols.dir, 'chromium');
+
+    this._executables.push({
+      type: 'tool',
+      name: 'chromium-with-symbols',
+      browserName: 'chromium',
+      directory: chromiumWithSymbols.dir,
+      executablePath: () => chromiumWithSymbolsExecutable,
+      executablePathOrDie: sdkLanguage => executablePathOrDie('chromium-with-symbols', chromiumWithSymbolsExecutable, chromiumWithSymbols.installByDefault, sdkLanguage),
+      installType: chromiumWithSymbols.installByDefault ? 'download-by-default' : 'download-on-demand',
+      validateHostRequirements: sdkLanguage => this._validateHostRequirements(sdkLanguage, 'chromium', chromiumWithSymbols.dir, ['chrome-linux'], [], ['chrome-win']),
+      _install: () => this._downloadExecutable(chromiumWithSymbols, chromiumWithSymbolsExecutable, DOWNLOAD_URLS['chromium-with-symbols'][_utils.hostPlatform], 'PLAYWRIGHT_CHROMIUM_DOWNLOAD_HOST'),
+      _dependencyGroup: 'chromium'
+    });
+
+    this._executables.push(this._createChromiumChannel('chrome', {
+      'linux': '/opt/google/chrome/chrome',
+      'darwin': '/Applications/Google Chrome.app/Contents/MacOS/Google Chrome',
+      'win32': `\\Google\\Chrome\\Application\\chrome.exe`
+    }, () => this._installChromiumChannel('chrome', {
+      'linux': 'reinstall_chrome_stable_linux.sh',
+      'darwin': 'reinstall_chrome_stable_mac.sh',
+      'win32': 'reinstall_chrome_stable_win.ps1'
+    })));
+
+    this._executables.push(this._createChromiumChannel('chrome-beta', {
+      'linux': '/opt/google/chrome-beta/chrome',
+      'darwin': '/Applications/Google Chrome Beta.app/Contents/MacOS/Google Chrome Beta',
+      'win32': `\\Google\\Chrome Beta\\Application\\chrome.exe`
+    }, () => this._installChromiumChannel('chrome-beta', {
+      'linux': 'reinstall_chrome_beta_linux.sh',
+      'darwin': 'reinstall_chrome_beta_mac.sh',
+      'win32': 'reinstall_chrome_beta_win.ps1'
+    })));
+
+    this._executables.push(this._createChromiumChannel('chrome-dev', {
+      'linux': '/opt/google/chrome-unstable/chrome',
+      'darwin': '/Applications/Google Chrome Dev.app/Contents/MacOS/Google Chrome Dev',
+      'win32': `\\Google\\Chrome Dev\\Application\\chrome.exe`
+    }));
+
+    this._executables.push(this._createChromiumChannel('chrome-canary', {
+      'linux': '',
+      'darwin': '/Applications/Google Chrome Canary.app/Contents/MacOS/Google Chrome Canary',
+      'win32': `\\Google\\Chrome SxS\\Application\\chrome.exe`
+    }));
+
+    this._executables.push(this._createChromiumChannel('msedge', {
+      'linux': '/opt/microsoft/msedge/msedge',
+      'darwin': '/Applications/Microsoft Edge.app/Contents/MacOS/Microsoft Edge',
+      'win32': `\\Microsoft\\Edge\\Application\\msedge.exe`
+    }, () => this._installMSEdgeChannel('msedge', {
+      'linux': 'reinstall_msedge_stable_linux.sh',
+      'darwin': 'reinstall_msedge_stable_mac.sh',
+      'win32': 'reinstall_msedge_stable_win.ps1'
+    })));
+
+    this._executables.push(this._createChromiumChannel('msedge-beta', {
+      'linux': '/opt/microsoft/msedge-beta/msedge',
+      'darwin': '/Applications/Microsoft Edge Beta.app/Contents/MacOS/Microsoft Edge Beta',
+      'win32': `\\Microsoft\\Edge Beta\\Application\\msedge.exe`
+    }, () => this._installMSEdgeChannel('msedge-beta', {
+      'darwin': 'reinstall_msedge_beta_mac.sh',
+      'linux': 'reinstall_msedge_beta_linux.sh',
+      'win32': 'reinstall_msedge_beta_win.ps1'
+    })));
+
+    this._executables.push(this._createChromiumChannel('msedge-dev', {
+      'linux': '/opt/microsoft/msedge-dev/msedge',
+      'darwin': '/Applications/Microsoft Edge Dev.app/Contents/MacOS/Microsoft Edge Dev',
+      'win32': `\\Microsoft\\Edge Dev\\Application\\msedge.exe`
+    }, () => this._installMSEdgeChannel('msedge-dev', {
+      'darwin': 'reinstall_msedge_dev_mac.sh',
+      'linux': 'reinstall_msedge_dev_linux.sh',
+      'win32': 'reinstall_msedge_dev_win.ps1'
+    })));
+
+    this._executables.push(this._createChromiumChannel('msedge-canary', {
+      'linux': '',
+      'darwin': '/Applications/Microsoft Edge Canary.app/Contents/MacOS/Microsoft Edge Canary',
+      'win32': `\\Microsoft\\Edge SxS\\Application\\msedge.exe`
+    }));
+
+    const firefox = descriptors.find(d => d.name === 'firefox');
+    const firefoxExecutable = findExecutablePath(firefox.dir, 'firefox');
+
+    this._executables.push({
+      type: 'browser',
+      name: 'firefox',
+      browserName: 'firefox',
+      directory: firefox.dir,
+      executablePath: () => firefoxExecutable,
+      executablePathOrDie: sdkLanguage => executablePathOrDie('firefox', firefoxExecutable, firefox.installByDefault, sdkLanguage),
+      installType: firefox.installByDefault ? 'download-by-default' : 'download-on-demand',
+      validateHostRequirements: sdkLanguage => this._validateHostRequirements(sdkLanguage, 'firefox', firefox.dir, ['firefox'], [], ['firefox']),
+      _install: () => this._downloadExecutable(firefox, firefoxExecutable, DOWNLOAD_URLS['firefox'][_utils.hostPlatform], 'PLAYWRIGHT_FIREFOX_DOWNLOAD_HOST'),
+      _dependencyGroup: 'firefox'
+    });
+
+    const firefoxBeta = descriptors.find(d => d.name === 'firefox-beta');
+    const firefoxBetaExecutable = findExecutablePath(firefoxBeta.dir, 'firefox');
+
+    this._executables.push({
+      type: 'tool',
+      name: 'firefox-beta',
+      browserName: 'firefox',
+      directory: firefoxBeta.dir,
+      executablePath: () => firefoxBetaExecutable,
+      executablePathOrDie: sdkLanguage => executablePathOrDie('firefox-beta', firefoxBetaExecutable, firefoxBeta.installByDefault, sdkLanguage),
+      installType: firefoxBeta.installByDefault ? 'download-by-default' : 'download-on-demand',
+      validateHostRequirements: sdkLanguage => this._validateHostRequirements(sdkLanguage, 'firefox', firefoxBeta.dir, ['firefox'], [], ['firefox']),
+      _install: () => this._downloadExecutable(firefoxBeta, firefoxBetaExecutable, DOWNLOAD_URLS['firefox-beta'][_utils.hostPlatform], 'PLAYWRIGHT_FIREFOX_DOWNLOAD_HOST'),
+      _dependencyGroup: 'firefox'
+    });
+
+    const webkit = descriptors.find(d => d.name === 'webkit');
+    const webkitExecutable = findExecutablePath(webkit.dir, 'webkit');
+    const webkitLinuxLddDirectories = [_path.default.join('minibrowser-gtk'), _path.default.join('minibrowser-gtk', 'bin'), _path.default.join('minibrowser-gtk', 'lib'), _path.default.join('minibrowser-wpe'), _path.default.join('minibrowser-wpe', 'bin'), _path.default.join('minibrowser-wpe', 'lib')];
+
+    this._executables.push({
+      type: 'browser',
+      name: 'webkit',
+      browserName: 'webkit',
+      directory: webkit.dir,
+      executablePath: () => webkitExecutable,
+      executablePathOrDie: sdkLanguage => executablePathOrDie('webkit', webkitExecutable, webkit.installByDefault, sdkLanguage),
+      installType: webkit.installByDefault ? 'download-by-default' : 'download-on-demand',
+      validateHostRequirements: sdkLanguage => this._validateHostRequirements(sdkLanguage, 'webkit', webkit.dir, webkitLinuxLddDirectories, ['libGLESv2.so.2', 'libx264.so'], ['']),
+      _install: () => this._downloadExecutable(webkit, webkitExecutable, DOWNLOAD_URLS['webkit'][_utils.hostPlatform], 'PLAYWRIGHT_WEBKIT_DOWNLOAD_HOST'),
+      _dependencyGroup: 'webkit'
+    });
+
+    const ffmpeg = descriptors.find(d => d.name === 'ffmpeg');
+    const ffmpegExecutable = findExecutablePath(ffmpeg.dir, 'ffmpeg');
+
+    this._executables.push({
+      type: 'tool',
+      name: 'ffmpeg',
+      browserName: undefined,
+      directory: ffmpeg.dir,
+      executablePath: () => ffmpegExecutable,
+      executablePathOrDie: sdkLanguage => executablePathOrDie('ffmpeg', ffmpegExecutable, ffmpeg.installByDefault, sdkLanguage),
+      installType: ffmpeg.installByDefault ? 'download-by-default' : 'download-on-demand',
+      validateHostRequirements: () => Promise.resolve(),
+      _install: () => this._downloadExecutable(ffmpeg, ffmpegExecutable, DOWNLOAD_URLS['ffmpeg'][_utils.hostPlatform], 'PLAYWRIGHT_FFMPEG_DOWNLOAD_HOST'),
+      _dependencyGroup: 'tools'
+    });
+  }
+
+  _createChromiumChannel(name, lookAt, install) {
+    const executablePath = (sdkLanguage, shouldThrow) => {
+      const suffix = lookAt[process.platform];
+
+      if (!suffix) {
*** 468 LINES SKIPPED ***