summaryrefslogtreecommitdiffstats
path: root/dom/network
diff options
context:
space:
mode:
Diffstat (limited to 'dom/network')
-rw-r--r--dom/network/Connection.cpp88
-rw-r--r--dom/network/Connection.h99
-rw-r--r--dom/network/ConnectionMainThread.cpp40
-rw-r--r--dom/network/ConnectionMainThread.h40
-rw-r--r--dom/network/ConnectionWorker.cpp213
-rw-r--r--dom/network/ConnectionWorker.h34
-rw-r--r--dom/network/Constants.h21
-rw-r--r--dom/network/PTCPServerSocket.ipdl35
-rw-r--r--dom/network/PTCPSocket.ipdl79
-rw-r--r--dom/network/PUDPSocket.ipdl72
-rw-r--r--dom/network/TCPServerSocket.cpp172
-rw-r--r--dom/network/TCPServerSocket.h81
-rw-r--r--dom/network/TCPServerSocketChild.cpp75
-rw-r--r--dom/network/TCPServerSocketChild.h59
-rw-r--r--dom/network/TCPServerSocketParent.cpp118
-rw-r--r--dom/network/TCPServerSocketParent.h54
-rw-r--r--dom/network/TCPSocket.cpp1157
-rw-r--r--dom/network/TCPSocket.h247
-rw-r--r--dom/network/TCPSocketChild.cpp177
-rw-r--r--dom/network/TCPSocketChild.h77
-rw-r--r--dom/network/TCPSocketParent.cpp223
-rw-r--r--dom/network/TCPSocketParent.h82
-rw-r--r--dom/network/UDPSocket.cpp713
-rw-r--r--dom/network/UDPSocket.h177
-rw-r--r--dom/network/UDPSocketChild.cpp234
-rw-r--r--dom/network/UDPSocketChild.h109
-rw-r--r--dom/network/UDPSocketParent.cpp551
-rw-r--r--dom/network/UDPSocketParent.h84
-rw-r--r--dom/network/interfaces/moz.build12
-rw-r--r--dom/network/interfaces/nsITCPSocketCallback.idl58
-rw-r--r--dom/network/interfaces/nsIUDPSocketChild.idl30
-rw-r--r--dom/network/moz.build55
-rw-r--r--dom/network/tests/chrome.ini11
-rw-r--r--dom/network/tests/file_postMessage_opener.html11
-rw-r--r--dom/network/tests/file_udpsocket_iframe.html22
-rw-r--r--dom/network/tests/mochitest.ini9
-rw-r--r--dom/network/tests/tcpsocket_test.sys.mjs12
-rw-r--r--dom/network/tests/test_network_basics.html38
-rw-r--r--dom/network/tests/test_network_basics_worker.html35
-rw-r--r--dom/network/tests/test_tcpsocket_client_and_server_basics.html46
-rw-r--r--dom/network/tests/test_tcpsocket_client_and_server_basics.js617
-rw-r--r--dom/network/tests/test_tcpsocket_jsm.html30
-rw-r--r--dom/network/tests/test_tcpsocket_legacy.html57
-rw-r--r--dom/network/tests/test_tcpsocket_not_exposed_to_content.html25
-rw-r--r--dom/network/tests/test_udpsocket.html403
-rw-r--r--dom/network/tests/worker_network_basics.js28
46 files changed, 6610 insertions, 0 deletions
diff --git a/dom/network/Connection.cpp b/dom/network/Connection.cpp
new file mode 100644
index 0000000000..217a937cf6
--- /dev/null
+++ b/dom/network/Connection.cpp
@@ -0,0 +1,88 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "Connection.h"
+#include "ConnectionMainThread.h"
+#include "ConnectionWorker.h"
+#include "Constants.h"
+#include "mozilla/Telemetry.h"
+#include "mozilla/dom/WorkerPrivate.h"
+
+/**
+ * We have to use macros here because our leak analysis tool things we are
+ * leaking strings when we have |static const nsString|. Sad :(
+ */
+#define CHANGE_EVENT_NAME u"typechange"_ns
+
+namespace mozilla::dom::network {
+
+// Don't use |Connection| alone, since that confuses nsTraceRefcnt since
+// we're not the only class with that name.
+NS_IMPL_ISUPPORTS_INHERITED0(dom::network::Connection, DOMEventTargetHelper)
+
+Connection::Connection(nsPIDOMWindowInner* aWindow,
+ bool aShouldResistFingerprinting)
+ : DOMEventTargetHelper(aWindow),
+ mShouldResistFingerprinting(aShouldResistFingerprinting),
+ mType(static_cast<ConnectionType>(kDefaultType)),
+ mIsWifi(kDefaultIsWifi),
+ mDHCPGateway(kDefaultDHCPGateway),
+ mBeenShutDown(false) {
+ Telemetry::Accumulate(Telemetry::NETWORK_CONNECTION_COUNT, 1);
+}
+
+Connection::~Connection() {
+ NS_ASSERT_OWNINGTHREAD(Connection);
+ MOZ_ASSERT(mBeenShutDown);
+}
+
+void Connection::Shutdown() {
+ NS_ASSERT_OWNINGTHREAD(Connection);
+
+ if (mBeenShutDown) {
+ return;
+ }
+
+ mBeenShutDown = true;
+ ShutdownInternal();
+}
+
+JSObject* Connection::WrapObject(JSContext* aCx,
+ JS::Handle<JSObject*> aGivenProto) {
+ return NetworkInformation_Binding::Wrap(aCx, this, aGivenProto);
+}
+
+void Connection::Update(ConnectionType aType, bool aIsWifi,
+ uint32_t aDHCPGateway, bool aNotify) {
+ NS_ASSERT_OWNINGTHREAD(Connection);
+
+ ConnectionType previousType = mType;
+
+ mType = aType;
+ mIsWifi = aIsWifi;
+ mDHCPGateway = aDHCPGateway;
+
+ if (aNotify && previousType != aType && !mShouldResistFingerprinting) {
+ DispatchTrustedEvent(CHANGE_EVENT_NAME);
+ }
+}
+
+/* static */
+Connection* Connection::CreateForWindow(nsPIDOMWindowInner* aWindow,
+ bool aShouldResistFingerprinting) {
+ MOZ_ASSERT(aWindow);
+ return new ConnectionMainThread(aWindow, aShouldResistFingerprinting);
+}
+
+/* static */
+already_AddRefed<Connection> Connection::CreateForWorker(
+ WorkerPrivate* aWorkerPrivate, ErrorResult& aRv) {
+ MOZ_ASSERT(aWorkerPrivate);
+ aWorkerPrivate->AssertIsOnWorkerThread();
+ return ConnectionWorker::Create(aWorkerPrivate, aRv);
+}
+
+} // namespace mozilla::dom::network
diff --git a/dom/network/Connection.h b/dom/network/Connection.h
new file mode 100644
index 0000000000..9d18ad089b
--- /dev/null
+++ b/dom/network/Connection.h
@@ -0,0 +1,99 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_dom_network_Connection_h
+#define mozilla_dom_network_Connection_h
+
+#include "mozilla/DOMEventTargetHelper.h"
+#include "mozilla/dom/NetworkInformationBinding.h"
+#include "nsCycleCollectionParticipant.h"
+
+namespace mozilla {
+
+namespace hal {
+class NetworkInformation;
+} // namespace hal
+
+namespace dom {
+
+class WorkerPrivate;
+
+namespace network {
+
+class Connection : public DOMEventTargetHelper {
+ public:
+ NS_DECL_ISUPPORTS_INHERITED
+
+ static Connection* CreateForWindow(nsPIDOMWindowInner* aWindow,
+ bool aShouldResistFingerprinting);
+
+ static already_AddRefed<Connection> CreateForWorker(
+ WorkerPrivate* aWorkerPrivate, ErrorResult& aRv);
+
+ void Shutdown();
+
+ // WebIDL
+
+ virtual JSObject* WrapObject(JSContext* aCx,
+ JS::Handle<JSObject*> aGivenProto) override;
+
+ ConnectionType Type() const {
+ return mShouldResistFingerprinting
+ ? static_cast<ConnectionType>(ConnectionType::Unknown)
+ : mType;
+ }
+
+ bool GetIsWifi() const {
+ NS_ASSERT_OWNINGTHREAD(Connection);
+
+ return mIsWifi;
+ }
+ uint32_t GetDhcpGateway() const {
+ NS_ASSERT_OWNINGTHREAD(Connection);
+
+ return mDHCPGateway;
+ }
+
+ IMPL_EVENT_HANDLER(typechange)
+
+ protected:
+ Connection(nsPIDOMWindowInner* aWindow, bool aShouldResistFingerprinting);
+ virtual ~Connection();
+
+ void Update(ConnectionType aType, bool aIsWifi, uint32_t aDHCPGateway,
+ bool aNotify);
+
+ virtual void ShutdownInternal() = 0;
+
+ private:
+ /**
+ * If ResistFingerprinting is enabled or disabled.
+ */
+ bool mShouldResistFingerprinting;
+
+ /**
+ * The type of current connection.
+ */
+ ConnectionType mType;
+
+ /**
+ * If the connection is WIFI
+ */
+ bool mIsWifi;
+
+ /**
+ * DHCP Gateway information for IPV4, in network byte order. 0 if unassigned.
+ */
+ uint32_t mDHCPGateway;
+
+ bool mBeenShutDown;
+};
+
+} // namespace network
+} // namespace dom
+} // namespace mozilla
+
+#endif // mozilla_dom_network_Connection_h
diff --git a/dom/network/ConnectionMainThread.cpp b/dom/network/ConnectionMainThread.cpp
new file mode 100644
index 0000000000..210cbaf015
--- /dev/null
+++ b/dom/network/ConnectionMainThread.cpp
@@ -0,0 +1,40 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include <limits>
+#include "mozilla/Hal.h"
+#include "ConnectionMainThread.h"
+
+namespace mozilla::dom::network {
+
+ConnectionMainThread::ConnectionMainThread(nsPIDOMWindowInner* aWindow,
+ bool aShouldResistFingerprinting)
+ : Connection(aWindow, aShouldResistFingerprinting) {
+ hal::RegisterNetworkObserver(this);
+
+ hal::NetworkInformation networkInfo;
+ hal::GetCurrentNetworkInformation(&networkInfo);
+
+ UpdateFromNetworkInfo(networkInfo, false);
+}
+
+ConnectionMainThread::~ConnectionMainThread() { Shutdown(); }
+
+void ConnectionMainThread::ShutdownInternal() {
+ hal::UnregisterNetworkObserver(this);
+}
+
+void ConnectionMainThread::UpdateFromNetworkInfo(
+ const hal::NetworkInformation& aNetworkInfo, bool aNotify) {
+ Update(static_cast<ConnectionType>(aNetworkInfo.type()),
+ aNetworkInfo.isWifi(), aNetworkInfo.dhcpGateway(), aNotify);
+}
+
+void ConnectionMainThread::Notify(const hal::NetworkInformation& aNetworkInfo) {
+ UpdateFromNetworkInfo(aNetworkInfo, true);
+}
+
+} // namespace mozilla::dom::network
diff --git a/dom/network/ConnectionMainThread.h b/dom/network/ConnectionMainThread.h
new file mode 100644
index 0000000000..1e6a12f9b1
--- /dev/null
+++ b/dom/network/ConnectionMainThread.h
@@ -0,0 +1,40 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_dom_network_ConnectionMainThread_h
+#define mozilla_dom_network_ConnectionMainThread_h
+
+#include "Connection.h"
+#include "mozilla/Hal.h"
+#include "mozilla/Observer.h"
+
+namespace mozilla::dom::network {
+
+class ConnectionMainThread final : public Connection,
+ public hal::NetworkObserver {
+ public:
+ explicit ConnectionMainThread(nsPIDOMWindowInner* aWindow,
+ bool aShouldResistFingerprinting);
+
+ // For IObserver
+ void Notify(const hal::NetworkInformation& aNetworkInfo) override;
+
+ private:
+ ~ConnectionMainThread();
+
+ virtual void ShutdownInternal() override;
+
+ /**
+ * Update the connection information stored in the object using a
+ * NetworkInformation object.
+ */
+ void UpdateFromNetworkInfo(const hal::NetworkInformation& aNetworkInfo,
+ bool aNotify);
+};
+
+} // namespace mozilla::dom::network
+
+#endif // mozilla_dom_network_ConnectionMainThread_h
diff --git a/dom/network/ConnectionWorker.cpp b/dom/network/ConnectionWorker.cpp
new file mode 100644
index 0000000000..a72d771de0
--- /dev/null
+++ b/dom/network/ConnectionWorker.cpp
@@ -0,0 +1,213 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include <limits>
+#include "mozilla/Hal.h"
+#include "ConnectionWorker.h"
+#include "mozilla/dom/WorkerPrivate.h"
+#include "mozilla/dom/WorkerRef.h"
+#include "mozilla/dom/WorkerRunnable.h"
+#include "mozilla/dom/WorkerScope.h"
+
+namespace mozilla::dom::network {
+
+class ConnectionProxy final : public hal::NetworkObserver {
+ public:
+ NS_INLINE_DECL_THREADSAFE_REFCOUNTING(ConnectionProxy)
+
+ static already_AddRefed<ConnectionProxy> Create(
+ WorkerPrivate* aWorkerPrivate, ConnectionWorker* aConnection) {
+ RefPtr<ConnectionProxy> proxy = new ConnectionProxy(aConnection);
+
+ RefPtr<StrongWorkerRef> workerRef = StrongWorkerRef::Create(
+ aWorkerPrivate, "ConnectionProxy", [proxy]() { proxy->Shutdown(); });
+ if (NS_WARN_IF(!workerRef)) {
+ return nullptr;
+ }
+
+ proxy->mWorkerRef = new ThreadSafeWorkerRef(workerRef);
+ return proxy.forget();
+ }
+
+ ThreadSafeWorkerRef* WorkerRef() const { return mWorkerRef; }
+
+ // For IObserver - main-thread only.
+ void Notify(const hal::NetworkInformation& aNetworkInfo) override;
+
+ void Shutdown();
+
+ void Update(ConnectionType aType, bool aIsWifi, uint32_t aDHCPGateway) {
+ MOZ_ASSERT(mConnection);
+ MOZ_ASSERT(IsCurrentThreadRunningWorker());
+ mConnection->Update(aType, aIsWifi, aDHCPGateway, true);
+ }
+
+ private:
+ explicit ConnectionProxy(ConnectionWorker* aConnection)
+ : mConnection(aConnection) {}
+
+ ~ConnectionProxy() = default;
+
+ // Raw pointer because the ConnectionWorker keeps alive the proxy.
+ // This is touched only on the worker-thread and it's nullified when the
+ // shutdown procedure starts.
+ ConnectionWorker* mConnection;
+
+ RefPtr<ThreadSafeWorkerRef> mWorkerRef;
+};
+
+namespace {
+
+// This class initializes the hal observer on the main-thread.
+class InitializeRunnable : public WorkerMainThreadRunnable {
+ private:
+ // raw pointer because this is a sync runnable.
+ ConnectionProxy* mProxy;
+ hal::NetworkInformation& mNetworkInfo;
+
+ public:
+ InitializeRunnable(WorkerPrivate* aWorkerPrivate, ConnectionProxy* aProxy,
+ hal::NetworkInformation& aNetworkInfo)
+ : WorkerMainThreadRunnable(aWorkerPrivate,
+ "ConnectionWorker :: Initialize"_ns),
+ mProxy(aProxy),
+ mNetworkInfo(aNetworkInfo) {
+ MOZ_ASSERT(aProxy);
+ aWorkerPrivate->AssertIsOnWorkerThread();
+ }
+
+ bool MainThreadRun() override {
+ MOZ_ASSERT(NS_IsMainThread());
+ hal::RegisterNetworkObserver(mProxy);
+ hal::GetCurrentNetworkInformation(&mNetworkInfo);
+ return true;
+ }
+};
+
+// This class turns down the hal observer on the main-thread.
+class ShutdownRunnable : public WorkerMainThreadRunnable {
+ private:
+ // raw pointer because this is a sync runnable.
+ ConnectionProxy* mProxy;
+
+ public:
+ ShutdownRunnable(WorkerPrivate* aWorkerPrivate, ConnectionProxy* aProxy)
+ : WorkerMainThreadRunnable(aWorkerPrivate,
+ "ConnectionWorker :: Shutdown"_ns),
+ mProxy(aProxy) {
+ MOZ_ASSERT(aProxy);
+ aWorkerPrivate->AssertIsOnWorkerThread();
+ }
+
+ bool MainThreadRun() override {
+ MOZ_ASSERT(NS_IsMainThread());
+ hal::UnregisterNetworkObserver(mProxy);
+ return true;
+ }
+};
+
+class NotifyRunnable : public WorkerRunnable {
+ private:
+ RefPtr<ConnectionProxy> mProxy;
+
+ const ConnectionType mConnectionType;
+ const bool mIsWifi;
+ const uint32_t mDHCPGateway;
+
+ public:
+ NotifyRunnable(WorkerPrivate* aWorkerPrivate, ConnectionProxy* aProxy,
+ ConnectionType aType, bool aIsWifi, uint32_t aDHCPGateway)
+ : WorkerRunnable(aWorkerPrivate),
+ mProxy(aProxy),
+ mConnectionType(aType),
+ mIsWifi(aIsWifi),
+ mDHCPGateway(aDHCPGateway) {
+ MOZ_ASSERT(aProxy);
+ MOZ_ASSERT(NS_IsMainThread());
+ }
+
+ bool WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate) override {
+ aWorkerPrivate->AssertIsOnWorkerThread();
+ mProxy->Update(mConnectionType, mIsWifi, mDHCPGateway);
+ return true;
+ }
+};
+
+} // anonymous namespace
+
+/* static */
+already_AddRefed<ConnectionWorker> ConnectionWorker::Create(
+ WorkerPrivate* aWorkerPrivate, ErrorResult& aRv) {
+ bool shouldResistFingerprinting =
+ aWorkerPrivate->GlobalScope()->ShouldResistFingerprinting(
+ RFPTarget::Unknown);
+ RefPtr<ConnectionWorker> c = new ConnectionWorker(shouldResistFingerprinting);
+ c->mProxy = ConnectionProxy::Create(aWorkerPrivate, c);
+ if (!c->mProxy) {
+ aRv.ThrowTypeError("The Worker thread is shutting down.");
+ return nullptr;
+ }
+
+ hal::NetworkInformation networkInfo;
+ RefPtr<InitializeRunnable> runnable =
+ new InitializeRunnable(aWorkerPrivate, c->mProxy, networkInfo);
+
+ runnable->Dispatch(Canceling, aRv);
+ if (NS_WARN_IF(aRv.Failed())) {
+ return nullptr;
+ }
+
+ c->Update(static_cast<ConnectionType>(networkInfo.type()),
+ networkInfo.isWifi(), networkInfo.dhcpGateway(), false);
+ return c.forget();
+}
+
+ConnectionWorker::ConnectionWorker(bool aShouldResistFingerprinting)
+ : Connection(nullptr, aShouldResistFingerprinting) {
+ MOZ_ASSERT(IsCurrentThreadRunningWorker());
+}
+
+ConnectionWorker::~ConnectionWorker() { Shutdown(); }
+
+void ConnectionWorker::ShutdownInternal() {
+ MOZ_ASSERT(IsCurrentThreadRunningWorker());
+ mProxy->Shutdown();
+}
+
+void ConnectionProxy::Notify(const hal::NetworkInformation& aNetworkInfo) {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ RefPtr<NotifyRunnable> runnable =
+ new NotifyRunnable(mWorkerRef->Private(), this,
+ static_cast<ConnectionType>(aNetworkInfo.type()),
+ aNetworkInfo.isWifi(), aNetworkInfo.dhcpGateway());
+ runnable->Dispatch();
+}
+
+void ConnectionProxy::Shutdown() {
+ MOZ_ASSERT(IsCurrentThreadRunningWorker());
+
+ // Already shut down.
+ if (!mConnection) {
+ return;
+ }
+
+ mConnection = nullptr;
+
+ RefPtr<ShutdownRunnable> runnable =
+ new ShutdownRunnable(mWorkerRef->Private(), this);
+
+ ErrorResult rv;
+ // This runnable _must_ be executed.
+ runnable->Dispatch(Killing, rv);
+ if (NS_WARN_IF(rv.Failed())) {
+ rv.SuppressException();
+ }
+
+ mWorkerRef = nullptr;
+}
+
+} // namespace mozilla::dom::network
diff --git a/dom/network/ConnectionWorker.h b/dom/network/ConnectionWorker.h
new file mode 100644
index 0000000000..7a705e2f08
--- /dev/null
+++ b/dom/network/ConnectionWorker.h
@@ -0,0 +1,34 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_dom_network_ConnectionWorker_h
+#define mozilla_dom_network_ConnectionWorker_h
+
+#include "Connection.h"
+
+namespace mozilla::dom::network {
+
+class ConnectionProxy;
+
+class ConnectionWorker final : public Connection {
+ friend class ConnectionProxy;
+
+ public:
+ static already_AddRefed<ConnectionWorker> Create(
+ WorkerPrivate* aWorkerPrivate, ErrorResult& aRv);
+
+ private:
+ explicit ConnectionWorker(bool aShouldResistFingerprinting);
+ ~ConnectionWorker();
+
+ virtual void ShutdownInternal() override;
+
+ RefPtr<ConnectionProxy> mProxy;
+};
+
+} // namespace mozilla::dom::network
+
+#endif // mozilla_dom_network_ConnectionWorker_h
diff --git a/dom/network/Constants.h b/dom/network/Constants.h
new file mode 100644
index 0000000000..53817bd6b7
--- /dev/null
+++ b/dom/network/Constants.h
@@ -0,0 +1,21 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_dom_network_Constants_h__
+#define mozilla_dom_network_Constants_h__
+
+/**
+ * A set of constants to be used by network backends.
+ */
+namespace mozilla::dom::network {
+
+static const uint32_t kDefaultType = 5; // ConnectionType::None
+static const bool kDefaultIsWifi = false;
+static const uint32_t kDefaultDHCPGateway = 0;
+
+} // namespace mozilla::dom::network
+
+#endif // mozilla_dom_network_Constants_h__
diff --git a/dom/network/PTCPServerSocket.ipdl b/dom/network/PTCPServerSocket.ipdl
new file mode 100644
index 0000000000..8370cf945c
--- /dev/null
+++ b/dom/network/PTCPServerSocket.ipdl
@@ -0,0 +1,35 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set sw=2 ts=8 et tw=80 ft=cpp : */
+
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+include protocol PNecko;
+include protocol PTCPSocket;
+
+include "mozilla/net/NeckoMessageUtils.h";
+include "mozilla/dom/network/TCPServerSocketParent.h";
+include "mozilla/dom/network/TCPServerSocketChild.h";
+
+namespace mozilla {
+namespace net {
+
+//-------------------------------------------------------------------
+[ManualDealloc, ChildImpl="mozilla::dom::TCPServerSocketChild", ParentImpl="mozilla::dom::TCPServerSocketParent"]
+protocol PTCPServerSocket
+{
+ manager PNecko;
+
+parent:
+ async Close();
+ async RequestDelete();
+
+child:
+ async CallbackAccept(PTCPSocket socket);
+ async __delete__();
+};
+
+} // namespace net
+} // namespace mozilla
+
diff --git a/dom/network/PTCPSocket.ipdl b/dom/network/PTCPSocket.ipdl
new file mode 100644
index 0000000000..c255408dd7
--- /dev/null
+++ b/dom/network/PTCPSocket.ipdl
@@ -0,0 +1,79 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set sw=2 ts=8 et tw=80 ft=cpp : */
+
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+include protocol PNecko;
+
+include "mozilla/net/NeckoMessageUtils.h";
+include "mozilla/dom/network/TCPSocketParent.h";
+include "mozilla/dom/network/TCPSocketChild.h";
+
+using struct mozilla::void_t from "mozilla/ipc/IPCCore.h";
+
+struct TCPError {
+ nsString name;
+ nsString message;
+ nsresult errorCode;
+};
+
+union SendableData {
+ uint8_t[];
+ nsCString;
+};
+
+union CallbackData {
+ void_t;
+ SendableData;
+ TCPError;
+};
+
+namespace mozilla {
+namespace net {
+
+//-------------------------------------------------------------------
+[ManualDealloc, ChildImpl="mozilla::dom::TCPSocketChild", ParentImpl="mozilla::dom::TCPSocketParent"]
+protocol PTCPSocket
+{
+ manager PNecko;
+
+parent:
+ // Forward calling to child's open() method to parent, expect TCPOptions
+ // is expanded to |useSSL| (from TCPOptions.useSecureTransport) and
+ // |binaryType| (from TCPOption.binaryType).
+ async Open(nsString host, uint16_t port, bool useSSL, bool useArrayBuffers);
+
+ async Data(SendableData data);
+
+ // Forward calling to child's upgradeToSecure() method to parent.
+ async StartTLS();
+
+ // Forward calling to child's send() method to parent.
+ async Suspend();
+
+ // Forward calling to child's resume() method to parent.
+ async Resume();
+
+ // Forward calling to child's close() method to parent.
+ async Close();
+
+child:
+ // Forward events that are dispatched by parent.
+ async Callback(nsString type, CallbackData data, uint32_t readyState);
+
+ // Update child's bufferedAmount when parent's bufferedAmount is updated.
+ // trackingNumber is also passed back to child to ensure the bufferedAmount
+ // is corresponding the last call to send().
+ async UpdateBufferedAmount(uint32_t bufferedAmount, uint32_t trackingNumber);
+
+both:
+ async RequestDelete();
+ async __delete__();
+};
+
+
+} // namespace net
+} // namespace mozilla
+
diff --git a/dom/network/PUDPSocket.ipdl b/dom/network/PUDPSocket.ipdl
new file mode 100644
index 0000000000..cc5a1729f3
--- /dev/null
+++ b/dom/network/PUDPSocket.ipdl
@@ -0,0 +1,72 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set sw=2 ts=8 et tw=80 ft=cpp : */
+
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+include protocol PNecko;
+include protocol PBackground;
+
+include IPCStream;
+
+include "mozilla/net/NeckoMessageUtils.h";
+include "mozilla/net/DNS.h";
+include "prio.h";
+include "mozilla/dom/network/UDPSocketParent.h";
+include "mozilla/dom/network/UDPSocketChild.h";
+
+using mozilla::net::NetAddr from "mozilla/net/DNS.h";
+using struct mozilla::void_t from "mozilla/ipc/IPCCore.h";
+
+struct UDPAddressInfo {
+ nsCString addr;
+ uint16_t port;
+};
+
+union UDPSocketAddr {
+ UDPAddressInfo;
+ NetAddr;
+};
+
+union UDPData {
+ uint8_t[];
+ IPCStream;
+};
+
+namespace mozilla {
+namespace net {
+
+//-------------------------------------------------------------------
+[ManualDealloc, ChildImpl="mozilla::dom::UDPSocketChild", ParentImpl="mozilla::dom::UDPSocketParent"]
+protocol PUDPSocket
+{
+ manager PNecko or PBackground;
+
+parent:
+ async Bind(UDPAddressInfo addressInfo, bool addressReuse, bool loopback,
+ uint32_t recvBufferSize, uint32_t sendBufferSize);
+ async Connect(UDPAddressInfo addressInfo);
+
+ async OutgoingData(UDPData data, UDPSocketAddr addr);
+
+ async JoinMulticast(nsCString multicastAddress, nsCString iface);
+ async LeaveMulticast(nsCString multicastAddress, nsCString iface);
+
+ async Close();
+
+ async RequestDelete();
+
+child:
+ async CallbackOpened(UDPAddressInfo addressInfo);
+ async CallbackConnected(UDPAddressInfo addressInfo);
+ async CallbackClosed();
+ async CallbackReceivedData(UDPAddressInfo addressInfo, uint8_t[] data);
+ async CallbackError(nsCString message, nsCString filename, uint32_t lineNumber);
+ async __delete__();
+};
+
+
+} // namespace net
+} // namespace mozilla
+
diff --git a/dom/network/TCPServerSocket.cpp b/dom/network/TCPServerSocket.cpp
new file mode 100644
index 0000000000..b39cad0e14
--- /dev/null
+++ b/dom/network/TCPServerSocket.cpp
@@ -0,0 +1,172 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "mozilla/dom/TCPServerSocketBinding.h"
+#include "mozilla/dom/TCPServerSocketEvent.h"
+#include "mozilla/dom/TCPSocketBinding.h"
+#include "TCPServerSocketParent.h"
+#include "TCPServerSocketChild.h"
+#include "mozilla/dom/Event.h"
+#include "mozilla/ErrorResult.h"
+#include "TCPServerSocket.h"
+#include "TCPSocket.h"
+#include "nsComponentManagerUtils.h"
+
+using namespace mozilla::dom;
+
+NS_IMPL_CYCLE_COLLECTION_CLASS(TCPServerSocket)
+
+NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN_INHERITED(TCPServerSocket,
+ DOMEventTargetHelper)
+NS_IMPL_CYCLE_COLLECTION_TRACE_END
+
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(TCPServerSocket,
+ DOMEventTargetHelper)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mServerSocket)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mServerBridgeChild)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mServerBridgeParent)
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
+
+NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(TCPServerSocket,
+ DOMEventTargetHelper)
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mServerSocket)
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mServerBridgeChild)
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mServerBridgeParent)
+NS_IMPL_CYCLE_COLLECTION_UNLINK_END
+
+NS_IMPL_ADDREF_INHERITED(TCPServerSocket, DOMEventTargetHelper)
+NS_IMPL_RELEASE_INHERITED(TCPServerSocket, DOMEventTargetHelper)
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(TCPServerSocket)
+ NS_INTERFACE_MAP_ENTRY(nsIServerSocketListener)
+NS_INTERFACE_MAP_END_INHERITING(DOMEventTargetHelper)
+
+TCPServerSocket::TCPServerSocket(nsIGlobalObject* aGlobal, uint16_t aPort,
+ bool aUseArrayBuffers, uint16_t aBacklog)
+ : DOMEventTargetHelper(aGlobal),
+ mPort(aPort),
+ mBacklog(aBacklog),
+ mUseArrayBuffers(aUseArrayBuffers) {}
+
+TCPServerSocket::~TCPServerSocket() = default;
+
+nsresult TCPServerSocket::Init() {
+ if (mServerSocket || mServerBridgeChild) {
+ NS_WARNING("Child TCPServerSocket is already listening.");
+ return NS_ERROR_FAILURE;
+ }
+
+ if (XRE_GetProcessType() == GeckoProcessType_Content) {
+ mServerBridgeChild =
+ new TCPServerSocketChild(this, mPort, mBacklog, mUseArrayBuffers);
+ return NS_OK;
+ }
+
+ nsresult rv;
+ mServerSocket =
+ do_CreateInstance("@mozilla.org/network/server-socket;1", &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = mServerSocket->Init(mPort, false, mBacklog);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = mServerSocket->GetPort(&mPort);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = mServerSocket->AsyncListen(this);
+ NS_ENSURE_SUCCESS(rv, rv);
+ return NS_OK;
+}
+
+already_AddRefed<TCPServerSocket> TCPServerSocket::Constructor(
+ const GlobalObject& aGlobal, uint16_t aPort,
+ const ServerSocketOptions& aOptions, uint16_t aBacklog,
+ mozilla::ErrorResult& aRv) {
+ nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(aGlobal.GetAsSupports());
+ if (!global) {
+ aRv = NS_ERROR_FAILURE;
+ return nullptr;
+ }
+ bool useArrayBuffers =
+ aOptions.mBinaryType == TCPSocketBinaryType::Arraybuffer;
+ RefPtr<TCPServerSocket> socket =
+ new TCPServerSocket(global, aPort, useArrayBuffers, aBacklog);
+ nsresult rv = socket->Init();
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ aRv = NS_ERROR_FAILURE;
+ return nullptr;
+ }
+ return socket.forget();
+}
+
+uint16_t TCPServerSocket::LocalPort() const { return mPort; }
+
+void TCPServerSocket::Close() {
+ if (mServerBridgeChild) {
+ mServerBridgeChild->Close();
+ }
+ if (mServerSocket) {
+ mServerSocket->Close();
+ }
+}
+
+void TCPServerSocket::FireEvent(const nsAString& aType, TCPSocket* aSocket) {
+ TCPServerSocketEventInit init;
+ init.mBubbles = false;
+ init.mCancelable = false;
+ init.mSocket = aSocket;
+
+ RefPtr<TCPServerSocketEvent> event =
+ TCPServerSocketEvent::Constructor(this, aType, init);
+ event->SetTrusted(true);
+ DispatchEvent(*event);
+
+ if (mServerBridgeParent) {
+ mServerBridgeParent->OnConnect(event);
+ }
+}
+
+NS_IMETHODIMP
+TCPServerSocket::OnSocketAccepted(nsIServerSocket* aServer,
+ nsISocketTransport* aTransport) {
+ nsCOMPtr<nsIGlobalObject> global = GetOwnerGlobal();
+ RefPtr<TCPSocket> socket =
+ TCPSocket::CreateAcceptedSocket(global, aTransport, mUseArrayBuffers);
+ FireEvent(u"connect"_ns, socket);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+TCPServerSocket::OnStopListening(nsIServerSocket* aServer, nsresult aStatus) {
+ if (aStatus != NS_BINDING_ABORTED) {
+ RefPtr<Event> event = new Event(GetOwner());
+ event->InitEvent(u"error"_ns, false, false);
+ event->SetTrusted(true);
+ DispatchEvent(*event);
+
+ NS_WARNING("Server socket was closed by unexpected reason.");
+ return NS_ERROR_FAILURE;
+ }
+ mServerSocket = nullptr;
+ return NS_OK;
+}
+
+nsresult TCPServerSocket::AcceptChildSocket(TCPSocketChild* aSocketChild) {
+ nsCOMPtr<nsIGlobalObject> global = GetOwnerGlobal();
+ NS_ENSURE_TRUE(global, NS_ERROR_FAILURE);
+ RefPtr<TCPSocket> socket =
+ TCPSocket::CreateAcceptedSocket(global, aSocketChild, mUseArrayBuffers);
+ NS_ENSURE_TRUE(socket, NS_ERROR_FAILURE);
+ FireEvent(u"connect"_ns, socket);
+ return NS_OK;
+}
+
+void TCPServerSocket::SetServerBridgeParent(
+ TCPServerSocketParent* aBridgeParent) {
+ mServerBridgeParent = aBridgeParent;
+}
+
+JSObject* TCPServerSocket::WrapObject(JSContext* aCx,
+ JS::Handle<JSObject*> aGivenProto) {
+ return TCPServerSocket_Binding::Wrap(aCx, this, aGivenProto);
+}
diff --git a/dom/network/TCPServerSocket.h b/dom/network/TCPServerSocket.h
new file mode 100644
index 0000000000..550cba683e
--- /dev/null
+++ b/dom/network/TCPServerSocket.h
@@ -0,0 +1,81 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_dom_TCPServerSocket_h
+#define mozilla_dom_TCPServerSocket_h
+
+#include "mozilla/DOMEventTargetHelper.h"
+#include "nsIServerSocket.h"
+
+namespace mozilla {
+class ErrorResult;
+namespace dom {
+
+struct ServerSocketOptions;
+class GlobalObject;
+class TCPSocket;
+class TCPSocketChild;
+class TCPServerSocketChild;
+class TCPServerSocketParent;
+
+class TCPServerSocket final : public DOMEventTargetHelper,
+ public nsIServerSocketListener {
+ public:
+ TCPServerSocket(nsIGlobalObject* aGlobal, uint16_t aPort,
+ bool aUseArrayBuffers, uint16_t aBacklog);
+
+ NS_DECL_ISUPPORTS_INHERITED
+ NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS_INHERITED(TCPServerSocket,
+ DOMEventTargetHelper)
+ NS_DECL_NSISERVERSOCKETLISTENER
+
+ nsPIDOMWindowInner* GetParentObject() const { return GetOwner(); }
+
+ virtual JSObject* WrapObject(JSContext* aCx,
+ JS::Handle<JSObject*> aGivenProto) override;
+
+ nsresult Init();
+
+ uint16_t LocalPort() const;
+ void Close();
+
+ static already_AddRefed<TCPServerSocket> Constructor(
+ const GlobalObject& aGlobal, uint16_t aPort,
+ const ServerSocketOptions& aOptions, uint16_t aBacklog,
+ mozilla::ErrorResult& aRv);
+
+ IMPL_EVENT_HANDLER(connect);
+ IMPL_EVENT_HANDLER(error);
+
+ // Relay an accepted socket notification from the parent process and
+ // initialize this object with an existing child actor for the new socket.
+ nsresult AcceptChildSocket(TCPSocketChild* aSocketChild);
+ // Associate this object with an IPC actor in the parent process to relay
+ // notifications to content processes.
+ void SetServerBridgeParent(TCPServerSocketParent* aBridgeParent);
+
+ private:
+ ~TCPServerSocket();
+ // Dispatch a TCPServerSocketEvent event of a given type at this object.
+ void FireEvent(const nsAString& aType, TCPSocket* aSocket);
+
+ // The server socket associated with this object.
+ nsCOMPtr<nsIServerSocket> mServerSocket;
+ // The IPC actor in the content process.
+ RefPtr<TCPServerSocketChild> mServerBridgeChild;
+ // The IPC actor in the parent process.
+ RefPtr<TCPServerSocketParent> mServerBridgeParent;
+ int32_t mPort;
+ uint16_t mBacklog;
+ // True if any accepted sockets should use array buffers for received
+ // messages.
+ bool mUseArrayBuffers;
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif // mozilla_dom_TCPServerSocket_h
diff --git a/dom/network/TCPServerSocketChild.cpp b/dom/network/TCPServerSocketChild.cpp
new file mode 100644
index 0000000000..544c5391ee
--- /dev/null
+++ b/dom/network/TCPServerSocketChild.cpp
@@ -0,0 +1,75 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "TCPServerSocketChild.h"
+#include "TCPSocketChild.h"
+#include "TCPServerSocket.h"
+#include "mozilla/net/NeckoChild.h"
+#include "mozilla/dom/PBrowserChild.h"
+#include "mozilla/dom/BrowserChild.h"
+#include "nsJSUtils.h"
+#include "jsfriendapi.h"
+
+using mozilla::net::gNeckoChild;
+
+namespace mozilla::dom {
+
+NS_IMPL_CYCLE_COLLECTION(TCPServerSocketChildBase, mServerSocket)
+NS_IMPL_CYCLE_COLLECTING_ADDREF(TCPServerSocketChildBase)
+NS_IMPL_CYCLE_COLLECTING_RELEASE(TCPServerSocketChildBase)
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(TCPServerSocketChildBase)
+ NS_INTERFACE_MAP_ENTRY(nsISupports)
+NS_INTERFACE_MAP_END
+
+TCPServerSocketChildBase::TCPServerSocketChildBase() : mIPCOpen(false) {}
+
+TCPServerSocketChildBase::~TCPServerSocketChildBase() = default;
+
+NS_IMETHODIMP_(MozExternalRefCountType) TCPServerSocketChild::Release(void) {
+ nsrefcnt refcnt = TCPServerSocketChildBase::Release();
+ if (refcnt == 1 && mIPCOpen) {
+ PTCPServerSocketChild::SendRequestDelete();
+ return 1;
+ }
+ return refcnt;
+}
+
+TCPServerSocketChild::TCPServerSocketChild(TCPServerSocket* aServerSocket,
+ uint16_t aLocalPort,
+ uint16_t aBacklog,
+ bool aUseArrayBuffers) {
+ mServerSocket = aServerSocket;
+ AddIPDLReference();
+ gNeckoChild->SendPTCPServerSocketConstructor(this, aLocalPort, aBacklog,
+ aUseArrayBuffers);
+}
+
+void TCPServerSocketChildBase::ReleaseIPDLReference() {
+ MOZ_ASSERT(mIPCOpen);
+ mIPCOpen = false;
+ this->Release();
+}
+
+void TCPServerSocketChildBase::AddIPDLReference() {
+ MOZ_ASSERT(!mIPCOpen);
+ mIPCOpen = true;
+ this->AddRef();
+}
+
+TCPServerSocketChild::~TCPServerSocketChild() = default;
+
+mozilla::ipc::IPCResult TCPServerSocketChild::RecvCallbackAccept(
+ PTCPSocketChild* psocket) {
+ RefPtr<TCPSocketChild> socket = static_cast<TCPSocketChild*>(psocket);
+ nsresult rv = mServerSocket->AcceptChildSocket(socket);
+ NS_ENSURE_SUCCESS(rv, IPC_OK());
+ return IPC_OK();
+}
+
+void TCPServerSocketChild::Close() { SendClose(); }
+
+} // namespace mozilla::dom
diff --git a/dom/network/TCPServerSocketChild.h b/dom/network/TCPServerSocketChild.h
new file mode 100644
index 0000000000..b6773cb61b
--- /dev/null
+++ b/dom/network/TCPServerSocketChild.h
@@ -0,0 +1,59 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_dom_TCPServerSocketChild_h
+#define mozilla_dom_TCPServerSocketChild_h
+
+#include "mozilla/net/PTCPServerSocketChild.h"
+#include "nsCycleCollectionParticipant.h"
+#include "nsCOMPtr.h"
+
+#define TCPSERVERSOCKETCHILD_CID \
+ { \
+ 0x41a77ec8, 0xfd86, 0x409e, { \
+ 0xae, 0xa9, 0xaf, 0x2c, 0xa4, 0x07, 0xef, 0x8e \
+ } \
+ }
+
+class nsITCPServerSocketInternal;
+
+namespace mozilla::dom {
+
+class TCPServerSocket;
+
+class TCPServerSocketChildBase : public nsISupports {
+ public:
+ NS_DECL_CYCLE_COLLECTION_CLASS(TCPServerSocketChildBase)
+ NS_DECL_CYCLE_COLLECTING_ISUPPORTS
+
+ void AddIPDLReference();
+ void ReleaseIPDLReference();
+
+ protected:
+ TCPServerSocketChildBase();
+ virtual ~TCPServerSocketChildBase();
+
+ RefPtr<TCPServerSocket> mServerSocket;
+ bool mIPCOpen;
+};
+
+class TCPServerSocketChild : public mozilla::net::PTCPServerSocketChild,
+ public TCPServerSocketChildBase {
+ public:
+ NS_IMETHOD_(MozExternalRefCountType) Release() override;
+
+ TCPServerSocketChild(TCPServerSocket* aServerSocket, uint16_t aLocalPort,
+ uint16_t aBacklog, bool aUseArrayBuffers);
+ ~TCPServerSocketChild();
+
+ void Close();
+
+ mozilla::ipc::IPCResult RecvCallbackAccept(PTCPSocketChild* socket);
+};
+
+} // namespace mozilla::dom
+
+#endif // mozilla_dom_TCPServerSocketChild_h
diff --git a/dom/network/TCPServerSocketParent.cpp b/dom/network/TCPServerSocketParent.cpp
new file mode 100644
index 0000000000..c73ee7e0ff
--- /dev/null
+++ b/dom/network/TCPServerSocketParent.cpp
@@ -0,0 +1,118 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "TCPServerSocket.h"
+#include "TCPServerSocketParent.h"
+#include "nsJSUtils.h"
+#include "TCPSocket.h"
+#include "TCPSocketParent.h"
+#include "mozilla/Unused.h"
+#include "mozilla/dom/BrowserParent.h"
+#include "mozilla/dom/TCPServerSocketEvent.h"
+
+namespace mozilla::dom {
+
+NS_IMPL_CYCLE_COLLECTION(TCPServerSocketParent, mServerSocket)
+NS_IMPL_CYCLE_COLLECTING_ADDREF(TCPServerSocketParent)
+NS_IMPL_CYCLE_COLLECTING_RELEASE(TCPServerSocketParent)
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(TCPServerSocketParent)
+ NS_INTERFACE_MAP_ENTRY(nsISupports)
+NS_INTERFACE_MAP_END
+
+void TCPServerSocketParent::ReleaseIPDLReference() {
+ MOZ_ASSERT(mIPCOpen);
+ mIPCOpen = false;
+ this->Release();
+}
+
+void TCPServerSocketParent::AddIPDLReference() {
+ MOZ_ASSERT(!mIPCOpen);
+ mIPCOpen = true;
+ this->AddRef();
+}
+
+TCPServerSocketParent::TCPServerSocketParent(PNeckoParent* neckoParent,
+ uint16_t aLocalPort,
+ uint16_t aBacklog,
+ bool aUseArrayBuffers)
+ : mNeckoParent(neckoParent), mIPCOpen(false) {
+ mServerSocket =
+ new TCPServerSocket(nullptr, aLocalPort, aUseArrayBuffers, aBacklog);
+ mServerSocket->SetServerBridgeParent(this);
+}
+
+TCPServerSocketParent::~TCPServerSocketParent() = default;
+
+void TCPServerSocketParent::Init() {
+ NS_ENSURE_SUCCESS_VOID(mServerSocket->Init());
+}
+
+nsresult TCPServerSocketParent::SendCallbackAccept(TCPSocketParent* socket) {
+ nsresult rv;
+
+ nsString host;
+ rv = socket->GetHost(host);
+ if (NS_FAILED(rv)) {
+ NS_ERROR("Failed to get host from nsITCPSocketParent");
+ return NS_ERROR_FAILURE;
+ }
+
+ uint16_t port;
+ rv = socket->GetPort(&port);
+ if (NS_FAILED(rv)) {
+ NS_ERROR("Failed to get port from nsITCPSocketParent");
+ return NS_ERROR_FAILURE;
+ }
+
+ if (mNeckoParent) {
+ if (mNeckoParent->SendPTCPSocketConstructor(socket, host, port)) {
+ // Call |AddIPDLReference| after the consructor message is sent
+ // successfully, otherwise |socket| could be leaked.
+ socket->AddIPDLReference();
+
+ mozilla::Unused << PTCPServerSocketParent::SendCallbackAccept(
+ WrapNotNull(socket));
+ } else {
+ NS_ERROR("Sending data from PTCPSocketParent was failed.");
+ }
+ } else {
+ NS_ERROR("The member value for NeckoParent is wrong.");
+ }
+ return NS_OK;
+}
+
+mozilla::ipc::IPCResult TCPServerSocketParent::RecvClose() {
+ NS_ENSURE_TRUE(mServerSocket, IPC_OK());
+ mServerSocket->Close();
+ return IPC_OK();
+}
+
+void TCPServerSocketParent::ActorDestroy(ActorDestroyReason why) {
+ if (mServerSocket) {
+ mServerSocket->Close();
+ mServerSocket = nullptr;
+ }
+ mNeckoParent = nullptr;
+}
+
+mozilla::ipc::IPCResult TCPServerSocketParent::RecvRequestDelete() {
+ mozilla::Unused << Send__delete__(this);
+ return IPC_OK();
+}
+
+void TCPServerSocketParent::OnConnect(TCPServerSocketEvent* event) {
+ RefPtr<TCPSocket> socket = event->Socket();
+
+ RefPtr<TCPSocketParent> socketParent = new TCPSocketParent();
+ socketParent->SetSocket(socket);
+
+ socket->SetSocketBridgeParent(socketParent);
+
+ SendCallbackAccept(socketParent);
+}
+
+} // namespace mozilla::dom
diff --git a/dom/network/TCPServerSocketParent.h b/dom/network/TCPServerSocketParent.h
new file mode 100644
index 0000000000..bbebaa1860
--- /dev/null
+++ b/dom/network/TCPServerSocketParent.h
@@ -0,0 +1,54 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_dom_TCPServerSocketParent_h
+#define mozilla_dom_TCPServerSocketParent_h
+
+#include "mozilla/net/PNeckoParent.h"
+#include "mozilla/net/PTCPServerSocketParent.h"
+#include "nsCycleCollectionParticipant.h"
+#include "nsCOMPtr.h"
+
+namespace mozilla::dom {
+
+class TCPServerSocket;
+class TCPServerSocketEvent;
+class TCPSocketParent;
+
+class TCPServerSocketParent : public mozilla::net::PTCPServerSocketParent,
+ public nsISupports {
+ public:
+ NS_DECL_CYCLE_COLLECTION_CLASS(TCPServerSocketParent)
+ NS_DECL_CYCLE_COLLECTING_ISUPPORTS
+
+ TCPServerSocketParent(PNeckoParent* neckoParent, uint16_t aLocalPort,
+ uint16_t aBacklog, bool aUseArrayBuffers);
+
+ void Init();
+
+ mozilla::ipc::IPCResult RecvClose();
+ mozilla::ipc::IPCResult RecvRequestDelete();
+
+ void AddIPDLReference();
+ void ReleaseIPDLReference();
+
+ void OnConnect(TCPServerSocketEvent* event);
+
+ private:
+ ~TCPServerSocketParent();
+
+ nsresult SendCallbackAccept(TCPSocketParent* socket);
+
+ virtual void ActorDestroy(ActorDestroyReason why) override;
+
+ PNeckoParent* mNeckoParent;
+ RefPtr<TCPServerSocket> mServerSocket;
+ bool mIPCOpen;
+};
+
+} // namespace mozilla::dom
+
+#endif // mozilla_dom_TCPServerSocketParent_h
diff --git a/dom/network/TCPSocket.cpp b/dom/network/TCPSocket.cpp
new file mode 100644
index 0000000000..6dabd1a630
--- /dev/null
+++ b/dom/network/TCPSocket.cpp
@@ -0,0 +1,1157 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "TCPServerSocket.h"
+#include "TCPSocket.h"
+#include "TCPSocketChild.h"
+#include "TCPSocketParent.h"
+#include "mozilla/BasePrincipal.h"
+#include "mozilla/ErrorResult.h"
+#include "mozilla/SyncRunnable.h"
+#include "mozilla/dom/RootedDictionary.h"
+#include "mozilla/dom/ScriptSettings.h"
+#include "mozilla/dom/TCPSocketBinding.h"
+#include "mozilla/dom/TCPSocketErrorEvent.h"
+#include "mozilla/dom/TCPSocketErrorEventBinding.h"
+#include "mozilla/dom/TCPSocketEvent.h"
+#include "mozilla/dom/TCPSocketEventBinding.h"
+#include "mozilla/dom/ToJSValue.h"
+#include "nsComponentManagerUtils.h"
+#include "nsContentUtils.h"
+#include "nsIArrayBufferInputStream.h"
+#include "nsIAsyncInputStream.h"
+#include "nsIAsyncStreamCopier.h"
+#include "nsIBinaryInputStream.h"
+#include "nsICancelable.h"
+#include "nsIChannel.h"
+#include "nsIInputStream.h"
+#include "nsIInputStreamPump.h"
+#include "nsIMultiplexInputStream.h"
+#include "nsINSSErrorsService.h"
+#include "nsIObserverService.h"
+#include "nsIOutputStream.h"
+#include "nsIProtocolProxyService.h"
+#include "nsIScriptableInputStream.h"
+#include "nsISocketTransport.h"
+#include "nsISocketTransportService.h"
+#include "nsISupportsPrimitives.h"
+#include "nsITLSSocketControl.h"
+#include "nsITransport.h"
+#include "nsIURIMutator.h"
+#include "nsNetCID.h"
+#include "nsNetUtil.h"
+#include "nsServiceManagerUtils.h"
+#include "nsString.h"
+#include "nsStringStream.h"
+#include "secerr.h"
+#include "sslerr.h"
+
+#define BUFFER_SIZE 65536
+#define NETWORK_STATS_THRESHOLD 65536
+
+using namespace mozilla::dom;
+
+NS_IMPL_CYCLE_COLLECTION(LegacyMozTCPSocket, mGlobal)
+
+NS_IMPL_CYCLE_COLLECTING_ADDREF(LegacyMozTCPSocket)
+NS_IMPL_CYCLE_COLLECTING_RELEASE(LegacyMozTCPSocket)
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(LegacyMozTCPSocket)
+ NS_INTERFACE_MAP_ENTRY(nsISupports)
+NS_INTERFACE_MAP_END
+
+LegacyMozTCPSocket::LegacyMozTCPSocket(nsPIDOMWindowInner* aWindow)
+ : mGlobal(do_QueryInterface(aWindow)) {}
+
+LegacyMozTCPSocket::~LegacyMozTCPSocket() = default;
+
+already_AddRefed<TCPSocket> LegacyMozTCPSocket::Open(
+ const nsAString& aHost, uint16_t aPort, const SocketOptions& aOptions,
+ mozilla::ErrorResult& aRv) {
+ AutoJSAPI api;
+ if (NS_WARN_IF(!api.Init(mGlobal))) {
+ aRv.Throw(NS_ERROR_FAILURE);
+ return nullptr;
+ }
+ GlobalObject globalObj(api.cx(), mGlobal->GetGlobalJSObject());
+ return TCPSocket::Constructor(globalObj, aHost, aPort, aOptions, aRv);
+}
+
+already_AddRefed<TCPServerSocket> LegacyMozTCPSocket::Listen(
+ uint16_t aPort, const ServerSocketOptions& aOptions, uint16_t aBacklog,
+ mozilla::ErrorResult& aRv) {
+ AutoJSAPI api;
+ if (NS_WARN_IF(!api.Init(mGlobal))) {
+ return nullptr;
+ }
+ GlobalObject globalObj(api.cx(), mGlobal->GetGlobalJSObject());
+ return TCPServerSocket::Constructor(globalObj, aPort, aOptions, aBacklog,
+ aRv);
+}
+
+bool LegacyMozTCPSocket::WrapObject(JSContext* aCx,
+ JS::Handle<JSObject*> aGivenProto,
+ JS::MutableHandle<JSObject*> aReflector) {
+ return LegacyMozTCPSocket_Binding::Wrap(aCx, this, aGivenProto, aReflector);
+}
+
+NS_IMPL_CYCLE_COLLECTION_CLASS(TCPSocket)
+
+NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN_INHERITED(TCPSocket, DOMEventTargetHelper)
+NS_IMPL_CYCLE_COLLECTION_TRACE_END
+
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(TCPSocket,
+ DOMEventTargetHelper)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mTransport)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mSocketInputStream)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mSocketOutputStream)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mInputStreamPump)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mInputStreamScriptable)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mInputStreamBinary)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mPendingDataAfterStartTLS)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mSocketBridgeChild)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mSocketBridgeParent)
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
+
+NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(TCPSocket, DOMEventTargetHelper)
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mTransport)
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mSocketInputStream)
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mSocketOutputStream)
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mInputStreamPump)
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mInputStreamScriptable)
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mInputStreamBinary)
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mPendingDataAfterStartTLS)
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mSocketBridgeChild)
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mSocketBridgeParent)
+ NS_IMPL_CYCLE_COLLECTION_UNLINK_WEAK_REFERENCE
+NS_IMPL_CYCLE_COLLECTION_UNLINK_END
+
+NS_IMPL_ADDREF_INHERITED(TCPSocket, DOMEventTargetHelper)
+NS_IMPL_RELEASE_INHERITED(TCPSocket, DOMEventTargetHelper)
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(TCPSocket)
+ NS_INTERFACE_MAP_ENTRY(nsITransportEventSink)
+ NS_INTERFACE_MAP_ENTRY(nsIRequestObserver)
+ NS_INTERFACE_MAP_ENTRY(nsIStreamListener)
+ NS_INTERFACE_MAP_ENTRY(nsIInputStreamCallback)
+ NS_INTERFACE_MAP_ENTRY(nsIObserver)
+ NS_INTERFACE_MAP_ENTRY(nsISupportsWeakReference)
+ NS_INTERFACE_MAP_ENTRY(nsITCPSocketCallback)
+ NS_INTERFACE_MAP_ENTRY(nsIProtocolProxyCallback)
+NS_INTERFACE_MAP_END_INHERITING(DOMEventTargetHelper)
+
+TCPSocket::TCPSocket(nsIGlobalObject* aGlobal, const nsAString& aHost,
+ uint16_t aPort, bool aSsl, bool aUseArrayBuffers)
+ : DOMEventTargetHelper(aGlobal),
+ mReadyState(TCPReadyState::Closed),
+ mUseArrayBuffers(aUseArrayBuffers),
+ mHost(aHost),
+ mPort(aPort),
+ mSsl(aSsl),
+ mAsyncCopierActive(false),
+ mWaitingForDrain(false),
+ mInnerWindowID(0),
+ mBufferedAmount(0),
+ mSuspendCount(0),
+ mTrackingNumber(0),
+ mWaitingForStartTLS(false),
+ mObserversActive(false) {
+ if (aGlobal) {
+ nsCOMPtr<nsPIDOMWindowInner> window = do_QueryInterface(aGlobal);
+ if (window) {
+ mInnerWindowID = window->WindowID();
+ }
+ }
+}
+
+TCPSocket::~TCPSocket() {
+ if (mObserversActive) {
+ nsCOMPtr<nsIObserverService> obs =
+ do_GetService("@mozilla.org/observer-service;1");
+ if (obs) {
+ obs->RemoveObserver(this, "inner-window-destroyed");
+ obs->RemoveObserver(this, "profile-change-net-teardown");
+ }
+ }
+}
+
+nsresult TCPSocket::CreateStream() {
+ nsresult rv =
+ mTransport->OpenInputStream(0, 0, 0, getter_AddRefs(mSocketInputStream));
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = mTransport->OpenOutputStream(nsITransport::OPEN_UNBUFFERED, 0, 0,
+ getter_AddRefs(mSocketOutputStream));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // If the other side is not listening, we will
+ // get an onInputStreamReady callback where available
+ // raises to indicate the connection was refused.
+ nsCOMPtr<nsIAsyncInputStream> asyncStream =
+ do_QueryInterface(mSocketInputStream);
+ NS_ENSURE_TRUE(asyncStream, NS_ERROR_NOT_AVAILABLE);
+
+ nsCOMPtr<nsISerialEventTarget> mainTarget = GetMainThreadSerialEventTarget();
+ rv = asyncStream->AsyncWait(this, nsIAsyncInputStream::WAIT_CLOSURE_ONLY, 0,
+ mainTarget);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (mUseArrayBuffers) {
+ mInputStreamBinary =
+ do_CreateInstance("@mozilla.org/binaryinputstream;1", &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = mInputStreamBinary->SetInputStream(mSocketInputStream);
+ NS_ENSURE_SUCCESS(rv, rv);
+ } else {
+ mInputStreamScriptable =
+ do_CreateInstance("@mozilla.org/scriptableinputstream;1", &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = mInputStreamScriptable->Init(mSocketInputStream);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ return NS_OK;
+}
+
+nsresult TCPSocket::InitWithUnconnectedTransport(
+ nsISocketTransport* aTransport) {
+ mReadyState = TCPReadyState::Connecting;
+ mTransport = aTransport;
+
+ MOZ_ASSERT(XRE_GetProcessType() != GeckoProcessType_Content);
+
+ nsCOMPtr<nsISerialEventTarget> mainTarget = GetMainThreadSerialEventTarget();
+ mTransport->SetEventSink(this, mainTarget);
+
+ nsresult rv = CreateStream();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return NS_OK;
+}
+
+nsresult TCPSocket::Init(nsIProxyInfo* aProxyInfo) {
+ nsCOMPtr<nsIObserverService> obs =
+ do_GetService("@mozilla.org/observer-service;1");
+ if (obs) {
+ mObserversActive = true;
+ obs->AddObserver(this, "inner-window-destroyed", true); // weak reference
+ obs->AddObserver(this, "profile-change-net-teardown", true); // weak ref
+ }
+
+ if (XRE_GetProcessType() == GeckoProcessType_Content) {
+ mReadyState = TCPReadyState::Connecting;
+
+ nsCOMPtr<nsISerialEventTarget> target;
+ if (nsCOMPtr<nsIGlobalObject> global = GetOwnerGlobal()) {
+ target = global->EventTargetFor(TaskCategory::Other);
+ }
+ mSocketBridgeChild = new TCPSocketChild(mHost, mPort, target);
+ mSocketBridgeChild->SendOpen(this, mSsl, mUseArrayBuffers);
+ return NS_OK;
+ }
+
+ nsCOMPtr<nsISocketTransportService> sts =
+ do_GetService("@mozilla.org/network/socket-transport-service;1");
+
+ AutoTArray<nsCString, 1> socketTypes;
+ if (mSsl) {
+ socketTypes.AppendElement("ssl"_ns);
+ } else {
+ socketTypes.AppendElement("starttls"_ns);
+ }
+ nsCOMPtr<nsISocketTransport> transport;
+ nsresult rv =
+ sts->CreateTransport(socketTypes, NS_ConvertUTF16toUTF8(mHost), mPort,
+ aProxyInfo, nullptr, getter_AddRefs(transport));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return InitWithUnconnectedTransport(transport);
+}
+
+void TCPSocket::InitWithSocketChild(TCPSocketChild* aSocketBridge) {
+ mSocketBridgeChild = aSocketBridge;
+ mReadyState = TCPReadyState::Open;
+ mSocketBridgeChild->SetSocket(this);
+ mSocketBridgeChild->GetHost(mHost);
+ mSocketBridgeChild->GetPort(&mPort);
+}
+
+nsresult TCPSocket::InitWithTransport(nsISocketTransport* aTransport) {
+ mTransport = aTransport;
+ nsresult rv = CreateStream();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ mReadyState = TCPReadyState::Open;
+ rv = CreateInputStreamPump();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsAutoCString host;
+ mTransport->GetHost(host);
+ CopyUTF8toUTF16(host, mHost);
+ int32_t port;
+ mTransport->GetPort(&port);
+ mPort = port;
+
+ return NS_OK;
+}
+
+void TCPSocket::UpgradeToSecure(mozilla::ErrorResult& aRv) {
+ if (mReadyState != TCPReadyState::Open) {
+ aRv.Throw(NS_ERROR_FAILURE);
+ return;
+ }
+
+ if (mSsl) {
+ return;
+ }
+
+ mSsl = true;
+
+ if (mSocketBridgeChild) {
+ mSocketBridgeChild->SendStartTLS();
+ return;
+ }
+
+ if (!mAsyncCopierActive) {
+ ActivateTLS();
+ } else {
+ mWaitingForStartTLS = true;
+ }
+}
+
+namespace {
+class CopierCallbacks final : public nsIRequestObserver {
+ RefPtr<TCPSocket> mOwner;
+
+ public:
+ explicit CopierCallbacks(TCPSocket* aSocket) : mOwner(aSocket) {}
+
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIREQUESTOBSERVER
+ private:
+ ~CopierCallbacks() = default;
+};
+
+NS_IMPL_ISUPPORTS(CopierCallbacks, nsIRequestObserver)
+
+NS_IMETHODIMP
+CopierCallbacks::OnStartRequest(nsIRequest* aRequest) { return NS_OK; }
+
+NS_IMETHODIMP
+CopierCallbacks::OnStopRequest(nsIRequest* aRequest, nsresult aStatus) {
+ mOwner->NotifyCopyComplete(aStatus);
+ mOwner = nullptr;
+ return NS_OK;
+}
+} // unnamed namespace
+
+void TCPSocket::CalculateBufferedAmount() {
+ // Let's update the buffered amount of data.
+ uint64_t bufferedAmount = 0;
+ for (uint32_t i = 0, len = mPendingData.Length(); i < len; ++i) {
+ nsCOMPtr<nsIInputStream> stream = mPendingData[i];
+ uint64_t available = 0;
+ if (NS_SUCCEEDED(stream->Available(&available))) {
+ bufferedAmount += available;
+ }
+ }
+ mBufferedAmount = bufferedAmount;
+}
+
+nsresult TCPSocket::EnsureCopying() {
+ if (mAsyncCopierActive) {
+ return NS_OK;
+ }
+
+ mAsyncCopierActive = true;
+
+ nsresult rv;
+
+ nsCOMPtr<nsIMultiplexInputStream> multiplexStream =
+ do_CreateInstance("@mozilla.org/io/multiplex-input-stream;1", &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIInputStream> stream = do_QueryInterface(multiplexStream);
+
+ while (!mPendingData.IsEmpty()) {
+ nsCOMPtr<nsIInputStream> stream = mPendingData[0];
+ multiplexStream->AppendStream(stream);
+ mPendingData.RemoveElementAt(0);
+ }
+
+ nsCOMPtr<nsIAsyncStreamCopier> copier =
+ do_CreateInstance("@mozilla.org/network/async-stream-copier;1", &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsISocketTransportService> sts =
+ do_GetService("@mozilla.org/network/socket-transport-service;1");
+
+ nsCOMPtr<nsISerialEventTarget> target = do_QueryInterface(sts);
+ rv = copier->Init(stream, mSocketOutputStream, target,
+ true, /* source buffered */
+ false, /* sink buffered */
+ BUFFER_SIZE, false, /* close source */
+ false); /* close sink */
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ RefPtr<CopierCallbacks> callbacks = new CopierCallbacks(this);
+ rv = copier->AsyncCopy(callbacks, nullptr);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return NS_OK;
+}
+
+void TCPSocket::NotifyCopyComplete(nsresult aStatus) {
+ mAsyncCopierActive = false;
+ CalculateBufferedAmount();
+
+ if (mSocketBridgeParent && mSocketBridgeParent->IPCOpen()) {
+ mozilla::Unused << mSocketBridgeParent->SendUpdateBufferedAmount(
+ BufferedAmount(), mTrackingNumber);
+ }
+
+ if (NS_FAILED(aStatus)) {
+ MaybeReportErrorAndCloseIfOpen(aStatus);
+ return;
+ }
+
+ if (BufferedAmount() != 0) {
+ EnsureCopying();
+ return;
+ }
+
+ // Maybe we have some empty stream. We want to have an empty queue now.
+ mPendingData.Clear();
+
+ // If we are waiting for initiating starttls, we can begin to
+ // activate tls now.
+ if (mWaitingForStartTLS && mReadyState == TCPReadyState::Open) {
+ ActivateTLS();
+ mWaitingForStartTLS = false;
+ // If we have pending data, we should send them, or fire
+ // a drain event if we are waiting for it.
+ if (!mPendingDataAfterStartTLS.IsEmpty()) {
+ mPendingData = std::move(mPendingDataAfterStartTLS);
+ EnsureCopying();
+ return;
+ }
+ }
+
+ // If we have a connected child, we let the child decide whether
+ // ondrain should be dispatched.
+ if (mWaitingForDrain && !mSocketBridgeParent) {
+ mWaitingForDrain = false;
+ FireEvent(u"drain"_ns);
+ }
+
+ if (mReadyState == TCPReadyState::Closing) {
+ if (mSocketOutputStream) {
+ mSocketOutputStream->Close();
+ mSocketOutputStream = nullptr;
+ }
+ mReadyState = TCPReadyState::Closed;
+ FireEvent(u"close"_ns);
+ }
+}
+
+void TCPSocket::ActivateTLS() {
+ nsresult rv;
+ nsCOMPtr<nsIEventTarget> socketThread =
+ do_GetService(NS_SOCKETTRANSPORTSERVICE_CONTRACTID, &rv);
+ if (NS_FAILED(rv)) {
+ return;
+ }
+
+ bool alreadyOnSTST = false;
+ if (NS_FAILED(socketThread->IsOnCurrentThread(&alreadyOnSTST))) {
+ return;
+ }
+
+ if (alreadyOnSTST) {
+ ActivateTLSHelper();
+ return;
+ }
+
+ auto CallActivateTLS = [sock = RefPtr{this}]() mutable {
+ sock->ActivateTLSHelper();
+ };
+ mozilla::SyncRunnable::DispatchToThread(
+ socketThread,
+ NS_NewRunnableFunction("TCPSocket::UpgradeToSecure->ActivateTLSHelper",
+ CallActivateTLS));
+}
+
+void TCPSocket::ActivateTLSHelper() {
+ nsCOMPtr<nsITLSSocketControl> tlsSocketControl;
+ mTransport->GetTlsSocketControl(getter_AddRefs(tlsSocketControl));
+ if (tlsSocketControl) {
+ tlsSocketControl->StartTLS();
+ }
+}
+
+NS_IMETHODIMP
+TCPSocket::FireErrorEvent(const nsAString& aName, const nsAString& aType,
+ nsresult aErrorCode) {
+ if (mSocketBridgeParent) {
+ mSocketBridgeParent->FireErrorEvent(aName, aType, aErrorCode, mReadyState);
+ return NS_OK;
+ }
+
+ TCPSocketErrorEventInit init;
+ init.mBubbles = false;
+ init.mCancelable = false;
+ init.mName = aName;
+ init.mMessage = aType;
+ static_assert(std::is_same_v<std::underlying_type_t<nsresult>, uint32_t>);
+ init.mErrorCode = uint32_t(aErrorCode);
+
+ RefPtr<TCPSocketErrorEvent> event =
+ TCPSocketErrorEvent::Constructor(this, u"error"_ns, init);
+ MOZ_ASSERT(event);
+ event->SetTrusted(true);
+ DispatchEvent(*event);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+TCPSocket::FireEvent(const nsAString& aType) {
+ if (mSocketBridgeParent) {
+ mSocketBridgeParent->FireEvent(aType, mReadyState);
+ return NS_OK;
+ }
+
+ AutoJSAPI api;
+ if (NS_WARN_IF(!api.Init(GetOwnerGlobal()))) {
+ return NS_ERROR_FAILURE;
+ }
+ JS::Rooted<JS::Value> val(api.cx());
+ return FireDataEvent(api.cx(), aType, val);
+}
+
+NS_IMETHODIMP
+TCPSocket::FireDataArrayEvent(const nsAString& aType,
+ const nsTArray<uint8_t>& buffer) {
+ AutoJSAPI api;
+ if (NS_WARN_IF(!api.Init(GetOwnerGlobal()))) {
+ return NS_ERROR_FAILURE;
+ }
+ JSContext* cx = api.cx();
+ JS::Rooted<JS::Value> val(cx);
+
+ bool ok = IPC::DeserializeArrayBuffer(cx, buffer, &val);
+ if (ok) {
+ return FireDataEvent(cx, aType, val);
+ }
+ return NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP
+TCPSocket::FireDataStringEvent(const nsAString& aType,
+ const nsACString& aString) {
+ AutoJSAPI api;
+ if (NS_WARN_IF(!api.Init(GetOwnerGlobal()))) {
+ return NS_ERROR_FAILURE;
+ }
+ JSContext* cx = api.cx();
+ JS::Rooted<JS::Value> val(cx);
+
+ bool ok = ToJSValue(cx, NS_ConvertASCIItoUTF16(aString), &val);
+ if (ok) {
+ return FireDataEvent(cx, aType, val);
+ }
+ return NS_ERROR_FAILURE;
+}
+
+nsresult TCPSocket::FireDataEvent(JSContext* aCx, const nsAString& aType,
+ JS::Handle<JS::Value> aData) {
+ MOZ_ASSERT(!mSocketBridgeParent);
+
+ RootedDictionary<TCPSocketEventInit> init(aCx);
+ init.mBubbles = false;
+ init.mCancelable = false;
+ init.mData = aData;
+
+ RefPtr<TCPSocketEvent> event = TCPSocketEvent::Constructor(this, aType, init);
+ event->SetTrusted(true);
+ DispatchEvent(*event);
+ return NS_OK;
+}
+
+JSObject* TCPSocket::WrapObject(JSContext* aCx,
+ JS::Handle<JSObject*> aGivenProto) {
+ return TCPSocket_Binding::Wrap(aCx, this, aGivenProto);
+}
+
+void TCPSocket::GetHost(nsAString& aHost) { aHost.Assign(mHost); }
+
+uint32_t TCPSocket::Port() const { return mPort; }
+
+bool TCPSocket::Ssl() const { return mSsl; }
+
+void TCPSocket::Suspend() {
+ if (mSocketBridgeChild) {
+ mSocketBridgeChild->SendSuspend();
+ return;
+ }
+ if (mInputStreamPump) {
+ mInputStreamPump->Suspend();
+ }
+ mSuspendCount++;
+}
+
+void TCPSocket::Resume(mozilla::ErrorResult& aRv) {
+ if (mSocketBridgeChild) {
+ mSocketBridgeChild->SendResume();
+ return;
+ }
+ if (!mSuspendCount) {
+ aRv.Throw(NS_ERROR_FAILURE);
+ return;
+ }
+
+ if (mInputStreamPump) {
+ mInputStreamPump->Resume();
+ }
+ mSuspendCount--;
+}
+
+nsresult TCPSocket::MaybeReportErrorAndCloseIfOpen(nsresult status) {
+ // If we're closed, we've already reported the error or just don't need to
+ // report the error.
+ if (mReadyState == TCPReadyState::Closed) {
+ return NS_OK;
+ }
+
+ // go through ::Closing state and then mark ::Closed
+ Close();
+ mReadyState = TCPReadyState::Closed;
+
+ if (NS_FAILED(status)) {
+ // Convert the status code to an appropriate error message.
+
+ nsString errorType, errName;
+
+ // security module? (and this is an error)
+ if ((static_cast<uint32_t>(status) & 0xFF0000) == 0x5a0000) {
+ nsCOMPtr<nsINSSErrorsService> errSvc =
+ do_GetService("@mozilla.org/nss_errors_service;1");
+ // getErrorClass will throw a generic NS_ERROR_FAILURE if the error code
+ // is somehow not in the set of covered errors.
+ uint32_t errorClass;
+ nsresult rv = errSvc->GetErrorClass(status, &errorClass);
+ if (NS_FAILED(rv)) {
+ errorType.AssignLiteral("SecurityProtocol");
+ } else {
+ switch (errorClass) {
+ case nsINSSErrorsService::ERROR_CLASS_BAD_CERT:
+ errorType.AssignLiteral("SecurityCertificate");
+ break;
+ default:
+ errorType.AssignLiteral("SecurityProtocol");
+ break;
+ }
+ }
+
+ // NSS_SEC errors (happen below the base value because of negative vals)
+ if ((static_cast<int32_t>(status) & 0xFFFF) <
+ abs(nsINSSErrorsService::NSS_SEC_ERROR_BASE)) {
+ switch (static_cast<SECErrorCodes>(status)) {
+ case SEC_ERROR_EXPIRED_CERTIFICATE:
+ errName.AssignLiteral("SecurityExpiredCertificateError");
+ break;
+ case SEC_ERROR_REVOKED_CERTIFICATE:
+ errName.AssignLiteral("SecurityRevokedCertificateError");
+ break;
+ // per bsmith, we will be unable to tell these errors apart very
+ // soon, so it makes sense to just folder them all together already.
+ case SEC_ERROR_UNKNOWN_ISSUER:
+ case SEC_ERROR_UNTRUSTED_ISSUER:
+ case SEC_ERROR_UNTRUSTED_CERT:
+ case SEC_ERROR_CA_CERT_INVALID:
+ errName.AssignLiteral("SecurityUntrustedCertificateIssuerError");
+ break;
+ case SEC_ERROR_INADEQUATE_KEY_USAGE:
+ errName.AssignLiteral("SecurityInadequateKeyUsageError");
+ break;
+ case SEC_ERROR_CERT_SIGNATURE_ALGORITHM_DISABLED:
+ errName.AssignLiteral(
+ "SecurityCertificateSignatureAlgorithmDisabledError");
+ break;
+ default:
+ errName.AssignLiteral("SecurityError");
+ break;
+ }
+ } else {
+ // NSS_SSL errors
+ switch (static_cast<SSLErrorCodes>(status)) {
+ case SSL_ERROR_NO_CERTIFICATE:
+ errName.AssignLiteral("SecurityNoCertificateError");
+ break;
+ case SSL_ERROR_BAD_CERTIFICATE:
+ errName.AssignLiteral("SecurityBadCertificateError");
+ break;
+ case SSL_ERROR_UNSUPPORTED_CERTIFICATE_TYPE:
+ errName.AssignLiteral("SecurityUnsupportedCertificateTypeError");
+ break;
+ case SSL_ERROR_UNSUPPORTED_VERSION:
+ errName.AssignLiteral("SecurityUnsupportedTLSVersionError");
+ break;
+ case SSL_ERROR_BAD_CERT_DOMAIN:
+ errName.AssignLiteral("SecurityCertificateDomainMismatchError");
+ break;
+ default:
+ errName.AssignLiteral("SecurityError");
+ break;
+ }
+ }
+ } else {
+ // must be network
+ errorType.AssignLiteral("Network");
+
+ switch (status) {
+ // connect to host:port failed
+ case NS_ERROR_CONNECTION_REFUSED:
+ errName.AssignLiteral("ConnectionRefusedError");
+ break;
+ // network timeout error
+ case NS_ERROR_NET_TIMEOUT:
+ errName.AssignLiteral("NetworkTimeoutError");
+ break;
+ // hostname lookup failed
+ case NS_ERROR_UNKNOWN_HOST:
+ errName.AssignLiteral("DomainNotFoundError");
+ break;
+ case NS_ERROR_NET_INTERRUPT:
+ errName.AssignLiteral("NetworkInterruptError");
+ break;
+ default:
+ errName.AssignLiteral("NetworkError");
+ break;
+ }
+ }
+
+ Unused << NS_WARN_IF(NS_FAILED(FireErrorEvent(errName, errorType, status)));
+ }
+
+ return FireEvent(u"close"_ns);
+}
+
+void TCPSocket::Close() { CloseHelper(true); }
+
+void TCPSocket::CloseImmediately() { CloseHelper(false); }
+
+void TCPSocket::CloseHelper(bool waitForUnsentData) {
+ if (mReadyState == TCPReadyState::Closed ||
+ mReadyState == TCPReadyState::Closing) {
+ return;
+ }
+
+ mReadyState = TCPReadyState::Closing;
+
+ if (mProxyRequest) {
+ mProxyRequest->Cancel(NS_BINDING_ABORTED);
+ mProxyRequest = nullptr;
+ }
+
+ if (mSocketBridgeChild) {
+ mSocketBridgeChild->SendClose();
+ return;
+ }
+
+ if (!mAsyncCopierActive || !waitForUnsentData) {
+ mPendingData.Clear();
+ mPendingDataAfterStartTLS.Clear();
+
+ if (mSocketOutputStream) {
+ mSocketOutputStream->Close();
+ mSocketOutputStream = nullptr;
+ }
+ }
+
+ if (mSocketInputStream) {
+ mSocketInputStream->Close();
+ mSocketInputStream = nullptr;
+ }
+}
+
+bool TCPSocket::Send(const nsACString& aData, mozilla::ErrorResult& aRv) {
+ if (mReadyState != TCPReadyState::Open) {
+ aRv.Throw(NS_ERROR_FAILURE);
+ return false;
+ }
+
+ uint64_t byteLength;
+ nsCOMPtr<nsIInputStream> stream;
+ if (mSocketBridgeChild) {
+ mSocketBridgeChild->SendSend(aData);
+ byteLength = aData.Length();
+ } else {
+ nsresult rv = NS_NewCStringInputStream(getter_AddRefs(stream), aData);
+ if (NS_FAILED(rv)) {
+ aRv.Throw(rv);
+ return false;
+ }
+ rv = stream->Available(&byteLength);
+ if (NS_FAILED(rv)) {
+ aRv.Throw(rv);
+ return false;
+ }
+ }
+ return Send(stream, byteLength);
+}
+
+bool TCPSocket::Send(const ArrayBuffer& aData, uint32_t aByteOffset,
+ const Optional<uint32_t>& aByteLength,
+ mozilla::ErrorResult& aRv) {
+ if (mReadyState != TCPReadyState::Open) {
+ aRv.Throw(NS_ERROR_FAILURE);
+ return false;
+ }
+
+ nsCOMPtr<nsIArrayBufferInputStream> stream;
+
+ aData.ComputeState();
+ uint32_t byteLength =
+ aByteLength.WasPassed() ? aByteLength.Value() : aData.Length();
+
+ if (mSocketBridgeChild) {
+ nsresult rv = mSocketBridgeChild->SendSend(aData, aByteOffset, byteLength);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ aRv.Throw(rv);
+ return false;
+ }
+ } else {
+ JS::Rooted<JS::Value> value(RootingCx(), JS::ObjectValue(*aData.Obj()));
+
+ stream = do_CreateInstance("@mozilla.org/io/arraybuffer-input-stream;1");
+ nsresult rv = stream->SetData(value, aByteOffset, byteLength);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ aRv.Throw(rv);
+ return false;
+ }
+ }
+ return Send(stream, byteLength);
+}
+
+bool TCPSocket::Send(nsIInputStream* aStream, uint32_t aByteLength) {
+ uint64_t newBufferedAmount = BufferedAmount() + aByteLength;
+ bool bufferFull = newBufferedAmount > BUFFER_SIZE;
+ if (bufferFull) {
+ // If we buffered more than some arbitrary amount of data,
+ // (65535 right now) we should tell the caller so they can
+ // wait until ondrain is called if they so desire. Once all the
+ // buffered data has been written to the socket, ondrain is
+ // called.
+ mWaitingForDrain = true;
+ }
+
+ if (mSocketBridgeChild) {
+ // In the child, we just add the buffer length to our bufferedAmount and let
+ // the parent update our bufferedAmount when the data have been sent.
+ mBufferedAmount = newBufferedAmount;
+ return !bufferFull;
+ }
+
+ // This is used to track how many packets we have been told to send. Signaling
+ // this back to the user of this API allows the user to know how many packets
+ // are currently in flight over IPC.
+ ++mTrackingNumber;
+ if (mWaitingForStartTLS) {
+ // When we are waiting for starttls, newStream is added to pendingData
+ // and will be appended to multiplexStream after tls had been set up.
+ mPendingDataAfterStartTLS.AppendElement(aStream);
+ } else {
+ mPendingData.AppendElement(aStream);
+ }
+
+ CalculateBufferedAmount();
+ EnsureCopying();
+
+ return !bufferFull;
+}
+
+TCPReadyState TCPSocket::ReadyState() { return mReadyState; }
+
+TCPSocketBinaryType TCPSocket::BinaryType() const {
+ if (mUseArrayBuffers) {
+ return TCPSocketBinaryType::Arraybuffer;
+ }
+ return TCPSocketBinaryType::String;
+}
+
+already_AddRefed<TCPSocket> TCPSocket::CreateAcceptedSocket(
+ nsIGlobalObject* aGlobal, nsISocketTransport* aTransport,
+ bool aUseArrayBuffers) {
+ RefPtr<TCPSocket> socket =
+ new TCPSocket(aGlobal, u""_ns, 0, false, aUseArrayBuffers);
+ nsresult rv = socket->InitWithTransport(aTransport);
+ NS_ENSURE_SUCCESS(rv, nullptr);
+ return socket.forget();
+}
+
+already_AddRefed<TCPSocket> TCPSocket::CreateAcceptedSocket(
+ nsIGlobalObject* aGlobal, TCPSocketChild* aBridge, bool aUseArrayBuffers) {
+ RefPtr<TCPSocket> socket =
+ new TCPSocket(aGlobal, u""_ns, 0, false, aUseArrayBuffers);
+ socket->InitWithSocketChild(aBridge);
+ return socket.forget();
+}
+
+already_AddRefed<TCPSocket> TCPSocket::Constructor(
+ const GlobalObject& aGlobal, const nsAString& aHost, uint16_t aPort,
+ const SocketOptions& aOptions, mozilla::ErrorResult& aRv) {
+ nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(aGlobal.GetAsSupports());
+ RefPtr<TCPSocket> socket =
+ new TCPSocket(global, aHost, aPort, aOptions.mUseSecureTransport,
+ aOptions.mBinaryType == TCPSocketBinaryType::Arraybuffer);
+ socket->ResolveProxy();
+
+ return socket.forget();
+}
+
+nsresult TCPSocket::ResolveProxy() {
+ nsresult rv;
+ nsCOMPtr<nsIProtocolProxyService> pps =
+ do_GetService(NS_PROTOCOLPROXYSERVICE_CONTRACTID, &rv);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ nsCOMPtr<nsIURI> uri;
+ nsCString spec = mSsl ? "https://"_ns : "http://"_ns;
+ bool maybeIPv6 = mHost.FindChar(':') != -1;
+ if (maybeIPv6) spec.Append('[');
+ if (!AppendUTF16toUTF8(mHost, spec, fallible)) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+ if (maybeIPv6) spec.Append(']');
+ rv = NS_MutateURI(NS_STANDARDURLMUTATOR_CONTRACTID)
+ .SetSpec(spec)
+ .SetPort(mPort)
+ .Finalize(uri);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ nsCOMPtr<nsIChannel> channel;
+ rv = NS_NewChannel(getter_AddRefs(channel), uri,
+ nsContentUtils::GetSystemPrincipal(),
+ nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_SEC_CONTEXT_IS_NULL,
+ nsIContentPolicy::TYPE_OTHER);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = pps->AsyncResolve(channel,
+ nsIProtocolProxyService::RESOLVE_PREFER_SOCKS_PROXY,
+ this, nullptr, getter_AddRefs(mProxyRequest));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+TCPSocket::OnProxyAvailable(nsICancelable* aRequest, nsIChannel* aChannel,
+ nsIProxyInfo* aProxyInfo, nsresult aResult) {
+ mProxyRequest = nullptr;
+ if (NS_SUCCEEDED(aResult) && aProxyInfo) {
+ nsCString proxyType;
+ nsresult rv = aProxyInfo->GetType(proxyType);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ Close();
+ return rv;
+ }
+ // Only supports SOCKS proxy for now.
+ if (proxyType == "socks" || proxyType == "socks4") {
+ return Init(aProxyInfo);
+ }
+ }
+ return Init(nullptr);
+}
+
+nsresult TCPSocket::CreateInputStreamPump() {
+ if (!mSocketInputStream) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+ nsresult rv;
+ mInputStreamPump =
+ do_CreateInstance("@mozilla.org/network/input-stream-pump;1", &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = mInputStreamPump->Init(mSocketInputStream, 0, 0, false, nullptr);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ uint64_t suspendCount = mSuspendCount;
+ while (suspendCount--) {
+ mInputStreamPump->Suspend();
+ }
+
+ rv = mInputStreamPump->AsyncRead(this);
+ NS_ENSURE_SUCCESS(rv, rv);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+TCPSocket::OnTransportStatus(nsITransport* aTransport, nsresult aStatus,
+ int64_t aProgress, int64_t aProgressMax) {
+ if (static_cast<uint32_t>(aStatus) !=
+ static_cast<uint32_t>(nsISocketTransport::STATUS_CONNECTED_TO)) {
+ return NS_OK;
+ }
+
+ mReadyState = TCPReadyState::Open;
+ nsresult rv = CreateInputStreamPump();
+ NS_ENSURE_SUCCESS(rv, rv);
+ FireEvent(u"open"_ns);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+TCPSocket::OnInputStreamReady(nsIAsyncInputStream* aStream) {
+ // Only used for detecting if the connection was refused.
+
+ uint64_t dummy;
+ nsresult rv = aStream->Available(&dummy);
+ if (NS_FAILED(rv)) {
+ MaybeReportErrorAndCloseIfOpen(NS_ERROR_CONNECTION_REFUSED);
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+TCPSocket::OnStartRequest(nsIRequest* aRequest) { return NS_OK; }
+
+NS_IMETHODIMP
+TCPSocket::OnDataAvailable(nsIRequest* aRequest, nsIInputStream* aStream,
+ uint64_t aOffset, uint32_t aCount) {
+ if (mUseArrayBuffers) {
+ nsTArray<uint8_t> buffer;
+ buffer.SetCapacity(aCount);
+ uint32_t actual;
+ nsresult rv = aStream->Read(reinterpret_cast<char*>(buffer.Elements()),
+ aCount, &actual);
+ NS_ENSURE_SUCCESS(rv, rv);
+ MOZ_ASSERT(actual == aCount);
+ buffer.SetLength(actual);
+
+ if (mSocketBridgeParent) {
+ mSocketBridgeParent->FireArrayBufferDataEvent(buffer, mReadyState);
+ return NS_OK;
+ }
+
+ AutoJSAPI api;
+ if (!api.Init(GetOwnerGlobal())) {
+ return NS_ERROR_FAILURE;
+ }
+ JSContext* cx = api.cx();
+
+ JS::Rooted<JS::Value> value(cx);
+ if (!ToJSValue(cx, TypedArrayCreator<ArrayBuffer>(buffer), &value)) {
+ return NS_ERROR_FAILURE;
+ }
+ FireDataEvent(cx, u"data"_ns, value);
+ return NS_OK;
+ }
+
+ nsCString data;
+ nsresult rv = mInputStreamScriptable->ReadBytes(aCount, data);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (mSocketBridgeParent) {
+ mSocketBridgeParent->FireStringDataEvent(data, mReadyState);
+ return NS_OK;
+ }
+
+ AutoJSAPI api;
+ if (!api.Init(GetOwnerGlobal())) {
+ return NS_ERROR_FAILURE;
+ }
+ JSContext* cx = api.cx();
+
+ JS::Rooted<JS::Value> value(cx);
+ if (!ToJSValue(cx, NS_ConvertASCIItoUTF16(data), &value)) {
+ return NS_ERROR_FAILURE;
+ }
+ FireDataEvent(cx, u"data"_ns, value);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+TCPSocket::OnStopRequest(nsIRequest* aRequest, nsresult aStatus) {
+ mInputStreamPump = nullptr;
+
+ if (mAsyncCopierActive && NS_SUCCEEDED(aStatus)) {
+ // If we have some buffered output still, and status is not an
+ // error, the other side has done a half-close, but we don't
+ // want to be in the close state until we are done sending
+ // everything that was buffered. We also don't want to call onclose
+ // yet.
+ return NS_OK;
+ }
+
+ // We call this even if there is no error.
+ MaybeReportErrorAndCloseIfOpen(aStatus);
+ return NS_OK;
+}
+
+void TCPSocket::SetSocketBridgeParent(TCPSocketParent* aBridgeParent) {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ mSocketBridgeParent = aBridgeParent;
+}
+
+NS_IMETHODIMP
+TCPSocket::UpdateReadyState(uint32_t aReadyState) {
+ MOZ_ASSERT(mSocketBridgeChild);
+ mReadyState = static_cast<TCPReadyState>(aReadyState);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+TCPSocket::UpdateBufferedAmount(uint32_t aBufferedAmount,
+ uint32_t aTrackingNumber) {
+ if (aTrackingNumber != mTrackingNumber) {
+ return NS_OK;
+ }
+ mBufferedAmount = aBufferedAmount;
+ if (!mBufferedAmount) {
+ if (mWaitingForDrain) {
+ mWaitingForDrain = false;
+ return FireEvent(u"drain"_ns);
+ }
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+TCPSocket::Observe(nsISupports* aSubject, const char* aTopic,
+ const char16_t* aData) {
+ if (!strcmp(aTopic, "inner-window-destroyed")) {
+ nsCOMPtr<nsISupportsPRUint64> wrapper = do_QueryInterface(aSubject);
+ NS_ENSURE_TRUE(wrapper, NS_ERROR_FAILURE);
+ uint64_t innerID;
+ nsresult rv = wrapper->GetData(&innerID);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ if (innerID == mInnerWindowID) {
+ Close();
+ }
+ } else if (!strcmp(aTopic, "profile-change-net-teardown")) {
+ Close();
+ }
+
+ return NS_OK;
+}
+
+/* static */
+bool TCPSocket::ShouldTCPSocketExist(JSContext* aCx, JSObject* aGlobal) {
+ JS::Rooted<JSObject*> global(aCx, aGlobal);
+ return nsContentUtils::ObjectPrincipal(global)->IsSystemPrincipal();
+}
diff --git a/dom/network/TCPSocket.h b/dom/network/TCPSocket.h
new file mode 100644
index 0000000000..a6d807ef54
--- /dev/null
+++ b/dom/network/TCPSocket.h
@@ -0,0 +1,247 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_dom_TCPSocket_h
+#define mozilla_dom_TCPSocket_h
+
+#include "mozilla/dom/TCPSocketBinding.h"
+#include "mozilla/dom/TypedArray.h"
+#include "mozilla/DOMEventTargetHelper.h"
+#include "nsIProxyInfo.h"
+#include "nsITransport.h"
+#include "nsIStreamListener.h"
+#include "nsIAsyncInputStream.h"
+#include "nsISupportsImpl.h"
+#include "nsIObserver.h"
+#include "nsWeakReference.h"
+#include "nsITCPSocketCallback.h"
+#include "nsIProtocolProxyCallback.h"
+#include "js/RootingAPI.h"
+
+class nsISocketTransport;
+class nsIInputStreamPump;
+class nsIScriptableInputStream;
+class nsIBinaryInputStream;
+class nsIMultiplexInputStream;
+class nsIAsyncStreamCopier;
+class nsIInputStream;
+class nsINetworkInfo;
+
+namespace mozilla {
+class ErrorResult;
+namespace dom {
+
+struct ServerSocketOptions;
+class TCPServerSocket;
+class TCPSocketChild;
+class TCPSocketParent;
+
+// This interface is only used for legacy navigator.mozTCPSocket API
+// compatibility.
+class LegacyMozTCPSocket : public nsISupports {
+ public:
+ NS_DECL_CYCLE_COLLECTING_ISUPPORTS
+ NS_DECL_CYCLE_COLLECTION_CLASS(LegacyMozTCPSocket)
+
+ explicit LegacyMozTCPSocket(nsPIDOMWindowInner* aWindow);
+
+ already_AddRefed<TCPServerSocket> Listen(uint16_t aPort,
+ const ServerSocketOptions& aOptions,
+ uint16_t aBacklog, ErrorResult& aRv);
+
+ already_AddRefed<TCPSocket> Open(const nsAString& aHost, uint16_t aPort,
+ const SocketOptions& aOptions,
+ ErrorResult& aRv);
+
+ bool WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto,
+ JS::MutableHandle<JSObject*> aReflector);
+
+ private:
+ virtual ~LegacyMozTCPSocket();
+
+ nsCOMPtr<nsIGlobalObject> mGlobal;
+};
+
+class TCPSocket final : public DOMEventTargetHelper,
+ public nsIStreamListener,
+ public nsITransportEventSink,
+ public nsIInputStreamCallback,
+ public nsIObserver,
+ public nsSupportsWeakReference,
+ public nsITCPSocketCallback,
+ public nsIProtocolProxyCallback {
+ public:
+ TCPSocket(nsIGlobalObject* aGlobal, const nsAString& aHost, uint16_t aPort,
+ bool aSsl, bool aUseArrayBuffers);
+
+ NS_DECL_ISUPPORTS_INHERITED
+ NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS_INHERITED(TCPSocket,
+ DOMEventTargetHelper)
+ NS_DECL_NSIREQUESTOBSERVER
+ NS_DECL_NSISTREAMLISTENER
+ NS_DECL_NSITRANSPORTEVENTSINK
+ NS_DECL_NSIINPUTSTREAMCALLBACK
+ NS_DECL_NSIOBSERVER
+ NS_DECL_NSITCPSOCKETCALLBACK
+ NS_DECL_NSIPROTOCOLPROXYCALLBACK
+
+ virtual JSObject* WrapObject(JSContext* aCx,
+ JS::Handle<JSObject*> aGivenProto) override;
+
+ static bool ShouldTCPSocketExist(JSContext* aCx, JSObject* aGlobal);
+
+ nsISocketTransport* GetTransport() const { return mTransport.get(); }
+
+ void GetHost(nsAString& aHost);
+ uint32_t Port() const;
+ bool Ssl() const;
+ uint64_t BufferedAmount() const { return mBufferedAmount; }
+ void Suspend();
+ void Resume(ErrorResult& aRv);
+ void Close();
+ void CloseImmediately();
+ bool Send(const nsACString& aData, ErrorResult& aRv);
+ bool Send(const ArrayBuffer& aData, uint32_t aByteOffset,
+ const Optional<uint32_t>& aByteLength, ErrorResult& aRv);
+ TCPReadyState ReadyState();
+ TCPSocketBinaryType BinaryType() const;
+ void UpgradeToSecure(ErrorResult& aRv);
+
+ static already_AddRefed<TCPSocket> Constructor(const GlobalObject& aGlobal,
+ const nsAString& aHost,
+ uint16_t aPort,
+ const SocketOptions& aOptions,
+ ErrorResult& aRv);
+
+ // Create a TCPSocket object from an existing low-level socket connection.
+ // Used by the TCPServerSocket implementation when a new connection is
+ // accepted.
+ static already_AddRefed<TCPSocket> CreateAcceptedSocket(
+ nsIGlobalObject* aGlobal, nsISocketTransport* aTransport,
+ bool aUseArrayBuffers);
+ // Create a TCPSocket object from an existing child-side IPC actor.
+ // Used by the TCPServerSocketChild implementation when a new connection is
+ // accepted.
+ static already_AddRefed<TCPSocket> CreateAcceptedSocket(
+ nsIGlobalObject* aGlobal, TCPSocketChild* aBridge, bool aUseArrayBuffers);
+
+ // Initialize this socket's associated IPC actor in the parent process.
+ void SetSocketBridgeParent(TCPSocketParent* aBridgeParent);
+
+ static bool SocketEnabled();
+
+ IMPL_EVENT_HANDLER(open);
+ IMPL_EVENT_HANDLER(drain);
+ IMPL_EVENT_HANDLER(data);
+ IMPL_EVENT_HANDLER(error);
+ IMPL_EVENT_HANDLER(close);
+
+ nsresult Init(nsIProxyInfo* aProxyInfo);
+
+ // Inform this socket that a buffered send() has completed sending.
+ void NotifyCopyComplete(nsresult aStatus);
+
+ // Initialize this socket from a low-level connection that hasn't connected
+ // yet (called from RecvOpenBind() in TCPSocketParent).
+ nsresult InitWithUnconnectedTransport(nsISocketTransport* aTransport);
+
+ private:
+ ~TCPSocket();
+
+ // Initialize this socket with an existing IPC actor.
+ void InitWithSocketChild(TCPSocketChild* aSocketBridge);
+ // Initialize this socket from an existing low-level connection.
+ nsresult InitWithTransport(nsISocketTransport* aTransport);
+ // Initialize the input/output streams for this socket object.
+ nsresult CreateStream();
+ // Initialize the asynchronous read operation from this socket's input stream.
+ nsresult CreateInputStreamPump();
+ // Send the contents of the provided input stream, which is assumed to be the
+ // given length for reporting and buffering purposes.
+ bool Send(nsIInputStream* aStream, uint32_t aByteLength);
+ // Begin an asynchronous copy operation if one is not already in progress.
+ nsresult EnsureCopying();
+ // Re-calculate buffered amount.
+ void CalculateBufferedAmount();
+ // Helper function, should be called by ActivateTLS(), only.
+ void ActivateTLSHelper();
+ // Enable TLS on this socket, dispatch to STSThread if necessary.
+ void ActivateTLS();
+ // Dispatch an error event if necessary, then dispatch a "close" event.
+ nsresult MaybeReportErrorAndCloseIfOpen(nsresult status);
+
+ // Helper for FireDataStringEvent/FireDataArrayEvent.
+ nsresult FireDataEvent(JSContext* aCx, const nsAString& aType,
+ JS::Handle<JS::Value> aData);
+ // Helper for Close/CloseImmediately
+ void CloseHelper(bool waitForUnsentData);
+
+ nsresult ResolveProxy();
+
+ TCPReadyState mReadyState;
+ // Whether to use strings or array buffers for the "data" event.
+ bool mUseArrayBuffers;
+ nsString mHost;
+ uint16_t mPort;
+ // Whether this socket is using a secure transport.
+ bool mSsl;
+
+ // The associated IPC actor in a child process.
+ RefPtr<TCPSocketChild> mSocketBridgeChild;
+ // The associated IPC actor in a parent process.
+ RefPtr<TCPSocketParent> mSocketBridgeParent;
+
+ // Raw socket streams
+ nsCOMPtr<nsISocketTransport> mTransport;
+ nsCOMPtr<nsIInputStream> mSocketInputStream;
+ nsCOMPtr<nsIOutputStream> mSocketOutputStream;
+
+ nsCOMPtr<nsICancelable> mProxyRequest;
+
+ // Input stream machinery
+ nsCOMPtr<nsIInputStreamPump> mInputStreamPump;
+ nsCOMPtr<nsIScriptableInputStream> mInputStreamScriptable;
+ nsCOMPtr<nsIBinaryInputStream> mInputStreamBinary;
+
+ // Is there an async copy operation in progress?
+ bool mAsyncCopierActive;
+ // True if the buffer is full and a "drain" event is expected by the client.
+ bool mWaitingForDrain;
+
+ // The id of the window that created this socket.
+ uint64_t mInnerWindowID;
+
+ // The current number of buffered bytes. Only used in content processes when
+ // IPC is enabled.
+ uint64_t mBufferedAmount;
+
+ // The number of times this socket has had `Suspend` called without a
+ // corresponding `Resume`.
+ uint32_t mSuspendCount;
+
+ // The current sequence number (ie. number of send operations) that have been
+ // processed. This is used in the IPC scenario by the child process to filter
+ // out outdated notifications about the amount of buffered data present in the
+ // parent process.
+ uint32_t mTrackingNumber;
+
+ // True if this socket has been upgraded to secure after the initial
+ // connection, but the actual upgrade is waiting for an in-progress copy
+ // operation to complete.
+ bool mWaitingForStartTLS;
+ // The buffered data awaiting the TLS upgrade to finish.
+ nsTArray<nsCOMPtr<nsIInputStream>> mPendingDataAfterStartTLS;
+
+ // The data to be sent.
+ nsTArray<nsCOMPtr<nsIInputStream>> mPendingData;
+
+ bool mObserversActive;
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif // mozilla_dom_TCPSocket_h
diff --git a/dom/network/TCPSocketChild.cpp b/dom/network/TCPSocketChild.cpp
new file mode 100644
index 0000000000..b6ccba021d
--- /dev/null
+++ b/dom/network/TCPSocketChild.cpp
@@ -0,0 +1,177 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include <algorithm>
+#include "TCPSocketChild.h"
+#include "mozilla/HoldDropJSObjects.h"
+#include "mozilla/Unused.h"
+#include "mozilla/UniquePtr.h"
+#include "mozilla/net/NeckoChild.h"
+#include "mozilla/dom/PBrowserChild.h"
+#include "mozilla/dom/BrowserChild.h"
+#include "nsITCPSocketCallback.h"
+#include "TCPSocket.h"
+#include "nsContentUtils.h"
+#include "js/ArrayBuffer.h" // JS::NewArrayBufferWithContents
+#include "js/RootingAPI.h" // JS::MutableHandle
+#include "js/Utility.h" // js::ArrayBufferContentsArena, JS::FreePolicy, js_pod_arena_malloc
+#include "js/Value.h" // JS::Value
+
+using mozilla::net::gNeckoChild;
+
+namespace IPC {
+
+bool DeserializeArrayBuffer(JSContext* cx, const nsTArray<uint8_t>& aBuffer,
+ JS::MutableHandle<JS::Value> aVal) {
+ mozilla::UniquePtr<uint8_t[], JS::FreePolicy> data(
+ js_pod_arena_malloc<uint8_t>(js::ArrayBufferContentsArena,
+ aBuffer.Length()));
+ if (!data) return false;
+ memcpy(data.get(), aBuffer.Elements(), aBuffer.Length());
+
+ JSObject* obj =
+ JS::NewArrayBufferWithContents(cx, aBuffer.Length(), data.get());
+ if (!obj) return false;
+ // If JS::NewArrayBufferWithContents returns non-null, the ownership of
+ // the data is transfered to obj, so we release the ownership here.
+ mozilla::Unused << data.release();
+
+ aVal.setObject(*obj);
+ return true;
+}
+
+} // namespace IPC
+
+namespace mozilla::dom {
+
+NS_IMPL_CYCLE_COLLECTION_CLASS(TCPSocketChildBase)
+
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(TCPSocketChildBase)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mSocket)
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
+
+NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(TCPSocketChildBase)
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mSocket)
+NS_IMPL_CYCLE_COLLECTION_UNLINK_END
+
+NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN(TCPSocketChildBase)
+NS_IMPL_CYCLE_COLLECTION_TRACE_END
+
+NS_IMPL_CYCLE_COLLECTING_ADDREF(TCPSocketChildBase)
+NS_IMPL_CYCLE_COLLECTING_RELEASE(TCPSocketChildBase)
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(TCPSocketChildBase)
+ NS_INTERFACE_MAP_ENTRY(nsISupports)
+NS_INTERFACE_MAP_END
+
+TCPSocketChildBase::TCPSocketChildBase() : mIPCOpen(false) {
+ mozilla::HoldJSObjects(this);
+}
+
+TCPSocketChildBase::~TCPSocketChildBase() { mozilla::DropJSObjects(this); }
+
+NS_IMETHODIMP_(MozExternalRefCountType) TCPSocketChild::Release(void) {
+ nsrefcnt refcnt = TCPSocketChildBase::Release();
+ if (refcnt == 1 && mIPCOpen) {
+ PTCPSocketChild::SendRequestDelete();
+ return 1;
+ }
+ return refcnt;
+}
+
+TCPSocketChild::TCPSocketChild(const nsAString& aHost, const uint16_t& aPort,
+ nsISerialEventTarget* aTarget)
+ : mHost(aHost), mPort(aPort), mIPCEventTarget(aTarget) {}
+
+void TCPSocketChild::SendOpen(nsITCPSocketCallback* aSocket, bool aUseSSL,
+ bool aUseArrayBuffers) {
+ mSocket = aSocket;
+
+ AddIPDLReference();
+ gNeckoChild->SendPTCPSocketConstructor(this, mHost, mPort);
+ PTCPSocketChild::SendOpen(mHost, mPort, aUseSSL, aUseArrayBuffers);
+}
+
+void TCPSocketChildBase::ReleaseIPDLReference() {
+ MOZ_ASSERT(mIPCOpen);
+ mIPCOpen = false;
+ mSocket = nullptr;
+ this->Release();
+}
+
+void TCPSocketChildBase::AddIPDLReference() {
+ MOZ_ASSERT(!mIPCOpen);
+ mIPCOpen = true;
+ this->AddRef();
+}
+
+TCPSocketChild::~TCPSocketChild() = default;
+
+mozilla::ipc::IPCResult TCPSocketChild::RecvUpdateBufferedAmount(
+ const uint32_t& aBuffered, const uint32_t& aTrackingNumber) {
+ mSocket->UpdateBufferedAmount(aBuffered, aTrackingNumber);
+ return IPC_OK();
+}
+
+mozilla::ipc::IPCResult TCPSocketChild::RecvCallback(
+ const nsString& aType, const CallbackData& aData,
+ const uint32_t& aReadyState) {
+ mSocket->UpdateReadyState(aReadyState);
+
+ if (aData.type() == CallbackData::Tvoid_t) {
+ mSocket->FireEvent(aType);
+
+ } else if (aData.type() == CallbackData::TTCPError) {
+ const TCPError& err(aData.get_TCPError());
+ mSocket->FireErrorEvent(err.name(), err.message(), err.errorCode());
+
+ } else if (aData.type() == CallbackData::TSendableData) {
+ const SendableData& data = aData.get_SendableData();
+
+ if (data.type() == SendableData::TArrayOfuint8_t) {
+ mSocket->FireDataArrayEvent(aType, data.get_ArrayOfuint8_t());
+ } else if (data.type() == SendableData::TnsCString) {
+ mSocket->FireDataStringEvent(aType, data.get_nsCString());
+ } else {
+ MOZ_CRASH("Invalid callback data type!");
+ }
+ } else {
+ MOZ_CRASH("Invalid callback type!");
+ }
+ return IPC_OK();
+}
+
+void TCPSocketChild::SendSend(const nsACString& aData) {
+ SendData(nsCString(aData));
+}
+
+nsresult TCPSocketChild::SendSend(const ArrayBuffer& aData,
+ uint32_t aByteOffset, uint32_t aByteLength) {
+ uint32_t buflen = aData.Length();
+ uint32_t offset = std::min(buflen, aByteOffset);
+ uint32_t nbytes = std::min(buflen - aByteOffset, aByteLength);
+ FallibleTArray<uint8_t> fallibleArr;
+ if (!fallibleArr.InsertElementsAt(0, aData.Data() + offset, nbytes,
+ fallible)) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ SendData(SendableData{std::move(fallibleArr)});
+ return NS_OK;
+}
+
+void TCPSocketChild::SetSocket(TCPSocket* aSocket) { mSocket = aSocket; }
+
+void TCPSocketChild::GetHost(nsAString& aHost) { aHost = mHost; }
+
+void TCPSocketChild::GetPort(uint16_t* aPort) const { *aPort = mPort; }
+
+mozilla::ipc::IPCResult TCPSocketChild::RecvRequestDelete() {
+ mozilla::Unused << Send__delete__(this);
+ return IPC_OK();
+}
+
+} // namespace mozilla::dom
diff --git a/dom/network/TCPSocketChild.h b/dom/network/TCPSocketChild.h
new file mode 100644
index 0000000000..b0896d78a3
--- /dev/null
+++ b/dom/network/TCPSocketChild.h
@@ -0,0 +1,77 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_dom_TCPSocketChild_h
+#define mozilla_dom_TCPSocketChild_h
+
+#include "mozilla/dom/TypedArray.h"
+#include "mozilla/net/PTCPSocketChild.h"
+#include "nsCycleCollectionParticipant.h"
+#include "nsCOMPtr.h"
+#include "js/TypeDecls.h"
+
+class nsITCPSocketCallback;
+
+namespace IPC {
+bool DeserializeArrayBuffer(JSContext* cx, const nsTArray<uint8_t>& aBuffer,
+ JS::MutableHandle<JS::Value> aVal);
+}
+
+namespace mozilla::dom {
+
+class TCPSocket;
+
+class TCPSocketChildBase : public nsISupports {
+ public:
+ NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(TCPSocketChildBase)
+ NS_DECL_CYCLE_COLLECTING_ISUPPORTS
+
+ void AddIPDLReference();
+ void ReleaseIPDLReference();
+
+ protected:
+ TCPSocketChildBase();
+ virtual ~TCPSocketChildBase();
+
+ nsCOMPtr<nsITCPSocketCallback> mSocket;
+ bool mIPCOpen;
+};
+
+class TCPSocketChild : public mozilla::net::PTCPSocketChild,
+ public TCPSocketChildBase {
+ public:
+ NS_IMETHOD_(MozExternalRefCountType) Release() override;
+
+ TCPSocketChild(const nsAString& aHost, const uint16_t& aPort,
+ nsISerialEventTarget* aTarget);
+ ~TCPSocketChild();
+
+ void SendOpen(nsITCPSocketCallback* aSocket, bool aUseSSL,
+ bool aUseArrayBuffers);
+ void SendSend(const nsACString& aData);
+ nsresult SendSend(const ArrayBuffer& aData, uint32_t aByteOffset,
+ uint32_t aByteLength);
+ void SetSocket(TCPSocket* aSocket);
+
+ void GetHost(nsAString& aHost);
+ void GetPort(uint16_t* aPort) const;
+
+ mozilla::ipc::IPCResult RecvCallback(const nsString& aType,
+ const CallbackData& aData,
+ const uint32_t& aReadyState);
+ mozilla::ipc::IPCResult RecvRequestDelete();
+ mozilla::ipc::IPCResult RecvUpdateBufferedAmount(
+ const uint32_t& aBufferred, const uint32_t& aTrackingNumber);
+
+ private:
+ nsString mHost;
+ uint16_t mPort;
+ nsCOMPtr<nsISerialEventTarget> mIPCEventTarget;
+};
+
+} // namespace mozilla::dom
+
+#endif
diff --git a/dom/network/TCPSocketParent.cpp b/dom/network/TCPSocketParent.cpp
new file mode 100644
index 0000000000..dc7bc3c530
--- /dev/null
+++ b/dom/network/TCPSocketParent.cpp
@@ -0,0 +1,223 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "TCPSocketParent.h"
+#include "jsapi.h"
+#include "jsfriendapi.h"
+#include "nsJSUtils.h"
+#include "mozilla/Unused.h"
+#include "mozilla/net/NeckoCommon.h"
+#include "mozilla/net/PNeckoParent.h"
+#include "mozilla/dom/ScriptSettings.h"
+#include "mozilla/dom/BrowserParent.h"
+#include "mozilla/HoldDropJSObjects.h"
+#include "nsISocketTransport.h"
+#include "nsNetUtil.h"
+#include "TCPSocket.h"
+
+namespace IPC {
+
+// Defined in TCPSocketChild.cpp
+extern bool DeserializeArrayBuffer(JSContext* aCx,
+ const nsTArray<uint8_t>& aBuffer,
+ JS::MutableHandle<JS::Value> aVal);
+
+} // namespace IPC
+
+namespace mozilla {
+
+namespace net {
+//
+// set MOZ_LOG=TCPSocket:5
+//
+extern LazyLogModule gTCPSocketLog;
+#define TCPSOCKET_LOG(args) MOZ_LOG(gTCPSocketLog, LogLevel::Debug, args)
+#define TCPSOCKET_LOG_ENABLED() MOZ_LOG_TEST(gTCPSocketLog, LogLevel::Debug)
+} // namespace net
+
+using namespace net;
+
+namespace dom {
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(TCPSocketParentBase)
+ NS_INTERFACE_MAP_ENTRY(nsISupports)
+NS_INTERFACE_MAP_END
+
+NS_IMPL_CYCLE_COLLECTION(TCPSocketParentBase, mSocket)
+NS_IMPL_CYCLE_COLLECTING_ADDREF(TCPSocketParentBase)
+NS_IMPL_CYCLE_COLLECTING_RELEASE(TCPSocketParentBase)
+
+TCPSocketParentBase::TCPSocketParentBase() : mIPCOpen(false) {}
+
+TCPSocketParentBase::~TCPSocketParentBase() = default;
+
+void TCPSocketParentBase::ReleaseIPDLReference() {
+ MOZ_ASSERT(mIPCOpen);
+ mIPCOpen = false;
+ this->Release();
+}
+
+void TCPSocketParentBase::AddIPDLReference() {
+ MOZ_ASSERT(!mIPCOpen);
+ mIPCOpen = true;
+ this->AddRef();
+}
+
+NS_IMETHODIMP_(MozExternalRefCountType) TCPSocketParent::Release(void) {
+ nsrefcnt refcnt = TCPSocketParentBase::Release();
+ if (refcnt == 1 && mIPCOpen) {
+ mozilla::Unused << PTCPSocketParent::SendRequestDelete();
+ return 1;
+ }
+ return refcnt;
+}
+
+mozilla::ipc::IPCResult TCPSocketParent::RecvOpen(
+ const nsString& aHost, const uint16_t& aPort, const bool& aUseSSL,
+ const bool& aUseArrayBuffers) {
+ mSocket = new TCPSocket(nullptr, aHost, aPort, aUseSSL, aUseArrayBuffers);
+ mSocket->SetSocketBridgeParent(this);
+ NS_ENSURE_SUCCESS(mSocket->Init(nullptr), IPC_OK());
+ return IPC_OK();
+}
+
+mozilla::ipc::IPCResult TCPSocketParent::RecvStartTLS() {
+ NS_ENSURE_TRUE(mSocket, IPC_OK());
+ ErrorResult rv;
+ mSocket->UpgradeToSecure(rv);
+ if (NS_WARN_IF(rv.Failed())) {
+ rv.SuppressException();
+ }
+
+ return IPC_OK();
+}
+
+mozilla::ipc::IPCResult TCPSocketParent::RecvSuspend() {
+ NS_ENSURE_TRUE(mSocket, IPC_OK());
+ mSocket->Suspend();
+ return IPC_OK();
+}
+
+mozilla::ipc::IPCResult TCPSocketParent::RecvResume() {
+ NS_ENSURE_TRUE(mSocket, IPC_OK());
+ ErrorResult rv;
+ mSocket->Resume(rv);
+ if (NS_WARN_IF(rv.Failed())) {
+ rv.SuppressException();
+ }
+
+ return IPC_OK();
+}
+
+mozilla::ipc::IPCResult TCPSocketParent::RecvData(const SendableData& aData) {
+ NS_ENSURE_TRUE(mSocket, IPC_OK());
+ ErrorResult rv;
+
+ switch (aData.type()) {
+ case SendableData::TArrayOfuint8_t: {
+ AutoSafeJSContext autoCx;
+ JS::Rooted<JS::Value> val(autoCx);
+ const nsTArray<uint8_t>& buffer = aData.get_ArrayOfuint8_t();
+ bool ok = IPC::DeserializeArrayBuffer(autoCx, buffer, &val);
+ NS_ENSURE_TRUE(ok, IPC_OK());
+ RootedSpiderMonkeyInterface<ArrayBuffer> data(autoCx);
+ if (!data.Init(&val.toObject())) {
+ TCPSOCKET_LOG(("%s: Failed to allocate memory", __FUNCTION__));
+ return IPC_FAIL_NO_REASON(this);
+ }
+ Optional<uint32_t> byteLength(buffer.Length());
+ mSocket->Send(data, 0, byteLength, rv);
+ break;
+ }
+
+ case SendableData::TnsCString: {
+ const nsCString& strData = aData.get_nsCString();
+ mSocket->Send(strData, rv);
+ break;
+ }
+
+ default:
+ MOZ_CRASH("unexpected SendableData type");
+ }
+ NS_ENSURE_SUCCESS(rv.StealNSResult(), IPC_OK());
+ return IPC_OK();
+}
+
+mozilla::ipc::IPCResult TCPSocketParent::RecvClose() {
+ NS_ENSURE_TRUE(mSocket, IPC_OK());
+ mSocket->Close();
+ return IPC_OK();
+}
+
+void TCPSocketParent::FireErrorEvent(const nsAString& aName,
+ const nsAString& aType, nsresult aError,
+ TCPReadyState aReadyState) {
+ SendEvent(u"error"_ns, TCPError(nsString(aName), nsString(aType), aError),
+ aReadyState);
+}
+
+void TCPSocketParent::FireEvent(const nsAString& aType,
+ TCPReadyState aReadyState) {
+ return SendEvent(aType, mozilla::void_t(), aReadyState);
+}
+
+void TCPSocketParent::FireArrayBufferDataEvent(nsTArray<uint8_t>& aBuffer,
+ TCPReadyState aReadyState) {
+ nsTArray<uint8_t> arr = std::move(aBuffer);
+
+ SendableData data(arr);
+ SendEvent(u"data"_ns, data, aReadyState);
+}
+
+void TCPSocketParent::FireStringDataEvent(const nsACString& aData,
+ TCPReadyState aReadyState) {
+ SendableData data((nsCString(aData)));
+
+ SendEvent(u"data"_ns, data, aReadyState);
+}
+
+void TCPSocketParent::SendEvent(const nsAString& aType, CallbackData aData,
+ TCPReadyState aReadyState) {
+ if (mIPCOpen) {
+ mozilla::Unused << PTCPSocketParent::SendCallback(
+ nsString(aType), aData, static_cast<uint32_t>(aReadyState));
+ }
+}
+
+void TCPSocketParent::SetSocket(TCPSocket* socket) { mSocket = socket; }
+
+nsresult TCPSocketParent::GetHost(nsAString& aHost) {
+ if (!mSocket) {
+ NS_ERROR("No internal socket instance mSocket!");
+ return NS_ERROR_FAILURE;
+ }
+ mSocket->GetHost(aHost);
+ return NS_OK;
+}
+
+nsresult TCPSocketParent::GetPort(uint16_t* aPort) {
+ if (!mSocket) {
+ NS_ERROR("No internal socket instance mSocket!");
+ return NS_ERROR_FAILURE;
+ }
+ *aPort = mSocket->Port();
+ return NS_OK;
+}
+
+void TCPSocketParent::ActorDestroy(ActorDestroyReason why) {
+ if (mSocket) {
+ mSocket->Close();
+ }
+ mSocket = nullptr;
+}
+
+mozilla::ipc::IPCResult TCPSocketParent::RecvRequestDelete() {
+ mozilla::Unused << Send__delete__(this);
+ return IPC_OK();
+}
+
+} // namespace dom
+} // namespace mozilla
diff --git a/dom/network/TCPSocketParent.h b/dom/network/TCPSocketParent.h
new file mode 100644
index 0000000000..388add862d
--- /dev/null
+++ b/dom/network/TCPSocketParent.h
@@ -0,0 +1,82 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_dom_TCPSocketParent_h
+#define mozilla_dom_TCPSocketParent_h
+
+#include "mozilla/dom/TCPSocketBinding.h"
+#include "mozilla/net/PTCPSocketParent.h"
+#include "nsCycleCollectionParticipant.h"
+#include "nsCOMPtr.h"
+#include "js/TypeDecls.h"
+
+#define TCPSOCKETPARENT_CID \
+ { \
+ 0x4e7246c6, 0xa8b3, 0x426d, { \
+ 0x9c, 0x17, 0x76, 0xda, 0xb1, 0xe1, 0xe1, 0x4a \
+ } \
+ }
+
+namespace mozilla::dom {
+
+class TCPSocket;
+
+class TCPSocketParentBase : public nsISupports {
+ public:
+ NS_DECL_CYCLE_COLLECTION_CLASS(TCPSocketParentBase)
+ NS_DECL_CYCLE_COLLECTING_ISUPPORTS
+
+ void AddIPDLReference();
+ void ReleaseIPDLReference();
+
+ bool IPCOpen() const { return mIPCOpen; }
+
+ protected:
+ TCPSocketParentBase();
+ virtual ~TCPSocketParentBase();
+
+ RefPtr<TCPSocket> mSocket;
+ bool mIPCOpen;
+};
+
+class TCPSocketParent : public mozilla::net::PTCPSocketParent,
+ public TCPSocketParentBase {
+ public:
+ NS_IMETHOD_(MozExternalRefCountType) Release() override;
+
+ TCPSocketParent() = default;
+
+ mozilla::ipc::IPCResult RecvOpen(const nsString& aHost, const uint16_t& aPort,
+ const bool& useSSL,
+ const bool& aUseArrayBuffers);
+
+ mozilla::ipc::IPCResult RecvStartTLS();
+ mozilla::ipc::IPCResult RecvSuspend();
+ mozilla::ipc::IPCResult RecvResume();
+ mozilla::ipc::IPCResult RecvClose();
+ mozilla::ipc::IPCResult RecvData(const SendableData& aData);
+ mozilla::ipc::IPCResult RecvRequestDelete();
+
+ void FireErrorEvent(const nsAString& aName, const nsAString& aType,
+ nsresult aError, TCPReadyState aReadyState);
+ void FireEvent(const nsAString& aType, TCPReadyState aReadyState);
+ void FireArrayBufferDataEvent(nsTArray<uint8_t>& aBuffer,
+ TCPReadyState aReadyState);
+ void FireStringDataEvent(const nsACString& aData, TCPReadyState aReadyState);
+
+ void SetSocket(TCPSocket* socket);
+ nsresult GetHost(nsAString& aHost);
+ nsresult GetPort(uint16_t* aPort);
+
+ private:
+ virtual void ActorDestroy(ActorDestroyReason why) override;
+ void SendEvent(const nsAString& aType, CallbackData aData,
+ TCPReadyState aReadyState);
+};
+
+} // namespace mozilla::dom
+
+#endif
diff --git a/dom/network/UDPSocket.cpp b/dom/network/UDPSocket.cpp
new file mode 100644
index 0000000000..7639c3b300
--- /dev/null
+++ b/dom/network/UDPSocket.cpp
@@ -0,0 +1,713 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "UDPSocket.h"
+#include "mozilla/AsyncEventDispatcher.h"
+#include "mozilla/dom/File.h"
+#include "mozilla/dom/ErrorEvent.h"
+#include "mozilla/dom/network/UDPSocketChild.h"
+#include "mozilla/dom/UDPMessageEvent.h"
+#include "mozilla/dom/UDPSocketBinding.h"
+#include "mozilla/dom/UnionTypes.h"
+#include "mozilla/dom/RootedDictionary.h"
+#include "mozilla/net/DNS.h"
+#include "nsComponentManagerUtils.h"
+#include "nsContentUtils.h"
+#include "nsINetAddr.h"
+#include "nsStringStream.h"
+
+namespace mozilla::dom {
+
+NS_IMPL_ISUPPORTS(UDPSocket::ListenerProxy, nsIUDPSocketListener,
+ nsIUDPSocketInternal)
+
+NS_IMPL_CYCLE_COLLECTION_CLASS(UDPSocket)
+
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(UDPSocket,
+ DOMEventTargetHelper)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mOpened)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mClosed)
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
+
+NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(UDPSocket, DOMEventTargetHelper)
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mOpened)
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mClosed)
+ tmp->CloseWithReason(NS_OK);
+NS_IMPL_CYCLE_COLLECTION_UNLINK_END
+
+NS_IMPL_ADDREF_INHERITED(UDPSocket, DOMEventTargetHelper)
+NS_IMPL_RELEASE_INHERITED(UDPSocket, DOMEventTargetHelper)
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(UDPSocket)
+ NS_INTERFACE_MAP_ENTRY(nsIUDPSocketListener)
+ NS_INTERFACE_MAP_ENTRY(nsIUDPSocketInternal)
+NS_INTERFACE_MAP_END_INHERITING(DOMEventTargetHelper)
+
+/* static */
+already_AddRefed<UDPSocket> UDPSocket::Constructor(const GlobalObject& aGlobal,
+ const UDPOptions& aOptions,
+ ErrorResult& aRv) {
+ nsCOMPtr<nsPIDOMWindowInner> ownerWindow =
+ do_QueryInterface(aGlobal.GetAsSupports());
+ if (!ownerWindow) {
+ aRv.Throw(NS_ERROR_FAILURE);
+ return nullptr;
+ }
+
+ bool addressReuse = aOptions.mAddressReuse;
+ bool loopback = aOptions.mLoopback;
+
+ nsCString remoteAddress;
+ if (aOptions.mRemoteAddress.WasPassed()) {
+ CopyUTF16toUTF8(aOptions.mRemoteAddress.Value(), remoteAddress);
+ } else {
+ remoteAddress.SetIsVoid(true);
+ }
+
+ Nullable<uint16_t> remotePort;
+ if (aOptions.mRemotePort.WasPassed()) {
+ remotePort.SetValue(aOptions.mRemotePort.Value());
+
+ if (remotePort.Value() == 0) {
+ aRv.Throw(NS_ERROR_DOM_INVALID_ACCESS_ERR);
+ return nullptr;
+ }
+ }
+
+ nsString localAddress;
+ if (aOptions.mLocalAddress.WasPassed()) {
+ localAddress = aOptions.mLocalAddress.Value();
+
+ // check if localAddress is a valid IPv4/6 address
+ NS_ConvertUTF16toUTF8 address(localAddress);
+ if (!net::HostIsIPLiteral(address)) {
+ aRv.Throw(NS_ERROR_DOM_INVALID_ACCESS_ERR);
+ return nullptr;
+ }
+ } else {
+ SetDOMStringToNull(localAddress);
+ }
+
+ Nullable<uint16_t> localPort;
+ if (aOptions.mLocalPort.WasPassed()) {
+ localPort.SetValue(aOptions.mLocalPort.Value());
+
+ if (localPort.Value() == 0) {
+ aRv.Throw(NS_ERROR_DOM_INVALID_ACCESS_ERR);
+ return nullptr;
+ }
+ }
+
+ RefPtr<UDPSocket> socket =
+ new UDPSocket(ownerWindow, remoteAddress, remotePort);
+ aRv = socket->Init(localAddress, localPort, addressReuse, loopback);
+
+ if (NS_WARN_IF(aRv.Failed())) {
+ return nullptr;
+ }
+
+ return socket.forget();
+}
+
+UDPSocket::UDPSocket(nsPIDOMWindowInner* aOwner,
+ const nsCString& aRemoteAddress,
+ const Nullable<uint16_t>& aRemotePort)
+ : DOMEventTargetHelper(aOwner),
+ mRemoteAddress(aRemoteAddress),
+ mRemotePort(aRemotePort),
+ mAddressReuse(false),
+ mLoopback(false),
+ mReadyState(SocketReadyState::Opening) {
+ MOZ_ASSERT(aOwner);
+
+ Document* aDoc = aOwner->GetExtantDoc();
+ if (aDoc) {
+ aDoc->DisallowBFCaching();
+ }
+}
+
+UDPSocket::~UDPSocket() { CloseWithReason(NS_OK); }
+
+JSObject* UDPSocket::WrapObject(JSContext* aCx,
+ JS::Handle<JSObject*> aGivenProto) {
+ return UDPSocket_Binding::Wrap(aCx, this, aGivenProto);
+}
+
+void UDPSocket::DisconnectFromOwner() {
+ DOMEventTargetHelper::DisconnectFromOwner();
+ CloseWithReason(NS_OK);
+}
+
+already_AddRefed<Promise> UDPSocket::Close() {
+ MOZ_ASSERT(mClosed);
+
+ RefPtr<Promise> promise = mClosed;
+
+ if (mReadyState == SocketReadyState::Closed) {
+ return promise.forget();
+ }
+
+ CloseWithReason(NS_OK);
+ return promise.forget();
+}
+
+void UDPSocket::CloseWithReason(nsresult aReason) {
+ if (mReadyState == SocketReadyState::Closed) {
+ return;
+ }
+
+ if (mOpened) {
+ if (mReadyState == SocketReadyState::Opening) {
+ // reject openedPromise with AbortError if socket is closed without error
+ nsresult openFailedReason =
+ NS_FAILED(aReason) ? aReason : NS_ERROR_DOM_ABORT_ERR;
+ mOpened->MaybeReject(openFailedReason);
+ }
+ }
+
+ mReadyState = SocketReadyState::Closed;
+
+ if (mListenerProxy) {
+ mListenerProxy->Disconnect();
+ mListenerProxy = nullptr;
+ }
+
+ if (mSocket) {
+ mSocket->Close();
+ mSocket = nullptr;
+ }
+
+ if (mSocketChild) {
+ mSocketChild->Close();
+ mSocketChild = nullptr;
+ }
+
+ if (mClosed) {
+ if (NS_SUCCEEDED(aReason)) {
+ mClosed->MaybeResolveWithUndefined();
+ } else {
+ mClosed->MaybeReject(aReason);
+ }
+ }
+
+ mPendingMcastCommands.Clear();
+}
+
+void UDPSocket::JoinMulticastGroup(const nsAString& aMulticastGroupAddress,
+ ErrorResult& aRv) {
+ if (mReadyState == SocketReadyState::Closed) {
+ aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
+ return;
+ }
+
+ if (mReadyState == SocketReadyState::Opening) {
+ MulticastCommand joinCommand(MulticastCommand::Join,
+ aMulticastGroupAddress);
+ mPendingMcastCommands.AppendElement(joinCommand);
+ return;
+ }
+
+ MOZ_ASSERT(mSocket || mSocketChild);
+
+ NS_ConvertUTF16toUTF8 address(aMulticastGroupAddress);
+
+ if (mSocket) {
+ MOZ_ASSERT(!mSocketChild);
+
+ aRv = mSocket->JoinMulticast(address, ""_ns);
+ NS_WARNING_ASSERTION(!aRv.Failed(), "JoinMulticast failed");
+
+ return;
+ }
+
+ MOZ_ASSERT(mSocketChild);
+
+ mSocketChild->JoinMulticast(address, ""_ns);
+}
+
+void UDPSocket::LeaveMulticastGroup(const nsAString& aMulticastGroupAddress,
+ ErrorResult& aRv) {
+ if (mReadyState == SocketReadyState::Closed) {
+ aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
+ return;
+ }
+
+ if (mReadyState == SocketReadyState::Opening) {
+ MulticastCommand leaveCommand(MulticastCommand::Leave,
+ aMulticastGroupAddress);
+ mPendingMcastCommands.AppendElement(leaveCommand);
+ return;
+ }
+
+ MOZ_ASSERT(mSocket || mSocketChild);
+
+ nsCString address = NS_ConvertUTF16toUTF8(aMulticastGroupAddress);
+ if (mSocket) {
+ MOZ_ASSERT(!mSocketChild);
+
+ aRv = mSocket->LeaveMulticast(address, ""_ns);
+ NS_WARNING_ASSERTION(!aRv.Failed(), "mSocket->LeaveMulticast failed");
+ return;
+ }
+
+ MOZ_ASSERT(mSocketChild);
+
+ mSocketChild->LeaveMulticast(address, ""_ns);
+}
+
+nsresult UDPSocket::DoPendingMcastCommand() {
+ MOZ_ASSERT(mReadyState == SocketReadyState::Open,
+ "Multicast command can only be executed after socket opened");
+
+ for (uint32_t i = 0; i < mPendingMcastCommands.Length(); ++i) {
+ MulticastCommand& command = mPendingMcastCommands[i];
+ ErrorResult rv;
+
+ switch (command.mCommand) {
+ case MulticastCommand::Join: {
+ JoinMulticastGroup(command.mAddress, rv);
+ break;
+ }
+ case MulticastCommand::Leave: {
+ LeaveMulticastGroup(command.mAddress, rv);
+ break;
+ }
+ }
+
+ if (NS_WARN_IF(rv.Failed())) {
+ return rv.StealNSResult();
+ }
+ }
+
+ mPendingMcastCommands.Clear();
+ return NS_OK;
+}
+
+bool UDPSocket::Send(const StringOrBlobOrArrayBufferOrArrayBufferView& aData,
+ const Optional<nsAString>& aRemoteAddress,
+ const Optional<Nullable<uint16_t>>& aRemotePort,
+ ErrorResult& aRv) {
+ if (mReadyState != SocketReadyState::Open) {
+ aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
+ return false;
+ }
+
+ MOZ_ASSERT(mSocket || mSocketChild);
+
+ // If the remote address and port were not specified in the constructor or as
+ // arguments, throw InvalidAccessError.
+ nsCString remoteAddress;
+ if (aRemoteAddress.WasPassed()) {
+ CopyUTF16toUTF8(aRemoteAddress.Value(), remoteAddress);
+ UDPSOCKET_LOG(("%s: Send to %s", __FUNCTION__, remoteAddress.get()));
+ } else if (!mRemoteAddress.IsVoid()) {
+ remoteAddress = mRemoteAddress;
+ UDPSOCKET_LOG(("%s: Send to %s", __FUNCTION__, remoteAddress.get()));
+ } else {
+ aRv.Throw(NS_ERROR_DOM_INVALID_ACCESS_ERR);
+ return false;
+ }
+
+ uint16_t remotePort;
+ if (aRemotePort.WasPassed() && !aRemotePort.Value().IsNull()) {
+ remotePort = aRemotePort.Value().Value();
+ } else if (!mRemotePort.IsNull()) {
+ remotePort = mRemotePort.Value();
+ } else {
+ aRv.Throw(NS_ERROR_DOM_INVALID_ACCESS_ERR);
+ return false;
+ }
+
+ nsCOMPtr<nsIInputStream> stream;
+ if (aData.IsBlob()) {
+ Blob& blob = aData.GetAsBlob();
+
+ blob.CreateInputStream(getter_AddRefs(stream), aRv);
+ if (NS_WARN_IF(aRv.Failed())) {
+ return false;
+ }
+ } else {
+ nsresult rv;
+ nsCOMPtr<nsIStringInputStream> strStream =
+ do_CreateInstance(NS_STRINGINPUTSTREAM_CONTRACTID, &rv);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ aRv.Throw(rv);
+ return false;
+ }
+
+ if (aData.IsString()) {
+ NS_ConvertUTF16toUTF8 data(aData.GetAsString());
+ aRv = strStream->SetData(data.BeginReading(), data.Length());
+ } else if (aData.IsArrayBuffer()) {
+ const ArrayBuffer& data = aData.GetAsArrayBuffer();
+ data.ComputeState();
+ aRv = strStream->SetData(reinterpret_cast<const char*>(data.Data()),
+ data.Length());
+ } else {
+ const ArrayBufferView& data = aData.GetAsArrayBufferView();
+ data.ComputeState();
+ aRv = strStream->SetData(reinterpret_cast<const char*>(data.Data()),
+ data.Length());
+ }
+
+ if (NS_WARN_IF(aRv.Failed())) {
+ return false;
+ }
+
+ stream = strStream;
+ }
+
+ if (mSocket) {
+ aRv = mSocket->SendBinaryStream(remoteAddress, remotePort, stream);
+ } else if (mSocketChild) {
+ aRv = mSocketChild->SendBinaryStream(remoteAddress, remotePort, stream);
+ }
+
+ if (NS_WARN_IF(aRv.Failed())) {
+ return false;
+ }
+
+ return true;
+}
+
+nsresult UDPSocket::InitLocal(const nsAString& aLocalAddress,
+ const uint16_t& aLocalPort) {
+ nsresult rv;
+
+ nsCOMPtr<nsIUDPSocket> sock =
+ do_CreateInstance("@mozilla.org/network/udp-socket;1", &rv);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(GetOwner(), &rv);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ nsCOMPtr<nsIPrincipal> principal = global->PrincipalOrNull();
+ if (!principal) {
+ return NS_ERROR_FAILURE;
+ }
+
+ if (aLocalAddress.IsEmpty()) {
+ rv = sock->Init(aLocalPort, /* loopback = */ false, principal,
+ mAddressReuse, /* optionalArgc = */ 1);
+ } else {
+ PRNetAddr prAddr;
+ PR_InitializeNetAddr(PR_IpAddrAny, aLocalPort, &prAddr);
+ PR_StringToNetAddr(NS_ConvertUTF16toUTF8(aLocalAddress).BeginReading(),
+ &prAddr);
+ UDPSOCKET_LOG(("%s: %s:%u", __FUNCTION__,
+ NS_ConvertUTF16toUTF8(aLocalAddress).get(), aLocalPort));
+
+ mozilla::net::NetAddr addr(&prAddr);
+ rv = sock->InitWithAddress(&addr, principal, mAddressReuse,
+ /* optionalArgc = */ 1);
+ }
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ rv = sock->SetMulticastLoopback(mLoopback);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ mSocket = sock;
+
+ // Get real local address and local port
+ nsCOMPtr<nsINetAddr> localAddr;
+ rv = mSocket->GetLocalAddr(getter_AddRefs(localAddr));
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ nsCString localAddress;
+ rv = localAddr->GetAddress(localAddress);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ CopyUTF8toUTF16(localAddress, mLocalAddress);
+
+ uint16_t localPort;
+ rv = localAddr->GetPort(&localPort);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ mLocalPort.SetValue(localPort);
+
+ mListenerProxy = new ListenerProxy(this);
+
+ rv = mSocket->AsyncListen(mListenerProxy);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ mReadyState = SocketReadyState::Open;
+ rv = DoPendingMcastCommand();
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ mOpened->MaybeResolveWithUndefined();
+
+ return NS_OK;
+}
+
+nsresult UDPSocket::InitRemote(const nsAString& aLocalAddress,
+ const uint16_t& aLocalPort) {
+ nsresult rv;
+
+ RefPtr<UDPSocketChild> sock = new UDPSocketChild();
+
+ mListenerProxy = new ListenerProxy(this);
+
+ nsCOMPtr<nsIGlobalObject> obj = do_QueryInterface(GetOwner(), &rv);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ nsCOMPtr<nsIPrincipal> principal = obj->PrincipalOrNull();
+ if (!principal) {
+ return NS_ERROR_FAILURE;
+ }
+
+ rv = sock->Bind(mListenerProxy, principal,
+ NS_ConvertUTF16toUTF8(aLocalAddress), aLocalPort,
+ mAddressReuse, mLoopback, 0, 0);
+
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ mSocketChild = sock;
+
+ return NS_OK;
+}
+
+nsresult UDPSocket::Init(const nsString& aLocalAddress,
+ const Nullable<uint16_t>& aLocalPort,
+ const bool& aAddressReuse, const bool& aLoopback) {
+ MOZ_ASSERT(!mSocket && !mSocketChild);
+
+ mLocalAddress = aLocalAddress;
+ mLocalPort = aLocalPort;
+ mAddressReuse = aAddressReuse;
+ mLoopback = aLoopback;
+
+ ErrorResult rv;
+ nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(GetOwner());
+
+ mOpened = Promise::Create(global, rv);
+ if (NS_WARN_IF(rv.Failed())) {
+ return rv.StealNSResult();
+ }
+
+ mClosed = Promise::Create(global, rv);
+ if (NS_WARN_IF(rv.Failed())) {
+ return rv.StealNSResult();
+ }
+
+ class OpenSocketRunnable final : public Runnable {
+ public:
+ explicit OpenSocketRunnable(UDPSocket* aSocket)
+ : mozilla::Runnable("OpenSocketRunnable"), mSocket(aSocket) {}
+
+ NS_IMETHOD Run() override {
+ MOZ_ASSERT(mSocket);
+
+ if (mSocket->mReadyState != SocketReadyState::Opening) {
+ return NS_OK;
+ }
+
+ uint16_t localPort = 0;
+ if (!mSocket->mLocalPort.IsNull()) {
+ localPort = mSocket->mLocalPort.Value();
+ }
+
+ nsresult rv;
+ if (!XRE_IsParentProcess()) {
+ rv = mSocket->InitRemote(mSocket->mLocalAddress, localPort);
+ } else {
+ rv = mSocket->InitLocal(mSocket->mLocalAddress, localPort);
+ }
+
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ mSocket->CloseWithReason(NS_ERROR_DOM_NETWORK_ERR);
+ }
+
+ return NS_OK;
+ }
+
+ private:
+ RefPtr<UDPSocket> mSocket;
+ };
+
+ nsCOMPtr<nsIRunnable> runnable = new OpenSocketRunnable(this);
+
+ return NS_DispatchToMainThread(runnable);
+}
+
+void UDPSocket::HandleReceivedData(const nsACString& aRemoteAddress,
+ const uint16_t& aRemotePort,
+ const nsTArray<uint8_t>& aData) {
+ if (mReadyState != SocketReadyState::Open) {
+ return;
+ }
+
+ if (NS_FAILED(CheckCurrentGlobalCorrectness())) {
+ return;
+ }
+
+ if (NS_FAILED(DispatchReceivedData(aRemoteAddress, aRemotePort, aData))) {
+ CloseWithReason(NS_ERROR_UNEXPECTED);
+ }
+}
+
+nsresult UDPSocket::DispatchReceivedData(const nsACString& aRemoteAddress,
+ const uint16_t& aRemotePort,
+ const nsTArray<uint8_t>& aData) {
+ AutoJSAPI jsapi;
+
+ if (NS_WARN_IF(!jsapi.Init(GetOwner()))) {
+ return NS_ERROR_FAILURE;
+ }
+
+ JSContext* cx = jsapi.cx();
+
+ // Copy packet data to ArrayBuffer
+ JS::Rooted<JSObject*> arrayBuf(
+ cx, ArrayBuffer::Create(cx, aData.Length(), aData.Elements()));
+
+ if (NS_WARN_IF(!arrayBuf)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ JS::Rooted<JS::Value> jsData(cx, JS::ObjectValue(*arrayBuf));
+
+ // Create DOM event
+ RootedDictionary<UDPMessageEventInit> init(cx);
+ CopyUTF8toUTF16(aRemoteAddress, init.mRemoteAddress);
+ init.mRemotePort = aRemotePort;
+ init.mData = jsData;
+
+ RefPtr<UDPMessageEvent> udpEvent =
+ UDPMessageEvent::Constructor(this, u"message"_ns, init);
+
+ if (NS_WARN_IF(!udpEvent)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ udpEvent->SetTrusted(true);
+
+ RefPtr<AsyncEventDispatcher> asyncDispatcher =
+ new AsyncEventDispatcher(this, udpEvent);
+
+ return asyncDispatcher->PostDOMEvent();
+}
+
+// nsIUDPSocketListener
+
+NS_IMETHODIMP
+UDPSocket::OnPacketReceived(nsIUDPSocket* aSocket, nsIUDPMessage* aMessage) {
+ // nsIUDPSocketListener callbacks should be invoked on main thread.
+ MOZ_ASSERT(NS_IsMainThread(), "Not running on main thread");
+
+ // Create appropriate JS object for message
+ FallibleTArray<uint8_t>& buffer = aMessage->GetDataAsTArray();
+
+ nsCOMPtr<nsINetAddr> addr;
+ if (NS_WARN_IF(NS_FAILED(aMessage->GetFromAddr(getter_AddRefs(addr))))) {
+ return NS_OK;
+ }
+
+ nsCString remoteAddress;
+ if (NS_WARN_IF(NS_FAILED(addr->GetAddress(remoteAddress)))) {
+ return NS_OK;
+ }
+
+ uint16_t remotePort;
+ if (NS_WARN_IF(NS_FAILED(addr->GetPort(&remotePort)))) {
+ return NS_OK;
+ }
+
+ HandleReceivedData(remoteAddress, remotePort, buffer);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+UDPSocket::OnStopListening(nsIUDPSocket* aSocket, nsresult aStatus) {
+ // nsIUDPSocketListener callbacks should be invoked on main thread.
+ MOZ_ASSERT(NS_IsMainThread(), "Not running on main thread");
+
+ CloseWithReason(aStatus);
+
+ return NS_OK;
+}
+
+// nsIUDPSocketInternal
+
+NS_IMETHODIMP
+UDPSocket::CallListenerError(const nsACString& aMessage,
+ const nsACString& aFilename,
+ uint32_t aLineNumber) {
+ CloseWithReason(NS_ERROR_DOM_NETWORK_ERR);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+UDPSocket::CallListenerReceivedData(const nsACString& aRemoteAddress,
+ uint16_t aRemotePort,
+ const nsTArray<uint8_t>& aData) {
+ HandleReceivedData(aRemoteAddress, aRemotePort, aData);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+UDPSocket::CallListenerOpened() {
+ if (mReadyState != SocketReadyState::Opening) {
+ return NS_OK;
+ }
+
+ MOZ_ASSERT(mSocketChild);
+
+ // Get real local address and local port
+ CopyUTF8toUTF16(mSocketChild->LocalAddress(), mLocalAddress);
+
+ mLocalPort.SetValue(mSocketChild->LocalPort());
+
+ mReadyState = SocketReadyState::Open;
+ nsresult rv = DoPendingMcastCommand();
+
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ CloseWithReason(rv);
+ return NS_OK;
+ }
+
+ mOpened->MaybeResolveWithUndefined();
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+UDPSocket::CallListenerConnected() {
+ // This shouldn't be called here.
+ MOZ_CRASH();
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+UDPSocket::CallListenerClosed() {
+ CloseWithReason(NS_OK);
+
+ return NS_OK;
+}
+
+} // namespace mozilla::dom
diff --git a/dom/network/UDPSocket.h b/dom/network/UDPSocket.h
new file mode 100644
index 0000000000..5453fe5448
--- /dev/null
+++ b/dom/network/UDPSocket.h
@@ -0,0 +1,177 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_dom_UDPSocket_h__
+#define mozilla_dom_UDPSocket_h__
+
+#include "mozilla/Attributes.h"
+#include "mozilla/DOMEventTargetHelper.h"
+#include "mozilla/dom/Promise.h"
+#include "mozilla/dom/SocketCommonBinding.h"
+#include "nsIUDPSocket.h"
+#include "nsIUDPSocketChild.h"
+#include "nsTArray.h"
+
+struct JSContext;
+
+//
+// set MOZ_LOG=UDPSocket:5
+//
+
+namespace mozilla {
+class ErrorResult;
+class LazyLogModule;
+
+namespace net {
+extern LazyLogModule gUDPSocketLog;
+#define UDPSOCKET_LOG(args) \
+ MOZ_LOG(::mozilla::net::gUDPSocketLog, LogLevel::Debug, args)
+#define UDPSOCKET_LOG_ENABLED() \
+ MOZ_LOG_TEST(::mozilla::net::gUDPSocketLog, LogLevel::Debug)
+} // namespace net
+
+namespace dom {
+
+struct UDPOptions;
+class StringOrBlobOrArrayBufferOrArrayBufferView;
+class UDPSocketChild;
+
+class UDPSocket final : public DOMEventTargetHelper,
+ public nsIUDPSocketListener,
+ public nsIUDPSocketInternal {
+ public:
+ NS_DECL_ISUPPORTS_INHERITED
+ NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(UDPSocket, DOMEventTargetHelper)
+ NS_DECL_NSIUDPSOCKETLISTENER
+ NS_DECL_NSIUDPSOCKETINTERNAL
+
+ public:
+ nsPIDOMWindowInner* GetParentObject() const { return GetOwner(); }
+
+ virtual JSObject* WrapObject(JSContext* aCx,
+ JS::Handle<JSObject*> aGivenProto) override;
+
+ virtual void DisconnectFromOwner() override;
+
+ static already_AddRefed<UDPSocket> Constructor(const GlobalObject& aGlobal,
+ const UDPOptions& aOptions,
+ ErrorResult& aRv);
+
+ void GetLocalAddress(nsString& aRetVal) const { aRetVal = mLocalAddress; }
+
+ Nullable<uint16_t> GetLocalPort() const { return mLocalPort; }
+
+ void GetRemoteAddress(nsString& aRetVal) const {
+ if (mRemoteAddress.IsVoid()) {
+ SetDOMStringToNull(aRetVal);
+ return;
+ }
+
+ CopyUTF8toUTF16(mRemoteAddress, aRetVal);
+ }
+
+ Nullable<uint16_t> GetRemotePort() const { return mRemotePort; }
+
+ bool AddressReuse() const { return mAddressReuse; }
+
+ bool Loopback() const { return mLoopback; }
+
+ SocketReadyState ReadyState() const { return mReadyState; }
+
+ Promise* Opened() const { return mOpened; }
+
+ Promise* Closed() const { return mClosed; }
+
+ IMPL_EVENT_HANDLER(message)
+
+ already_AddRefed<Promise> Close();
+
+ void JoinMulticastGroup(const nsAString& aMulticastGroupAddress,
+ ErrorResult& aRv);
+
+ void LeaveMulticastGroup(const nsAString& aMulticastGroupAddress,
+ ErrorResult& aRv);
+
+ bool Send(const StringOrBlobOrArrayBufferOrArrayBufferView& aData,
+ const Optional<nsAString>& aRemoteAddress,
+ const Optional<Nullable<uint16_t>>& aRemotePort, ErrorResult& aRv);
+
+ private:
+ class ListenerProxy : public nsIUDPSocketListener,
+ public nsIUDPSocketInternal {
+ public:
+ NS_DECL_ISUPPORTS
+ NS_FORWARD_SAFE_NSIUDPSOCKETLISTENER(mSocket)
+ NS_FORWARD_SAFE_NSIUDPSOCKETINTERNAL(mSocket)
+
+ explicit ListenerProxy(UDPSocket* aSocket) : mSocket(aSocket) {}
+
+ void Disconnect() { mSocket = nullptr; }
+
+ private:
+ virtual ~ListenerProxy() = default;
+
+ UDPSocket* mSocket;
+ };
+
+ UDPSocket(nsPIDOMWindowInner* aOwner, const nsCString& aRemoteAddress,
+ const Nullable<uint16_t>& aRemotePort);
+
+ virtual ~UDPSocket();
+
+ nsresult Init(const nsString& aLocalAddress,
+ const Nullable<uint16_t>& aLocalPort, const bool& aAddressReuse,
+ const bool& aLoopback);
+
+ nsresult InitLocal(const nsAString& aLocalAddress,
+ const uint16_t& aLocalPort);
+
+ nsresult InitRemote(const nsAString& aLocalAddress,
+ const uint16_t& aLocalPort);
+
+ void HandleReceivedData(const nsACString& aRemoteAddress,
+ const uint16_t& aRemotePort,
+ const nsTArray<uint8_t>& aData);
+
+ nsresult DispatchReceivedData(const nsACString& aRemoteAddress,
+ const uint16_t& aRemotePort,
+ const nsTArray<uint8_t>& aData);
+
+ void CloseWithReason(nsresult aReason);
+
+ nsresult DoPendingMcastCommand();
+
+ nsString mLocalAddress;
+ Nullable<uint16_t> mLocalPort;
+ nsCString mRemoteAddress;
+ Nullable<uint16_t> mRemotePort;
+ bool mAddressReuse;
+ bool mLoopback;
+ SocketReadyState mReadyState;
+ RefPtr<Promise> mOpened;
+ RefPtr<Promise> mClosed;
+
+ nsCOMPtr<nsIUDPSocket> mSocket;
+ RefPtr<UDPSocketChild> mSocketChild;
+ RefPtr<ListenerProxy> mListenerProxy;
+
+ struct MulticastCommand {
+ enum CommandType { Join, Leave };
+
+ MulticastCommand(CommandType aCommand, const nsAString& aAddress)
+ : mCommand(aCommand), mAddress(aAddress) {}
+
+ CommandType mCommand;
+ nsString mAddress;
+ };
+
+ nsTArray<MulticastCommand> mPendingMcastCommands;
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif // mozilla_dom_UDPSocket_h__
diff --git a/dom/network/UDPSocketChild.cpp b/dom/network/UDPSocketChild.cpp
new file mode 100644
index 0000000000..e5a09d56dd
--- /dev/null
+++ b/dom/network/UDPSocketChild.cpp
@@ -0,0 +1,234 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "UDPSocketChild.h"
+#include "UDPSocket.h"
+#include "mozilla/Unused.h"
+#include "mozilla/ipc/IPCStreamUtils.h"
+#include "mozilla/net/NeckoChild.h"
+#include "mozilla/dom/ContentChild.h"
+#include "mozilla/dom/PermissionMessageUtils.h"
+#include "mozilla/ipc/BackgroundChild.h"
+#include "mozilla/ipc/PBackgroundChild.h"
+#include "mozilla/ipc/BackgroundUtils.h"
+#include "mozilla/ipc/PBackgroundSharedTypes.h"
+
+using mozilla::net::gNeckoChild;
+
+namespace mozilla::dom {
+
+NS_IMPL_ISUPPORTS(UDPSocketChildBase, nsISupports)
+
+UDPSocketChildBase::UDPSocketChildBase() : mIPCOpen(false) {}
+
+UDPSocketChildBase::~UDPSocketChildBase() = default;
+
+void UDPSocketChildBase::ReleaseIPDLReference() {
+ MOZ_ASSERT(mIPCOpen);
+ mIPCOpen = false;
+ mSocket = nullptr;
+ this->Release();
+}
+
+void UDPSocketChildBase::AddIPDLReference() {
+ MOZ_ASSERT(!mIPCOpen);
+ mIPCOpen = true;
+ this->AddRef();
+}
+
+NS_IMETHODIMP_(MozExternalRefCountType) UDPSocketChild::Release(void) {
+ nsrefcnt refcnt = UDPSocketChildBase::Release();
+ if (refcnt == 1 && mIPCOpen) {
+ PUDPSocketChild::SendRequestDelete();
+ return 1;
+ }
+ return refcnt;
+}
+
+UDPSocketChild::UDPSocketChild() : mBackgroundManager(nullptr), mLocalPort(0) {}
+
+UDPSocketChild::~UDPSocketChild() = default;
+
+nsresult UDPSocketChild::SetBackgroundSpinsEvents() {
+ using mozilla::ipc::BackgroundChild;
+
+ mBackgroundManager = BackgroundChild::GetOrCreateForCurrentThread();
+ if (NS_WARN_IF(!mBackgroundManager)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ return NS_OK;
+}
+
+nsresult UDPSocketChild::Bind(nsIUDPSocketInternal* aSocket,
+ nsIPrincipal* aPrincipal, const nsACString& aHost,
+ uint16_t aPort, bool aAddressReuse,
+ bool aLoopback, uint32_t recvBufferSize,
+ uint32_t sendBufferSize) {
+ UDPSOCKET_LOG(
+ ("%s: %s:%u", __FUNCTION__, PromiseFlatCString(aHost).get(), aPort));
+
+ NS_ENSURE_ARG(aSocket);
+
+ if (NS_IsMainThread()) {
+ if (!gNeckoChild->SendPUDPSocketConstructor(this, aPrincipal,
+ mFilterName)) {
+ return NS_ERROR_FAILURE;
+ }
+ } else {
+ if (!mBackgroundManager) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ // If we want to support a passed-in principal here we'd need to
+ // convert it to a PrincipalInfo
+ MOZ_ASSERT(!aPrincipal);
+ if (!mBackgroundManager->SendPUDPSocketConstructor(this, Nothing(),
+ mFilterName)) {
+ return NS_ERROR_FAILURE;
+ }
+ }
+
+ mSocket = aSocket;
+ AddIPDLReference();
+
+ SendBind(UDPAddressInfo(nsCString(aHost), aPort), aAddressReuse, aLoopback,
+ recvBufferSize, sendBufferSize);
+ return NS_OK;
+}
+
+void UDPSocketChild::Connect(nsIUDPSocketInternal* aSocket,
+ const nsACString& aHost, uint16_t aPort) {
+ UDPSOCKET_LOG(
+ ("%s: %s:%u", __FUNCTION__, PromiseFlatCString(aHost).get(), aPort));
+
+ mSocket = aSocket;
+
+ SendConnect(UDPAddressInfo(nsCString(aHost), aPort));
+}
+
+void UDPSocketChild::Close() { SendClose(); }
+
+nsresult UDPSocketChild::SendWithAddress(const NetAddr* aAddr,
+ const uint8_t* aData,
+ uint32_t aByteLength) {
+ NS_ENSURE_ARG(aAddr);
+ NS_ENSURE_ARG(aData);
+
+ UDPSOCKET_LOG(("%s: %u bytes", __FUNCTION__, aByteLength));
+ return SendDataInternal(UDPSocketAddr(*aAddr), aData, aByteLength);
+}
+
+nsresult UDPSocketChild::SendDataInternal(const UDPSocketAddr& aAddr,
+ const uint8_t* aData,
+ const uint32_t aByteLength) {
+ NS_ENSURE_ARG(aData);
+
+ FallibleTArray<uint8_t> fallibleArray;
+ if (!fallibleArray.InsertElementsAt(0, aData, aByteLength, fallible)) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ SendOutgoingData(UDPData{std::move(fallibleArray)}, aAddr);
+
+ return NS_OK;
+}
+
+nsresult UDPSocketChild::SendBinaryStream(const nsACString& aHost,
+ uint16_t aPort,
+ nsIInputStream* aStream) {
+ NS_ENSURE_ARG(aStream);
+
+ mozilla::ipc::IPCStream stream;
+ if (NS_WARN_IF(!mozilla::ipc::SerializeIPCStream(do_AddRef(aStream), stream,
+ /* aAllowLazy */ false))) {
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ UDPSOCKET_LOG(
+ ("%s: %s:%u", __FUNCTION__, PromiseFlatCString(aHost).get(), aPort));
+ SendOutgoingData(UDPData(stream),
+ UDPSocketAddr(UDPAddressInfo(nsCString(aHost), aPort)));
+
+ return NS_OK;
+}
+
+void UDPSocketChild::JoinMulticast(const nsACString& aMulticastAddress,
+ const nsACString& aInterface) {
+ SendJoinMulticast(aMulticastAddress, aInterface);
+}
+
+void UDPSocketChild::LeaveMulticast(const nsACString& aMulticastAddress,
+ const nsACString& aInterface) {
+ SendLeaveMulticast(aMulticastAddress, aInterface);
+}
+
+nsresult UDPSocketChild::SetFilterName(const nsACString& aFilterName) {
+ if (!mFilterName.IsEmpty()) {
+ // filter name can only be set once.
+ return NS_ERROR_FAILURE;
+ }
+ mFilterName = aFilterName;
+ return NS_OK;
+}
+
+// PUDPSocketChild Methods
+mozilla::ipc::IPCResult UDPSocketChild::RecvCallbackOpened(
+ const UDPAddressInfo& aAddressInfo) {
+ mLocalAddress = aAddressInfo.addr();
+ mLocalPort = aAddressInfo.port();
+
+ UDPSOCKET_LOG(("%s: %s:%u", __FUNCTION__, mLocalAddress.get(), mLocalPort));
+ nsresult rv = mSocket->CallListenerOpened();
+ mozilla::Unused << NS_WARN_IF(NS_FAILED(rv));
+
+ return IPC_OK();
+}
+
+// PUDPSocketChild Methods
+mozilla::ipc::IPCResult UDPSocketChild::RecvCallbackConnected(
+ const UDPAddressInfo& aAddressInfo) {
+ mLocalAddress = aAddressInfo.addr();
+ mLocalPort = aAddressInfo.port();
+
+ UDPSOCKET_LOG(("%s: %s:%u", __FUNCTION__, mLocalAddress.get(), mLocalPort));
+ nsresult rv = mSocket->CallListenerConnected();
+ mozilla::Unused << NS_WARN_IF(NS_FAILED(rv));
+
+ return IPC_OK();
+}
+
+mozilla::ipc::IPCResult UDPSocketChild::RecvCallbackClosed() {
+ nsresult rv = mSocket->CallListenerClosed();
+ mozilla::Unused << NS_WARN_IF(NS_FAILED(rv));
+
+ return IPC_OK();
+}
+
+mozilla::ipc::IPCResult UDPSocketChild::RecvCallbackReceivedData(
+ const UDPAddressInfo& aAddressInfo, nsTArray<uint8_t>&& aData) {
+ UDPSOCKET_LOG(("%s: %s:%u length %zu", __FUNCTION__,
+ aAddressInfo.addr().get(), aAddressInfo.port(),
+ aData.Length()));
+ nsresult rv = mSocket->CallListenerReceivedData(aAddressInfo.addr(),
+ aAddressInfo.port(), aData);
+ mozilla::Unused << NS_WARN_IF(NS_FAILED(rv));
+
+ return IPC_OK();
+}
+
+mozilla::ipc::IPCResult UDPSocketChild::RecvCallbackError(
+ const nsCString& aMessage, const nsCString& aFilename,
+ const uint32_t& aLineNumber) {
+ UDPSOCKET_LOG(("%s: %s:%s:%u", __FUNCTION__, aMessage.get(), aFilename.get(),
+ aLineNumber));
+ nsresult rv = mSocket->CallListenerError(aMessage, aFilename, aLineNumber);
+ mozilla::Unused << NS_WARN_IF(NS_FAILED(rv));
+
+ return IPC_OK();
+}
+
+} // namespace mozilla::dom
diff --git a/dom/network/UDPSocketChild.h b/dom/network/UDPSocketChild.h
new file mode 100644
index 0000000000..81e5e39776
--- /dev/null
+++ b/dom/network/UDPSocketChild.h
@@ -0,0 +1,109 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_dom_UDPSocketChild_h__
+#define mozilla_dom_UDPSocketChild_h__
+
+#include "mozilla/net/PUDPSocketChild.h"
+#include "nsCycleCollectionParticipant.h"
+#include "nsCOMPtr.h"
+
+#define UDPSOCKETCHILD_CID \
+ { \
+ 0xb47e5a0f, 0xd384, 0x48ef, { \
+ 0x88, 0x85, 0x42, 0x59, 0x79, 0x3d, 0x9c, 0xf0 \
+ } \
+ }
+
+class nsIInputStream;
+class nsIPrincipal;
+class nsIUDPSocketInternal;
+
+namespace mozilla::dom {
+
+class UDPSocketChildBase : public nsISupports {
+ public:
+ NS_DECL_ISUPPORTS
+
+ void AddIPDLReference();
+ void ReleaseIPDLReference();
+
+ protected:
+ UDPSocketChildBase();
+ virtual ~UDPSocketChildBase();
+ nsCOMPtr<nsIUDPSocketInternal> mSocket;
+ bool mIPCOpen;
+};
+
+class UDPSocketChild : public mozilla::net::PUDPSocketChild,
+ public UDPSocketChildBase {
+ public:
+ NS_IMETHOD_(MozExternalRefCountType) Release() override;
+
+ UDPSocketChild();
+ virtual ~UDPSocketChild();
+
+ uint16_t LocalPort() const { return mLocalPort; }
+ // Local address as UTF-8.
+ const nsACString& LocalAddress() const { return mLocalAddress; }
+
+ nsresult SetFilterName(const nsACString& aFilterName);
+
+ // Allow hosting this over PBackground instead of PNecko
+ nsresult SetBackgroundSpinsEvents();
+
+ // Tell the chrome process to bind the UDP socket to a given local host and
+ // port
+ nsresult Bind(nsIUDPSocketInternal* aSocket, nsIPrincipal* aPrincipal,
+ const nsACString& aHost, uint16_t aPort, bool aAddressReuse,
+ bool aLoopback, uint32_t recvBufferSize,
+ uint32_t sendBufferSize);
+
+ // Tell the chrome process to connect the UDP socket to a given remote host
+ // and port
+ void Connect(nsIUDPSocketInternal* aSocket, const nsACString& aHost,
+ uint16_t aPort);
+
+ // Send the given data to the given address.
+ nsresult SendWithAddress(const NetAddr* aAddr, const uint8_t* aData,
+ uint32_t aByteLength);
+
+ // Send input stream. This must be a buffered stream implementation.
+ nsresult SendBinaryStream(const nsACString& aHost, uint16_t aPort,
+ nsIInputStream* aStream);
+
+ void Close();
+
+ // Address and interface are both UTF-8.
+ void JoinMulticast(const nsACString& aMulticastAddress,
+ const nsACString& aInterface);
+ void LeaveMulticast(const nsACString& aMulticastAddress,
+ const nsACString& aInterface);
+
+ mozilla::ipc::IPCResult RecvCallbackOpened(
+ const UDPAddressInfo& aAddressInfo);
+ mozilla::ipc::IPCResult RecvCallbackConnected(
+ const UDPAddressInfo& aAddressInfo);
+ mozilla::ipc::IPCResult RecvCallbackClosed();
+ mozilla::ipc::IPCResult RecvCallbackReceivedData(
+ const UDPAddressInfo& aAddressInfo, nsTArray<uint8_t>&& aData);
+ mozilla::ipc::IPCResult RecvCallbackError(const nsCString& aMessage,
+ const nsCString& aFilename,
+ const uint32_t& aLineNumber);
+
+ private:
+ nsresult SendDataInternal(const UDPSocketAddr& aAddr, const uint8_t* aData,
+ const uint32_t aByteLength);
+
+ mozilla::ipc::PBackgroundChild* mBackgroundManager;
+ uint16_t mLocalPort;
+ nsCString mLocalAddress;
+ nsCString mFilterName;
+};
+
+} // namespace mozilla::dom
+
+#endif // !defined(mozilla_dom_UDPSocketChild_h__)
diff --git a/dom/network/UDPSocketParent.cpp b/dom/network/UDPSocketParent.cpp
new file mode 100644
index 0000000000..3b7fa190e7
--- /dev/null
+++ b/dom/network/UDPSocketParent.cpp
@@ -0,0 +1,551 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "UDPSocketParent.h"
+#include "UDPSocket.h"
+#include "nsComponentManagerUtils.h"
+#include "nsIUDPSocket.h"
+#include "nsINetAddr.h"
+#include "nsNetCID.h"
+#include "mozilla/Unused.h"
+#include "mozilla/dom/ContentParent.h"
+#include "mozilla/ipc/InputStreamUtils.h"
+#include "mozilla/net/DNS.h"
+#include "mozilla/net/NeckoCommon.h"
+#include "mozilla/net/PNeckoParent.h"
+#include "nsIPermissionManager.h"
+#include "mozilla/ipc/PBackgroundParent.h"
+#include "transport/runnable_utils.h"
+
+namespace mozilla {
+
+using namespace net;
+
+namespace dom {
+
+NS_IMPL_ISUPPORTS(UDPSocketParent, nsIUDPSocketListener)
+
+UDPSocketParent::UDPSocketParent(PBackgroundParent* aManager)
+ : mBackgroundManager(aManager), mIPCOpen(true) {}
+
+UDPSocketParent::UDPSocketParent(PNeckoParent* aManager)
+ : mBackgroundManager(nullptr), mIPCOpen(true) {}
+
+UDPSocketParent::~UDPSocketParent() = default;
+
+bool UDPSocketParent::Init(nsIPrincipal* aPrincipal,
+ const nsACString& aFilter) {
+ MOZ_ASSERT_IF(mBackgroundManager, !aPrincipal);
+ // will be used once we move all UDPSocket to PBackground, or
+ // if we add in Principal checking for dom/media/webrtc/transport
+ Unused << mBackgroundManager;
+
+ mPrincipal = aPrincipal;
+
+ if (!aFilter.IsEmpty()) {
+ nsAutoCString contractId(NS_NETWORK_UDP_SOCKET_FILTER_HANDLER_PREFIX);
+ contractId.Append(aFilter);
+ nsCOMPtr<nsISocketFilterHandler> filterHandler =
+ do_GetService(contractId.get());
+ if (filterHandler) {
+ nsresult rv = filterHandler->NewFilter(getter_AddRefs(mFilter));
+ if (NS_FAILED(rv)) {
+ printf_stderr(
+ "Cannot create filter that content specified. "
+ "filter name: %s, error code: %u.",
+ aFilter.BeginReading(), static_cast<uint32_t>(rv));
+ return false;
+ }
+ } else {
+ printf_stderr(
+ "Content doesn't have a valid filter. "
+ "filter name: %s.",
+ aFilter.BeginReading());
+ return false;
+ }
+ }
+
+ return true;
+}
+
+// PUDPSocketParent methods
+
+mozilla::ipc::IPCResult UDPSocketParent::RecvBind(
+ const UDPAddressInfo& aAddressInfo, const bool& aAddressReuse,
+ const bool& aLoopback, const uint32_t& recvBufferSize,
+ const uint32_t& sendBufferSize) {
+ UDPSOCKET_LOG(("%s: %s:%u", __FUNCTION__, aAddressInfo.addr().get(),
+ aAddressInfo.port()));
+
+ if (NS_FAILED(BindInternal(aAddressInfo.addr(), aAddressInfo.port(),
+ aAddressReuse, aLoopback, recvBufferSize,
+ sendBufferSize))) {
+ FireInternalError(__LINE__);
+ return IPC_OK();
+ }
+
+ nsCOMPtr<nsINetAddr> localAddr;
+ mSocket->GetLocalAddr(getter_AddRefs(localAddr));
+
+ nsCString addr;
+ if (NS_FAILED(localAddr->GetAddress(addr))) {
+ FireInternalError(__LINE__);
+ return IPC_OK();
+ }
+
+ uint16_t port;
+ if (NS_FAILED(localAddr->GetPort(&port))) {
+ FireInternalError(__LINE__);
+ return IPC_OK();
+ }
+
+ UDPSOCKET_LOG(
+ ("%s: SendCallbackOpened: %s:%u", __FUNCTION__, addr.get(), port));
+ mAddress = {addr, port};
+ mozilla::Unused << SendCallbackOpened(UDPAddressInfo(addr, port));
+
+ return IPC_OK();
+}
+
+nsresult UDPSocketParent::BindInternal(const nsCString& aHost,
+ const uint16_t& aPort,
+ const bool& aAddressReuse,
+ const bool& aLoopback,
+ const uint32_t& recvBufferSize,
+ const uint32_t& sendBufferSize) {
+ nsresult rv;
+
+ UDPSOCKET_LOG(
+ ("%s: [this=%p] %s:%u addressReuse: %d loopback: %d recvBufferSize: "
+ "%" PRIu32 ", sendBufferSize: %" PRIu32,
+ __FUNCTION__, this, nsCString(aHost).get(), aPort, aAddressReuse,
+ aLoopback, recvBufferSize, sendBufferSize));
+
+ nsCOMPtr<nsIUDPSocket> sock =
+ do_CreateInstance("@mozilla.org/network/udp-socket;1", &rv);
+
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ if (aHost.IsEmpty()) {
+ rv = sock->Init(aPort, false, mPrincipal, aAddressReuse,
+ /* optional_argc = */ 1);
+ } else {
+ PRNetAddr prAddr;
+ PR_InitializeNetAddr(PR_IpAddrAny, aPort, &prAddr);
+ PRStatus status = PR_StringToNetAddr(aHost.BeginReading(), &prAddr);
+ if (status != PR_SUCCESS) {
+ return NS_ERROR_FAILURE;
+ }
+
+ mozilla::net::NetAddr addr(&prAddr);
+ rv = sock->InitWithAddress(&addr, mPrincipal, aAddressReuse,
+ /* optional_argc = */ 1);
+ }
+
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ nsCOMPtr<nsINetAddr> laddr;
+ rv = sock->GetLocalAddr(getter_AddRefs(laddr));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+ uint16_t family;
+ rv = laddr->GetFamily(&family);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+ if (family == nsINetAddr::FAMILY_INET) {
+ rv = sock->SetMulticastLoopback(aLoopback);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+ }
+ // TODO: once bug 1252759 is fixed query buffer first and only increase
+ if (recvBufferSize != 0) {
+ rv = sock->SetRecvBufferSize(recvBufferSize);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ UDPSOCKET_LOG(
+ ("%s: [this=%p] %s:%u failed to set recv buffer size to: %" PRIu32,
+ __FUNCTION__, this, nsCString(aHost).get(), aPort, recvBufferSize));
+ }
+ }
+ if (sendBufferSize != 0) {
+ rv = sock->SetSendBufferSize(sendBufferSize);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ UDPSOCKET_LOG(
+ ("%s: [this=%p] %s:%u failed to set send buffer size to: %" PRIu32,
+ __FUNCTION__, this, nsCString(aHost).get(), aPort, sendBufferSize));
+ }
+ }
+
+ // register listener
+ rv = sock->AsyncListen(this);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ mSocket = sock;
+
+ return NS_OK;
+}
+
+static nsCOMPtr<nsIEventTarget> GetSTSThread() {
+ nsresult rv;
+
+ nsCOMPtr<nsIEventTarget> sts_thread;
+
+ sts_thread = do_GetService(NS_SOCKETTRANSPORTSERVICE_CONTRACTID, &rv);
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+
+ return sts_thread;
+}
+
+static void CheckSTSThread() {
+ DebugOnly<nsCOMPtr<nsIEventTarget>> sts_thread = GetSTSThread();
+
+ ASSERT_ON_THREAD(sts_thread.value);
+}
+
+// Proxy the Connect() request to the STS thread, since it may block and
+// should be done there.
+mozilla::ipc::IPCResult UDPSocketParent::RecvConnect(
+ const UDPAddressInfo& aAddressInfo) {
+ nsCOMPtr<nsIEventTarget> target = GetCurrentSerialEventTarget();
+ Unused << NS_WARN_IF(NS_FAILED(GetSTSThread()->Dispatch(
+ WrapRunnable(RefPtr<UDPSocketParent>(this), &UDPSocketParent::DoConnect,
+ mSocket, target, aAddressInfo),
+ NS_DISPATCH_NORMAL)));
+ return IPC_OK();
+}
+
+void UDPSocketParent::DoSendConnectResponse(
+ const UDPAddressInfo& aAddressInfo) {
+ // can't use directly with WrapRunnable due to warnings
+ mozilla::Unused << SendCallbackConnected(aAddressInfo);
+}
+
+void UDPSocketParent::SendConnectResponse(
+ const nsCOMPtr<nsIEventTarget>& aThread,
+ const UDPAddressInfo& aAddressInfo) {
+ Unused << NS_WARN_IF(NS_FAILED(aThread->Dispatch(
+ WrapRunnable(RefPtr<UDPSocketParent>(this),
+ &UDPSocketParent::DoSendConnectResponse, aAddressInfo),
+ NS_DISPATCH_NORMAL)));
+}
+
+// Runs on STS thread
+void UDPSocketParent::DoConnect(const nsCOMPtr<nsIUDPSocket>& aSocket,
+ const nsCOMPtr<nsIEventTarget>& aReturnThread,
+ const UDPAddressInfo& aAddressInfo) {
+ UDPSOCKET_LOG(("%s: %s:%u", __FUNCTION__, aAddressInfo.addr().get(),
+ aAddressInfo.port()));
+ if (NS_FAILED(ConnectInternal(aAddressInfo.addr(), aAddressInfo.port()))) {
+ SendInternalError(aReturnThread, __LINE__);
+ return;
+ }
+ CheckSTSThread();
+
+ nsCOMPtr<nsINetAddr> localAddr;
+ aSocket->GetLocalAddr(getter_AddRefs(localAddr));
+
+ nsCString addr;
+ if (NS_FAILED(localAddr->GetAddress(addr))) {
+ SendInternalError(aReturnThread, __LINE__);
+ return;
+ }
+
+ uint16_t port;
+ if (NS_FAILED(localAddr->GetPort(&port))) {
+ SendInternalError(aReturnThread, __LINE__);
+ return;
+ }
+
+ UDPSOCKET_LOG(
+ ("%s: SendConnectResponse: %s:%u", __FUNCTION__, addr.get(), port));
+ SendConnectResponse(aReturnThread, UDPAddressInfo(addr, port));
+}
+
+nsresult UDPSocketParent::ConnectInternal(const nsCString& aHost,
+ const uint16_t& aPort) {
+ nsresult rv;
+
+ UDPSOCKET_LOG(("%s: %s:%u", __FUNCTION__, nsCString(aHost).get(), aPort));
+
+ if (!mSocket) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ PRNetAddr prAddr;
+ memset(&prAddr, 0, sizeof(prAddr));
+ PR_InitializeNetAddr(PR_IpAddrAny, aPort, &prAddr);
+ PRStatus status = PR_StringToNetAddr(aHost.BeginReading(), &prAddr);
+ if (status != PR_SUCCESS) {
+ return NS_ERROR_FAILURE;
+ }
+
+ mozilla::net::NetAddr addr(&prAddr);
+ rv = mSocket->Connect(&addr);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ return NS_OK;
+}
+
+mozilla::ipc::IPCResult UDPSocketParent::RecvOutgoingData(
+ const UDPData& aData, const UDPSocketAddr& aAddr) {
+ if (!mSocket) {
+ NS_WARNING("sending socket is closed");
+ FireInternalError(__LINE__);
+ return IPC_OK();
+ }
+
+ nsresult rv;
+ if (mFilter) {
+ if (aAddr.type() != UDPSocketAddr::TNetAddr) {
+ return IPC_OK();
+ }
+
+ // TODO, Packet filter doesn't support input stream yet.
+ if (aData.type() != UDPData::TArrayOfuint8_t) {
+ return IPC_OK();
+ }
+
+ bool allowed;
+ const nsTArray<uint8_t>& data(aData.get_ArrayOfuint8_t());
+ UDPSOCKET_LOG(("%s(%s:%d): Filtering outgoing packet", __FUNCTION__,
+ mAddress.addr().get(), mAddress.port()));
+
+ rv = mFilter->FilterPacket(&aAddr.get_NetAddr(), data.Elements(),
+ data.Length(), nsISocketFilter::SF_OUTGOING,
+ &allowed);
+
+ // Sending unallowed data, kill content.
+ if (NS_WARN_IF(NS_FAILED(rv)) || !allowed) {
+ return IPC_FAIL(this, "Content tried to send non STUN packet");
+ }
+ }
+
+ switch (aData.type()) {
+ case UDPData::TArrayOfuint8_t:
+ Send(aData.get_ArrayOfuint8_t(), aAddr);
+ break;
+ case UDPData::TIPCStream:
+ Send(aData.get_IPCStream(), aAddr);
+ break;
+ default:
+ MOZ_ASSERT(false, "Invalid data type!");
+ return IPC_OK();
+ }
+
+ return IPC_OK();
+}
+
+void UDPSocketParent::Send(const nsTArray<uint8_t>& aData,
+ const UDPSocketAddr& aAddr) {
+ nsresult rv;
+ uint32_t count;
+ switch (aAddr.type()) {
+ case UDPSocketAddr::TUDPAddressInfo: {
+ const UDPAddressInfo& addrInfo(aAddr.get_UDPAddressInfo());
+ rv = mSocket->Send(addrInfo.addr(), addrInfo.port(), aData, &count);
+ break;
+ }
+ case UDPSocketAddr::TNetAddr: {
+ const NetAddr& addr(aAddr.get_NetAddr());
+ rv = mSocket->SendWithAddress(&addr, aData, &count);
+ break;
+ }
+ default:
+ MOZ_ASSERT(false, "Invalid address type!");
+ return;
+ }
+
+ if (NS_WARN_IF(NS_FAILED(rv)) || count == 0) {
+ FireInternalError(__LINE__);
+ }
+}
+
+void UDPSocketParent::Send(const IPCStream& aStream,
+ const UDPSocketAddr& aAddr) {
+ nsCOMPtr<nsIInputStream> stream = DeserializeIPCStream(aStream);
+
+ if (NS_WARN_IF(!stream)) {
+ return;
+ }
+
+ nsresult rv;
+ switch (aAddr.type()) {
+ case UDPSocketAddr::TUDPAddressInfo: {
+ const UDPAddressInfo& addrInfo(aAddr.get_UDPAddressInfo());
+ rv = mSocket->SendBinaryStream(addrInfo.addr(), addrInfo.port(), stream);
+ break;
+ }
+ case UDPSocketAddr::TNetAddr: {
+ const NetAddr& addr(aAddr.get_NetAddr());
+ rv = mSocket->SendBinaryStreamWithAddress(&addr, stream);
+ break;
+ }
+ default:
+ MOZ_ASSERT(false, "Invalid address type!");
+ return;
+ }
+
+ if (NS_FAILED(rv)) {
+ FireInternalError(__LINE__);
+ }
+}
+
+mozilla::ipc::IPCResult UDPSocketParent::RecvJoinMulticast(
+ const nsCString& aMulticastAddress, const nsCString& aInterface) {
+ if (!mSocket) {
+ NS_WARNING("multicast socket is closed");
+ FireInternalError(__LINE__);
+ return IPC_OK();
+ }
+
+ nsresult rv = mSocket->JoinMulticast(aMulticastAddress, aInterface);
+
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ FireInternalError(__LINE__);
+ }
+
+ return IPC_OK();
+}
+
+mozilla::ipc::IPCResult UDPSocketParent::RecvLeaveMulticast(
+ const nsCString& aMulticastAddress, const nsCString& aInterface) {
+ if (!mSocket) {
+ NS_WARNING("multicast socket is closed");
+ FireInternalError(__LINE__);
+ return IPC_OK();
+ }
+
+ nsresult rv = mSocket->LeaveMulticast(aMulticastAddress, aInterface);
+
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ FireInternalError(__LINE__);
+ }
+
+ return IPC_OK();
+}
+
+mozilla::ipc::IPCResult UDPSocketParent::RecvClose() {
+ if (!mSocket) {
+ return IPC_OK();
+ }
+
+ nsresult rv = mSocket->Close();
+ mSocket = nullptr;
+
+ mozilla::Unused << NS_WARN_IF(NS_FAILED(rv));
+
+ return IPC_OK();
+}
+
+mozilla::ipc::IPCResult UDPSocketParent::RecvRequestDelete() {
+ mozilla::Unused << Send__delete__(this);
+ return IPC_OK();
+}
+
+void UDPSocketParent::ActorDestroy(ActorDestroyReason why) {
+ MOZ_ASSERT(mIPCOpen);
+ mIPCOpen = false;
+ if (mSocket) {
+ mSocket->Close();
+ }
+ mSocket = nullptr;
+}
+
+// nsIUDPSocketListener
+
+NS_IMETHODIMP
+UDPSocketParent::OnPacketReceived(nsIUDPSocket* aSocket,
+ nsIUDPMessage* aMessage) {
+ // receiving packet from remote host, forward the message content to child
+ // process
+ if (!mIPCOpen) {
+ return NS_OK;
+ }
+
+ uint16_t port;
+ nsCString ip;
+ nsCOMPtr<nsINetAddr> fromAddr;
+ aMessage->GetFromAddr(getter_AddRefs(fromAddr));
+ fromAddr->GetPort(&port);
+ fromAddr->GetAddress(ip);
+
+ nsCString data;
+ aMessage->GetData(data);
+
+ const char* buffer = data.get();
+ uint32_t len = data.Length();
+ UDPSOCKET_LOG(("%s: %s:%u, length %u", __FUNCTION__, ip.get(), port, len));
+
+ if (mFilter) {
+ bool allowed;
+ mozilla::net::NetAddr addr;
+ fromAddr->GetNetAddr(&addr);
+ UDPSOCKET_LOG(("%s(%s:%d): Filtering incoming packet", __FUNCTION__,
+ mAddress.addr().get(), mAddress.port()));
+ nsresult rv = mFilter->FilterPacket(&addr, (const uint8_t*)buffer, len,
+ nsISocketFilter::SF_INCOMING, &allowed);
+ // Receiving unallowed data, drop.
+ if (NS_WARN_IF(NS_FAILED(rv)) || !allowed) {
+ if (!allowed) {
+ UDPSOCKET_LOG(("%s: not allowed", __FUNCTION__));
+ }
+ return NS_OK;
+ }
+ }
+
+ FallibleTArray<uint8_t> fallibleArray;
+ if (!fallibleArray.InsertElementsAt(0, buffer, len, fallible)) {
+ FireInternalError(__LINE__);
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+ nsTArray<uint8_t> infallibleArray{std::move(fallibleArray)};
+
+ // compose callback
+ mozilla::Unused << SendCallbackReceivedData(UDPAddressInfo(ip, port),
+ infallibleArray);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+UDPSocketParent::OnStopListening(nsIUDPSocket* aSocket, nsresult aStatus) {
+ // underlying socket is dead, send state update to child process
+ if (mIPCOpen) {
+ mozilla::Unused << SendCallbackClosed();
+ }
+ return NS_OK;
+}
+
+void UDPSocketParent::FireInternalError(uint32_t aLineNo) {
+ if (!mIPCOpen) {
+ return;
+ }
+
+ mozilla::Unused << SendCallbackError("Internal error"_ns,
+ nsLiteralCString(__FILE__), aLineNo);
+}
+
+void UDPSocketParent::SendInternalError(const nsCOMPtr<nsIEventTarget>& aThread,
+ uint32_t aLineNo) {
+ UDPSOCKET_LOG(("SendInternalError: %u", aLineNo));
+ Unused << NS_WARN_IF(NS_FAILED(aThread->Dispatch(
+ WrapRunnable(RefPtr<UDPSocketParent>(this),
+ &UDPSocketParent::FireInternalError, aLineNo),
+ NS_DISPATCH_NORMAL)));
+}
+
+} // namespace dom
+} // namespace mozilla
diff --git a/dom/network/UDPSocketParent.h b/dom/network/UDPSocketParent.h
new file mode 100644
index 0000000000..6d4a982bae
--- /dev/null
+++ b/dom/network/UDPSocketParent.h
@@ -0,0 +1,84 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_dom_UDPSocketParent_h__
+#define mozilla_dom_UDPSocketParent_h__
+
+#include "mozilla/net/PUDPSocketParent.h"
+#include "nsCOMPtr.h"
+#include "nsIUDPSocket.h"
+#include "nsISocketFilter.h"
+#include "mozilla/dom/PermissionMessageUtils.h"
+
+namespace mozilla {
+namespace net {
+class PNeckoParent;
+} // namespace net
+
+namespace dom {
+
+class UDPSocketParent : public mozilla::net::PUDPSocketParent,
+ public nsIUDPSocketListener {
+ public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSIUDPSOCKETLISTENER
+
+ explicit UDPSocketParent(PBackgroundParent* aManager);
+ explicit UDPSocketParent(PNeckoParent* aManager);
+
+ bool Init(nsIPrincipal* aPrincipal, const nsACString& aFilter);
+
+ mozilla::ipc::IPCResult RecvBind(const UDPAddressInfo& aAddressInfo,
+ const bool& aAddressReuse,
+ const bool& aLoopback,
+ const uint32_t& recvBufferSize,
+ const uint32_t& sendBufferSize);
+ mozilla::ipc::IPCResult RecvConnect(const UDPAddressInfo& aAddressInfo);
+ void DoSendConnectResponse(const UDPAddressInfo& aAddressInfo);
+ void SendConnectResponse(const nsCOMPtr<nsIEventTarget>& aThread,
+ const UDPAddressInfo& aAddressInfo);
+ void DoConnect(const nsCOMPtr<nsIUDPSocket>& aSocket,
+ const nsCOMPtr<nsIEventTarget>& aReturnThread,
+ const UDPAddressInfo& aAddressInfo);
+
+ mozilla::ipc::IPCResult RecvOutgoingData(const UDPData& aData,
+ const UDPSocketAddr& aAddr);
+
+ mozilla::ipc::IPCResult RecvClose();
+ mozilla::ipc::IPCResult RecvRequestDelete();
+ mozilla::ipc::IPCResult RecvJoinMulticast(const nsCString& aMulticastAddress,
+ const nsCString& aInterface);
+ mozilla::ipc::IPCResult RecvLeaveMulticast(const nsCString& aMulticastAddress,
+ const nsCString& aInterface);
+
+ private:
+ virtual ~UDPSocketParent();
+
+ virtual void ActorDestroy(ActorDestroyReason why) override;
+ void Send(const nsTArray<uint8_t>& aData, const UDPSocketAddr& aAddr);
+ void Send(const IPCStream& aStream, const UDPSocketAddr& aAddr);
+ nsresult BindInternal(const nsCString& aHost, const uint16_t& aPort,
+ const bool& aAddressReuse, const bool& aLoopback,
+ const uint32_t& recvBufferSize,
+ const uint32_t& sendBufferSize);
+ nsresult ConnectInternal(const nsCString& aHost, const uint16_t& aPort);
+ void FireInternalError(uint32_t aLineNo);
+ void SendInternalError(const nsCOMPtr<nsIEventTarget>& aThread,
+ uint32_t aLineNo);
+
+ PBackgroundParent* mBackgroundManager;
+
+ bool mIPCOpen;
+ nsCOMPtr<nsIUDPSocket> mSocket;
+ nsCOMPtr<nsISocketFilter> mFilter;
+ nsCOMPtr<nsIPrincipal> mPrincipal;
+ UDPAddressInfo mAddress;
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif // !defined(mozilla_dom_UDPSocketParent_h__)
diff --git a/dom/network/interfaces/moz.build b/dom/network/interfaces/moz.build
new file mode 100644
index 0000000000..362ad4c228
--- /dev/null
+++ b/dom/network/interfaces/moz.build
@@ -0,0 +1,12 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+XPIDL_SOURCES += [
+ "nsITCPSocketCallback.idl",
+ "nsIUDPSocketChild.idl",
+]
+
+XPIDL_MODULE = "dom_network"
diff --git a/dom/network/interfaces/nsITCPSocketCallback.idl b/dom/network/interfaces/nsITCPSocketCallback.idl
new file mode 100644
index 0000000000..4a2b7a9ef2
--- /dev/null
+++ b/dom/network/interfaces/nsITCPSocketCallback.idl
@@ -0,0 +1,58 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/**
+ * MozTCPSocket exposes a TCP client and server sockets
+ * to highly privileged apps. It provides a buffered, non-blocking
+ * interface for sending. For receiving, it uses an asynchronous,
+ * event handler based interface.
+ */
+
+#include "domstubs.idl"
+
+%{C++
+#include "nsTArrayForwardDeclare.h"
+%}
+[ref] native nsUint8TArrayRef(nsTArray<uint8_t>);
+[ptr] native JSContextPtr(JSContext);
+
+
+/*
+ * This interface is implemented in TCPSocket.cpp as an internal interface
+ * for use in cross-process socket implementation.
+ * Needed to account for multiple possible types that can be provided to
+ * the socket callbacks as arguments.
+ */
+[scriptable, uuid(ac2c4b69-cb79-4767-b1ce-bcf62945cd39)]
+interface nsITCPSocketCallback : nsISupports {
+ // Limitation of TCPSocket's buffer size.
+ const unsigned long BUFFER_SIZE = 65536;
+
+ // Dispatch an "error" event at this object with the given name and type.
+ void fireErrorEvent(in AString name, in AString type, in nsresult errorCode);
+
+ // Dispatch a "data" event at this object with a string
+ void fireDataStringEvent(in AString type, in ACString data);
+
+ // Dispatch a "data" event at this object with an Array
+ void fireDataArrayEvent(in AString type, [const] in nsUint8TArrayRef data);
+
+ // Dispatch an event of the given type at this object.
+ void fireEvent(in AString type);
+
+ // Update the DOM object's readyState.
+ // @param readyState
+ // new ready state
+ void updateReadyState(in unsigned long readystate);
+
+ // Update the DOM object's bufferedAmount value with a tracking number to
+ // to allow tracking of which writes are "in-flight"
+ // @param bufferedAmount
+ // TCPSocket parent's bufferedAmount.
+ // @param trackingNumber
+ // A number to ensure the bufferedAmount is updated after data
+ // from child are sent to parent.
+ void updateBufferedAmount(in uint32_t bufferedAmount,
+ in uint32_t trackingNumber);
+};
diff --git a/dom/network/interfaces/nsIUDPSocketChild.idl b/dom/network/interfaces/nsIUDPSocketChild.idl
new file mode 100644
index 0000000000..38a8e43576
--- /dev/null
+++ b/dom/network/interfaces/nsIUDPSocketChild.idl
@@ -0,0 +1,30 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+#include "nsINetAddr.idl"
+
+interface nsIUDPSocketInternal;
+interface nsIInputStream;
+interface nsIPrincipal;
+interface nsIEventTarget;
+
+/*
+ * Internal interface for callback from chrome process
+ */
+[scriptable, builtinclass, uuid(613dd3ad-598b-4da9-ad63-bbda50c20098)]
+interface nsIUDPSocketInternal : nsISupports
+{
+ // callback while socket is opened. localPort and localAddress is ready until this time.
+ void callListenerOpened();
+ // callback while socket is connected.
+ void callListenerConnected();
+ // callback while socket is closed.
+ void callListenerClosed();
+ // callback while incoming packet is received.
+ void callListenerReceivedData(in AUTF8String host, in unsigned short port,
+ in Array<uint8_t> data);
+ // callback while any error happened.
+ void callListenerError(in AUTF8String message, in AUTF8String filename, in uint32_t lineNumber);
+};
diff --git a/dom/network/moz.build b/dom/network/moz.build
new file mode 100644
index 0000000000..c99c91cc1c
--- /dev/null
+++ b/dom/network/moz.build
@@ -0,0 +1,55 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+with Files("**"):
+ BUG_COMPONENT = ("Core", "Networking")
+
+DIRS += ["interfaces"]
+
+MOCHITEST_CHROME_MANIFESTS += ["tests/chrome.ini"]
+MOCHITEST_MANIFESTS += ["tests/mochitest.ini"]
+
+EXPORTS.mozilla.dom += [
+ "TCPServerSocket.h",
+ "TCPSocket.h",
+ "UDPSocket.h",
+]
+
+EXPORTS.mozilla.dom.network += [
+ "Connection.h",
+ "Constants.h",
+ "TCPServerSocketChild.h",
+ "TCPServerSocketParent.h",
+ "TCPSocketChild.h",
+ "TCPSocketParent.h",
+ "UDPSocketChild.h",
+ "UDPSocketParent.h",
+]
+
+UNIFIED_SOURCES += [
+ "Connection.cpp",
+ "ConnectionMainThread.cpp",
+ "ConnectionWorker.cpp",
+ "TCPServerSocket.cpp",
+ "TCPServerSocketChild.cpp",
+ "TCPServerSocketParent.cpp",
+ "TCPSocket.cpp",
+ "TCPSocketChild.cpp",
+ "TCPSocketParent.cpp",
+ "UDPSocket.cpp",
+ "UDPSocketChild.cpp",
+ "UDPSocketParent.cpp",
+]
+
+IPDL_SOURCES += [
+ "PTCPServerSocket.ipdl",
+ "PTCPSocket.ipdl",
+ "PUDPSocket.ipdl",
+]
+
+include("/ipc/chromium/chromium-config.mozbuild")
+
+FINAL_LIBRARY = "xul"
diff --git a/dom/network/tests/chrome.ini b/dom/network/tests/chrome.ini
new file mode 100644
index 0000000000..a0b6c2ae3f
--- /dev/null
+++ b/dom/network/tests/chrome.ini
@@ -0,0 +1,11 @@
+[DEFAULT]
+support-files =
+ tcpsocket_test.sys.mjs
+ test_tcpsocket_client_and_server_basics.js
+ file_postMessage_opener.html
+ file_udpsocket_iframe.html
+
+[test_tcpsocket_jsm.html]
+[test_tcpsocket_client_and_server_basics.html]
+[test_tcpsocket_legacy.html]
+[test_udpsocket.html]
diff --git a/dom/network/tests/file_postMessage_opener.html b/dom/network/tests/file_postMessage_opener.html
new file mode 100644
index 0000000000..87359e25c2
--- /dev/null
+++ b/dom/network/tests/file_postMessage_opener.html
@@ -0,0 +1,11 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+ <meta charset="UTF-8">
+ <script>
+ opener.postMessage("hello", "*");
+ </script>
+</head>
+<body>
+</body>
+</html>
diff --git a/dom/network/tests/file_udpsocket_iframe.html b/dom/network/tests/file_udpsocket_iframe.html
new file mode 100644
index 0000000000..d48e03ee2b
--- /dev/null
+++ b/dom/network/tests/file_udpsocket_iframe.html
@@ -0,0 +1,22 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test UDPSocket BFCache</title>
+</head>
+<body>
+<script type="application/javascript">
+'use strict';
+window.addEventListener('load', function() {
+ let remotePort = parseInt(window.location.search.substring(1), 10);
+ let socket = new UDPSocket();
+ socket.addEventListener('message', function () {
+ socket.send('fail', '127.0.0.1', remotePort);
+ });
+
+ socket.opened.then(function() {
+ socket.send('ready', '127.0.0.1', remotePort);
+ });
+}, {once: true});
+</script>
+</body>
+</html>
diff --git a/dom/network/tests/mochitest.ini b/dom/network/tests/mochitest.ini
new file mode 100644
index 0000000000..3fdb2ebd0a
--- /dev/null
+++ b/dom/network/tests/mochitest.ini
@@ -0,0 +1,9 @@
+[DEFAULT]
+support-files =
+ worker_network_basics.js
+
+[test_network_basics.html]
+skip-if = toolkit == 'android'
+[test_network_basics_worker.html]
+skip-if = toolkit == 'android'
+[test_tcpsocket_not_exposed_to_content.html]
diff --git a/dom/network/tests/tcpsocket_test.sys.mjs b/dom/network/tests/tcpsocket_test.sys.mjs
new file mode 100644
index 0000000000..7a51aced41
--- /dev/null
+++ b/dom/network/tests/tcpsocket_test.sys.mjs
@@ -0,0 +1,12 @@
+export var createSocket = function (host, port, options) {
+ return new TCPSocket(host, port, options);
+};
+
+export var createServer = function (port, options, backlog) {
+ return new TCPServerSocket(port, options, backlog);
+};
+
+// See test_tcpsocket_client_and_server_basics.html's version for rationale.
+export var socketCompartmentInstanceOfArrayBuffer = function (obj) {
+ return obj instanceof ArrayBuffer;
+};
diff --git a/dom/network/tests/test_network_basics.html b/dom/network/tests/test_network_basics.html
new file mode 100644
index 0000000000..e657af916d
--- /dev/null
+++ b/dom/network/tests/test_network_basics.html
@@ -0,0 +1,38 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test for Network API</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<p id="display"></p>
+<div id="content" style="display: none">
+</div>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Network Information API **/
+function test() {
+ ok('connection' in navigator, "navigator.connection should exist");
+
+ ok(navigator.connection, "navigator.connection returns an object");
+
+ ok(navigator.connection instanceof EventTarget,
+ "navigator.connection is a EventTarget object");
+
+ ok('type' in navigator.connection,
+ "type should be a Connection attribute");
+ is(navigator.connection.type, "none",
+ "By default connection.type equals to none");
+
+ SimpleTest.finish();
+}
+
+SimpleTest.waitForExplicitFinish();
+SpecialPowers.pushPrefEnv({'set': [["dom.netinfo.enabled", true]]}, test);
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/network/tests/test_network_basics_worker.html b/dom/network/tests/test_network_basics_worker.html
new file mode 100644
index 0000000000..5763ee361b
--- /dev/null
+++ b/dom/network/tests/test_network_basics_worker.html
@@ -0,0 +1,35 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test for Network in workers API</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<p id="display"></p>
+<div id="content" style="display: none">
+</div>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Network Information API **/
+function test() {
+ let w = new Worker('worker_network_basics.js');
+ w.onmessage = function(e) {
+ if (e.data.type == 'status') {
+ ok(e.data.status, e.data.msg);
+ } else if (e.data.type == 'finish') {
+ SimpleTest.finish();
+ } else {
+ ok(false, "Unknown message type");
+ }
+ }
+}
+
+SimpleTest.waitForExplicitFinish();
+SpecialPowers.pushPrefEnv({'set': [["dom.netinfo.enabled", true]]}, test);
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/network/tests/test_tcpsocket_client_and_server_basics.html b/dom/network/tests/test_tcpsocket_client_and_server_basics.html
new file mode 100644
index 0000000000..d7e40a4328
--- /dev/null
+++ b/dom/network/tests/test_tcpsocket_client_and_server_basics.html
@@ -0,0 +1,46 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+Core tests for TCPSocket and TCPServerSocket that replace their previous
+separate xpcshell incarnations. This migration and cleanup occurred as part
+of bug 1084245 in order to get coverage of the tests from content.
+
+https://bugzilla.mozilla.org/show_bug.cgi?id=1084245
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 1084245</title>
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"/>
+ <script type="application/javascript">
+ function createServer(port, options, backlog) {
+ return new TCPServerSocket(port, options, backlog);
+ }
+
+ function createSocket(host, port, options) {
+ return new TCPSocket(host, port, options);
+ }
+
+ // In the JSM case, ArrayBuffers will be created in the compartment of the
+ // JSM with different globals than the
+ // test_tcpsocket_client_and_server_basics.js test logic sees, so we (and
+ // tcpsocket_test.sys.mjs) need to do something. To avoid complexity relating
+ // to wrappers and the varying nuances of the module scope and global scope
+ // in JSM's (they differ on B2G), we hardcode ArrayBuffer rather than taking
+ // a string that we look up, etc.
+ function socketCompartmentInstanceOfArrayBuffer(obj) {
+ return obj instanceof ArrayBuffer;
+ }
+ </script>
+ <script type="application/javascript" src="test_tcpsocket_client_and_server_basics.js"></script>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1084245">Mozilla Bug 1084245</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+</pre>
+</body>
+</html>
diff --git a/dom/network/tests/test_tcpsocket_client_and_server_basics.js b/dom/network/tests/test_tcpsocket_client_and_server_basics.js
new file mode 100644
index 0000000000..ad653838be
--- /dev/null
+++ b/dom/network/tests/test_tcpsocket_client_and_server_basics.js
@@ -0,0 +1,617 @@
+"use strict";
+
+// These are defined in test_tcpsocket_client_and_server_basics.html
+/* global createServer, createSocket, socketCompartmentInstanceOfArrayBuffer */
+
+// Bug 788960 and later bug 1329245 have taught us that attempting to connect to
+// a port that is not listening or is no longer listening fails to consistently
+// result in the error (or any) event we expect on Darwin/OSX/"OS X".
+const isOSX = Services.appinfo.OS === "Darwin";
+const testConnectingToNonListeningPort = !isOSX;
+
+const SERVER_BACKLOG = -1;
+
+const SOCKET_EVENTS = ["open", "data", "drain", "error", "close"];
+
+function concatUint8Arrays(a, b) {
+ let newArr = new Uint8Array(a.length + b.length);
+ newArr.set(a, 0);
+ newArr.set(b, a.length);
+ return newArr;
+}
+
+function assertUint8ArraysEqual(a, b, comparingWhat) {
+ if (a.length !== b.length) {
+ ok(
+ false,
+ comparingWhat +
+ " arrays do not have the same length; " +
+ a.length +
+ " versus " +
+ b.length
+ );
+ return;
+ }
+ for (let i = 0; i < a.length; i++) {
+ if (a[i] !== b[i]) {
+ ok(
+ false,
+ comparingWhat +
+ " arrays differ at index " +
+ i +
+ a[i] +
+ " versus " +
+ b[i]
+ );
+ return;
+ }
+ }
+ ok(true, comparingWhat + " arrays were equivalent.");
+}
+
+/**
+ * Helper method to add event listeners to a socket and provide two Promise-returning
+ * helpers (see below for docs on them). This *must* be called during the turn of
+ * the event loop where TCPSocket's constructor is called or the onconnect method is being
+ * invoked.
+ */
+function listenForEventsOnSocket(socket, socketType) {
+ let wantDataLength = null;
+ let wantDataAndClose = false;
+ let pendingResolve = null;
+ let receivedEvents = [];
+ let receivedData = null;
+ let handleGenericEvent = function (event) {
+ dump("(" + socketType + " event: " + event.type + ")\n");
+ if (pendingResolve && wantDataLength === null) {
+ pendingResolve(event);
+ pendingResolve = null;
+ } else {
+ receivedEvents.push(event);
+ }
+ };
+
+ socket.onopen = handleGenericEvent;
+ socket.ondrain = handleGenericEvent;
+ socket.onerror = handleGenericEvent;
+ socket.onclose = function (event) {
+ if (!wantDataAndClose) {
+ handleGenericEvent(event);
+ } else if (pendingResolve) {
+ dump("(" + socketType + " event: close)\n");
+ pendingResolve(receivedData);
+ pendingResolve = null;
+ wantDataAndClose = false;
+ }
+ };
+ socket.ondata = function (event) {
+ dump(
+ "(" +
+ socketType +
+ " event: " +
+ event.type +
+ " length: " +
+ event.data.byteLength +
+ ")\n"
+ );
+ ok(
+ socketCompartmentInstanceOfArrayBuffer(event.data),
+ "payload is ArrayBuffer"
+ );
+ var arr = new Uint8Array(event.data);
+ if (receivedData === null) {
+ receivedData = arr;
+ } else {
+ receivedData = concatUint8Arrays(receivedData, arr);
+ }
+ if (wantDataLength !== null && receivedData.length >= wantDataLength) {
+ pendingResolve(receivedData);
+ pendingResolve = null;
+ receivedData = null;
+ wantDataLength = null;
+ }
+ };
+
+ return {
+ /**
+ * Return a Promise that will be resolved with the next (non-data) event
+ * received by the socket. If there are queued events, the Promise will
+ * be immediately resolved (but you won't see that until a future turn of
+ * the event loop).
+ */
+ waitForEvent() {
+ if (pendingResolve) {
+ throw new Error("only one wait allowed at a time.");
+ }
+
+ if (receivedEvents.length) {
+ return Promise.resolve(receivedEvents.shift());
+ }
+
+ dump("(" + socketType + " waiting for event)\n");
+ return new Promise(function (resolve, reject) {
+ pendingResolve = resolve;
+ });
+ },
+ /**
+ * Return a Promise that will be resolved with a Uint8Array of at least the
+ * given length. We buffer / accumulate received data until we have enough
+ * data. Data is buffered even before you call this method, so be sure to
+ * explicitly wait for any and all data sent by the other side.
+ */
+ waitForDataWithAtLeastLength(length) {
+ if (pendingResolve) {
+ throw new Error("only one wait allowed at a time.");
+ }
+ if (receivedData && receivedData.length >= length) {
+ let promise = Promise.resolve(receivedData);
+ receivedData = null;
+ return promise;
+ }
+ dump("(" + socketType + " waiting for " + length + " bytes)\n");
+ return new Promise(function (resolve, reject) {
+ pendingResolve = resolve;
+ wantDataLength = length;
+ });
+ },
+ waitForAnyDataAndClose() {
+ if (pendingResolve) {
+ throw new Error("only one wait allowed at a time.");
+ }
+
+ return new Promise(function (resolve, reject) {
+ pendingResolve = resolve;
+ // we may receive no data before getting close, in which case we want to
+ // return an empty array
+ receivedData = new Uint8Array();
+ wantDataAndClose = true;
+ });
+ },
+ };
+}
+
+/**
+ * Return a promise that is resolved when the server receives a connection. The
+ * promise is resolved with { socket, queue } where `queue` is the result of
+ * calling listenForEventsOnSocket(socket). This must be done because we need
+ * to add the event listener during the connection.
+ */
+function waitForConnection(listeningServer) {
+ return new Promise(function (resolve, reject) {
+ // Because of the event model of sockets, we can't use the
+ // listenForEventsOnSocket mechanism; we need to hook up listeners during
+ // the connect event.
+ listeningServer.onconnect = function (event) {
+ // Clobber the listener to get upset if it receives any more connections
+ // after this.
+ listeningServer.onconnect = function () {
+ ok(false, "Received a connection when not expecting one.");
+ };
+ ok(true, "Listening server accepted socket");
+ resolve({
+ socket: event.socket,
+ queue: listenForEventsOnSocket(event.socket, "server"),
+ });
+ };
+ });
+}
+
+function defer() {
+ var deferred = {};
+ deferred.promise = new Promise(function (resolve, reject) {
+ deferred.resolve = resolve;
+ deferred.reject = reject;
+ });
+ return deferred;
+}
+
+async function test_basics() {
+ // See bug 903830; in e10s mode we never get to find out the localPort if we
+ // let it pick a free port by choosing 0. This is the same port the xpcshell
+ // test was using.
+ let serverPort = 8085;
+
+ // - Start up a listening socket.
+ let listeningServer = createServer(
+ serverPort,
+ { binaryType: "arraybuffer" },
+ SERVER_BACKLOG
+ );
+
+ let connectedPromise = waitForConnection(listeningServer);
+
+ // -- Open a connection to the server
+ let clientSocket = createSocket("127.0.0.1", serverPort, {
+ binaryType: "arraybuffer",
+ });
+ let clientQueue = listenForEventsOnSocket(clientSocket, "client");
+
+ // (the client connects)
+ is((await clientQueue.waitForEvent()).type, "open", "got open event");
+ is(clientSocket.readyState, "open", "client readyState is open");
+
+ // (the server connected)
+ let { socket: serverSocket, queue: serverQueue } = await connectedPromise;
+ is(serverSocket.readyState, "open", "server readyState is open");
+
+ // -- Simple send / receive
+ // - Send data from client to server
+ // (But not so much we cross the drain threshold.)
+ let smallUint8Array = new Uint8Array(256);
+ for (let i = 0; i < smallUint8Array.length; i++) {
+ smallUint8Array[i] = i;
+ }
+ is(
+ clientSocket.send(smallUint8Array.buffer, 0, smallUint8Array.length),
+ true,
+ "Client sending less than 64k, buffer should not be full."
+ );
+
+ let serverReceived = await serverQueue.waitForDataWithAtLeastLength(256);
+ assertUint8ArraysEqual(
+ serverReceived,
+ smallUint8Array,
+ "Server received/client sent"
+ );
+
+ // - Send data from server to client
+ // (But not so much we cross the drain threshold.)
+ is(
+ serverSocket.send(smallUint8Array.buffer, 0, smallUint8Array.length),
+ true,
+ "Server sending less than 64k, buffer should not be full."
+ );
+
+ let clientReceived = await clientQueue.waitForDataWithAtLeastLength(256);
+ assertUint8ArraysEqual(
+ clientReceived,
+ smallUint8Array,
+ "Client received/server sent"
+ );
+
+ // -- Perform sending multiple times with different buffer slices
+ // - Send data from client to server
+ // (But not so much we cross the drain threshold.)
+ is(
+ clientSocket.send(smallUint8Array.buffer, 0, 7),
+ true,
+ "Client sending less than 64k, buffer should not be full."
+ );
+ is(
+ clientSocket.send(smallUint8Array.buffer, 7, smallUint8Array.length - 7),
+ true,
+ "Client sending less than 64k, buffer should not be full."
+ );
+
+ serverReceived = await serverQueue.waitForDataWithAtLeastLength(256);
+ assertUint8ArraysEqual(
+ serverReceived,
+ smallUint8Array,
+ "Server received/client sent"
+ );
+
+ // - Send data from server to client
+ // (But not so much we cross the drain threshold.)
+ is(
+ serverSocket.send(smallUint8Array.buffer, 0, 7),
+ true,
+ "Server sending less than 64k, buffer should not be full."
+ );
+ is(
+ serverSocket.send(smallUint8Array.buffer, 7, smallUint8Array.length - 7),
+ true,
+ "Server sending less than 64k, buffer should not be full."
+ );
+
+ clientReceived = await clientQueue.waitForDataWithAtLeastLength(256);
+ assertUint8ArraysEqual(
+ clientReceived,
+ smallUint8Array,
+ "Client received/server sent"
+ );
+
+ // -- Send "big" data in both directions
+ // (Enough to cross the buffering/drain threshold; 64KiB)
+ let bigUint8Array = new Uint8Array(65536 + 3);
+ for (let i = 0; i < bigUint8Array.length; i++) {
+ bigUint8Array[i] = i % 256;
+ }
+ // This can be anything from 1 to 65536. The idea is spliting and sending
+ // bigUint8Array in two chunks should trigger ondrain the same as sending
+ // bigUint8Array in one chunk.
+ let lengthOfChunk1 = 65536;
+ is(
+ clientSocket.send(bigUint8Array.buffer, 0, lengthOfChunk1),
+ true,
+ "Client sending chunk1 should not result in the buffer being full."
+ );
+ // Do this twice so we have confidence that the 'drain' event machinery
+ // doesn't break after the first use. The first time we send bigUint8Array in
+ // two chunks, the second time we send bigUint8Array in one chunk.
+ for (let iSend = 0; iSend < 2; iSend++) {
+ // - Send "big" data from the client to the server
+ let offset = iSend == 0 ? lengthOfChunk1 : 0;
+ is(
+ clientSocket.send(bigUint8Array.buffer, offset, bigUint8Array.length),
+ false,
+ "Client sending more than 64k should result in the buffer being full."
+ );
+ is(
+ (await clientQueue.waitForEvent()).type,
+ "drain",
+ "The drain event should fire after a large send that indicated full."
+ );
+
+ serverReceived = await serverQueue.waitForDataWithAtLeastLength(
+ bigUint8Array.length
+ );
+ assertUint8ArraysEqual(
+ serverReceived,
+ bigUint8Array,
+ "server received/client sent"
+ );
+
+ if (iSend == 0) {
+ is(
+ serverSocket.send(bigUint8Array.buffer, 0, lengthOfChunk1),
+ true,
+ "Server sending chunk1 should not result in the buffer being full."
+ );
+ }
+ // - Send "big" data from the server to the client
+ is(
+ serverSocket.send(bigUint8Array.buffer, offset, bigUint8Array.length),
+ false,
+ "Server sending more than 64k should result in the buffer being full."
+ );
+ is(
+ (await serverQueue.waitForEvent()).type,
+ "drain",
+ "The drain event should fire after a large send that indicated full."
+ );
+
+ clientReceived = await clientQueue.waitForDataWithAtLeastLength(
+ bigUint8Array.length
+ );
+ assertUint8ArraysEqual(
+ clientReceived,
+ bigUint8Array,
+ "client received/server sent"
+ );
+ }
+
+ // -- Server closes the connection
+ serverSocket.close();
+ is(
+ serverSocket.readyState,
+ "closing",
+ "readyState should be closing immediately after calling close"
+ );
+
+ is(
+ (await clientQueue.waitForEvent()).type,
+ "close",
+ "The client should get a close event when the server closes."
+ );
+ is(
+ clientSocket.readyState,
+ "closed",
+ "client readyState should be closed after close event"
+ );
+ is(
+ (await serverQueue.waitForEvent()).type,
+ "close",
+ "The server should get a close event when it closes itself."
+ );
+ is(
+ serverSocket.readyState,
+ "closed",
+ "server readyState should be closed after close event"
+ );
+
+ // -- Re-establish connection
+ connectedPromise = waitForConnection(listeningServer);
+ clientSocket = createSocket("127.0.0.1", serverPort, {
+ binaryType: "arraybuffer",
+ });
+ clientQueue = listenForEventsOnSocket(clientSocket, "client");
+ is((await clientQueue.waitForEvent()).type, "open", "got open event");
+
+ let connectedResult = await connectedPromise;
+ // destructuring assignment is not yet ES6 compliant, must manually unpack
+ serverSocket = connectedResult.socket;
+ serverQueue = connectedResult.queue;
+
+ // -- Client closes the connection
+ clientSocket.close();
+ is(
+ clientSocket.readyState,
+ "closing",
+ "client readyState should be losing immediately after calling close"
+ );
+
+ is(
+ (await clientQueue.waitForEvent()).type,
+ "close",
+ "The client should get a close event when it closes itself."
+ );
+ is(
+ clientSocket.readyState,
+ "closed",
+ "client readyState should be closed after the close event is received"
+ );
+ is(
+ (await serverQueue.waitForEvent()).type,
+ "close",
+ "The server should get a close event when the client closes."
+ );
+ is(
+ serverSocket.readyState,
+ "closed",
+ "server readyState should be closed after the close event is received"
+ );
+
+ // -- Re-establish connection
+ connectedPromise = waitForConnection(listeningServer);
+ clientSocket = createSocket("127.0.0.1", serverPort, {
+ binaryType: "arraybuffer",
+ });
+ clientQueue = listenForEventsOnSocket(clientSocket, "client");
+ is((await clientQueue.waitForEvent()).type, "open", "got open event");
+
+ connectedResult = await connectedPromise;
+ // destructuring assignment is not yet ES6 compliant, must manually unpack
+ serverSocket = connectedResult.socket;
+ serverQueue = connectedResult.queue;
+
+ // -- Call close after enqueueing a lot of data, make sure it goes through.
+ // We'll have the client send and close.
+ is(
+ clientSocket.send(bigUint8Array.buffer, 0, bigUint8Array.length),
+ false,
+ "Client sending more than 64k should result in the buffer being full."
+ );
+ clientSocket.close();
+ // The drain will still fire
+ is(
+ (await clientQueue.waitForEvent()).type,
+ "drain",
+ "The drain event should fire after a large send that returned true."
+ );
+ // Then we'll get a close
+ is(
+ (await clientQueue.waitForEvent()).type,
+ "close",
+ "The close event should fire after the drain event."
+ );
+
+ // The server will get its data
+ serverReceived = await serverQueue.waitForDataWithAtLeastLength(
+ bigUint8Array.length
+ );
+ assertUint8ArraysEqual(
+ serverReceived,
+ bigUint8Array,
+ "server received/client sent"
+ );
+ // And a close.
+ is(
+ (await serverQueue.waitForEvent()).type,
+ "close",
+ "The drain event should fire after a large send that returned true."
+ );
+
+ // -- Re-establish connection
+ connectedPromise = waitForConnection(listeningServer);
+ clientSocket = createSocket("127.0.0.1", serverPort, {
+ binaryType: "string",
+ });
+ clientQueue = listenForEventsOnSocket(clientSocket, "client");
+ is((await clientQueue.waitForEvent()).type, "open", "got open event");
+
+ connectedResult = await connectedPromise;
+ // destructuring assignment is not yet ES6 compliant, must manually unpack
+ serverSocket = connectedResult.socket;
+ serverQueue = connectedResult.queue;
+
+ // -- Attempt to send non-string data.
+ // Restore the original behavior by replacing toString with
+ // Object.prototype.toString. (bug 1121938)
+ bigUint8Array.toString = Object.prototype.toString;
+ is(
+ clientSocket.send(bigUint8Array),
+ true,
+ "Client sending a large non-string should only send a small string."
+ );
+ clientSocket.close();
+ // The server will get its data
+ serverReceived = await serverQueue.waitForDataWithAtLeastLength(
+ bigUint8Array.toString().length
+ );
+ // Then we'll get a close
+ is(
+ (await clientQueue.waitForEvent()).type,
+ "close",
+ "The close event should fire after the drain event."
+ );
+
+ // -- Re-establish connection (Test for Close Immediately)
+ connectedPromise = waitForConnection(listeningServer);
+ clientSocket = createSocket("127.0.0.1", serverPort, {
+ binaryType: "arraybuffer",
+ });
+ clientQueue = listenForEventsOnSocket(clientSocket, "client");
+ is((await clientQueue.waitForEvent()).type, "open", "got open event");
+
+ connectedResult = await connectedPromise;
+ // destructuring assignment is not yet ES6 compliant, must manually unpack
+ serverSocket = connectedResult.socket;
+ serverQueue = connectedResult.queue;
+
+ // -- Attempt to send two non-string data.
+ is(
+ clientSocket.send(bigUint8Array.buffer, 0, bigUint8Array.length),
+ false,
+ "Server sending more than 64k should result in the buffer being full."
+ );
+ is(
+ clientSocket.send(bigUint8Array.buffer, 0, bigUint8Array.length),
+ false,
+ "Server sending more than 64k should result in the buffer being full."
+ );
+ clientSocket.closeImmediately();
+
+ serverReceived = await serverQueue.waitForAnyDataAndClose();
+
+ is(
+ serverReceived.length < 2 * bigUint8Array.length,
+ true,
+ "Received array length less than sent array length"
+ );
+
+ // -- Close the listening server (and try to connect)
+ // We want to verify that the server actually closes / stops listening when
+ // we tell it to.
+ listeningServer.close();
+
+ // (We don't run this check on OS X where it's flakey; see definition up top.)
+ if (testConnectingToNonListeningPort) {
+ // - try and connect, get an error
+ clientSocket = createSocket("127.0.0.1", serverPort, {
+ binaryType: "arraybuffer",
+ });
+ clientQueue = listenForEventsOnSocket(clientSocket, "client");
+ is((await clientQueue.waitForEvent()).type, "error", "fail to connect");
+ is(
+ clientSocket.readyState,
+ "closed",
+ "client readyState should be closed after the failure to connect"
+ );
+ }
+}
+
+add_task(test_basics);
+
+/**
+ * Test that TCPSocket works with ipv6 address.
+ */
+add_task(async function test_ipv6() {
+ const { HttpServer } = ChromeUtils.import(
+ "resource://testing-common/httpd.js"
+ );
+ let deferred = defer();
+ let httpServer = new HttpServer();
+ httpServer.start_ipv6(-1);
+
+ let clientSocket = new TCPSocket("::1", httpServer.identity.primaryPort);
+ clientSocket.onopen = () => {
+ ok(true, "Connect to ipv6 address succeeded");
+ deferred.resolve();
+ };
+ clientSocket.onerror = () => {
+ ok(false, "Connect to ipv6 address failed");
+ deferred.reject();
+ };
+ await deferred.promise;
+ await httpServer.stop();
+});
diff --git a/dom/network/tests/test_tcpsocket_jsm.html b/dom/network/tests/test_tcpsocket_jsm.html
new file mode 100644
index 0000000000..026d71ed14
--- /dev/null
+++ b/dom/network/tests/test_tcpsocket_jsm.html
@@ -0,0 +1,30 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for 1207090</title>
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"/>
+ <script type="application/javascript">
+ // These are used in the loaded script file.
+ // eslint-disable-next-line no-unused-vars
+ let {
+ createSocket,
+ createServer,
+ socketCompartmentInstanceOfArrayBuffer
+ } = ChromeUtils.importESModule("chrome://mochitests/content/chrome/dom/network/tests/tcpsocket_test.sys.mjs");
+ </script>
+ <script type="application/javascript" src="test_tcpsocket_client_and_server_basics.js"></script>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1207090">Mozilla Bug 1207090</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+</div>
+<pre id="test">
+</pre>
+<div id="container"></div>
+</body>
+</html>
diff --git a/dom/network/tests/test_tcpsocket_legacy.html b/dom/network/tests/test_tcpsocket_legacy.html
new file mode 100644
index 0000000000..f5c76fb37f
--- /dev/null
+++ b/dom/network/tests/test_tcpsocket_legacy.html
@@ -0,0 +1,57 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+Test of legacy navigator interface for opening TCPSocket/TCPServerSocket.
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 885982</title>
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1084245">Mozilla Bug 1084245</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+</pre>
+<script>
+ SimpleTest.waitForExplicitFinish();
+
+ function runTest() {
+ // See bug 903830; in e10s mode we never get to find out the localPort if we
+ // let it pick a free port by choosing 0. This is the same port the xpcshell
+ // test was using.
+ var serverPort = 8085;
+
+ var listeningServer = navigator.mozTCPSocket.listen(serverPort,
+ { binaryType: 'arraybuffer' },
+ -1);
+ listeningServer.onconnect = function(ev) {
+ ok(true, "got server connect");
+ listeningServer.close();
+ listeningServer = null;
+ ev.socket.close()
+ }
+
+ var clientSocket = navigator.mozTCPSocket.open('127.0.0.1', serverPort,
+ { binaryType: 'arraybuffer' });
+ clientSocket.onopen = function() { ok(true, "got client open"); }
+ clientSocket.onclose = function() {
+ ok(true, "got client close");
+ clientSocket.close();
+ clientSocket = null;
+ setTimeout(function() {
+ // This just helps the test harness clean up quickly
+ SpecialPowers.forceCC();
+ SpecialPowers.forceGC();
+ SimpleTest.finish();
+ }, 0);
+ }
+ }
+ runTest(); // we used to invoke this as part of a moot pref-setting callback
+</script>
+</body>
+</html>
diff --git a/dom/network/tests/test_tcpsocket_not_exposed_to_content.html b/dom/network/tests/test_tcpsocket_not_exposed_to_content.html
new file mode 100644
index 0000000000..4154eeca5c
--- /dev/null
+++ b/dom/network/tests/test_tcpsocket_not_exposed_to_content.html
@@ -0,0 +1,25 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test to ensure TCPSocket permission enabled and no tcp-socket perm does not allow open</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<p id="display"></p>
+<div id="content" style="display: none">
+</div>
+<pre id="test">
+<script type="application/javascript">
+/**
+ * TCPSocket and its legacy mozTCPSocket variant should never be exposed to
+ * content.
+ */
+
+is('TCPSocket' in this, false, "TCPSocket should not be accessible to content");
+is('TCPServerSocket' in this, false, "TCPServerSocket should not be accessible to content");
+is('mozTCPSocket' in navigator, false, "mozTCPSocket should not be accessible to content");
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/network/tests/test_udpsocket.html b/dom/network/tests/test_udpsocket.html
new file mode 100644
index 0000000000..1ca42f2432
--- /dev/null
+++ b/dom/network/tests/test_udpsocket.html
@@ -0,0 +1,403 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test UDPSocket API</title>
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<p id="display"></p>
+<div id="content" style="display: none">
+</div>
+<iframe id="iframe"></iframe>
+<pre id="test">
+<script type="application/javascript">
+'use strict';
+SimpleTest.waitForExplicitFinish();
+SimpleTest.requestFlakyTimeout("untriaged");
+
+const HELLO_WORLD = 'hlo wrld. ';
+const DATA_ARRAY = [0, 255, 254, 0, 1, 2, 3, 0, 255, 255, 254, 0];
+const DATA_ARRAY_BUFFER = new ArrayBuffer(DATA_ARRAY.length);
+const TYPED_DATA_ARRAY = new Uint8Array(DATA_ARRAY_BUFFER);
+const BIG_ARRAY = new Array(4096);
+const BIG_ARRAY_BUFFER = new ArrayBuffer(BIG_ARRAY.length);
+const BIG_TYPED_ARRAY = new Uint8Array(BIG_ARRAY_BUFFER);
+
+for (let i = 0; i < BIG_ARRAY.length; i++) {
+ BIG_ARRAY[i] = Math.floor(Math.random() * 256);
+}
+
+TYPED_DATA_ARRAY.set(DATA_ARRAY);
+BIG_TYPED_ARRAY.set(BIG_ARRAY);
+
+function is_same_buffer(recv_data, expect_data) {
+ let recv_dataview = new Uint8Array(recv_data);
+ let expected_dataview = new Uint8Array(expect_data);
+
+ if (recv_dataview.length !== expected_dataview.length) {
+ return false;
+ }
+
+ for (let i = 0; i < recv_dataview.length; i++) {
+ if (recv_dataview[i] != expected_dataview[i]) {
+ info('discover byte differenct at ' + i);
+ return false;
+ }
+ }
+ return true;
+}
+
+function testOpen() {
+ info('test for creating an UDP Socket');
+ let socket = new UDPSocket();
+ is(socket.localPort, null, 'expect no local port before socket opened');
+ is(socket.localAddress, null, 'expect no local address before socket opened');
+ is(socket.remotePort, null, 'expected no default remote port');
+ is(socket.remoteAddress, null, 'expected no default remote address');
+ is(socket.readyState, 'opening', 'expected ready state = opening');
+ is(socket.loopback, false, 'expected no loopback');
+ is(socket.addressReuse, true, 'expect to reuse address');
+
+ return socket.opened.then(function() {
+ ok(true, 'expect openedPromise to be resolved after successful socket binding');
+ ok(!(socket.localPort === 0), 'expect allocated a local port');
+ is(socket.localAddress, '0.0.0.0', 'expect assigned to default address');
+ is(socket.readyState, 'open', 'expected ready state = open');
+
+ return socket;
+ });
+}
+
+function testSendString(socket) {
+ info('test for sending string data');
+
+ socket.send(HELLO_WORLD, '127.0.0.1', socket.localPort);
+
+ return new Promise(function(resolve, reject) {
+ socket.addEventListener('message', function(msg) {
+ let recvData= String.fromCharCode.apply(null, new Uint8Array(msg.data));
+ is(msg.remotePort, socket.localPort, 'expected packet from ' + socket.localPort);
+ is(recvData, HELLO_WORLD, 'expected same string data');
+ resolve(socket);
+ }, {once: true});
+ });
+}
+
+function testSendArrayBuffer(socket) {
+ info('test for sending ArrayBuffer');
+
+ socket.send(DATA_ARRAY_BUFFER, '127.0.0.1', socket.localPort);
+
+ return new Promise(function(resolve, reject) {
+ socket.addEventListener('message', function(msg) {
+ is(msg.remotePort, socket.localPort, 'expected packet from ' + socket.localPort);
+ ok(is_same_buffer(msg.data, DATA_ARRAY_BUFFER), 'expected same buffer data');
+ resolve(socket);
+ }, {once: true});
+ });
+}
+
+function testSendArrayBufferView(socket) {
+ info('test for sending ArrayBufferView');
+
+ socket.send(TYPED_DATA_ARRAY, '127.0.0.1', socket.localPort);
+
+ return new Promise(function(resolve, reject) {
+ socket.addEventListener('message', function(msg) {
+ is(msg.remotePort, socket.localPort, 'expected packet from ' + socket.localPort);
+ ok(is_same_buffer(msg.data, TYPED_DATA_ARRAY), 'expected same buffer data');
+ resolve(socket);
+ }, {once: true});
+ });
+}
+
+function testSendBlob(socket) {
+ info('test for sending Blob');
+
+ let blob = new Blob([HELLO_WORLD], {type : 'text/plain'});
+ socket.send(blob, '127.0.0.1', socket.localPort);
+
+ return new Promise(function(resolve, reject) {
+ socket.addEventListener('message', function(msg) {
+ let recvData= String.fromCharCode.apply(null, new Uint8Array(msg.data));
+ is(msg.remotePort, socket.localPort, 'expected packet from ' + socket.localPort);
+ is(recvData, HELLO_WORLD, 'expected same string data');
+ resolve(socket);
+ }, {once: true});
+ });
+}
+
+function testSendBigArray(socket) {
+ info('test for sending Big ArrayBuffer');
+
+ socket.send(BIG_TYPED_ARRAY, '127.0.0.1', socket.localPort);
+
+ return new Promise(function(resolve, reject) {
+ let byteReceived = 0;
+ socket.addEventListener('message', function recv_callback(msg) {
+ let byteBegin = byteReceived;
+ byteReceived += msg.data.byteLength;
+ is(msg.remotePort, socket.localPort, 'expected packet from ' + socket.localPort);
+ ok(is_same_buffer(msg.data, BIG_TYPED_ARRAY.subarray(byteBegin, byteReceived)), 'expected same buffer data [' + byteBegin+ '-' + byteReceived + ']');
+ if (byteReceived >= BIG_TYPED_ARRAY.length) {
+ socket.removeEventListener('message', recv_callback);
+ resolve(socket);
+ }
+ });
+ });
+}
+
+function testSendBigBlob(socket) {
+ info('test for sending Big Blob');
+
+ let blob = new Blob([BIG_TYPED_ARRAY]);
+ socket.send(blob, '127.0.0.1', socket.localPort);
+
+ return new Promise(function(resolve, reject) {
+ let byteReceived = 0;
+ socket.addEventListener('message', function recv_callback(msg) {
+ let byteBegin = byteReceived;
+ byteReceived += msg.data.byteLength;
+ is(msg.remotePort, socket.localPort, 'expected packet from ' + socket.localPort);
+ ok(is_same_buffer(msg.data, BIG_TYPED_ARRAY.subarray(byteBegin, byteReceived)), 'expected same buffer data [' + byteBegin+ '-' + byteReceived + ']');
+ if (byteReceived >= BIG_TYPED_ARRAY.length) {
+ socket.removeEventListener('message', recv_callback);
+ resolve(socket);
+ }
+ });
+ });
+}
+
+function testUDPOptions(socket) {
+ info('test for UDP init options');
+
+ let remoteSocket = new UDPSocket({addressReuse: false,
+ loopback: true,
+ localAddress: '127.0.0.1',
+ remoteAddress: '127.0.0.1',
+ remotePort: socket.localPort});
+ is(remoteSocket.localAddress, '127.0.0.1', 'expected local address');
+ is(remoteSocket.remoteAddress, '127.0.0.1', 'expected remote address');
+ is(remoteSocket.remotePort, socket.localPort, 'expected remote port');
+ is(remoteSocket.addressReuse, false, 'expected address not reusable');
+ is(remoteSocket.loopback, true, 'expected loopback mode is on');
+
+ return remoteSocket.opened.then(function() {
+ remoteSocket.send(HELLO_WORLD);
+ return new Promise(function(resolve, reject) {
+ socket.addEventListener('message', function(msg) {
+ let recvData= String.fromCharCode.apply(null, new Uint8Array(msg.data));
+ is(msg.remotePort, remoteSocket.localPort, 'expected packet from ' + remoteSocket.localPort);
+ is(recvData, HELLO_WORLD, 'expected same string data');
+ resolve(socket);
+ }, {once: true});
+ });
+ });
+}
+
+function testClose(socket) {
+ info('test for close');
+
+ socket.close();
+ is(socket.readyState, 'closed', 'expect ready state to be "closed"');
+ try {
+ socket.send(HELLO_WORLD, '127.0.0.1', socket.localPort);
+ ok(false, 'unexpect to send successfully');
+ } catch (e) {
+ ok(true, 'expected send fail after socket closed');
+ }
+
+ return socket.closed.then(function() {
+ ok(true, 'expected closedPromise is resolved after socket.close()');
+ });
+}
+
+function testMulticast() {
+ info('test for multicast');
+
+ let socket = new UDPSocket({loopback: true});
+
+ const MCAST_ADDRESS = '224.0.0.255';
+ socket.joinMulticastGroup(MCAST_ADDRESS);
+
+ return socket.opened.then(function() {
+ socket.send(HELLO_WORLD, MCAST_ADDRESS, socket.localPort);
+
+ return new Promise(function(resolve, reject) {
+ socket.addEventListener('message', function(msg) {
+ let recvData= String.fromCharCode.apply(null, new Uint8Array(msg.data));
+ is(msg.remotePort, socket.localPort, 'expected packet from ' + socket.localPort);
+ is(recvData, HELLO_WORLD, 'expected same string data');
+ socket.leaveMulticastGroup(MCAST_ADDRESS);
+ resolve();
+ }, {once: true});
+ });
+ });
+}
+
+function testInvalidUDPOptions() {
+ info('test for invalid UDPOptions');
+ try {
+ new UDPSocket({localAddress: 'not-a-valid-address'});
+ ok(false, 'should not create an UDPSocket with an invalid localAddress');
+ } catch (e) {
+ is(e.name, 'InvalidAccessError', 'expected InvalidAccessError will be thrown if localAddress is not a valid IPv4/6 address');
+ }
+
+ try {
+ new UDPSocket({localPort: 0});
+ ok(false, 'should not create an UDPSocket with an invalid localPort');
+ } catch (e) {
+ is(e.name, 'InvalidAccessError', 'expected InvalidAccessError will be thrown if localPort is not a valid port number');
+ }
+
+ try {
+ new UDPSocket({remotePort: 0});
+ ok(false, 'should not create an UDPSocket with an invalid remotePort');
+ } catch (e) {
+ is(e.name, 'InvalidAccessError', 'expected InvalidAccessError will be thrown if localPort is not a valid port number');
+ }
+}
+
+function testOpenFailed() {
+ info('test for falied on open');
+
+ //according to RFC5737, address block 192.0.2.0/24 should not be used in both local and public contexts
+ let socket = new UDPSocket({localAddress: '192.0.2.0'});
+
+ return socket.opened.then(function() {
+ ok(false, 'should not resolve openedPromise while fail to bind socket');
+ socket.close();
+ }).catch(function(reason) {
+ is(reason.name, 'NetworkError', 'expected openedPromise to be rejected while fail to bind socket');
+ });
+}
+
+function testSendBeforeOpen() {
+ info('test for send before open');
+
+ let socket = new UDPSocket();
+
+ try {
+ socket.send(HELLO_WORLD, '127.0.0.1', 9);
+ ok(false, 'unexpect to send successfully');
+ } catch (e) {
+ ok(true, 'expected send fail before openedPromise is resolved');
+ }
+
+ return socket.opened.then(function() {
+ socket.close();
+ });
+}
+
+function testCloseBeforeOpened() {
+ info('test for close socket before opened');
+
+ let socket = new UDPSocket();
+ socket.opened.then(function() {
+ ok(false, 'should not resolve openedPromise if it has already been closed');
+ }).catch(function(reason) {
+ is(reason.name, 'AbortError', 'expected openedPromise to be rejected while socket is closed during opening');
+ });
+
+ return socket.close().then(function() {
+ ok(true, 'expected closedPromise to be resolved');
+ }).then(socket.opened);
+}
+
+function testOpenWithoutClose() {
+ info('test for open without close');
+
+ let closed = [];
+ for (let i = 0; i < 50; i++) {
+ let socket = new UDPSocket();
+ closed.push(socket.closed);
+ }
+
+ SpecialPowers.gc();
+ info('all unrefereced socket should be closed right after GC');
+
+ return Promise.all(closed);
+}
+
+function awaitEvent(target, event) {
+ return new Promise(resolve => {
+ target.addEventListener(event, resolve, { once: true });
+ });
+}
+
+async function testBFCache() {
+ info('test for bfcache behavior');
+
+ let socket = new UDPSocket();
+
+ await socket.opened;
+
+ let win = window.open(`file_udpsocket_iframe.html?${socket.localPort}`);
+
+ let msg = await awaitEvent(socket, "message");
+
+ win.location = "file_postMessage_opener.html";
+ await awaitEvent(window, "message");
+
+ socket.send(HELLO_WORLD, '127.0.0.1', msg.remotePort);
+
+ await new Promise(resolve => {
+ function recv_again_callback(message) {
+ socket.removeEventListener('message', recv_again_callback);
+ ok(false, 'should not receive packet after page unload');
+ }
+
+ socket.addEventListener('message', recv_again_callback);
+
+ setTimeout(function() {
+ socket.removeEventListener('message', recv_again_callback);
+ socket.close();
+ resolve();
+ }, 5000);
+ });
+
+ win.close();
+}
+
+function runTest() {
+ testOpen()
+ .then(testSendString)
+ .then(testSendArrayBuffer)
+ .then(testSendArrayBufferView)
+ .then(testSendBlob)
+ .then(testSendBigArray)
+ .then(testSendBigBlob)
+ .then(testUDPOptions)
+ .then(testClose)
+ .then(testMulticast)
+ .then(testInvalidUDPOptions)
+ .then(testOpenFailed)
+ .then(testSendBeforeOpen)
+ .then(testCloseBeforeOpened)
+ .then(testOpenWithoutClose)
+ .then(testBFCache)
+ .then(function() {
+ info('test finished');
+ SimpleTest.finish();
+ })
+ .catch(function(err) {
+ ok(false, 'test failed due to: ' + err);
+ SimpleTest.finish();
+ });
+}
+
+window.addEventListener('load', function () {
+ SpecialPowers.pushPrefEnv({
+ 'set': [
+ ['dom.udpsocket.enabled', true],
+ ['browser.sessionhistory.max_total_viewers', 10]
+ ]
+ }, runTest);
+});
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/network/tests/worker_network_basics.js b/dom/network/tests/worker_network_basics.js
new file mode 100644
index 0000000000..25e8c5dd8e
--- /dev/null
+++ b/dom/network/tests/worker_network_basics.js
@@ -0,0 +1,28 @@
+function ok(a, msg) {
+ postMessage({ type: "status", status: !!a, msg });
+}
+
+function is(a, b, msg) {
+ ok(a === b, msg);
+}
+
+function finish() {
+ postMessage({ type: "finish" });
+}
+
+ok("connection" in navigator, "navigator.connection should exist");
+
+ok(navigator.connection, "navigator.connection returns an object");
+
+ok(
+ navigator.connection instanceof EventTarget,
+ "navigator.connection is a EventTarget object"
+);
+
+ok("type" in navigator.connection, "type should be a Connection attribute");
+is(
+ navigator.connection.type,
+ "none",
+ "By default connection.type equals to none"
+);
+finish();