blob: f46defae17b5c9132656f1d97574d7f24bcaea0c [file] [log] [blame]
// Copyright 2022 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include <string_view>
#include <type_traits>
#include "base/check_deref.h"
#include "base/command_line.h"
#include "base/files/file_path.h"
#include "base/json/json_writer.h"
#include "base/strings/stringprintf.h"
#include "base/test/scoped_feature_list.h"
#include "build/build_config.h"
#include "chrome/browser/content_settings/host_content_settings_map_factory.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/ui/browser.h"
#include "chrome/browser/ui/web_applications/test/isolated_web_app_test_utils.h"
#include "chrome/browser/web_applications/isolated_web_apps/isolated_web_app_url_info.h"
#include "chrome/browser/web_applications/isolated_web_apps/test/isolated_web_app_builder.h"
#include "chrome/test/base/in_process_browser_test.h"
#include "chrome/test/base/ui_test_utils.h"
#include "components/content_settings/core/browser/host_content_settings_map.h"
#include "content/public/browser/render_frame_host.h"
#include "content/public/browser/storage_partition.h"
#include "content/public/common/content_switches.h"
#include "content/public/test/browser_test.h"
#include "content/public/test/browser_test_base.h"
#include "content/public/test/browser_test_utils.h"
#include "extensions/browser/api/sockets_udp/test_udp_echo_server.h"
#include "extensions/browser/extension_host.h"
#include "extensions/browser/process_manager.h"
#include "extensions/buildflags/buildflags.h"
#include "extensions/common/manifest_constants.h"
#include "net/base/host_port_pair.h"
#include "net/dns/mock_host_resolver.h"
#include "net/test/embedded_test_server/embedded_test_server.h"
#include "services/network/public/mojom/network_context.mojom.h"
#include "services/network/public/mojom/permissions_policy/permissions_policy_feature.mojom-shared.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/blink/public/common/features_generated.h"
#if BUILDFLAG(ENABLE_EXTENSIONS)
#include "chrome/browser/extensions/extension_apitest.h"
#include "extensions/browser/api/sockets_udp/test_udp_echo_server.h"
#include "extensions/common/extension.h"
#include "extensions/test/extension_test_message_listener.h"
#include "extensions/test/result_catcher.h"
#include "extensions/test/test_extension_dir.h"
#endif // BUILDFLAG(ENABLE_EXTENSIONS)
#if BUILDFLAG(IS_MAC)
#include "base/mac/mac_util.h"
#endif
namespace {
constexpr char kHostname[] = "direct-sockets.com";
constexpr char kPrivateAddress[] = "10.8.0.1";
// It is fine to reuse the same address, because
// the port will be generated unique on this machine.
// multicast addresses are in range 224.0.0.0 to 239.255.255.255.
constexpr char kMulticastAddress[] = "237.132.100.17";
constexpr std::string_view kTcpReadWriteScript = R"(
new Promise(async (resolve, reject) => {
try {
const socket = new TCPSocket($1, $2);
const { readable, writable } = await socket.opened;
const reader = readable.getReader();
const writer = writable.getWriter();
const kTcpPacket =
"POST /echo HTTP/1.1\r\n" +
"Content-Length: 19\r\n\r\n" +
"0100000005320000005";
// The echo server can send back the response in multiple chunks.
// We must wait for at least `kTcpMinExpectedResponseLength` bytes to
// be received before matching the response with `kTcpResponsePattern`.
const kTcpMinExpectedResponseLength = 102;
const kTcpResponsePattern = "0100000005320000005";
let tcpResponse = "";
const readUntil = async () => {
const { value, done } = await reader.read();
if (done) {
reject("ReadableStream must not be exhausted at this point.");
}
const message = (new TextDecoder()).decode(value);
tcpResponse += message;
if (tcpResponse.length >= kTcpMinExpectedResponseLength) {
if (!tcpResponse.match(kTcpResponsePattern)) {
reject("The data returned must match the data sent.");
}
resolve();
} else {
readUntil();
}
};
writer.write((new TextEncoder()).encode(kTcpPacket));
readUntil();
} catch (err) {
reject(err);
}
});
)";
constexpr std::string_view kMulticastJoinLeaveGroup = R"(
(async () => {
const socket = new UDPSocket({ localAddress: $1 });
const { multicastController } = await socket.opened;
await multicastController.joinGroup($2);
await multicastController.leaveGroup($2);
})();
)";
constexpr std::string_view kUdpConnectedReadWriteScript = R"(
new Promise(async (resolve, reject) => {
try {
const socket = new UDPSocket({ remoteAddress: $1, remotePort: $2 });
const { readable, writable } = await socket.opened;
const kUdpMessage = "udp_message";
writable.getWriter().write({
data: (new TextEncoder()).encode(kUdpMessage)
});
return await readable.getReader().read().then(packet => {
const { value, done } = packet;
if (done) {
reject("ReadableStream must not be exhausted at this point.");
}
const { data } = value;
if ((new TextDecoder()).decode(data) !== kUdpMessage) {
reject("The data returned must match the data sent.");
}
resolve();
});
} catch (err) {
reject(err);
}
});
)";
constexpr std::string_view kUdpBoundReadWriteScript = R"(
new Promise(async (resolve, reject) => {
try {
const socket = new UDPSocket({ localAddress: "127.0.0.1" });
const { readable, writable } = await socket.opened;
const kUdpMessage = "udp_message";
writable.getWriter().write({
data: (new TextEncoder()).encode(kUdpMessage),
remoteAddress: $1,
remotePort: $2,
});
return await readable.getReader().read().then(packet => {
const { value, done } = packet;
if (done) {
reject("ReadableStream must not be exhausted at this point.");
}
const { data, remoteAddress, remotePort } = value;
if ((new TextDecoder()).decode(data) !== kUdpMessage) {
reject("The data returned must match the data sent.");
}
if (remoteAddress !== "127.0.0.1") {
reject(`Expected remoteAddress = 127.0.0.1, got ${remoteAddress}`);
}
if (remotePort !== $2) {
reject(`Expected remotePort = $2, got ${remotePort}`);
}
resolve();
});
} catch (err) {
reject(err);
}
});
)";
static constexpr std::string_view kTcpServerExchangePacketWithTcpScript = R"(
new Promise(async (resolve, reject) => {
const assertEq = (actual, expected) => {
const jf = e => JSON.stringify(e);
if (actual !== expected) {
reject(`Expected ${jf(expected)}, got ${jf(actual)}`);
}
};
const kPacket = "I'm a netcat. Meow-meow!";
// |localPort| is intentionally omitted so that the OS will pick one itself.
const serverSocket = new TCPServerSocket('127.0.0.1');
const { localPort: serverSocketPort } = await serverSocket.opened;
// Connect a client to the server.
const clientSocket = new TCPSocket('127.0.0.1', serverSocketPort);
async function acceptOnce() {
const { readable } = await serverSocket.opened;
const reader = readable.getReader();
const { value: acceptedSocket, done } = await reader.read();
assertEq(done, false);
reader.releaseLock();
return acceptedSocket;
};
const acceptedSocket = await acceptOnce();
await clientSocket.opened;
const encoder = new TextEncoder();
const decoder = new TextDecoder();
async function acceptedSocketSend() {
const { writable } = await acceptedSocket.opened;
const writer = writable.getWriter();
await writer.ready;
await writer.write(encoder.encode(kPacket));
writer.releaseLock();
}
async function clientSocketReceive() {
const { readable } = await clientSocket.opened;
const reader = readable.getReader();
let result = "";
while (result.length < kPacket.length) {
const { value, done } = await reader.read();
assertEq(done, false);
result += decoder.decode(value);
}
reader.releaseLock();
assertEq(result, kPacket);
}
acceptedSocketSend();
await clientSocketReceive();
await clientSocket.close();
await acceptedSocket.close();
await serverSocket.close();
resolve();
});
)";
const std::string kMulticastFunctionsScript = R"(
const assertEq = (actual, expected) => {
if (actual !== expected) {
throw `Expected ${JSON.stringify(expected)},
got ${JSON.stringify(actual)}`;
}
};
async function sendLoop(socket, requiredBytes) {
let bytesWritten = 0;
let chunkLength = 0;
const { writable } = await socket.opened;
const writer = writable.getWriter();
while (bytesWritten < requiredBytes) {
chunkLength = Math.min(chunkLength + 1,
requiredBytes - bytesWritten);
let chunk = new Uint8Array(chunkLength);
for (let index = 0; index < chunkLength; index++) {
chunk[index] = bytesWritten % 256;
bytesWritten++;
}
await writer.ready;
await writer.write({ data: chunk });
}
assertEq(bytesWritten, requiredBytes);
writer.releaseLock();
}
async function readLoop(socket, requiredBytes) {
let bytesRead = 0;
const { readable } = await socket.opened;
const reader = readable.getReader();
while (bytesRead < requiredBytes) {
const { value: { data }, done } = await reader.read();
assertEq(done, false);
for (let index = 0; index < data.length; index++) {
assertEq(data[index], bytesRead % 256);
bytesRead++;
}
}
assertEq(bytesRead, requiredBytes);
reader.releaseLock();
}
)";
#if BUILDFLAG(ENABLE_EXTENSIONS)
base::Value::Dict GenerateManifest(
std::optional<base::Value::Dict> socket_permissions = {}) {
auto manifest = base::Value::Dict()
.Set(extensions::manifest_keys::kName,
"Direct Sockets in Chrome Apps")
.Set(extensions::manifest_keys::kManifestVersion, 2)
.Set(extensions::manifest_keys::kVersion, "1.0");
manifest.SetByDottedPath(
extensions::manifest_keys::kPlatformAppBackgroundScripts,
base::Value::List().Append("background.js"));
if (socket_permissions) {
manifest.Set(extensions::manifest_keys::kSockets,
std::move(*socket_permissions));
}
return manifest;
}
auto AccessBlocked() {
return testing::HasSubstr("Access to the requested host or port is blocked");
}
auto PrivateNetworkAccessBlocked() {
return testing::HasSubstr("Access to local network is blocked");
}
auto ErrorIs(const auto& matcher) {
return content::EvalJsResult::ErrorIs(matcher);
}
auto IsOk() {
return content::EvalJsResult::IsOk();
}
#endif
class TestServer {
public:
virtual ~TestServer() = default;
virtual void Start(network::mojom::NetworkContext* network_context) = 0;
virtual void Stop() = 0;
virtual uint16_t port() const = 0;
};
class TcpHttpTestServer : public TestServer {
public:
void Start(network::mojom::NetworkContext* network_context) override {
DCHECK(!test_server_);
test_server_ = std::make_unique<net::EmbeddedTestServer>(
net::EmbeddedTestServer::TYPE_HTTP);
test_server_->AddDefaultHandlers();
ASSERT_TRUE(test_server_->Start());
}
void Stop() override { test_server_.reset(); }
uint16_t port() const override {
DCHECK(test_server_);
return test_server_->port();
}
private:
std::unique_ptr<net::EmbeddedTestServer> test_server_;
};
class UdpEchoTestServer : public TestServer {
public:
void Start(network::mojom::NetworkContext* network_context) override {
DCHECK(!udp_echo_server_);
udp_echo_server_ = std::make_unique<extensions::TestUdpEchoServer>();
net::HostPortPair host_port_pair;
ASSERT_TRUE(udp_echo_server_->Start(network_context, &host_port_pair));
port_ = host_port_pair.port();
ASSERT_GT(*port_, 0);
}
void Stop() override { udp_echo_server_.reset(); }
uint16_t port() const override {
DCHECK(port_);
return *port_;
}
private:
std::unique_ptr<extensions::TestUdpEchoServer> udp_echo_server_;
std::optional<uint16_t> port_;
};
template <typename TestHarness>
requires(std::is_base_of_v<InProcessBrowserTest, TestHarness>)
class ChromeDirectSocketsTest : public TestHarness {
public:
ChromeDirectSocketsTest() = delete;
void SetUpOnMainThread() override {
TestHarness::SetUpOnMainThread();
TestHarness::host_resolver()->AddRule(kHostname, "127.0.0.1");
test_server()->Start(InProcessBrowserTest::browser()
->profile()
->GetDefaultStoragePartition()
->GetNetworkContext());
}
void TearDownOnMainThread() override {
TestHarness::TearDownOnMainThread();
test_server()->Stop();
}
protected:
explicit ChromeDirectSocketsTest(std::unique_ptr<TestServer> test_server)
: test_server_{std::move(test_server)} {}
TestServer* test_server() const {
DCHECK(test_server_);
return test_server_.get();
}
private:
std::unique_ptr<TestServer> test_server_;
};
template <typename TestHarness>
class ChromeDirectSocketsTcpTest : public ChromeDirectSocketsTest<TestHarness> {
public:
ChromeDirectSocketsTcpTest()
: ChromeDirectSocketsTest<TestHarness>{
std::make_unique<TcpHttpTestServer>()} {}
};
template <typename TestHarness>
class ChromeDirectSocketsUdpTest : public ChromeDirectSocketsTest<TestHarness> {
public:
ChromeDirectSocketsUdpTest()
: ChromeDirectSocketsTest<TestHarness>{
std::make_unique<UdpEchoTestServer>()} {}
};
#if BUILDFLAG(ENABLE_EXTENSIONS)
class ChromeAppApiTest : public extensions::ExtensionApiTest {
public:
static constexpr std::string_view kWorkerScriptTemplate = R"(
self.onmessage = async e => {
try {
await %s;
self.postMessage(null);
} catch (err) {
self.postMessage({ error: err });
}
};
)";
static constexpr std::string_view kWorkerConnect = R"(
new Promise((resolve, reject) => {
const policy = trustedTypes.createPolicy("default", {
createScriptURL: (url) => url,
});
const worker = new Worker(
policy.createScriptURL('/worker.js')
);
worker.onmessage = e => {
if (e.data) {
reject(e.data.error);
} else {
resolve();
}
};
worker.postMessage(null);
});
)";
content::RenderFrameHost* InstallAndOpenChromeApp(
const base::Value::Dict& manifest) {
dir_.WriteManifest(manifest);
dir_.WriteFile(FILE_PATH_LITERAL("background.js"), "");
return InstallAndOpenChromeApp();
}
content::RenderFrameHost* InstallAndOpenChromeAppWithWorkerScript(
const base::Value::Dict& manifest,
std::string_view worker_script) {
dir_.WriteManifest(manifest);
dir_.WriteFile(FILE_PATH_LITERAL("background.js"), "");
dir_.WriteFile(FILE_PATH_LITERAL("worker.js"), worker_script);
return InstallAndOpenChromeApp();
}
private:
content::RenderFrameHost* InstallAndOpenChromeApp() {
const extensions::Extension& extension =
CHECK_DEREF(LoadExtension(dir_.UnpackedPath()));
return CHECK_DEREF(extensions::ProcessManager::Get(profile())
->GetBackgroundHostForExtension(extension.id()))
.main_frame_host();
}
extensions::TestExtensionDir dir_;
};
using ChromeDirectSocketsTcpApiTest =
ChromeDirectSocketsTcpTest<ChromeAppApiTest>;
IN_PROC_BROWSER_TEST_F(ChromeDirectSocketsTcpApiTest, TcpReadWrite) {
content::RenderFrameHost* app_frame = InstallAndOpenChromeApp(
GenerateManifest(/*socket_permissions=*/
base::Value::Dict().Set(
"tcp", base::Value::Dict().Set("connect", "*"))));
ASSERT_THAT(
EvalJs(app_frame, content::JsReplace(kTcpReadWriteScript, kHostname,
test_server()->port())),
IsOk());
}
IN_PROC_BROWSER_TEST_F(ChromeDirectSocketsTcpApiTest, TcpReadWriteFromWorker) {
const std::string worker_script = base::StringPrintf(
kWorkerScriptTemplate, content::JsReplace(kTcpReadWriteScript, kHostname,
test_server()->port()));
content::RenderFrameHost* app_frame = InstallAndOpenChromeAppWithWorkerScript(
GenerateManifest(/*socket_permissions=*/base::Value::Dict().Set(
"tcp", base::Value::Dict().Set("connect", "*"))),
worker_script);
ASSERT_THAT(EvalJs(app_frame, kWorkerConnect), IsOk());
}
IN_PROC_BROWSER_TEST_F(ChromeDirectSocketsTcpApiTest,
TcpSocketUndefinedWithoutSocketsPermission) {
// "sockets" key is not present in the manifest.
content::RenderFrameHost* app_frame =
InstallAndOpenChromeApp(GenerateManifest());
static constexpr std::string_view kScript = R"(
(async () => {
const socket = new TCPSocket($1, $2);
await socket.opened;
})();
)";
EXPECT_THAT(EvalJs(app_frame, content::JsReplace(kScript, kHostname, 0)),
ErrorIs(AccessBlocked()));
}
IN_PROC_BROWSER_TEST_F(ChromeDirectSocketsTcpApiTest,
TcpFailsWithoutSocketsTcpConnectPermission) {
// "sockets" key is present in the manifest, but "sockets.tcp.connect" is not.
content::RenderFrameHost* app_frame = InstallAndOpenChromeApp(
GenerateManifest(/*socket_permissions=*/base::Value::Dict()));
static constexpr std::string_view kScript = R"(
(async () => {
const socket = new TCPSocket($1, $2);
await socket.opened;
})();
)";
EXPECT_THAT(EvalJs(app_frame, content::JsReplace(kScript, kHostname,
test_server()->port())),
ErrorIs(AccessBlocked()));
}
using ChromeDirectSocketsUdpApiTest =
ChromeDirectSocketsUdpTest<ChromeAppApiTest>;
IN_PROC_BROWSER_TEST_F(ChromeDirectSocketsUdpApiTest, UdpReadWrite) {
content::RenderFrameHost* app_frame = InstallAndOpenChromeApp(
GenerateManifest(/*socket_permissions=*/base::Value::Dict().Set(
"udp", base::Value::Dict().Set("send", "*"))));
ASSERT_THAT(
EvalJs(app_frame, content::JsReplace(kUdpConnectedReadWriteScript,
kHostname, test_server()->port())),
IsOk());
}
IN_PROC_BROWSER_TEST_F(ChromeDirectSocketsUdpApiTest, UdpReadWriteFromWorker) {
const std::string worker_script =
base::StringPrintf(kWorkerScriptTemplate,
content::JsReplace(kUdpConnectedReadWriteScript,
kHostname, test_server()->port()));
content::RenderFrameHost* app_frame = InstallAndOpenChromeAppWithWorkerScript(
GenerateManifest(/*socket_permissions=*/base::Value::Dict().Set(
"udp", base::Value::Dict().Set("send", "*"))),
worker_script);
ASSERT_THAT(EvalJs(app_frame, kWorkerConnect), IsOk());
}
IN_PROC_BROWSER_TEST_F(ChromeDirectSocketsUdpApiTest,
UdpSocketUndefinedWithoutSocketsPermission) {
// "sockets" key is not present in the manifest.
content::RenderFrameHost* app_frame =
InstallAndOpenChromeApp(GenerateManifest());
static constexpr std::string_view kScript = R"(
(async () => {
const socket = new UDPSocket({ remoteAddress: $1, remotePort: $2 });
await socket.opened;
})();
)";
EXPECT_THAT(EvalJs(app_frame, content::JsReplace(kScript, kHostname, 0)),
ErrorIs(AccessBlocked()));
}
IN_PROC_BROWSER_TEST_F(ChromeDirectSocketsUdpApiTest,
UdpConnectedFailsWithoutSocketsUdpSendPermission) {
// "sockets" key is present in the manifest, but "sockets.udp.send" is
// not.
content::RenderFrameHost* app_frame = InstallAndOpenChromeApp(
GenerateManifest(/*socket_permissions=*/base::Value::Dict()));
static constexpr std::string_view kScript = R"(
(async () => {
const socket = new UDPSocket({ remoteAddress: $1, remotePort: $2 });
await socket.opened;
})();
)";
EXPECT_THAT(EvalJs(app_frame, content::JsReplace(kScript, kHostname,
test_server()->port())),
ErrorIs(AccessBlocked()));
}
IN_PROC_BROWSER_TEST_F(ChromeDirectSocketsUdpApiTest,
UdpBoundFailsWithoutSocketsUdpBindPermission) {
// "sockets" key is present in the manifest as well as "sockets.udp.send",
// but "sockets.udp.bind" is not.
content::RenderFrameHost* app_frame = InstallAndOpenChromeApp(
GenerateManifest(/*socket_permissions=*/base::Value::Dict()));
static constexpr std::string_view kScript = R"(
(async () => {
const socket = new UDPSocket({ localAddress: "::" });
await socket.opened;
})();
)";
EXPECT_THAT(EvalJs(app_frame, kScript), ErrorIs(AccessBlocked()));
}
IN_PROC_BROWSER_TEST_F(ChromeDirectSocketsUdpApiTest, UdpServerReadWrite) {
content::RenderFrameHost* app_frame = InstallAndOpenChromeApp(
GenerateManifest(/*socket_permissions=*/base::Value::Dict().Set(
"udp", base::Value::Dict().Set("bind", "*").Set("send", "*"))));
ASSERT_THAT(
EvalJs(app_frame, content::JsReplace(kUdpBoundReadWriteScript, kHostname,
test_server()->port())),
IsOk());
}
IN_PROC_BROWSER_TEST_F(ChromeDirectSocketsUdpApiTest,
UdpServerNotAffectedByPNAContentSettingInChromeApps) {
content::RenderFrameHost* app_frame = InstallAndOpenChromeApp(
GenerateManifest(/*socket_permissions=*/base::Value::Dict().Set(
"udp", base::Value::Dict().Set("bind", "*").Set("send", "*"))));
HostContentSettingsMapFactory::GetForProfile(profile())
->SetDefaultContentSetting(
ContentSettingsType::DIRECT_SOCKETS_PRIVATE_NETWORK_ACCESS,
ContentSetting::CONTENT_SETTING_BLOCK);
constexpr std::string_view kUdpBoundPna = R"(
(async () => {
const socket = new UDPSocket({ localAddress: "0.0.0.0" });
await socket.opened;
})();
)";
ASSERT_THAT(EvalJs(app_frame, kUdpBoundPna), IsOk());
}
using ChromeDirectSocketsTcpServerApiTest = ChromeAppApiTest;
IN_PROC_BROWSER_TEST_F(ChromeDirectSocketsTcpServerApiTest,
TcpServerSocketUndefinedWithoutSocketsPermission) {
// "sockets" key is not present in the manifest.
content::RenderFrameHost* app_frame =
InstallAndOpenChromeApp(GenerateManifest());
static constexpr std::string_view kScript = R"(
(async () => {
const socket = new TCPServerSocket("::");
await socket.opened;
})();
)";
EXPECT_THAT(EvalJs(app_frame, kScript), ErrorIs(AccessBlocked()));
}
IN_PROC_BROWSER_TEST_F(ChromeDirectSocketsTcpServerApiTest,
TcpServerFailsWithoutSocketsTcpServerListenPermission) {
// "sockets" key is present in the manifest, but "sockets.tcpServer.listen" is
// not.
content::RenderFrameHost* app_frame = InstallAndOpenChromeApp(
GenerateManifest(/*socket_permissions=*/base::Value::Dict()));
static constexpr std::string_view kScript = R"(
(async () => {
const socket = new TCPServerSocket("::");
await socket.opened;
})();
)";
EXPECT_THAT(EvalJs(app_frame, kScript), ErrorIs(AccessBlocked()));
}
IN_PROC_BROWSER_TEST_F(ChromeDirectSocketsTcpServerApiTest,
TcpServerExchangePacketWithTcpSocket) {
content::RenderFrameHost* app_frame =
InstallAndOpenChromeApp(GenerateManifest(
/*socket_permissions=*/base::Value::Dict()
.Set("tcpServer", base::Value::Dict().Set("listen", "*"))
.Set("tcp", base::Value::Dict().Set("connect", "*"))));
EXPECT_THAT(EvalJs(app_frame, kTcpServerExchangePacketWithTcpScript), IsOk());
}
#endif
class IsolatedWebAppApiTest : public web_app::IsolatedWebAppBrowserTestHarness {
public:
content::RenderFrameHost* InstallAndOpenIsolatedWebApp(
bool with_pna = false,
bool with_multicast = false) {
using PermissionsPolicyFeature = network::mojom::PermissionsPolicyFeature;
auto manifest_builder =
web_app::ManifestBuilder().AddPermissionsPolicyWildcard(
PermissionsPolicyFeature::kDirectSockets);
if (with_pna) {
manifest_builder.AddPermissionsPolicyWildcard(
PermissionsPolicyFeature::kDirectSocketsPrivate);
}
if (with_multicast) {
manifest_builder.AddPermissionsPolicyWildcard(
PermissionsPolicyFeature::kMulticastInDirectSockets);
}
auto app = web_app::IsolatedWebAppBuilder(std::move(manifest_builder))
.BuildBundle();
web_app::IsolatedWebAppUrlInfo url_info = app->Install(profile()).value();
return OpenApp(url_info.app_id());
}
};
class IsolatedWebAppMulticastApiTest : public IsolatedWebAppApiTest {
private:
base::test::ScopedFeatureList features_{
blink::features::kMulticastInDirectSockets};
};
class IsolatedWebAppSharedWorkerApiTest
: public web_app::IsolatedWebAppBrowserTestHarness {
public:
static constexpr std::string_view kSharedWorkerScriptTemplate = R"(
onconnect = async e => {
const port = e.ports[0];
port.start();
try {
await %s;
port.postMessage(null);
} catch (err) {
port.postMessage({ 'error': err });
}
};
)";
static constexpr std::string_view kSharedWorkerConnect = R"(
new Promise((resolve, reject) => {
const policy = trustedTypes.createPolicy("default", {
createScriptURL: (url) => url,
});
const worker = new SharedWorker(
policy.createScriptURL('/shared_worker.js')
);
worker.port.onmessage = e => {
if (e.data) {
reject(e.data.error);
} else {
resolve();
}
};
});
)";
IsolatedWebAppSharedWorkerApiTest() {
features_.InitWithFeatures({blink::features::kDirectSocketsInSharedWorkers,
blink::features::kMulticastInDirectSockets},
{});
}
content::RenderFrameHost* InstallAndOpenIsolatedWebAppWithSharedWorkerScript(
std::string_view shared_worker_script,
bool with_pna = false,
bool with_multicast = false) {
using PermissionsPolicyFeature = network::mojom::PermissionsPolicyFeature;
auto manifest_builder =
web_app::ManifestBuilder().AddPermissionsPolicyWildcard(
PermissionsPolicyFeature::kDirectSockets);
if (with_pna) {
manifest_builder.AddPermissionsPolicyWildcard(
PermissionsPolicyFeature::kDirectSocketsPrivate);
}
if (with_multicast) {
manifest_builder.AddPermissionsPolicyWildcard(
PermissionsPolicyFeature::kMulticastInDirectSockets);
}
auto app = web_app::IsolatedWebAppBuilder(std::move(manifest_builder))
.AddJs("/shared_worker.js", shared_worker_script)
.BuildBundle();
web_app::IsolatedWebAppUrlInfo url_info = app->Install(profile()).value();
return OpenApp(url_info.app_id());
}
private:
base::test::ScopedFeatureList features_;
};
class IsolatedWebAppServiceWorkerApiTest
: public web_app::IsolatedWebAppBrowserTestHarness {
public:
static constexpr std::string_view kServiceWorkerScriptTemplate = R"(
addEventListener('message', async (e) => {
try {
await %s;
e.source.postMessage(null);
} catch (err) {
e.source.postMessage({ 'error': err });
}
});
)";
static constexpr std::string_view kServiceWorkerConnect = R"(
new Promise(async (resolve, reject) => {
const policy = trustedTypes.createPolicy("default", {
createScriptURL: (url) => url,
});
await navigator.serviceWorker.register(
policy.createScriptURL('/service_worker.js')
);
navigator.serviceWorker.addEventListener('message', e => {
if (e.data) {
reject(e.data.error);
} else {
resolve();
}
});
const reg = await navigator.serviceWorker.ready;
reg.active.postMessage(null);
});
)";
IsolatedWebAppServiceWorkerApiTest() {
features_.InitWithFeatures({blink::features::kDirectSocketsInServiceWorkers,
blink::features::kMulticastInDirectSockets},
{});
}
content::RenderFrameHost* InstallAndOpenIsolatedWebAppWithServiceWorkerScript(
std::string_view service_worker_script,
bool with_pna = false,
bool with_multicast = false) {
using PermissionsPolicyFeature = network::mojom::PermissionsPolicyFeature;
auto manifest_builder =
web_app::ManifestBuilder().AddPermissionsPolicyWildcard(
PermissionsPolicyFeature::kDirectSockets);
if (with_pna) {
manifest_builder.AddPermissionsPolicyWildcard(
PermissionsPolicyFeature::kDirectSocketsPrivate);
}
if (with_multicast) {
manifest_builder.AddPermissionsPolicyWildcard(
PermissionsPolicyFeature::kMulticastInDirectSockets);
}
auto app = web_app::IsolatedWebAppBuilder(std::move(manifest_builder))
.AddJs("/service_worker.js", service_worker_script)
.BuildBundle();
web_app::IsolatedWebAppUrlInfo url_info = app->Install(profile()).value();
return OpenApp(url_info.app_id());
}
private:
base::test::ScopedFeatureList features_;
};
template <typename T>
class ChromeDirectSocketsTcpIsolatedWebAppTestBase
: public ChromeDirectSocketsTcpTest<T> {
void SetUpOnMainThread() override {
#if BUILDFLAG(IS_MAC)
if (base::mac::MacOSMajorVersion() == 13) {
GTEST_SKIP()
<< "Skipping flaky test on MacOS 13, see crbug.com/397993345";
}
#endif // BUILDFLAG(IS_MAC)
ChromeDirectSocketsTcpTest<T>::SetUpOnMainThread();
}
};
using ChromeDirectSocketsTcpIsolatedWebAppTest =
ChromeDirectSocketsTcpIsolatedWebAppTestBase<IsolatedWebAppApiTest>;
using ChromeDirectSocketsTcpIsolatedWebAppSharedWorkerTest =
ChromeDirectSocketsTcpIsolatedWebAppTestBase<
IsolatedWebAppSharedWorkerApiTest>;
using ChromeDirectSocketsTcpIsolatedWebAppServiceWorkerTest =
ChromeDirectSocketsTcpIsolatedWebAppTestBase<
IsolatedWebAppServiceWorkerApiTest>;
IN_PROC_BROWSER_TEST_F(ChromeDirectSocketsTcpIsolatedWebAppTest, TcpReadWrite) {
content::RenderFrameHost* app_frame = InstallAndOpenIsolatedWebApp();
ASSERT_THAT(
EvalJs(app_frame, content::JsReplace(kTcpReadWriteScript, kHostname,
test_server()->port())),
IsOk());
}
IN_PROC_BROWSER_TEST_F(ChromeDirectSocketsTcpIsolatedWebAppSharedWorkerTest,
TcpReadWrite) {
const std::string shared_worker_script =
base::StringPrintf(kSharedWorkerScriptTemplate,
content::JsReplace(kTcpReadWriteScript, kHostname,
test_server()->port()));
content::RenderFrameHost* app_frame =
InstallAndOpenIsolatedWebAppWithSharedWorkerScript(shared_worker_script);
ASSERT_THAT(EvalJs(app_frame, kSharedWorkerConnect), IsOk());
}
IN_PROC_BROWSER_TEST_F(ChromeDirectSocketsTcpIsolatedWebAppServiceWorkerTest,
TcpReadWrite) {
const std::string service_worker_script =
base::StringPrintf(kServiceWorkerScriptTemplate,
content::JsReplace(kTcpReadWriteScript, kHostname,
test_server()->port()));
content::RenderFrameHost* app_frame =
InstallAndOpenIsolatedWebAppWithServiceWorkerScript(
service_worker_script);
ASSERT_THAT(EvalJs(app_frame, kServiceWorkerConnect), IsOk());
}
IN_PROC_BROWSER_TEST_F(ChromeDirectSocketsTcpIsolatedWebAppTest,
TcpConnectionToPrivateFailsWithoutPNAPermission) {
content::RenderFrameHost* app_frame =
InstallAndOpenIsolatedWebApp(/*with_pna=*/false);
constexpr std::string_view kTcpPna = R"(
(async () => {
const socket = new TCPSocket($1, 459);
await socket.opened;
})();
)";
ASSERT_THAT(EvalJs(app_frame, content::JsReplace(kTcpPna, kPrivateAddress)),
ErrorIs(PrivateNetworkAccessBlocked()));
}
IN_PROC_BROWSER_TEST_F(ChromeDirectSocketsTcpIsolatedWebAppTest,
TcpConnectionToPrivateFailsWithoutPNAContentSetting) {
content::RenderFrameHost* app_frame =
InstallAndOpenIsolatedWebApp(/*with_pna=*/true);
HostContentSettingsMapFactory::GetForProfile(profile())
->SetDefaultContentSetting(
ContentSettingsType::DIRECT_SOCKETS_PRIVATE_NETWORK_ACCESS,
ContentSetting::CONTENT_SETTING_BLOCK);
constexpr std::string_view kTcpPna = R"(
(async () => {
const socket = new TCPSocket($1, 459);
await socket.opened;
})();
)";
ASSERT_THAT(EvalJs(app_frame, content::JsReplace(kTcpPna, kPrivateAddress)),
ErrorIs(PrivateNetworkAccessBlocked()));
}
template <typename T>
class ChromeDirectSocketsUdpIsolatedWebAppTestBase
: public ChromeDirectSocketsUdpTest<T> {
void SetUpOnMainThread() override {
#if BUILDFLAG(IS_MAC)
if (base::mac::MacOSMajorVersion() == 13) {
GTEST_SKIP()
<< "Skipping flaky test on MacOS 13, see crbug.com/397993345";
}
#endif // BUILDFLAG(IS_MAC)
ChromeDirectSocketsUdpTest<T>::SetUpOnMainThread();
}
};
using ChromeDirectSocketsUdpIsolatedWebAppTest =
ChromeDirectSocketsUdpIsolatedWebAppTestBase<IsolatedWebAppApiTest>;
using ChromeDirectSocketsUdpIsolatedWebAppSharedWorkerTest =
ChromeDirectSocketsUdpIsolatedWebAppTestBase<
IsolatedWebAppSharedWorkerApiTest>;
using ChromeDirectSocketsUdpIsolatedWebAppServiceWorkerTest =
ChromeDirectSocketsUdpIsolatedWebAppTestBase<
IsolatedWebAppServiceWorkerApiTest>;
using ChromeDirectSocketsUdpIsolatedWebAppMulticastTest =
ChromeDirectSocketsUdpIsolatedWebAppTestBase<
IsolatedWebAppMulticastApiTest>;
IN_PROC_BROWSER_TEST_F(ChromeDirectSocketsUdpIsolatedWebAppTest, UdpReadWrite) {
content::RenderFrameHost* app_frame = InstallAndOpenIsolatedWebApp();
ASSERT_THAT(
EvalJs(app_frame, content::JsReplace(kUdpConnectedReadWriteScript,
kHostname, test_server()->port())),
IsOk());
}
IN_PROC_BROWSER_TEST_F(ChromeDirectSocketsUdpIsolatedWebAppSharedWorkerTest,
UdpReadWrite) {
const std::string shared_worker_script =
base::StringPrintf(kSharedWorkerScriptTemplate,
content::JsReplace(kUdpConnectedReadWriteScript,
kHostname, test_server()->port()));
content::RenderFrameHost* app_frame =
InstallAndOpenIsolatedWebAppWithSharedWorkerScript(shared_worker_script);
ASSERT_THAT(EvalJs(app_frame, kSharedWorkerConnect), IsOk());
}
IN_PROC_BROWSER_TEST_F(ChromeDirectSocketsUdpIsolatedWebAppServiceWorkerTest,
UdpReadWrite) {
const std::string service_worker_script =
base::StringPrintf(kServiceWorkerScriptTemplate,
content::JsReplace(kUdpConnectedReadWriteScript,
kHostname, test_server()->port()));
content::RenderFrameHost* app_frame =
InstallAndOpenIsolatedWebAppWithServiceWorkerScript(
service_worker_script);
ASSERT_THAT(EvalJs(app_frame, kServiceWorkerConnect), IsOk());
}
IN_PROC_BROWSER_TEST_F(ChromeDirectSocketsUdpIsolatedWebAppTest,
UdpConnectionToPrivateFailsWithoutPNAPermission) {
content::RenderFrameHost* app_frame =
InstallAndOpenIsolatedWebApp(/*with_pna=*/false);
constexpr std::string_view kUdpPna = R"(
(async () => {
const socket = new UDPSocket({ remoteAddress: $1, remotePort: 459, });
await socket.opened;
})();
)";
ASSERT_THAT(EvalJs(app_frame, content::JsReplace(kUdpPna, kPrivateAddress)),
ErrorIs(PrivateNetworkAccessBlocked()));
}
IN_PROC_BROWSER_TEST_F(ChromeDirectSocketsUdpIsolatedWebAppTest,
UdpConnectionToPrivateFailsWithoutPNAContentSetting) {
content::RenderFrameHost* app_frame =
InstallAndOpenIsolatedWebApp(/*with_pna=*/true);
HostContentSettingsMapFactory::GetForProfile(profile())
->SetDefaultContentSetting(
ContentSettingsType::DIRECT_SOCKETS_PRIVATE_NETWORK_ACCESS,
ContentSetting::CONTENT_SETTING_BLOCK);
constexpr std::string_view kUdpPna = R"(
(async () => {
const socket = new UDPSocket({ remoteAddress: $1, remotePort: 459 });
await socket.opened;
})();
)";
ASSERT_THAT(EvalJs(app_frame, content::JsReplace(kUdpPna, kPrivateAddress)),
ErrorIs(PrivateNetworkAccessBlocked()));
}
IN_PROC_BROWSER_TEST_F(
ChromeDirectSocketsUdpIsolatedWebAppMulticastTest,
UdpSocketWithMulticastParamsFailsWithoutMulticastPermissionPolicy) {
content::RenderFrameHost* app_frame =
InstallAndOpenIsolatedWebApp(/*with_pna=*/true, /*with_multicast=*/false);
constexpr std::string_view script = R"(
(async () => {
const socket = new UDPSocket({ remoteAddress: $1, remotePort: 459,
multicastAllowAddressSharing: true, multicastTimeToLive: 5, multicastLoopback: true });
})();
)";
ASSERT_THAT(EvalJs(app_frame, content::JsReplace(script, kPrivateAddress)),
ErrorIs(testing::HasSubstr(
"Cannot use Multicast options if permission "
"policy 'direct-sockets-multicast' is absent")));
}
IN_PROC_BROWSER_TEST_F(
ChromeDirectSocketsUdpIsolatedWebAppMulticastTest,
UdpSocketHasNoMulticastControllerWithoutMulticastPermissionPolicy) {
content::RenderFrameHost* app_frame =
InstallAndOpenIsolatedWebApp(/*with_pna=*/true, /*with_multicast=*/false);
constexpr std::string_view script = R"(
(async () => {
const socket = new UDPSocket({ localAddress: $1 });
const { multicastController } = await socket.opened;
multicastController.joinGroup($2);
})();
)";
ASSERT_THAT(
EvalJs(app_frame, content::JsReplace(
script, net::IPAddress::IPv4AllZeros().ToString(),
kMulticastAddress)),
ErrorIs(testing::HasSubstr("Cannot read properties of undefined")));
}
IN_PROC_BROWSER_TEST_F(ChromeDirectSocketsUdpIsolatedWebAppMulticastTest,
MulticastJoinLeaveGroup) {
content::RenderFrameHost* app_frame =
InstallAndOpenIsolatedWebApp(/*with_pna=*/true, /*with_multicast=*/true);
ASSERT_THAT(EvalJs(app_frame, content::JsReplace(
kMulticastJoinLeaveGroup,
net::IPAddress::IPv4AllZeros().ToString(),
kMulticastAddress)),
IsOk());
}
// TODO(crbug.com/443716695): Fails on mac-rel bots.
#if BUILDFLAG(IS_MAC)
#define MAYBE_UdpSocketMulticastExchange DISABLED_UdpSocketMulticastExchange
#else
#define MAYBE_UdpSocketMulticastExchange UdpSocketMulticastExchange
#endif
IN_PROC_BROWSER_TEST_F(ChromeDirectSocketsUdpIsolatedWebAppMulticastTest,
MAYBE_UdpSocketMulticastExchange) {
content::RenderFrameHost* app_frame =
InstallAndOpenIsolatedWebApp(/*with_pna=*/true, /*with_multicast=*/true);
std::string script = kMulticastFunctionsScript + R"(
(async () => {
const kRequiredDatagrams = 35;
const kRequiredBytes = kRequiredDatagrams * (kRequiredDatagrams + 1) / 2;
const receiverSocket = new UDPSocket({ localAddress: $1 });
const {localPort: multicastPort, multicastController} =
await receiverSocket.opened;
multicastController.joinGroup($2);
const senderSocket = new UDPSocket({
remoteAddress: $2,
remotePort: multicastPort,
multicastTimeToLive: 0,
multicastLoopback: true
});
const sendLoopPromise = sendLoop(senderSocket, kRequiredBytes);
const readLoopPromise = readLoop(receiverSocket, kRequiredBytes);
await Promise.all([sendLoopPromise, readLoopPromise]);
await senderSocket.close();
await receiverSocket.close();
})();
)";
ASSERT_THAT(
EvalJs(app_frame, content::JsReplace(
script, net::IPAddress::IPv4AllZeros().ToString(),
kMulticastAddress)),
IsOk());
}
// TODO(crbug.com/443716695): Fails on mac-rel bots.
#if BUILDFLAG(IS_MAC)
#define MAYBE_UdpSocketMulticastExchangeMultipleReceivers \
DISABLED_UdpSocketMulticastExchangeMultipleReceivers
#else
#define MAYBE_UdpSocketMulticastExchangeMultipleReceivers \
UdpSocketMulticastExchangeMultipleReceivers
#endif
IN_PROC_BROWSER_TEST_F(ChromeDirectSocketsUdpIsolatedWebAppMulticastTest,
MAYBE_UdpSocketMulticastExchangeMultipleReceivers) {
content::RenderFrameHost* app_frame =
InstallAndOpenIsolatedWebApp(/*with_pna=*/true, /*with_multicast=*/true);
std::string script = kMulticastFunctionsScript + R"(
(async () => {
const kRequiredDatagrams = 35;
const kRequiredBytes = kRequiredDatagrams * (kRequiredDatagrams + 1) / 2;
// Create receiver 1.
const receiverSocket1 = new UDPSocket(
{localAddress: $1, multicastAllowAddressSharing: true});
const {
localPort: multicastPort,
multicastController: multicastController1
} = await receiverSocket1.opened;
multicastController1.joinGroup($2);
// Create receiver 2.
const receiverSocket2 = new UDPSocket({
localAddress: $1,
localPort: multicastPort,
multicastAllowAddressSharing: true
});
const {multicastController: multicastController2} =
await receiverSocket2.opened;
multicastController2.joinGroup($2);
const senderSocket = new UDPSocket({
remoteAddress: $2,
remotePort: multicastPort,
multicastTimeToLive: 0,
multicastLoopback: true
});
const readLoopPromise1 = readLoop(receiverSocket1, kRequiredBytes);
const readLoopPromise2 = readLoop(receiverSocket2, kRequiredBytes);
const sendLoopPromise = sendLoop(senderSocket, kRequiredBytes);
await Promise.all([sendLoopPromise, readLoopPromise1, readLoopPromise2]);
await senderSocket.close();
await receiverSocket1.close();
await receiverSocket2.close();
})();
)";
ASSERT_THAT(
EvalJs(app_frame, content::JsReplace(
script, net::IPAddress::IPv4AllZeros().ToString(),
kMulticastAddress)),
IsOk());
}
IN_PROC_BROWSER_TEST_F(ChromeDirectSocketsUdpIsolatedWebAppTest,
UdpServerReadWrite) {
// UDP Bound Mode requires direct-sockets-private permissions policy.
content::RenderFrameHost* app_frame =
InstallAndOpenIsolatedWebApp(/*with_pna=*/true);
ASSERT_THAT(
EvalJs(app_frame, content::JsReplace(kUdpBoundReadWriteScript, kHostname,
test_server()->port())),
IsOk());
}
IN_PROC_BROWSER_TEST_F(ChromeDirectSocketsUdpIsolatedWebAppSharedWorkerTest,
UdpServerReadWrite) {
const std::string shared_worker_script =
base::StringPrintf(kSharedWorkerScriptTemplate,
content::JsReplace(kUdpBoundReadWriteScript, kHostname,
test_server()->port()));
content::RenderFrameHost* app_frame =
InstallAndOpenIsolatedWebAppWithSharedWorkerScript(shared_worker_script);
ASSERT_THAT(EvalJs(app_frame, kSharedWorkerConnect), IsOk());
}
IN_PROC_BROWSER_TEST_F(ChromeDirectSocketsUdpIsolatedWebAppServiceWorkerTest,
UdpServerReadWrite) {
const std::string service_worker_script =
base::StringPrintf(kServiceWorkerScriptTemplate,
content::JsReplace(kUdpBoundReadWriteScript, kHostname,
test_server()->port()));
content::RenderFrameHost* app_frame =
InstallAndOpenIsolatedWebAppWithServiceWorkerScript(
service_worker_script);
ASSERT_THAT(EvalJs(app_frame, kServiceWorkerConnect), IsOk());
}
IN_PROC_BROWSER_TEST_F(ChromeDirectSocketsUdpIsolatedWebAppSharedWorkerTest,
MulticastJoinLeaveGroup) {
const std::string shared_worker_script = base::StringPrintf(
kSharedWorkerScriptTemplate,
content::JsReplace(kMulticastJoinLeaveGroup,
net::IPAddress::IPv4AllZeros().ToString(),
kMulticastAddress));
content::RenderFrameHost* app_frame =
InstallAndOpenIsolatedWebAppWithSharedWorkerScript(shared_worker_script);
ASSERT_THAT(EvalJs(app_frame, kSharedWorkerConnect), IsOk());
}
IN_PROC_BROWSER_TEST_F(ChromeDirectSocketsUdpIsolatedWebAppServiceWorkerTest,
MulticastJoinLeaveGroup) {
const std::string service_worker_script = base::StringPrintf(
kServiceWorkerScriptTemplate,
content::JsReplace(kMulticastJoinLeaveGroup,
net::IPAddress::IPv4AllZeros().ToString(),
kMulticastAddress));
content::RenderFrameHost* app_frame =
InstallAndOpenIsolatedWebAppWithServiceWorkerScript(
service_worker_script);
ASSERT_THAT(EvalJs(app_frame, kServiceWorkerConnect), IsOk());
}
IN_PROC_BROWSER_TEST_F(ChromeDirectSocketsUdpIsolatedWebAppTest,
UdpServerFailsWithoutPNAPermission) {
content::RenderFrameHost* app_frame =
InstallAndOpenIsolatedWebApp(/*with_pna=*/false);
constexpr std::string_view kUdpBoundPna = R"(
(async () => {
const socket = new UDPSocket({ localAddress: "0.0.0.0" });
await socket.opened;
})();
)";
ASSERT_THAT(EvalJs(app_frame, kUdpBoundPna),
ErrorIs(PrivateNetworkAccessBlocked()));
}
IN_PROC_BROWSER_TEST_F(ChromeDirectSocketsUdpIsolatedWebAppTest,
UdpServerFailsWithoutPNAContentSetting) {
content::RenderFrameHost* app_frame =
InstallAndOpenIsolatedWebApp(/*with_pna=*/true);
HostContentSettingsMapFactory::GetForProfile(profile())
->SetDefaultContentSetting(
ContentSettingsType::DIRECT_SOCKETS_PRIVATE_NETWORK_ACCESS,
ContentSetting::CONTENT_SETTING_BLOCK);
constexpr std::string_view kUdpBoundPna = R"(
(async () => {
const socket = new UDPSocket({ localAddress: "0.0.0.0" });
await socket.opened;
})();
)";
ASSERT_THAT(EvalJs(app_frame, kUdpBoundPna),
ErrorIs(PrivateNetworkAccessBlocked()));
}
IN_PROC_BROWSER_TEST_F(ChromeDirectSocketsUdpIsolatedWebAppTest,
UdpServerPortRestrictions) {
content::RenderFrameHost* app_frame =
InstallAndOpenIsolatedWebApp(/*with_pna=*/true);
constexpr std::string_view kUdpBoundPortBelow1024 = R"(
(async () => {
const socket = new UDPSocket({ localAddress: "0.0.0.0", localPort: 455 });
await socket.opened;
})();
)";
ASSERT_THAT(EvalJs(app_frame, kUdpBoundPortBelow1024),
ErrorIs(AccessBlocked()));
constexpr std::string_view kUdpBoundPortNumberHighEnough = R"(
(async () => {
const socket = new UDPSocket({ localAddress: "0.0.0.0", localPort: 2558 });
await socket.opened;
})();
)";
ASSERT_THAT(EvalJs(app_frame, kUdpBoundPortNumberHighEnough), IsOk());
}
using ChromeDirectSocketsTcpServerIsolatedWebAppTest = IsolatedWebAppApiTest;
using ChromeDirectSocketsTcpServerIsolatedWebAppSharedWorkerTest =
IsolatedWebAppSharedWorkerApiTest;
using ChromeDirectSocketsTcpServerIsolatedWebAppServiceWorkerTest =
IsolatedWebAppServiceWorkerApiTest;
IN_PROC_BROWSER_TEST_F(ChromeDirectSocketsTcpServerIsolatedWebAppTest,
TcpServerExchangePacketWithTcpSocket) {
content::RenderFrameHost* app_frame = InstallAndOpenIsolatedWebApp();
EXPECT_THAT(EvalJs(app_frame, kTcpServerExchangePacketWithTcpScript), IsOk());
}
IN_PROC_BROWSER_TEST_F(
ChromeDirectSocketsTcpServerIsolatedWebAppSharedWorkerTest,
TcpServerExchangePacketWithTcpSocket) {
const std::string shared_worker_script = base::StringPrintf(
kSharedWorkerScriptTemplate, kTcpServerExchangePacketWithTcpScript);
content::RenderFrameHost* app_frame =
InstallAndOpenIsolatedWebAppWithSharedWorkerScript(shared_worker_script);
ASSERT_THAT(EvalJs(app_frame, kSharedWorkerConnect), IsOk());
}
IN_PROC_BROWSER_TEST_F(
ChromeDirectSocketsTcpServerIsolatedWebAppServiceWorkerTest,
TcpServerExchangePacketWithTcpSocket) {
const std::string service_worker_script = base::StringPrintf(
kServiceWorkerScriptTemplate, kTcpServerExchangePacketWithTcpScript);
content::RenderFrameHost* app_frame =
InstallAndOpenIsolatedWebAppWithServiceWorkerScript(
service_worker_script);
ASSERT_THAT(EvalJs(app_frame, kServiceWorkerConnect), IsOk());
}
IN_PROC_BROWSER_TEST_F(ChromeDirectSocketsTcpServerIsolatedWebAppTest,
TcpServerPortRestrictions) {
content::RenderFrameHost* app_frame =
InstallAndOpenIsolatedWebApp(/*with_pna=*/true);
constexpr std::string_view kTcpServerPortBelow32678 = R"(
(async () => {
const socket = new TCPServerSocket("0.0.0.0", { localPort: 7845 });
await socket.opened;
})();
)";
ASSERT_THAT(EvalJs(app_frame, kTcpServerPortBelow32678),
ErrorIs(AccessBlocked()));
constexpr std::string_view kTcpServerPortNumberHighEnough = R"(
(async () => {
const socket = new TCPServerSocket("0.0.0.0", { localPort: 35588 });
await socket.opened;
})();
)";
ASSERT_THAT(EvalJs(app_frame, kTcpServerPortNumberHighEnough), IsOk());
}
} // namespace