summaryrefslogtreecommitdiff
path: root/dom/network
diff options
context:
space:
mode:
Diffstat (limited to 'dom/network')
-rw-r--r--dom/network/Connection.cpp95
-rw-r--r--dom/network/Connection.h (renamed from dom/network/src/Connection.h)48
-rw-r--r--dom/network/Constants.h (renamed from dom/network/src/Constants.h)5
-rw-r--r--dom/network/NetUtils.cpp196
-rw-r--r--dom/network/NetUtils.h73
-rw-r--r--dom/network/NetworkStatsDB.jsm1263
-rw-r--r--dom/network/NetworkStatsManager.js462
-rw-r--r--dom/network/NetworkStatsManager.manifest15
-rw-r--r--dom/network/NetworkStatsService.jsm1166
-rw-r--r--dom/network/NetworkStatsServiceProxy.js90
-rw-r--r--dom/network/NetworkStatsServiceProxy.manifest2
-rw-r--r--dom/network/PTCPServerSocket.ipdl (renamed from dom/network/src/PTCPSocket.ipdl)29
-rw-r--r--dom/network/PTCPSocket.ipdl76
-rw-r--r--dom/network/PUDPSocket.ipdl65
-rw-r--r--dom/network/TCPServerSocket.js187
-rw-r--r--dom/network/TCPServerSocketChild.cpp115
-rw-r--r--dom/network/TCPServerSocketChild.h52
-rw-r--r--dom/network/TCPServerSocketParent.cpp175
-rw-r--r--dom/network/TCPServerSocketParent.h52
-rw-r--r--dom/network/TCPSocket.js (renamed from dom/network/src/TCPSocket.js)383
-rw-r--r--dom/network/TCPSocket.manifest (renamed from dom/network/src/TCPSocket.manifest)4
-rw-r--r--dom/network/TCPSocketChild.cpp (renamed from dom/network/src/TCPSocketChild.cpp)164
-rw-r--r--dom/network/TCPSocketChild.h (renamed from dom/network/src/TCPSocketChild.h)22
-rw-r--r--dom/network/TCPSocketParent.cpp410
-rw-r--r--dom/network/TCPSocketParent.h75
-rw-r--r--dom/network/TCPSocketParentIntermediary.js117
-rw-r--r--dom/network/Types.h (renamed from dom/network/src/Types.h)0
-rw-r--r--dom/network/UDPSocket.cpp723
-rw-r--r--dom/network/UDPSocket.h222
-rw-r--r--dom/network/UDPSocketChild.cpp259
-rw-r--r--dom/network/UDPSocketChild.h64
-rw-r--r--dom/network/UDPSocketParent.cpp429
-rw-r--r--dom/network/UDPSocketParent.h65
-rw-r--r--dom/network/interfaces/Makefile.in15
-rw-r--r--dom/network/interfaces/moz.build23
-rw-r--r--dom/network/interfaces/nsIDOMCFStateChangeEvent.idl74
-rw-r--r--dom/network/interfaces/nsIDOMConnection.idl16
-rw-r--r--dom/network/interfaces/nsIDOMDataErrorEvent.idl21
-rw-r--r--dom/network/interfaces/nsIDOMMobileConnection.idl533
-rw-r--r--dom/network/interfaces/nsIDOMNetworkStats.idl35
-rw-r--r--dom/network/interfaces/nsIDOMNetworkStatsManager.idl92
-rw-r--r--dom/network/interfaces/nsIDOMTCPServerSocket.idl92
-rw-r--r--dom/network/interfaces/nsIDOMTCPSocket.idl107
-rw-r--r--dom/network/interfaces/nsIDOMUSSDReceivedEvent.idl24
-rw-r--r--dom/network/interfaces/nsIMobileConnectionProvider.idl74
-rw-r--r--dom/network/interfaces/nsIMozNavigatorNetwork.idl (renamed from dom/network/interfaces/nsIDOMNavigatorNetwork.idl)8
-rw-r--r--dom/network/interfaces/nsINavigatorMobileConnection.idl13
-rw-r--r--dom/network/interfaces/nsINetworkStatsServiceProxy.idl59
-rw-r--r--dom/network/interfaces/nsITCPServerSocketChild.idl39
-rw-r--r--dom/network/interfaces/nsITCPServerSocketParent.idl42
-rw-r--r--dom/network/interfaces/nsITCPSocketChild.idl41
-rw-r--r--dom/network/interfaces/nsITCPSocketParent.idl80
-rw-r--r--dom/network/interfaces/nsIUDPSocketChild.idl67
-rw-r--r--dom/network/moz.build82
-rw-r--r--dom/network/src/Connection.cpp117
-rw-r--r--dom/network/src/Makefile.in44
-rw-r--r--dom/network/src/MobileConnection.cpp512
-rw-r--r--dom/network/src/MobileConnection.h57
-rw-r--r--dom/network/src/NetworkStatsDB.jsm389
-rw-r--r--dom/network/src/NetworkStatsManager.js246
-rw-r--r--dom/network/src/NetworkStatsManager.manifest9
-rw-r--r--dom/network/src/NetworkStatsService.jsm394
-rw-r--r--dom/network/src/TCPSocketParent.cpp217
-rw-r--r--dom/network/src/TCPSocketParent.h48
-rw-r--r--dom/network/src/TCPSocketParentIntermediary.js55
-rw-r--r--dom/network/src/Utils.cpp21
-rw-r--r--dom/network/src/Utils.h24
-rw-r--r--dom/network/src/ipdl.mk7
-rw-r--r--dom/network/src/moz.build41
-rw-r--r--dom/network/tests/Makefile.in30
-rw-r--r--dom/network/tests/add_task.js83
-rw-r--r--dom/network/tests/file_udpsocket_iframe.html23
-rw-r--r--dom/network/tests/marionette/manifest.ini17
-rw-r--r--dom/network/tests/marionette/test_call_barring_get_option.js31
-rw-r--r--dom/network/tests/marionette/test_call_barring_set_error.js65
-rw-r--r--dom/network/tests/marionette/test_mobile_data_location.js119
-rw-r--r--dom/network/tests/marionette/test_mobile_data_state.js121
-rw-r--r--dom/network/tests/marionette/test_mobile_iccinfo.js85
-rw-r--r--dom/network/tests/marionette/test_mobile_mmi.js76
-rw-r--r--dom/network/tests/marionette/test_mobile_networks.js240
-rw-r--r--dom/network/tests/marionette/test_mobile_operator_names.js209
-rw-r--r--dom/network/tests/marionette/test_mobile_preferred_network_type.js65
-rw-r--r--dom/network/tests/marionette/test_mobile_voice_state.js153
-rw-r--r--dom/network/tests/mochitest.ini26
-rw-r--r--dom/network/tests/moz.build12
-rw-r--r--dom/network/tests/test_network_basics.html36
-rw-r--r--dom/network/tests/test_networkstats_alarms.html194
-rw-r--r--dom/network/tests/test_networkstats_basics.html363
-rw-r--r--dom/network/tests/test_networkstats_disabled.html9
-rw-r--r--dom/network/tests/test_networkstats_enabled_no_perm.html43
-rw-r--r--dom/network/tests/test_networkstats_enabled_perm.html25
-rw-r--r--dom/network/tests/test_tcpsocket_client_and_server_basics.html27
-rw-r--r--dom/network/tests/test_tcpsocket_client_and_server_basics.js363
-rw-r--r--dom/network/tests/test_tcpsocket_enabled_no_perm.html22
-rw-r--r--dom/network/tests/test_tcpsocket_enabled_with_perm.html15
-rw-r--r--dom/network/tests/test_udpsocket.html408
-rw-r--r--dom/network/tests/unit/test_multisend.js152
-rw-r--r--dom/network/tests/unit/test_tcpsocket.js285
-rw-r--r--dom/network/tests/unit/xpcshell.ini2
-rw-r--r--dom/network/tests/unit_ipc/xpcshell.ini1
-rw-r--r--dom/network/tests/unit_stats/test_networkstats_db.js920
-rw-r--r--dom/network/tests/unit_stats/test_networkstats_service.js254
-rw-r--r--dom/network/tests/unit_stats/test_networkstats_service_proxy.js240
-rw-r--r--dom/network/tests/unit_stats/xpcshell.ini1
104 files changed, 10352 insertions, 5184 deletions
diff --git a/dom/network/Connection.cpp b/dom/network/Connection.cpp
new file mode 100644
index 000000000..744912296
--- /dev/null
+++ b/dom/network/Connection.cpp
@@ -0,0 +1,95 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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 "mozilla/dom/network/Connection.h"
+#include "nsIDOMClassInfo.h"
+#include "mozilla/Preferences.h"
+#include "Constants.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 NS_LITERAL_STRING("typechange")
+
+namespace mozilla {
+namespace dom {
+namespace network {
+
+NS_IMPL_QUERY_INTERFACE_INHERITED(Connection, DOMEventTargetHelper,
+ nsINetworkProperties)
+
+// Don't use |Connection| alone, since that confuses nsTraceRefcnt since
+// we're not the only class with that name.
+NS_IMPL_ADDREF_INHERITED(dom::network::Connection, DOMEventTargetHelper)
+NS_IMPL_RELEASE_INHERITED(dom::network::Connection, DOMEventTargetHelper)
+
+Connection::Connection(nsPIDOMWindow* aWindow)
+ : DOMEventTargetHelper(aWindow)
+ , mType(static_cast<ConnectionType>(kDefaultType))
+ , mIsWifi(kDefaultIsWifi)
+ , mDHCPGateway(kDefaultDHCPGateway)
+{
+ hal::RegisterNetworkObserver(this);
+
+ hal::NetworkInformation networkInfo;
+ hal::GetCurrentNetworkInformation(&networkInfo);
+
+ UpdateFromNetworkInfo(networkInfo);
+}
+
+void
+Connection::Shutdown()
+{
+ hal::UnregisterNetworkObserver(this);
+}
+
+NS_IMETHODIMP
+Connection::GetIsWifi(bool *aIsWifi)
+{
+ *aIsWifi = mIsWifi;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+Connection::GetDhcpGateway(uint32_t *aGW)
+{
+ *aGW = mDHCPGateway;
+ return NS_OK;
+}
+
+void
+Connection::UpdateFromNetworkInfo(const hal::NetworkInformation& aNetworkInfo)
+{
+ mType = static_cast<ConnectionType>(aNetworkInfo.type());
+ mIsWifi = aNetworkInfo.isWifi();
+ mDHCPGateway = aNetworkInfo.dhcpGateway();
+}
+
+void
+Connection::Notify(const hal::NetworkInformation& aNetworkInfo)
+{
+ ConnectionType previousType = mType;
+
+ UpdateFromNetworkInfo(aNetworkInfo);
+
+ if (previousType == mType) {
+ return;
+ }
+
+ DispatchTrustedEvent(CHANGE_EVENT_NAME);
+}
+
+JSObject*
+Connection::WrapObject(JSContext* aCx)
+{
+ return NetworkInformationBinding::Wrap(aCx, this);
+}
+
+} // namespace network
+} // namespace dom
+} // namespace mozilla
diff --git a/dom/network/src/Connection.h b/dom/network/Connection.h
index 015994d13..c061137bd 100644
--- a/dom/network/src/Connection.h
+++ b/dom/network/Connection.h
@@ -6,11 +6,12 @@
#ifndef mozilla_dom_network_Connection_h
#define mozilla_dom_network_Connection_h
-#include "nsIDOMConnection.h"
-#include "nsDOMEventTargetHelper.h"
-#include "nsCycleCollectionParticipant.h"
-#include "mozilla/Observer.h"
#include "Types.h"
+#include "mozilla/DOMEventTargetHelper.h"
+#include "mozilla/Observer.h"
+#include "mozilla/dom/NetworkInformationBinding.h"
+#include "nsCycleCollectionParticipant.h"
+#include "nsINetworkProperties.h"
namespace mozilla {
@@ -21,25 +22,34 @@ class NetworkInformation;
namespace dom {
namespace network {
-class Connection : public nsDOMEventTargetHelper
- , public nsIDOMMozConnection
- , public NetworkObserver
+class Connection final : public DOMEventTargetHelper
+ , public NetworkObserver
+ , public nsINetworkProperties
{
public:
NS_DECL_ISUPPORTS_INHERITED
- NS_DECL_NSIDOMMOZCONNECTION
+ NS_DECL_NSINETWORKPROPERTIES
- NS_REALLY_FORWARD_NSIDOMEVENTTARGET(nsDOMEventTargetHelper)
+ NS_REALLY_FORWARD_NSIDOMEVENTTARGET(DOMEventTargetHelper)
- Connection();
+ explicit Connection(nsPIDOMWindow *aWindow);
- void Init(nsPIDOMWindow *aWindow);
void Shutdown();
// For IObserver
- void Notify(const hal::NetworkInformation& aNetworkInfo);
+ void Notify(const hal::NetworkInformation& aNetworkInfo) override;
+
+ // WebIDL
+
+ virtual JSObject* WrapObject(JSContext* aCx) override;
+
+ ConnectionType Type() const { return mType; }
+
+ IMPL_EVENT_HANDLER(typechange)
private:
+ ~Connection() {}
+
/**
* Update the connection information stored in the object using a
* NetworkInformation object.
@@ -47,17 +57,19 @@ private:
void UpdateFromNetworkInfo(const hal::NetworkInformation& aNetworkInfo);
/**
- * If the connection is of a type that can be metered.
+ * The type of current connection.
*/
- bool mCanBeMetered;
+ ConnectionType mType;
/**
- * The connection bandwidth.
+ * If the connection is WIFI
*/
- double mBandwidth;
+ bool mIsWifi;
- static const char* sMeteredPrefName;
- static const bool sMeteredDefaultValue;
+ /**
+ * DHCP Gateway information for IPV4, in network byte order. 0 if unassigned.
+ */
+ uint32_t mDHCPGateway;
};
} // namespace network
diff --git a/dom/network/src/Constants.h b/dom/network/Constants.h
index 1c98f5ebb..68c2a1dee 100644
--- a/dom/network/src/Constants.h
+++ b/dom/network/Constants.h
@@ -13,8 +13,9 @@ namespace mozilla {
namespace dom {
namespace network {
- static const double kDefaultBandwidth = -1.0;
- static const bool kDefaultCanBeMetered = false;
+ static const uint32_t kDefaultType = 5; // ConnectionType::None
+ static const bool kDefaultIsWifi = false;
+ static const uint32_t kDefaultDHCPGateway = 0;
} // namespace network
} // namespace dom
diff --git a/dom/network/NetUtils.cpp b/dom/network/NetUtils.cpp
new file mode 100644
index 000000000..32a331b5d
--- /dev/null
+++ b/dom/network/NetUtils.cpp
@@ -0,0 +1,196 @@
+/* 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 "NetUtils.h"
+#include <dlfcn.h>
+#include <errno.h>
+#include <cutils/properties.h>
+#include "prinit.h"
+#include "mozilla/Assertions.h"
+#include "nsDebug.h"
+
+static void* sNetUtilsLib;
+static PRCallOnceType sInitNetUtilsLib;
+
+static PRStatus
+InitNetUtilsLib()
+{
+ sNetUtilsLib = dlopen("/system/lib/libnetutils.so", RTLD_LAZY);
+ // We might fail to open the hardware lib. That's OK.
+ return PR_SUCCESS;
+}
+
+static void*
+GetNetUtilsLibHandle()
+{
+ PR_CallOnce(&sInitNetUtilsLib, InitNetUtilsLib);
+ return sNetUtilsLib;
+}
+
+// static
+void*
+NetUtils::GetSharedLibrary()
+{
+ void* netLib = GetNetUtilsLibHandle();
+ if (!netLib) {
+ NS_WARNING("No /system/lib/libnetutils.so");
+ }
+ return netLib;
+}
+
+// static
+int32_t
+NetUtils::SdkVersion()
+{
+ char propVersion[PROPERTY_VALUE_MAX];
+ property_get("ro.build.version.sdk", propVersion, "0");
+ int32_t version = strtol(propVersion, nullptr, 10);
+ return version;
+}
+
+DEFINE_DLFUNC(ifc_enable, int32_t, const char*)
+DEFINE_DLFUNC(ifc_disable, int32_t, const char*)
+DEFINE_DLFUNC(ifc_configure, int32_t, const char*, in_addr_t, uint32_t,
+ in_addr_t, in_addr_t, in_addr_t)
+DEFINE_DLFUNC(ifc_reset_connections, int32_t, const char*, const int32_t)
+DEFINE_DLFUNC(ifc_set_default_route, int32_t, const char*, in_addr_t)
+DEFINE_DLFUNC(ifc_add_route, int32_t, const char*, const char*, uint32_t, const char*)
+DEFINE_DLFUNC(ifc_remove_route, int32_t, const char*, const char*, uint32_t, const char*)
+DEFINE_DLFUNC(ifc_remove_host_routes, int32_t, const char*)
+DEFINE_DLFUNC(ifc_remove_default_route, int32_t, const char*)
+DEFINE_DLFUNC(dhcp_stop, int32_t, const char*)
+
+NetUtils::NetUtils()
+{
+}
+
+int32_t NetUtils::do_ifc_enable(const char *ifname)
+{
+ USE_DLFUNC(ifc_enable)
+ return ifc_enable(ifname);
+}
+
+int32_t NetUtils::do_ifc_disable(const char *ifname)
+{
+ USE_DLFUNC(ifc_disable)
+ return ifc_disable(ifname);
+}
+
+int32_t NetUtils::do_ifc_configure(const char *ifname,
+ in_addr_t address,
+ uint32_t prefixLength,
+ in_addr_t gateway,
+ in_addr_t dns1,
+ in_addr_t dns2)
+{
+ USE_DLFUNC(ifc_configure)
+ int32_t ret = ifc_configure(ifname, address, prefixLength, gateway, dns1, dns2);
+ return ret;
+}
+
+int32_t NetUtils::do_ifc_reset_connections(const char *ifname,
+ const int32_t resetMask)
+{
+ USE_DLFUNC(ifc_reset_connections)
+ return ifc_reset_connections(ifname, resetMask);
+}
+
+int32_t NetUtils::do_ifc_set_default_route(const char *ifname,
+ in_addr_t gateway)
+{
+ USE_DLFUNC(ifc_set_default_route)
+ return ifc_set_default_route(ifname, gateway);
+}
+
+int32_t NetUtils::do_ifc_add_route(const char *ifname,
+ const char *dst,
+ uint32_t prefixLength,
+ const char *gateway)
+{
+ USE_DLFUNC(ifc_add_route)
+ return ifc_add_route(ifname, dst, prefixLength, gateway);
+}
+
+int32_t NetUtils::do_ifc_remove_route(const char *ifname,
+ const char *dst,
+ uint32_t prefixLength,
+ const char *gateway)
+{
+ USE_DLFUNC(ifc_remove_route)
+ return ifc_remove_route(ifname, dst, prefixLength, gateway);
+}
+
+int32_t NetUtils::do_ifc_remove_host_routes(const char *ifname)
+{
+ USE_DLFUNC(ifc_remove_host_routes)
+ return ifc_remove_host_routes(ifname);
+}
+
+int32_t NetUtils::do_ifc_remove_default_route(const char *ifname)
+{
+ USE_DLFUNC(ifc_remove_default_route)
+ return ifc_remove_default_route(ifname);
+}
+
+int32_t NetUtils::do_dhcp_stop(const char *ifname)
+{
+ USE_DLFUNC(dhcp_stop)
+ return dhcp_stop(ifname);
+}
+
+int32_t NetUtils::do_dhcp_do_request(const char *ifname,
+ char *ipaddr,
+ char *gateway,
+ uint32_t *prefixLength,
+ char *dns1,
+ char *dns2,
+ char *server,
+ uint32_t *lease,
+ char* vendorinfo)
+{
+ int32_t ret = -1;
+ uint32_t sdkVersion = SdkVersion();
+
+ if (sdkVersion == 15) {
+ // ICS
+ // http://androidxref.com/4.0.4/xref/system/core/libnetutils/dhcp_utils.c#149
+ DEFINE_DLFUNC(dhcp_do_request, int32_t, const char*, char*, char*, uint32_t*, char*, char*, char*, uint32_t*)
+ USE_DLFUNC(dhcp_do_request)
+ vendorinfo[0] = '\0';
+
+ ret = dhcp_do_request(ifname, ipaddr, gateway, prefixLength, dns1, dns2,
+ server, lease);
+ } else if (sdkVersion == 16 || sdkVersion == 17) {
+ // JB 4.1 and 4.2
+ // http://androidxref.com/4.1.2/xref/system/core/libnetutils/dhcp_utils.c#175
+ // http://androidxref.com/4.2.2_r1/xref/system/core/include/netutils/dhcp.h#26
+ DEFINE_DLFUNC(dhcp_do_request, int32_t, const char*, char*, char*, uint32_t*, char*, char*, char*, uint32_t*, char*)
+ USE_DLFUNC(dhcp_do_request)
+ ret = dhcp_do_request(ifname, ipaddr, gateway, prefixLength, dns1, dns2,
+ server, lease, vendorinfo);
+ } else if (sdkVersion == 18) {
+ // JB 4.3
+ // http://androidxref.com/4.3_r2.1/xref/system/core/libnetutils/dhcp_utils.c#181
+ DEFINE_DLFUNC(dhcp_do_request, int32_t, const char*, char*, char*, uint32_t*, char**, char*, uint32_t*, char*, char*)
+ USE_DLFUNC(dhcp_do_request)
+ char *dns[3] = {dns1, dns2, nullptr};
+ char domains[PROPERTY_VALUE_MAX];
+ ret = dhcp_do_request(ifname, ipaddr, gateway, prefixLength, dns,
+ server, lease, vendorinfo, domains);
+ } else if (sdkVersion >= 19) {
+ // KitKat 4.4.X
+ // http://androidxref.com/4.4_r1/xref/system/core/libnetutils/dhcp_utils.c#18
+ // Lollipop 5.0
+ //http://androidxref.com/5.0.0_r2/xref/system/core/libnetutils/dhcp_utils.c#186
+ DEFINE_DLFUNC(dhcp_do_request, int32_t, const char*, char*, char*, uint32_t*, char**, char*, uint32_t*, char*, char*, char*)
+ USE_DLFUNC(dhcp_do_request)
+ char *dns[3] = {dns1, dns2, nullptr};
+ char domains[PROPERTY_VALUE_MAX];
+ char mtu[PROPERTY_VALUE_MAX];
+ ret = dhcp_do_request(ifname, ipaddr, gateway, prefixLength, dns, server, lease, vendorinfo, domains, mtu);
+ } else {
+ NS_WARNING("Unable to perform do_dhcp_request: unsupported sdk version!");
+ }
+ return ret;
+}
diff --git a/dom/network/NetUtils.h b/dom/network/NetUtils.h
new file mode 100644
index 000000000..6ae2894ab
--- /dev/null
+++ b/dom/network/NetUtils.h
@@ -0,0 +1,73 @@
+/* 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/. */
+
+/**
+ * Abstraction on top of the network support from libnetutils that we
+ * use to set up network connections.
+ */
+
+#ifndef NetUtils_h
+#define NetUtils_h
+
+#include "arpa/inet.h"
+
+// Copied from ifc.h
+#define RESET_IPV4_ADDRESSES 0x01
+#define RESET_IPV6_ADDRESSES 0x02
+#define RESET_ALL_ADDRESSES (RESET_IPV4_ADDRESSES | RESET_IPV6_ADDRESSES)
+
+// Implements netutils functions. No need for an abstract class here since we
+// only have a one sdk specific method (dhcp_do_request)
+class NetUtils
+{
+public:
+ static void* GetSharedLibrary();
+
+ NetUtils();
+
+ int32_t do_ifc_enable(const char *ifname);
+ int32_t do_ifc_disable(const char *ifname);
+ int32_t do_ifc_configure(const char *ifname,
+ in_addr_t address,
+ uint32_t prefixLength,
+ in_addr_t gateway,
+ in_addr_t dns1,
+ in_addr_t dns2);
+ int32_t do_ifc_reset_connections(const char *ifname, const int32_t resetMask);
+ int32_t do_ifc_set_default_route(const char *ifname, in_addr_t gateway);
+ int32_t do_ifc_add_route(const char *ifname,
+ const char *dst,
+ uint32_t prefixLength,
+ const char *gateway);
+ int32_t do_ifc_remove_route(const char *ifname,
+ const char *dst,
+ uint32_t prefixLength,
+ const char *gateway);
+ int32_t do_ifc_remove_host_routes(const char *ifname);
+ int32_t do_ifc_remove_default_route(const char *ifname);
+ int32_t do_dhcp_stop(const char *ifname);
+ int32_t do_dhcp_do_request(const char *ifname,
+ char *ipaddr,
+ char *gateway,
+ uint32_t *prefixLength,
+ char *dns1,
+ char *dns2,
+ char *server,
+ uint32_t *lease,
+ char* vendorinfo);
+
+ static int32_t SdkVersion();
+};
+
+// Defines a function type with the right arguments and return type.
+#define DEFINE_DLFUNC(name, ret, args...) typedef ret (*FUNC##name)(args);
+
+// Set up a dlsymed function ready to use.
+#define USE_DLFUNC(name) \
+ FUNC##name name = (FUNC##name) dlsym(GetSharedLibrary(), #name); \
+ if (!name) { \
+ MOZ_CRASH("Symbol not found in shared library : " #name); \
+ }
+
+#endif // NetUtils_h
diff --git a/dom/network/NetworkStatsDB.jsm b/dom/network/NetworkStatsDB.jsm
new file mode 100644
index 000000000..0ca075a0d
--- /dev/null
+++ b/dom/network/NetworkStatsDB.jsm
@@ -0,0 +1,1263 @@
+/* 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/. */
+
+"use strict";
+
+this.EXPORTED_SYMBOLS = ['NetworkStatsDB'];
+
+const DEBUG = false;
+function debug(s) { dump("-*- NetworkStatsDB: " + s + "\n"); }
+
+const {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components;
+
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+Cu.import("resource://gre/modules/Services.jsm");
+Cu.import("resource://gre/modules/IndexedDBHelper.jsm");
+Cu.importGlobalProperties(["indexedDB"]);
+
+XPCOMUtils.defineLazyServiceGetter(this, "appsService",
+ "@mozilla.org/AppsService;1",
+ "nsIAppsService");
+
+const DB_NAME = "net_stats";
+const DB_VERSION = 9;
+const DEPRECATED_STATS_STORE_NAME =
+ [
+ "net_stats_v2", // existed only in DB version 2
+ "net_stats", // existed in DB version 1 and 3 to 5
+ "net_stats_store", // existed in DB version 6 to 8
+ ];
+const STATS_STORE_NAME = "net_stats_store_v3"; // since DB version 9
+const ALARMS_STORE_NAME = "net_alarm";
+
+// Constant defining the maximum values allowed per interface. If more, older
+// will be erased.
+const VALUES_MAX_LENGTH = 6 * 30;
+
+// Constant defining the rate of the samples. Daily.
+const SAMPLE_RATE = 1000 * 60 * 60 * 24;
+
+this.NetworkStatsDB = function NetworkStatsDB() {
+ if (DEBUG) {
+ debug("Constructor");
+ }
+ this.initDBHelper(DB_NAME, DB_VERSION, [STATS_STORE_NAME, ALARMS_STORE_NAME]);
+}
+
+NetworkStatsDB.prototype = {
+ __proto__: IndexedDBHelper.prototype,
+
+ dbNewTxn: function dbNewTxn(store_name, txn_type, callback, txnCb) {
+ function successCb(result) {
+ txnCb(null, result);
+ }
+ function errorCb(error) {
+ txnCb(error, null);
+ }
+ return this.newTxn(txn_type, store_name, callback, successCb, errorCb);
+ },
+
+ /**
+ * The onupgradeneeded handler of the IDBOpenDBRequest.
+ * This function is called in IndexedDBHelper open() method.
+ *
+ * @param {IDBTransaction} aTransaction
+ * {IDBDatabase} aDb
+ * {64-bit integer} aOldVersion The version number on local storage.
+ * {64-bit integer} aNewVersion The version number to be upgraded to.
+ *
+ * @note Be careful with the database upgrade pattern.
+ * Because IndexedDB operations are performed asynchronously, we must
+ * apply a recursive approach instead of an iterative approach while
+ * upgrading versions.
+ */
+ upgradeSchema: function upgradeSchema(aTransaction, aDb, aOldVersion, aNewVersion) {
+ if (DEBUG) {
+ debug("upgrade schema from: " + aOldVersion + " to " + aNewVersion + " called!");
+ }
+ let db = aDb;
+ let objectStore;
+
+ // An array of upgrade functions for each version.
+ let upgradeSteps = [
+ function upgrade0to1() {
+ if (DEBUG) debug("Upgrade 0 to 1: Create object stores and indexes.");
+
+ // Create the initial database schema.
+ objectStore = db.createObjectStore(DEPRECATED_STATS_STORE_NAME[1],
+ { keyPath: ["connectionType", "timestamp"] });
+ objectStore.createIndex("connectionType", "connectionType", { unique: false });
+ objectStore.createIndex("timestamp", "timestamp", { unique: false });
+ objectStore.createIndex("rxBytes", "rxBytes", { unique: false });
+ objectStore.createIndex("txBytes", "txBytes", { unique: false });
+ objectStore.createIndex("rxTotalBytes", "rxTotalBytes", { unique: false });
+ objectStore.createIndex("txTotalBytes", "txTotalBytes", { unique: false });
+
+ upgradeNextVersion();
+ },
+
+ function upgrade1to2() {
+ if (DEBUG) debug("Upgrade 1 to 2: Do nothing.");
+ upgradeNextVersion();
+ },
+
+ function upgrade2to3() {
+ if (DEBUG) debug("Upgrade 2 to 3: Add keyPath appId to object store.");
+
+ // In order to support per-app traffic data storage, the original
+ // objectStore needs to be replaced by a new objectStore with new
+ // key path ("appId") and new index ("appId").
+ // Also, since now networks are identified by their
+ // [networkId, networkType] not just by their connectionType,
+ // to modify the keyPath is mandatory to delete the object store
+ // and create it again. Old data is going to be deleted because the
+ // networkId for each sample can not be set.
+
+ // In version 1.2 objectStore name was 'net_stats_v2', to avoid errors when
+ // upgrading from 1.2 to 1.3 objectStore name should be checked.
+ let stores = db.objectStoreNames;
+ let deprecatedName = DEPRECATED_STATS_STORE_NAME[0];
+ let storeName = DEPRECATED_STATS_STORE_NAME[1];
+ if(stores.contains(deprecatedName)) {
+ // Delete the obsolete stats store.
+ db.deleteObjectStore(deprecatedName);
+ } else {
+ // Re-create stats object store without copying records.
+ db.deleteObjectStore(storeName);
+ }
+
+ objectStore = db.createObjectStore(storeName, { keyPath: ["appId", "network", "timestamp"] });
+ objectStore.createIndex("appId", "appId", { unique: false });
+ objectStore.createIndex("network", "network", { unique: false });
+ objectStore.createIndex("networkType", "networkType", { unique: false });
+ objectStore.createIndex("timestamp", "timestamp", { unique: false });
+ objectStore.createIndex("rxBytes", "rxBytes", { unique: false });
+ objectStore.createIndex("txBytes", "txBytes", { unique: false });
+ objectStore.createIndex("rxTotalBytes", "rxTotalBytes", { unique: false });
+ objectStore.createIndex("txTotalBytes", "txTotalBytes", { unique: false });
+
+ upgradeNextVersion();
+ },
+
+ function upgrade3to4() {
+ if (DEBUG) debug("Upgrade 3 to 4: Delete redundant indexes.");
+
+ // Delete redundant indexes (leave "network" only).
+ objectStore = aTransaction.objectStore(DEPRECATED_STATS_STORE_NAME[1]);
+ if (objectStore.indexNames.contains("appId")) {
+ objectStore.deleteIndex("appId");
+ }
+ if (objectStore.indexNames.contains("networkType")) {
+ objectStore.deleteIndex("networkType");
+ }
+ if (objectStore.indexNames.contains("timestamp")) {
+ objectStore.deleteIndex("timestamp");
+ }
+ if (objectStore.indexNames.contains("rxBytes")) {
+ objectStore.deleteIndex("rxBytes");
+ }
+ if (objectStore.indexNames.contains("txBytes")) {
+ objectStore.deleteIndex("txBytes");
+ }
+ if (objectStore.indexNames.contains("rxTotalBytes")) {
+ objectStore.deleteIndex("rxTotalBytes");
+ }
+ if (objectStore.indexNames.contains("txTotalBytes")) {
+ objectStore.deleteIndex("txTotalBytes");
+ }
+
+ upgradeNextVersion();
+ },
+
+ function upgrade4to5() {
+ if (DEBUG) debug("Upgrade 4 to 5: Create object store for alarms.");
+
+ // In order to manage alarms, it is necessary to use a global counter
+ // (totalBytes) that will increase regardless of the system reboot.
+ objectStore = aTransaction.objectStore(DEPRECATED_STATS_STORE_NAME[1]);
+
+ // Now, systemBytes will hold the old totalBytes and totalBytes will
+ // keep the increasing counter. |counters| will keep the track of
+ // accumulated values.
+ let counters = {};
+
+ objectStore.openCursor().onsuccess = function(event) {
+ let cursor = event.target.result;
+ if (!cursor){
+ // upgrade4to5 completed now.
+ upgradeNextVersion();
+ return;
+ }
+
+ cursor.value.rxSystemBytes = cursor.value.rxTotalBytes;
+ cursor.value.txSystemBytes = cursor.value.txTotalBytes;
+
+ if (cursor.value.appId == 0) {
+ let netId = cursor.value.network[0] + '' + cursor.value.network[1];
+ if (!counters[netId]) {
+ counters[netId] = {
+ rxCounter: 0,
+ txCounter: 0,
+ lastRx: 0,
+ lastTx: 0
+ };
+ }
+
+ let rxDiff = cursor.value.rxSystemBytes - counters[netId].lastRx;
+ let txDiff = cursor.value.txSystemBytes - counters[netId].lastTx;
+ if (rxDiff < 0 || txDiff < 0) {
+ // System reboot between samples, so take the current one.
+ rxDiff = cursor.value.rxSystemBytes;
+ txDiff = cursor.value.txSystemBytes;
+ }
+
+ counters[netId].rxCounter += rxDiff;
+ counters[netId].txCounter += txDiff;
+ cursor.value.rxTotalBytes = counters[netId].rxCounter;
+ cursor.value.txTotalBytes = counters[netId].txCounter;
+
+ counters[netId].lastRx = cursor.value.rxSystemBytes;
+ counters[netId].lastTx = cursor.value.txSystemBytes;
+ } else {
+ cursor.value.rxTotalBytes = cursor.value.rxSystemBytes;
+ cursor.value.txTotalBytes = cursor.value.txSystemBytes;
+ }
+
+ cursor.update(cursor.value);
+ cursor.continue();
+ };
+
+ // Create object store for alarms.
+ objectStore = db.createObjectStore(ALARMS_STORE_NAME, { keyPath: "id", autoIncrement: true });
+ objectStore.createIndex("alarm", ['networkId','threshold'], { unique: false });
+ objectStore.createIndex("manifestURL", "manifestURL", { unique: false });
+ },
+
+ function upgrade5to6() {
+ if (DEBUG) debug("Upgrade 5 to 6: Add keyPath serviceType to object store.");
+
+ // In contrast to "per-app" traffic data, "system-only" traffic data
+ // refers to data which can not be identified by any applications.
+ // To further support "system-only" data storage, the data can be
+ // saved by service type (e.g., Tethering, OTA). Thus it's needed to
+ // have a new key ("serviceType") for the ojectStore.
+ let newObjectStore;
+ let deprecatedName = DEPRECATED_STATS_STORE_NAME[1];
+ newObjectStore = db.createObjectStore(DEPRECATED_STATS_STORE_NAME[2],
+ { keyPath: ["appId", "serviceType", "network", "timestamp"] });
+ newObjectStore.createIndex("network", "network", { unique: false });
+
+ // Copy the data from the original objectStore to the new objectStore.
+ objectStore = aTransaction.objectStore(deprecatedName);
+ objectStore.openCursor().onsuccess = function(event) {
+ let cursor = event.target.result;
+ if (!cursor) {
+ db.deleteObjectStore(deprecatedName);
+ // upgrade5to6 completed now.
+ upgradeNextVersion();
+ return;
+ }
+
+ let newStats = cursor.value;
+ newStats.serviceType = "";
+ newObjectStore.put(newStats);
+ cursor.continue();
+ };
+ },
+
+ function upgrade6to7() {
+ if (DEBUG) debug("Upgrade 6 to 7: Replace alarm threshold by relativeThreshold.");
+
+ // Replace threshold attribute of alarm index by relativeThreshold in alarms DB.
+ // Now alarms are indexed by relativeThreshold, which is the threshold relative
+ // to current system stats.
+ let alarmsStore = aTransaction.objectStore(ALARMS_STORE_NAME);
+
+ // Delete "alarm" index.
+ if (alarmsStore.indexNames.contains("alarm")) {
+ alarmsStore.deleteIndex("alarm");
+ }
+
+ // Create new "alarm" index.
+ alarmsStore.createIndex("alarm", ['networkId','relativeThreshold'], { unique: false });
+
+ // Populate new "alarm" index attributes.
+ alarmsStore.openCursor().onsuccess = function(event) {
+ let cursor = event.target.result;
+ if (!cursor) {
+ upgrade6to7_updateTotalBytes();
+ return;
+ }
+
+ cursor.value.relativeThreshold = cursor.value.threshold;
+ cursor.value.absoluteThreshold = cursor.value.threshold;
+ delete cursor.value.threshold;
+
+ cursor.update(cursor.value);
+ cursor.continue();
+ }
+
+ function upgrade6to7_updateTotalBytes() {
+ if (DEBUG) debug("Upgrade 6 to 7: Update TotalBytes.");
+ // Previous versions save accumulative totalBytes, increasing although the system
+ // reboots or resets stats. But is necessary to reset the total counters when reset
+ // through 'clearInterfaceStats'.
+ let statsStore = aTransaction.objectStore(DEPRECATED_STATS_STORE_NAME[2]);
+ let networks = [];
+
+ // Find networks stored in the database.
+ statsStore.index("network").openKeyCursor(null, "nextunique").onsuccess = function(event) {
+ let cursor = event.target.result;
+
+ // Store each network into an array.
+ if (cursor) {
+ networks.push(cursor.key);
+ cursor.continue();
+ return;
+ }
+
+ // Start to deal with each network.
+ let pending = networks.length;
+
+ if (pending === 0) {
+ // Found no records of network. upgrade6to7 completed now.
+ upgradeNextVersion();
+ return;
+ }
+
+ networks.forEach(function(network) {
+ let lowerFilter = [0, "", network, 0];
+ let upperFilter = [0, "", network, ""];
+ let range = IDBKeyRange.bound(lowerFilter, upperFilter, false, false);
+
+ // Find number of samples for a given network.
+ statsStore.count(range).onsuccess = function(event) {
+ let recordCount = event.target.result;
+
+ // If there are more samples than the max allowed, there is no way to know
+ // when does reset take place.
+ if (recordCount === 0 || recordCount >= VALUES_MAX_LENGTH) {
+ pending--;
+ if (pending === 0) {
+ upgradeNextVersion();
+ }
+ return;
+ }
+
+ let last = null;
+ // Reset detected if the first sample totalCounters are different than bytes
+ // counters. If so, the total counters should be recalculated.
+ statsStore.openCursor(range).onsuccess = function(event) {
+ let cursor = event.target.result;
+ if (!cursor) {
+ pending--;
+ if (pending === 0) {
+ upgradeNextVersion();
+ }
+ return;
+ }
+ if (!last) {
+ if (cursor.value.rxTotalBytes == cursor.value.rxBytes &&
+ cursor.value.txTotalBytes == cursor.value.txBytes) {
+ pending--;
+ if (pending === 0) {
+ upgradeNextVersion();
+ }
+ return;
+ }
+
+ cursor.value.rxTotalBytes = cursor.value.rxBytes;
+ cursor.value.txTotalBytes = cursor.value.txBytes;
+ cursor.update(cursor.value);
+ last = cursor.value;
+ cursor.continue();
+ return;
+ }
+
+ // Recalculate the total counter for last / current sample
+ cursor.value.rxTotalBytes = last.rxTotalBytes + cursor.value.rxBytes;
+ cursor.value.txTotalBytes = last.txTotalBytes + cursor.value.txBytes;
+ cursor.update(cursor.value);
+ last = cursor.value;
+ cursor.continue();
+ }
+ }
+ }, this); // end of networks.forEach()
+ }; // end of statsStore.index("network").openKeyCursor().onsuccess callback
+ } // end of function upgrade6to7_updateTotalBytes
+ },
+
+ function upgrade7to8() {
+ if (DEBUG) debug("Upgrade 7 to 8: Create index serviceType.");
+
+ // Create index for 'ServiceType' in order to make it retrievable.
+ let statsStore = aTransaction.objectStore(DEPRECATED_STATS_STORE_NAME[2]);
+ statsStore.createIndex("serviceType", "serviceType", { unique: false });
+
+ upgradeNextVersion();
+ },
+
+ function upgrade8to9() {
+ if (DEBUG) debug("Upgrade 8 to 9: Add keyPath isInBrowser to " +
+ "network stats object store");
+
+ // Since B2G v2.0, there is no stand-alone browser app anymore.
+ // The browser app is a mozbrowser iframe element owned by system app.
+ // In order to separate traffic generated from system and browser, we
+ // have to add a new attribute |isInBrowser| as keyPath.
+ // Refer to bug 1070944 for more detail.
+ let newObjectStore;
+ let deprecatedName = DEPRECATED_STATS_STORE_NAME[2];
+ newObjectStore = db.createObjectStore(STATS_STORE_NAME,
+ { keyPath: ["appId", "isInBrowser", "serviceType",
+ "network", "timestamp"] });
+ newObjectStore.createIndex("network", "network", { unique: false });
+ newObjectStore.createIndex("serviceType", "serviceType", { unique: false });
+
+ // Copy records from the current object store to the new one.
+ objectStore = aTransaction.objectStore(deprecatedName);
+ objectStore.openCursor().onsuccess = function (event) {
+ let cursor = event.target.result;
+ if (!cursor) {
+ db.deleteObjectStore(deprecatedName);
+ // upgrade8to9 completed now.
+ return;
+ }
+ let newStats = cursor.value;
+ // Augment records by adding the new isInBrowser attribute.
+ // Notes:
+ // 1. Key value cannot be boolean type. Use 1/0 instead of true/false.
+ // 2. Most traffic of system app should come from its browser iframe,
+ // thus assign isInBrowser as 1 for system app.
+ let manifestURL = appsService.getManifestURLByLocalId(newStats.appId);
+ if (manifestURL && manifestURL.search(/app:\/\/system\./) === 0) {
+ newStats.isInBrowser = 1;
+ } else {
+ newStats.isInBrowser = 0;
+ }
+ newObjectStore.put(newStats);
+ cursor.continue();
+ };
+ }
+ ];
+
+ let index = aOldVersion;
+ let outer = this;
+
+ function upgradeNextVersion() {
+ if (index == aNewVersion) {
+ debug("Upgrade finished.");
+ return;
+ }
+
+ try {
+ var i = index++;
+ if (DEBUG) debug("Upgrade step: " + i + "\n");
+ upgradeSteps[i].call(outer);
+ } catch (ex) {
+ dump("Caught exception " + ex);
+ throw ex;
+ return;
+ }
+ }
+
+ if (aNewVersion > upgradeSteps.length) {
+ debug("No migration steps for the new version!");
+ aTransaction.abort();
+ return;
+ }
+
+ upgradeNextVersion();
+ },
+
+ importData: function importData(aStats) {
+ let stats = { appId: aStats.appId,
+ isInBrowser: aStats.isInBrowser ? 1 : 0,
+ serviceType: aStats.serviceType,
+ network: [aStats.networkId, aStats.networkType],
+ timestamp: aStats.timestamp,
+ rxBytes: aStats.rxBytes,
+ txBytes: aStats.txBytes,
+ rxSystemBytes: aStats.rxSystemBytes,
+ txSystemBytes: aStats.txSystemBytes,
+ rxTotalBytes: aStats.rxTotalBytes,
+ txTotalBytes: aStats.txTotalBytes };
+
+ return stats;
+ },
+
+ exportData: function exportData(aStats) {
+ let stats = { appId: aStats.appId,
+ isInBrowser: aStats.isInBrowser ? true : false,
+ serviceType: aStats.serviceType,
+ networkId: aStats.network[0],
+ networkType: aStats.network[1],
+ timestamp: aStats.timestamp,
+ rxBytes: aStats.rxBytes,
+ txBytes: aStats.txBytes,
+ rxTotalBytes: aStats.rxTotalBytes,
+ txTotalBytes: aStats.txTotalBytes };
+
+ return stats;
+ },
+
+ normalizeDate: function normalizeDate(aDate) {
+ // Convert to UTC according to timezone and
+ // filter timestamp to get SAMPLE_RATE precission
+ let timestamp = aDate.getTime() - aDate.getTimezoneOffset() * 60 * 1000;
+ timestamp = Math.floor(timestamp / SAMPLE_RATE) * SAMPLE_RATE;
+ return timestamp;
+ },
+
+ saveStats: function saveStats(aStats, aResultCb) {
+ let isAccumulative = aStats.isAccumulative;
+ let timestamp = this.normalizeDate(aStats.date);
+
+ let stats = { appId: aStats.appId,
+ isInBrowser: aStats.isInBrowser,
+ serviceType: aStats.serviceType,
+ networkId: aStats.networkId,
+ networkType: aStats.networkType,
+ timestamp: timestamp,
+ rxBytes: isAccumulative ? 0 : aStats.rxBytes,
+ txBytes: isAccumulative ? 0 : aStats.txBytes,
+ rxSystemBytes: isAccumulative ? aStats.rxBytes : 0,
+ txSystemBytes: isAccumulative ? aStats.txBytes : 0,
+ rxTotalBytes: isAccumulative ? aStats.rxBytes : 0,
+ txTotalBytes: isAccumulative ? aStats.txBytes : 0 };
+
+ stats = this.importData(stats);
+
+ this.dbNewTxn(STATS_STORE_NAME, "readwrite", function(aTxn, aStore) {
+ if (DEBUG) {
+ debug("Filtered time: " + new Date(timestamp));
+ debug("New stats: " + JSON.stringify(stats));
+ }
+
+ let lowerFilter = [stats.appId, stats.isInBrowser, stats.serviceType,
+ stats.network, 0];
+ let upperFilter = [stats.appId, stats.isInBrowser, stats.serviceType,
+ stats.network, ""];
+ let range = IDBKeyRange.bound(lowerFilter, upperFilter, false, false);
+
+ let request = aStore.openCursor(range, 'prev');
+ request.onsuccess = function onsuccess(event) {
+ let cursor = event.target.result;
+ if (!cursor) {
+ // Empty, so save first element.
+
+ if (!isAccumulative) {
+ this._saveStats(aTxn, aStore, stats);
+ return;
+ }
+
+ // There could be a time delay between the point when the network
+ // interface comes up and the point when the database is initialized.
+ // In this short interval some traffic data are generated but are not
+ // registered by the first sample.
+ stats.rxBytes = stats.rxTotalBytes;
+ stats.txBytes = stats.txTotalBytes;
+
+ // However, if the interface is not switched on after the database is
+ // initialized (dual sim use case) stats should be set to 0.
+ let req = aStore.index("network").openKeyCursor(null, "nextunique");
+ req.onsuccess = function onsuccess(event) {
+ let cursor = event.target.result;
+ if (cursor) {
+ if (cursor.key[1] == stats.network[1]) {
+ stats.rxBytes = 0;
+ stats.txBytes = 0;
+ this._saveStats(aTxn, aStore, stats);
+ return;
+ }
+
+ cursor.continue();
+ return;
+ }
+
+ this._saveStats(aTxn, aStore, stats);
+ }.bind(this);
+
+ return;
+ }
+
+ // There are old samples
+ if (DEBUG) {
+ debug("Last value " + JSON.stringify(cursor.value));
+ }
+
+ // Remove stats previous to now - VALUE_MAX_LENGTH
+ this._removeOldStats(aTxn, aStore, stats.appId, stats.isInBrowser,
+ stats.serviceType, stats.network, stats.timestamp);
+
+ // Process stats before save
+ this._processSamplesDiff(aTxn, aStore, cursor, stats, isAccumulative);
+ }.bind(this);
+ }.bind(this), aResultCb);
+ },
+
+ /*
+ * This function check that stats are saved in the database following the sample rate.
+ * In this way is easier to find elements when stats are requested.
+ */
+ _processSamplesDiff: function _processSamplesDiff(aTxn,
+ aStore,
+ aLastSampleCursor,
+ aNewSample,
+ aIsAccumulative) {
+ let lastSample = aLastSampleCursor.value;
+
+ // Get difference between last and new sample.
+ let diff = (aNewSample.timestamp - lastSample.timestamp) / SAMPLE_RATE;
+ if (diff % 1) {
+ // diff is decimal, so some error happened because samples are stored as a multiple
+ // of SAMPLE_RATE
+ aTxn.abort();
+ throw new Error("Error processing samples");
+ }
+
+ if (DEBUG) {
+ debug("New: " + aNewSample.timestamp + " - Last: " +
+ lastSample.timestamp + " - diff: " + diff);
+ }
+
+ // If the incoming data has a accumulation feature, the new
+ // |txBytes|/|rxBytes| is assigend by differnces between the new
+ // |txTotalBytes|/|rxTotalBytes| and the last |txTotalBytes|/|rxTotalBytes|.
+ // Else, if incoming data is non-accumulative, the |txBytes|/|rxBytes|
+ // is the new |txBytes|/|rxBytes|.
+ let rxDiff = 0;
+ let txDiff = 0;
+ if (aIsAccumulative) {
+ rxDiff = aNewSample.rxSystemBytes - lastSample.rxSystemBytes;
+ txDiff = aNewSample.txSystemBytes - lastSample.txSystemBytes;
+ if (rxDiff < 0 || txDiff < 0) {
+ rxDiff = aNewSample.rxSystemBytes;
+ txDiff = aNewSample.txSystemBytes;
+ }
+ aNewSample.rxBytes = rxDiff;
+ aNewSample.txBytes = txDiff;
+
+ aNewSample.rxTotalBytes = lastSample.rxTotalBytes + rxDiff;
+ aNewSample.txTotalBytes = lastSample.txTotalBytes + txDiff;
+ } else {
+ rxDiff = aNewSample.rxBytes;
+ txDiff = aNewSample.txBytes;
+ }
+
+ if (diff == 1) {
+ // New element.
+
+ // If the incoming data is non-accumulative, the new
+ // |rxTotalBytes|/|txTotalBytes| needs to be updated by adding new
+ // |rxBytes|/|txBytes| to the last |rxTotalBytes|/|txTotalBytes|.
+ if (!aIsAccumulative) {
+ aNewSample.rxTotalBytes = aNewSample.rxBytes + lastSample.rxTotalBytes;
+ aNewSample.txTotalBytes = aNewSample.txBytes + lastSample.txTotalBytes;
+ }
+
+ this._saveStats(aTxn, aStore, aNewSample);
+ return;
+ }
+ if (diff > 1) {
+ // Some samples lost. Device off during one or more samplerate periods.
+ // Time or timezone changed
+ // Add lost samples with 0 bytes and the actual one.
+ if (diff > VALUES_MAX_LENGTH) {
+ diff = VALUES_MAX_LENGTH;
+ }
+
+ let data = [];
+ for (let i = diff - 2; i >= 0; i--) {
+ let time = aNewSample.timestamp - SAMPLE_RATE * (i + 1);
+ let sample = { appId: aNewSample.appId,
+ isInBrowser: aNewSample.isInBrowser,
+ serviceType: aNewSample.serviceType,
+ network: aNewSample.network,
+ timestamp: time,
+ rxBytes: 0,
+ txBytes: 0,
+ rxSystemBytes: lastSample.rxSystemBytes,
+ txSystemBytes: lastSample.txSystemBytes,
+ rxTotalBytes: lastSample.rxTotalBytes,
+ txTotalBytes: lastSample.txTotalBytes };
+
+ data.push(sample);
+ }
+
+ data.push(aNewSample);
+ this._saveStats(aTxn, aStore, data);
+ return;
+ }
+ if (diff == 0 || diff < 0) {
+ // New element received before samplerate period. It means that device has
+ // been restarted (or clock / timezone change).
+ // Update element. If diff < 0, clock or timezone changed back. Place data
+ // in the last sample.
+
+ // Old |rxTotalBytes|/|txTotalBytes| needs to get updated by adding the
+ // last |rxTotalBytes|/|txTotalBytes|.
+ lastSample.rxBytes += rxDiff;
+ lastSample.txBytes += txDiff;
+ lastSample.rxSystemBytes = aNewSample.rxSystemBytes;
+ lastSample.txSystemBytes = aNewSample.txSystemBytes;
+ lastSample.rxTotalBytes += rxDiff;
+ lastSample.txTotalBytes += txDiff;
+
+ if (DEBUG) {
+ debug("Update: " + JSON.stringify(lastSample));
+ }
+ let req = aLastSampleCursor.update(lastSample);
+ }
+ },
+
+ _saveStats: function _saveStats(aTxn, aStore, aNetworkStats) {
+ if (DEBUG) {
+ debug("_saveStats: " + JSON.stringify(aNetworkStats));
+ }
+
+ if (Array.isArray(aNetworkStats)) {
+ let len = aNetworkStats.length - 1;
+ for (let i = 0; i <= len; i++) {
+ aStore.put(aNetworkStats[i]);
+ }
+ } else {
+ aStore.put(aNetworkStats);
+ }
+ },
+
+ _removeOldStats: function _removeOldStats(aTxn, aStore, aAppId, aIsInBrowser,
+ aServiceType, aNetwork, aDate) {
+ // Callback function to remove old items when new ones are added.
+ let filterDate = aDate - (SAMPLE_RATE * VALUES_MAX_LENGTH - 1);
+ let lowerFilter = [aAppId, aIsInBrowser, aServiceType, aNetwork, 0];
+ let upperFilter = [aAppId, aIsInBrowser, aServiceType, aNetwork, filterDate];
+ let range = IDBKeyRange.bound(lowerFilter, upperFilter, false, false);
+ let lastSample = null;
+ let self = this;
+
+ aStore.openCursor(range).onsuccess = function(event) {
+ var cursor = event.target.result;
+ if (cursor) {
+ lastSample = cursor.value;
+ cursor.delete();
+ cursor.continue();
+ return;
+ }
+
+ // If all samples for a network are removed, an empty sample
+ // has to be saved to keep the totalBytes in order to compute
+ // future samples because system counters are not set to 0.
+ // Thus, if there are no samples left, the last sample removed
+ // will be saved again after setting its bytes to 0.
+ let request = aStore.index("network").openCursor(aNetwork);
+ request.onsuccess = function onsuccess(event) {
+ let cursor = event.target.result;
+ if (!cursor && lastSample != null) {
+ let timestamp = new Date();
+ timestamp = self.normalizeDate(timestamp);
+ lastSample.timestamp = timestamp;
+ lastSample.rxBytes = 0;
+ lastSample.txBytes = 0;
+ self._saveStats(aTxn, aStore, lastSample);
+ }
+ };
+ };
+ },
+
+ clearInterfaceStats: function clearInterfaceStats(aNetwork, aResultCb) {
+ let network = [aNetwork.network.id, aNetwork.network.type];
+ let self = this;
+
+ // Clear and save an empty sample to keep sync with system counters
+ this.dbNewTxn(STATS_STORE_NAME, "readwrite", function(aTxn, aStore) {
+ let sample = null;
+ let request = aStore.index("network").openCursor(network, "prev");
+ request.onsuccess = function onsuccess(event) {
+ let cursor = event.target.result;
+ if (cursor) {
+ if (!sample && cursor.value.appId == 0) {
+ sample = cursor.value;
+ }
+
+ cursor.delete();
+ cursor.continue();
+ return;
+ }
+
+ if (sample) {
+ let timestamp = new Date();
+ timestamp = self.normalizeDate(timestamp);
+ sample.timestamp = timestamp;
+ sample.appId = 0;
+ sample.isInBrowser = 0;
+ sample.serviceType = "";
+ sample.rxBytes = 0;
+ sample.txBytes = 0;
+ sample.rxTotalBytes = 0;
+ sample.txTotalBytes = 0;
+
+ self._saveStats(aTxn, aStore, sample);
+ }
+ };
+ }, this._resetAlarms.bind(this, aNetwork.networkId, aResultCb));
+ },
+
+ clearStats: function clearStats(aNetworks, aResultCb) {
+ let index = 0;
+ let stats = [];
+ let self = this;
+
+ let callback = function(aError, aResult) {
+ index++;
+
+ if (!aError && index < aNetworks.length) {
+ self.clearInterfaceStats(aNetworks[index], callback);
+ return;
+ }
+
+ aResultCb(aError, aResult);
+ };
+
+ if (!aNetworks[index]) {
+ aResultCb(null, true);
+ return;
+ }
+ this.clearInterfaceStats(aNetworks[index], callback);
+ },
+
+ getCurrentStats: function getCurrentStats(aNetwork, aDate, aResultCb) {
+ if (DEBUG) {
+ debug("Get current stats for " + JSON.stringify(aNetwork) + " since " + aDate);
+ }
+
+ let network = [aNetwork.id, aNetwork.type];
+ if (aDate) {
+ this._getCurrentStatsFromDate(network, aDate, aResultCb);
+ return;
+ }
+
+ this._getCurrentStats(network, aResultCb);
+ },
+
+ _getCurrentStats: function _getCurrentStats(aNetwork, aResultCb) {
+ this.dbNewTxn(STATS_STORE_NAME, "readonly", function(txn, store) {
+ let request = null;
+ let upperFilter = [0, 1, "", aNetwork, Date.now()];
+ let range = IDBKeyRange.upperBound(upperFilter, false);
+ let result = { rxBytes: 0, txBytes: 0,
+ rxTotalBytes: 0, txTotalBytes: 0 };
+
+ request = store.openCursor(range, "prev");
+
+ request.onsuccess = function onsuccess(event) {
+ let cursor = event.target.result;
+ if (cursor) {
+ result.rxBytes = result.rxTotalBytes = cursor.value.rxTotalBytes;
+ result.txBytes = result.txTotalBytes = cursor.value.txTotalBytes;
+ }
+
+ txn.result = result;
+ };
+ }.bind(this), aResultCb);
+ },
+
+ _getCurrentStatsFromDate: function _getCurrentStatsFromDate(aNetwork, aDate, aResultCb) {
+ aDate = new Date(aDate);
+ this.dbNewTxn(STATS_STORE_NAME, "readonly", function(txn, store) {
+ let request = null;
+ let start = this.normalizeDate(aDate);
+ let upperFilter = [0, 1, "", aNetwork, Date.now()];
+ let range = IDBKeyRange.upperBound(upperFilter, false);
+ let result = { rxBytes: 0, txBytes: 0,
+ rxTotalBytes: 0, txTotalBytes: 0 };
+
+ request = store.openCursor(range, "prev");
+
+ request.onsuccess = function onsuccess(event) {
+ let cursor = event.target.result;
+ if (cursor) {
+ result.rxBytes = result.rxTotalBytes = cursor.value.rxTotalBytes;
+ result.txBytes = result.txTotalBytes = cursor.value.txTotalBytes;
+ }
+
+ let timestamp = cursor.value.timestamp;
+ let range = IDBKeyRange.lowerBound(lowerFilter, false);
+ request = store.openCursor(range);
+
+ request.onsuccess = function onsuccess(event) {
+ let cursor = event.target.result;
+ if (cursor) {
+ if (cursor.value.timestamp == timestamp) {
+ // There is one sample only.
+ result.rxBytes = cursor.value.rxBytes;
+ result.txBytes = cursor.value.txBytes;
+ } else {
+ result.rxBytes -= cursor.value.rxTotalBytes;
+ result.txBytes -= cursor.value.txTotalBytes;
+ }
+ }
+
+ txn.result = result;
+ };
+ };
+ }.bind(this), aResultCb);
+ },
+
+ find: function find(aResultCb, aAppId, aBrowsingTrafficOnly, aServiceType,
+ aNetwork, aStart, aEnd, aAppManifestURL) {
+ let offset = (new Date()).getTimezoneOffset() * 60 * 1000;
+ let start = this.normalizeDate(aStart);
+ let end = this.normalizeDate(aEnd);
+
+ if (DEBUG) {
+ debug("Find samples for appId: " + aAppId +
+ " browsingTrafficOnly: " + aBrowsingTrafficOnly +
+ " serviceType: " + aServiceType +
+ " network: " + JSON.stringify(aNetwork) + " from " + start +
+ " until " + end);
+ debug("Start time: " + new Date(start));
+ debug("End time: " + new Date(end));
+ }
+
+ // Find samples of browsing traffic (isInBrowser = 1) first since they are
+ // needed no matter browsingTrafficOnly is true or false.
+ // We have to make two queries to database because we cannot filter correct
+ // records by a single query that sets ranges for two keys (isInBrowser and
+ // timestamp). We think it is because the keyPath contains an array
+ // (network) so such query does not work.
+ this.dbNewTxn(STATS_STORE_NAME, "readonly", function(aTxn, aStore) {
+ let network = [aNetwork.id, aNetwork.type];
+ let lowerFilter = [aAppId, 1, aServiceType, network, start];
+ let upperFilter = [aAppId, 1, aServiceType, network, end];
+ let range = IDBKeyRange.bound(lowerFilter, upperFilter, false, false);
+
+ let data = [];
+
+ if (!aTxn.result) {
+ aTxn.result = {};
+ }
+ aTxn.result.appManifestURL = aAppManifestURL;
+ aTxn.result.browsingTrafficOnly = aBrowsingTrafficOnly;
+ aTxn.result.serviceType = aServiceType;
+ aTxn.result.network = aNetwork;
+ aTxn.result.start = aStart;
+ aTxn.result.end = aEnd;
+
+ let request = aStore.openCursor(range).onsuccess = function(event) {
+ var cursor = event.target.result;
+ if (cursor){
+ data.push({ rxBytes: cursor.value.rxBytes,
+ txBytes: cursor.value.txBytes,
+ date: new Date(cursor.value.timestamp + offset) });
+ cursor.continue();
+ return;
+ }
+
+ if (aBrowsingTrafficOnly) {
+ this.fillResultSamples(start + offset, end + offset, data);
+ aTxn.result.data = data;
+ return;
+ }
+
+ // Find samples of app traffic (isInBrowser = 0) as well if
+ // browsingTrafficOnly is false.
+ lowerFilter = [aAppId, 0, aServiceType, network, start];
+ upperFilter = [aAppId, 0, aServiceType, network, end];
+ range = IDBKeyRange.bound(lowerFilter, upperFilter, false, false);
+ request = aStore.openCursor(range).onsuccess = function(event) {
+ cursor = event.target.result;
+ if (cursor) {
+ var date = new Date(cursor.value.timestamp + offset);
+ var foundData = data.find(function (element, index, array) {
+ if (element.date.getTime() !== date.getTime()) {
+ return false;
+ }
+ return element;
+ }, date);
+
+ if (foundData) {
+ foundData.rxBytes += cursor.value.rxBytes;
+ foundData.txBytes += cursor.value.txBytes;
+ } else {
+ data.push({ rxBytes: cursor.value.rxBytes,
+ txBytes: cursor.value.txBytes,
+ date: new Date(cursor.value.timestamp + offset) });
+ }
+ cursor.continue();
+ return;
+ }
+ this.fillResultSamples(start + offset, end + offset, data);
+ aTxn.result.data = data;
+ }.bind(this); // openCursor(range).onsuccess() callback
+ }.bind(this); // openCursor(range).onsuccess() callback
+ }.bind(this), aResultCb);
+ },
+
+ /*
+ * Fill data array (samples from database) with empty samples to match
+ * requested start / end dates.
+ */
+ fillResultSamples: function fillResultSamples(aStart, aEnd, aData) {
+ if (aData.length == 0) {
+ aData.push({ rxBytes: undefined,
+ txBytes: undefined,
+ date: new Date(aStart) });
+ }
+
+ while (aStart < aData[0].date.getTime()) {
+ aData.unshift({ rxBytes: undefined,
+ txBytes: undefined,
+ date: new Date(aData[0].date.getTime() - SAMPLE_RATE) });
+ }
+
+ while (aEnd > aData[aData.length - 1].date.getTime()) {
+ aData.push({ rxBytes: undefined,
+ txBytes: undefined,
+ date: new Date(aData[aData.length - 1].date.getTime() + SAMPLE_RATE) });
+ }
+ },
+
+ getAvailableNetworks: function getAvailableNetworks(aResultCb) {
+ this.dbNewTxn(STATS_STORE_NAME, "readonly", function(aTxn, aStore) {
+ if (!aTxn.result) {
+ aTxn.result = [];
+ }
+
+ let request = aStore.index("network").openKeyCursor(null, "nextunique");
+ request.onsuccess = function onsuccess(event) {
+ let cursor = event.target.result;
+ if (cursor) {
+ aTxn.result.push({ id: cursor.key[0],
+ type: cursor.key[1] });
+ cursor.continue();
+ return;
+ }
+ };
+ }, aResultCb);
+ },
+
+ isNetworkAvailable: function isNetworkAvailable(aNetwork, aResultCb) {
+ this.dbNewTxn(STATS_STORE_NAME, "readonly", function(aTxn, aStore) {
+ if (!aTxn.result) {
+ aTxn.result = false;
+ }
+
+ let network = [aNetwork.id, aNetwork.type];
+ let request = aStore.index("network").openKeyCursor(IDBKeyRange.only(network));
+ request.onsuccess = function onsuccess(event) {
+ if (event.target.result) {
+ aTxn.result = true;
+ }
+ };
+ }, aResultCb);
+ },
+
+ getAvailableServiceTypes: function getAvailableServiceTypes(aResultCb) {
+ this.dbNewTxn(STATS_STORE_NAME, "readonly", function(aTxn, aStore) {
+ if (!aTxn.result) {
+ aTxn.result = [];
+ }
+
+ let request = aStore.index("serviceType").openKeyCursor(null, "nextunique");
+ request.onsuccess = function onsuccess(event) {
+ let cursor = event.target.result;
+ if (cursor && cursor.key != "") {
+ aTxn.result.push({ serviceType: cursor.key });
+ cursor.continue();
+ return;
+ }
+ };
+ }, aResultCb);
+ },
+
+ get sampleRate () {
+ return SAMPLE_RATE;
+ },
+
+ get maxStorageSamples () {
+ return VALUES_MAX_LENGTH;
+ },
+
+ logAllRecords: function logAllRecords(aResultCb) {
+ this.dbNewTxn(STATS_STORE_NAME, "readonly", function(aTxn, aStore) {
+ aStore.mozGetAll().onsuccess = function onsuccess(event) {
+ aTxn.result = event.target.result;
+ };
+ }, aResultCb);
+ },
+
+ alarmToRecord: function alarmToRecord(aAlarm) {
+ let record = { networkId: aAlarm.networkId,
+ absoluteThreshold: aAlarm.absoluteThreshold,
+ relativeThreshold: aAlarm.relativeThreshold,
+ startTime: aAlarm.startTime,
+ data: aAlarm.data,
+ manifestURL: aAlarm.manifestURL,
+ pageURL: aAlarm.pageURL };
+
+ if (aAlarm.id) {
+ record.id = aAlarm.id;
+ }
+
+ return record;
+ },
+
+ recordToAlarm: function recordToalarm(aRecord) {
+ let alarm = { networkId: aRecord.networkId,
+ absoluteThreshold: aRecord.absoluteThreshold,
+ relativeThreshold: aRecord.relativeThreshold,
+ startTime: aRecord.startTime,
+ data: aRecord.data,
+ manifestURL: aRecord.manifestURL,
+ pageURL: aRecord.pageURL };
+
+ if (aRecord.id) {
+ alarm.id = aRecord.id;
+ }
+
+ return alarm;
+ },
+
+ addAlarm: function addAlarm(aAlarm, aResultCb) {
+ this.dbNewTxn(ALARMS_STORE_NAME, "readwrite", function(txn, store) {
+ if (DEBUG) {
+ debug("Going to add " + JSON.stringify(aAlarm));
+ }
+
+ let record = this.alarmToRecord(aAlarm);
+ store.put(record).onsuccess = function setResult(aEvent) {
+ txn.result = aEvent.target.result;
+ if (DEBUG) {
+ debug("Request successful. New record ID: " + txn.result);
+ }
+ };
+ }.bind(this), aResultCb);
+ },
+
+ getFirstAlarm: function getFirstAlarm(aNetworkId, aResultCb) {
+ let self = this;
+
+ this.dbNewTxn(ALARMS_STORE_NAME, "readonly", function(txn, store) {
+ if (DEBUG) {
+ debug("Get first alarm for network " + aNetworkId);
+ }
+
+ let lowerFilter = [aNetworkId, 0];
+ let upperFilter = [aNetworkId, ""];
+ let range = IDBKeyRange.bound(lowerFilter, upperFilter);
+
+ store.index("alarm").openCursor(range).onsuccess = function onsuccess(event) {
+ let cursor = event.target.result;
+ txn.result = null;
+ if (cursor) {
+ txn.result = self.recordToAlarm(cursor.value);
+ }
+ };
+ }, aResultCb);
+ },
+
+ removeAlarm: function removeAlarm(aAlarmId, aManifestURL, aResultCb) {
+ this.dbNewTxn(ALARMS_STORE_NAME, "readwrite", function(txn, store) {
+ if (DEBUG) {
+ debug("Remove alarm " + aAlarmId);
+ }
+
+ store.get(aAlarmId).onsuccess = function onsuccess(event) {
+ let record = event.target.result;
+ txn.result = false;
+ if (!record || (aManifestURL && record.manifestURL != aManifestURL)) {
+ return;
+ }
+
+ store.delete(aAlarmId);
+ txn.result = true;
+ }
+ }, aResultCb);
+ },
+
+ removeAlarms: function removeAlarms(aManifestURL, aResultCb) {
+ this.dbNewTxn(ALARMS_STORE_NAME, "readwrite", function(txn, store) {
+ if (DEBUG) {
+ debug("Remove alarms of " + aManifestURL);
+ }
+
+ store.index("manifestURL").openCursor(aManifestURL)
+ .onsuccess = function onsuccess(event) {
+ let cursor = event.target.result;
+ if (cursor) {
+ cursor.delete();
+ cursor.continue();
+ }
+ }
+ }, aResultCb);
+ },
+
+ updateAlarm: function updateAlarm(aAlarm, aResultCb) {
+ let self = this;
+ this.dbNewTxn(ALARMS_STORE_NAME, "readwrite", function(txn, store) {
+ if (DEBUG) {
+ debug("Update alarm " + aAlarm.id);
+ }
+
+ let record = self.alarmToRecord(aAlarm);
+ store.openCursor(record.id).onsuccess = function onsuccess(event) {
+ let cursor = event.target.result;
+ txn.result = false;
+ if (cursor) {
+ cursor.update(record);
+ txn.result = true;
+ }
+ }
+ }, aResultCb);
+ },
+
+ getAlarms: function getAlarms(aNetworkId, aManifestURL, aResultCb) {
+ let self = this;
+ this.dbNewTxn(ALARMS_STORE_NAME, "readonly", function(txn, store) {
+ if (DEBUG) {
+ debug("Get alarms for " + aManifestURL);
+ }
+
+ txn.result = [];
+ store.index("manifestURL").openCursor(aManifestURL)
+ .onsuccess = function onsuccess(event) {
+ let cursor = event.target.result;
+ if (!cursor) {
+ return;
+ }
+
+ if (!aNetworkId || cursor.value.networkId == aNetworkId) {
+ txn.result.push(self.recordToAlarm(cursor.value));
+ }
+
+ cursor.continue();
+ }
+ }, aResultCb);
+ },
+
+ _resetAlarms: function _resetAlarms(aNetworkId, aResultCb) {
+ this.dbNewTxn(ALARMS_STORE_NAME, "readwrite", function(txn, store) {
+ if (DEBUG) {
+ debug("Reset alarms for network " + aNetworkId);
+ }
+
+ let lowerFilter = [aNetworkId, 0];
+ let upperFilter = [aNetworkId, ""];
+ let range = IDBKeyRange.bound(lowerFilter, upperFilter);
+
+ store.index("alarm").openCursor(range).onsuccess = function onsuccess(event) {
+ let cursor = event.target.result;
+ if (cursor) {
+ if (cursor.value.startTime) {
+ cursor.value.relativeThreshold = cursor.value.threshold;
+ cursor.update(cursor.value);
+ }
+ cursor.continue();
+ return;
+ }
+ };
+ }, aResultCb);
+ }
+};
diff --git a/dom/network/NetworkStatsManager.js b/dom/network/NetworkStatsManager.js
new file mode 100644
index 000000000..83644fc32
--- /dev/null
+++ b/dom/network/NetworkStatsManager.js
@@ -0,0 +1,462 @@
+/* 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/. */
+
+"use strict";
+
+const DEBUG = false;
+function debug(s) { dump("-*- NetworkStatsManager: " + s + "\n"); }
+
+const {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components;
+
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+Cu.import("resource://gre/modules/Services.jsm");
+Cu.import("resource://gre/modules/DOMRequestHelper.jsm");
+
+// Ensure NetworkStatsService and NetworkStatsDB are loaded in the parent process
+// to receive messages from the child processes.
+let appInfo = Cc["@mozilla.org/xre/app-info;1"];
+let isParentProcess = !appInfo || appInfo.getService(Ci.nsIXULRuntime)
+ .processType == Ci.nsIXULRuntime.PROCESS_TYPE_DEFAULT;
+if (isParentProcess) {
+ Cu.import("resource://gre/modules/NetworkStatsService.jsm");
+}
+
+XPCOMUtils.defineLazyServiceGetter(this, "cpmm",
+ "@mozilla.org/childprocessmessagemanager;1",
+ "nsISyncMessageSender");
+
+// NetworkStatsData
+const nsIClassInfo = Ci.nsIClassInfo;
+const NETWORKSTATSDATA_CID = Components.ID("{3b16fe17-5583-483a-b486-b64a3243221c}");
+
+function NetworkStatsData(aWindow, aData) {
+ this.rxBytes = aData.rxBytes;
+ this.txBytes = aData.txBytes;
+ this.date = new aWindow.Date(aData.date.getTime());
+}
+
+NetworkStatsData.prototype = {
+ classID : NETWORKSTATSDATA_CID,
+
+ QueryInterface : XPCOMUtils.generateQI([])
+};
+
+// NetworkStatsInterface
+const NETWORKSTATSINTERFACE_CONTRACTID = "@mozilla.org/networkstatsinterface;1";
+const NETWORKSTATSINTERFACE_CID = Components.ID("{f540615b-d803-43ff-8200-2a9d145a5645}");
+
+function NetworkStatsInterface() {
+ if (DEBUG) {
+ debug("NetworkStatsInterface Constructor");
+ }
+}
+
+NetworkStatsInterface.prototype = {
+ __init: function(aNetwork) {
+ this.type = aNetwork.type;
+ this.id = aNetwork.id;
+ },
+
+ classID : NETWORKSTATSINTERFACE_CID,
+
+ contractID: NETWORKSTATSINTERFACE_CONTRACTID,
+ QueryInterface : XPCOMUtils.generateQI([])
+}
+
+// NetworkStats
+const NETWORKSTATS_CID = Components.ID("{28904f59-8497-4ac0-904f-2af14b7fd3de}");
+
+function NetworkStats(aWindow, aStats) {
+ if (DEBUG) {
+ debug("NetworkStats Constructor");
+ }
+ this.appManifestURL = aStats.appManifestURL || null;
+ this.browsingTrafficOnly = aStats.browsingTrafficOnly || false;
+ this.serviceType = aStats.serviceType || null;
+ this.network = new aWindow.MozNetworkStatsInterface(aStats.network);
+ this.start = aStats.start ? new aWindow.Date(aStats.start.getTime()) : null;
+ this.end = aStats.end ? new aWindow.Date(aStats.end.getTime()) : null;
+
+ let samples = this.data = new aWindow.Array();
+ for (let i = 0; i < aStats.data.length; i++) {
+ samples.push(aWindow.MozNetworkStatsData._create(
+ aWindow, new NetworkStatsData(aWindow, aStats.data[i])));
+ }
+}
+
+NetworkStats.prototype = {
+ classID : NETWORKSTATS_CID,
+
+ QueryInterface : XPCOMUtils.generateQI()
+}
+
+// NetworkStatsAlarm
+const NETWORKSTATSALARM_CID = Components.ID("{a93ea13e-409c-4189-9b1e-95fff220be55}");
+
+function NetworkStatsAlarm(aWindow, aAlarm) {
+ this.alarmId = aAlarm.id;
+ this.network = new aWindow.MozNetworkStatsInterface(aAlarm.network);
+ this.threshold = aAlarm.threshold;
+ this.data = aAlarm.data;
+}
+
+NetworkStatsAlarm.prototype = {
+ classID : NETWORKSTATSALARM_CID,
+
+ QueryInterface : XPCOMUtils.generateQI([])
+};
+
+// NetworkStatsManager
+
+const NETWORKSTATSMANAGER_CONTRACTID = "@mozilla.org/networkStatsManager;1";
+const NETWORKSTATSMANAGER_CID = Components.ID("{ceb874cd-cc1a-4e65-b404-cc2d3e42425f}");
+const nsIDOMMozNetworkStatsManager = Ci.nsIDOMMozNetworkStatsManager;
+
+function NetworkStatsManager() {
+ if (DEBUG) {
+ debug("Constructor");
+ }
+}
+
+NetworkStatsManager.prototype = {
+ __proto__: DOMRequestIpcHelper.prototype,
+
+ checkPrivileges: function checkPrivileges() {
+ if (!this.hasPrivileges) {
+ throw Components.Exception("Permission denied", Cr.NS_ERROR_FAILURE);
+ }
+ },
+
+ getSamples: function getSamples(aNetwork, aStart, aEnd, aOptions) {
+ this.checkPrivileges();
+
+ if (aStart.constructor.name !== "Date" ||
+ aEnd.constructor.name !== "Date" ||
+ !(aNetwork instanceof this.window.MozNetworkStatsInterface) ||
+ aStart > aEnd) {
+ throw Components.results.NS_ERROR_INVALID_ARG;
+ }
+
+ let appManifestURL = null;
+ let browsingTrafficOnly = false;
+ let serviceType = null;
+ if (aOptions) {
+ // appManifestURL is used to query network statistics by app;
+ // serviceType is used to query network statistics by system service.
+ // It is illegal to specify both of them at the same time.
+ if (aOptions.appManifestURL && aOptions.serviceType) {
+ throw Components.results.NS_ERROR_NOT_IMPLEMENTED;
+ }
+ // browsingTrafficOnly is meaningful only when querying by app.
+ if (!aOptions.appManifestURL && aOptions.browsingTrafficOnly) {
+ throw Components.results.NS_ERROR_NOT_IMPLEMENTED;
+ }
+ appManifestURL = aOptions.appManifestURL;
+ serviceType = aOptions.serviceType;
+ browsingTrafficOnly = aOptions.browsingTrafficOnly || false;
+ }
+
+ // TODO Bug 929410 Date object cannot correctly pass through cpmm/ppmm IPC
+ // This is just a work-around by passing timestamp numbers.
+ aStart = aStart.getTime();
+ aEnd = aEnd.getTime();
+
+ let request = this.createRequest();
+ cpmm.sendAsyncMessage("NetworkStats:Get",
+ { network: aNetwork.toJSON(),
+ start: aStart,
+ end: aEnd,
+ appManifestURL: appManifestURL,
+ browsingTrafficOnly: browsingTrafficOnly,
+ serviceType: serviceType,
+ id: this.getRequestId(request) });
+ return request;
+ },
+
+ clearStats: function clearStats(aNetwork) {
+ this.checkPrivileges();
+
+ if (!aNetwork instanceof this.window.MozNetworkStatsInterface) {
+ throw Components.results.NS_ERROR_INVALID_ARG;
+ }
+
+ let request = this.createRequest();
+ cpmm.sendAsyncMessage("NetworkStats:Clear",
+ { network: aNetwork.toJSON(),
+ id: this.getRequestId(request) });
+ return request;
+ },
+
+ clearAllStats: function clearAllStats() {
+ this.checkPrivileges();
+
+ let request = this.createRequest();
+ cpmm.sendAsyncMessage("NetworkStats:ClearAll",
+ {id: this.getRequestId(request)});
+ return request;
+ },
+
+ addAlarm: function addAlarm(aNetwork, aThreshold, aOptions) {
+ this.checkPrivileges();
+
+ if (!aOptions) {
+ aOptions = Object.create(null);
+ }
+
+ if (aOptions.startTime && aOptions.startTime.constructor.name !== "Date" ||
+ !(aNetwork instanceof this.window.MozNetworkStatsInterface)) {
+ throw Components.results.NS_ERROR_INVALID_ARG;
+ }
+
+ let request = this.createRequest();
+ cpmm.sendAsyncMessage("NetworkStats:SetAlarm",
+ {id: this.getRequestId(request),
+ data: {network: aNetwork.toJSON(),
+ threshold: aThreshold,
+ startTime: aOptions.startTime,
+ data: aOptions.data,
+ manifestURL: this.manifestURL,
+ pageURL: this.pageURL}});
+ return request;
+ },
+
+ getAllAlarms: function getAllAlarms(aNetwork) {
+ this.checkPrivileges();
+
+ let network = null;
+ if (aNetwork) {
+ if (!aNetwork instanceof this.window.MozNetworkStatsInterface) {
+ throw Components.results.NS_ERROR_INVALID_ARG;
+ }
+ network = aNetwork.toJSON();
+ }
+
+ let request = this.createRequest();
+ cpmm.sendAsyncMessage("NetworkStats:GetAlarms",
+ {id: this.getRequestId(request),
+ data: {network: network,
+ manifestURL: this.manifestURL}});
+ return request;
+ },
+
+ removeAlarms: function removeAlarms(aAlarmId) {
+ this.checkPrivileges();
+
+ if (aAlarmId == 0) {
+ aAlarmId = -1;
+ }
+
+ let request = this.createRequest();
+ cpmm.sendAsyncMessage("NetworkStats:RemoveAlarms",
+ {id: this.getRequestId(request),
+ data: {alarmId: aAlarmId,
+ manifestURL: this.manifestURL}});
+
+ return request;
+ },
+
+ getAvailableNetworks: function getAvailableNetworks() {
+ this.checkPrivileges();
+
+ let request = this.createRequest();
+ cpmm.sendAsyncMessage("NetworkStats:GetAvailableNetworks",
+ { id: this.getRequestId(request) });
+ return request;
+ },
+
+ getAvailableServiceTypes: function getAvailableServiceTypes() {
+ this.checkPrivileges();
+
+ let request = this.createRequest();
+ cpmm.sendAsyncMessage("NetworkStats:GetAvailableServiceTypes",
+ { id: this.getRequestId(request) });
+ return request;
+ },
+
+ get sampleRate() {
+ this.checkPrivileges();
+ return cpmm.sendSyncMessage("NetworkStats:SampleRate")[0];
+ },
+
+ get maxStorageAge() {
+ this.checkPrivileges();
+ return cpmm.sendSyncMessage("NetworkStats:MaxStorageAge")[0];
+ },
+
+ receiveMessage: function(aMessage) {
+ if (DEBUG) {
+ debug("NetworkStatsmanager::receiveMessage: " + aMessage.name);
+ }
+
+ let msg = aMessage.json;
+ let req = this.takeRequest(msg.id);
+ if (!req) {
+ if (DEBUG) {
+ debug("No request stored with id " + msg.id);
+ }
+ return;
+ }
+
+ switch (aMessage.name) {
+ case "NetworkStats:Get:Return":
+ if (msg.error) {
+ Services.DOMRequest.fireError(req, msg.error);
+ return;
+ }
+
+ let result = this._window.MozNetworkStats._create(
+ this._window, new NetworkStats(this._window, msg.result));
+ if (DEBUG) {
+ debug("result: " + JSON.stringify(result));
+ }
+ Services.DOMRequest.fireSuccess(req, result);
+ break;
+
+ case "NetworkStats:GetAvailableNetworks:Return":
+ if (msg.error) {
+ Services.DOMRequest.fireError(req, msg.error);
+ return;
+ }
+
+ let networks = new this._window.Array();
+ for (let i = 0; i < msg.result.length; i++) {
+ let network = new this._window.MozNetworkStatsInterface(msg.result[i]);
+ networks.push(network);
+ }
+
+ Services.DOMRequest.fireSuccess(req, networks);
+ break;
+
+ case "NetworkStats:GetAvailableServiceTypes:Return":
+ if (msg.error) {
+ Services.DOMRequest.fireError(req, msg.error);
+ return;
+ }
+
+ let serviceTypes = new this._window.Array();
+ for (let i = 0; i < msg.result.length; i++) {
+ serviceTypes.push(msg.result[i]);
+ }
+
+ Services.DOMRequest.fireSuccess(req, serviceTypes);
+ break;
+
+ case "NetworkStats:Clear:Return":
+ case "NetworkStats:ClearAll:Return":
+ if (msg.error) {
+ Services.DOMRequest.fireError(req, msg.error);
+ return;
+ }
+
+ Services.DOMRequest.fireSuccess(req, true);
+ break;
+
+ case "NetworkStats:SetAlarm:Return":
+ case "NetworkStats:RemoveAlarms:Return":
+ if (msg.error) {
+ Services.DOMRequest.fireError(req, msg.error);
+ return;
+ }
+
+ Services.DOMRequest.fireSuccess(req, msg.result);
+ break;
+
+ case "NetworkStats:GetAlarms:Return":
+ if (msg.error) {
+ Services.DOMRequest.fireError(req, msg.error);
+ return;
+ }
+
+ let alarms = new this._window.Array();
+ for (let i = 0; i < msg.result.length; i++) {
+ // The WebIDL type of data is any, so we should manually clone it
+ // into the content window.
+ if ("data" in msg.result[i]) {
+ msg.result[i].data = Cu.cloneInto(msg.result[i].data, this._window);
+ }
+ let alarm = new NetworkStatsAlarm(this._window, msg.result[i]);
+ alarms.push(this._window.MozNetworkStatsAlarm._create(this._window, alarm));
+ }
+
+ Services.DOMRequest.fireSuccess(req, alarms);
+ break;
+
+ default:
+ if (DEBUG) {
+ debug("Wrong message: " + aMessage.name);
+ }
+ }
+ },
+
+ init: function(aWindow) {
+ // Set navigator.mozNetworkStats to null.
+ if (!Services.prefs.getBoolPref("dom.mozNetworkStats.enabled")) {
+ return null;
+ }
+
+ let principal = aWindow.document.nodePrincipal;
+ let secMan = Services.scriptSecurityManager;
+ let perm = principal == secMan.getSystemPrincipal() ?
+ Ci.nsIPermissionManager.ALLOW_ACTION :
+ Services.perms.testExactPermissionFromPrincipal(principal,
+ "networkstats-manage");
+
+ // Only pages with perm set can use the netstats.
+ this.hasPrivileges = perm == Ci.nsIPermissionManager.ALLOW_ACTION;
+ if (DEBUG) {
+ debug("has privileges: " + this.hasPrivileges);
+ }
+
+ if (!this.hasPrivileges) {
+ return null;
+ }
+
+ this.initDOMRequestHelper(aWindow, ["NetworkStats:Get:Return",
+ "NetworkStats:GetAvailableNetworks:Return",
+ "NetworkStats:GetAvailableServiceTypes:Return",
+ "NetworkStats:Clear:Return",
+ "NetworkStats:ClearAll:Return",
+ "NetworkStats:SetAlarm:Return",
+ "NetworkStats:GetAlarms:Return",
+ "NetworkStats:RemoveAlarms:Return"]);
+
+ // Init app properties.
+ let appsService = Cc["@mozilla.org/AppsService;1"]
+ .getService(Ci.nsIAppsService);
+
+ this.manifestURL = appsService.getManifestURLByLocalId(principal.appId);
+
+ let isApp = !!this.manifestURL.length;
+ if (isApp) {
+ this.pageURL = principal.URI.spec;
+ }
+
+ this.window = aWindow;
+ },
+
+ // Called from DOMRequestIpcHelper
+ uninit: function uninit() {
+ if (DEBUG) {
+ debug("uninit call");
+ }
+ },
+
+ classID : NETWORKSTATSMANAGER_CID,
+ QueryInterface : XPCOMUtils.generateQI([nsIDOMMozNetworkStatsManager,
+ Ci.nsIDOMGlobalPropertyInitializer,
+ Ci.nsISupportsWeakReference,
+ Ci.nsIObserver]),
+
+ classInfo : XPCOMUtils.generateCI({classID: NETWORKSTATSMANAGER_CID,
+ contractID: NETWORKSTATSMANAGER_CONTRACTID,
+ classDescription: "NetworkStatsManager",
+ interfaces: [nsIDOMMozNetworkStatsManager],
+ flags: nsIClassInfo.DOM_OBJECT})
+}
+
+this.NSGetFactory = XPCOMUtils.generateNSGetFactory([NetworkStatsAlarm,
+ NetworkStatsData,
+ NetworkStatsInterface,
+ NetworkStats,
+ NetworkStatsManager]);
diff --git a/dom/network/NetworkStatsManager.manifest b/dom/network/NetworkStatsManager.manifest
new file mode 100644
index 000000000..623128573
--- /dev/null
+++ b/dom/network/NetworkStatsManager.manifest
@@ -0,0 +1,15 @@
+component {3b16fe17-5583-483a-b486-b64a3243221c} NetworkStatsManager.js
+contract @mozilla.org/networkStatsdata;1 {3b16fe17-5583-483a-b486-b64a3243221c}
+
+component {28904f59-8497-4ac0-904f-2af14b7fd3de} NetworkStatsManager.js
+contract @mozilla.org/networkStats;1 {28904f59-8497-4ac0-904f-2af14b7fd3de}
+
+component {f540615b-d803-43ff-8200-2a9d145a5645} NetworkStatsManager.js
+contract @mozilla.org/networkstatsinterface;1 {f540615b-d803-43ff-8200-2a9d145a5645}
+
+component {a93ea13e-409c-4189-9b1e-95fff220be55} NetworkStatsManager.js
+contract @mozilla.org/networkstatsalarm;1 {a93ea13e-409c-4189-9b1e-95fff220be55}
+
+component {ceb874cd-cc1a-4e65-b404-cc2d3e42425f} NetworkStatsManager.js
+contract @mozilla.org/networkStatsManager;1 {ceb874cd-cc1a-4e65-b404-cc2d3e42425f}
+category JavaScript-navigator-property mozNetworkStats @mozilla.org/networkStatsManager;1
diff --git a/dom/network/NetworkStatsService.jsm b/dom/network/NetworkStatsService.jsm
new file mode 100644
index 000000000..3a632903d
--- /dev/null
+++ b/dom/network/NetworkStatsService.jsm
@@ -0,0 +1,1166 @@
+/* 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/. */
+
+"use strict";
+
+const DEBUG = false;
+function debug(s) {
+ if (DEBUG) {
+ dump("-*- NetworkStatsService: " + s + "\n");
+ }
+}
+
+const {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components;
+
+this.EXPORTED_SYMBOLS = ["NetworkStatsService"];
+
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+Cu.import("resource://gre/modules/Services.jsm");
+Cu.import("resource://gre/modules/NetworkStatsDB.jsm");
+Cu.import("resource://gre/modules/Timer.jsm");
+
+const NET_NETWORKSTATSSERVICE_CONTRACTID = "@mozilla.org/network/netstatsservice;1";
+const NET_NETWORKSTATSSERVICE_CID = Components.ID("{18725604-e9ac-488a-8aa0-2471e7f6c0a4}");
+
+const TOPIC_BANDWIDTH_CONTROL = "netd-bandwidth-control"
+
+const TOPIC_CONNECTION_STATE_CHANGED = "network-connection-state-changed";
+const NET_TYPE_WIFI = Ci.nsINetworkInterface.NETWORK_TYPE_WIFI;
+const NET_TYPE_MOBILE = Ci.nsINetworkInterface.NETWORK_TYPE_MOBILE;
+
+// Networks have different status that NetworkStats API needs to be aware of.
+// Network is present and ready, so NetworkManager provides the whole info.
+const NETWORK_STATUS_READY = 0;
+// Network is present but hasn't established a connection yet (e.g. SIM that has not
+// enabled 3G since boot).
+const NETWORK_STATUS_STANDBY = 1;
+// Network is not present, but stored in database by the previous connections.
+const NETWORK_STATUS_AWAY = 2;
+
+// The maximum traffic amount can be saved in the |cachedStats|.
+const MAX_CACHED_TRAFFIC = 500 * 1000 * 1000; // 500 MB
+
+const QUEUE_TYPE_UPDATE_STATS = 0;
+const QUEUE_TYPE_UPDATE_CACHE = 1;
+const QUEUE_TYPE_WRITE_CACHE = 2;
+
+XPCOMUtils.defineLazyServiceGetter(this, "ppmm",
+ "@mozilla.org/parentprocessmessagemanager;1",
+ "nsIMessageListenerManager");
+
+XPCOMUtils.defineLazyServiceGetter(this, "gRil",
+ "@mozilla.org/ril;1",
+ "nsIRadioInterfaceLayer");
+
+XPCOMUtils.defineLazyServiceGetter(this, "networkService",
+ "@mozilla.org/network/service;1",
+ "nsINetworkService");
+
+XPCOMUtils.defineLazyServiceGetter(this, "appsService",
+ "@mozilla.org/AppsService;1",
+ "nsIAppsService");
+
+XPCOMUtils.defineLazyServiceGetter(this, "gSettingsService",
+ "@mozilla.org/settingsService;1",
+ "nsISettingsService");
+
+XPCOMUtils.defineLazyServiceGetter(this, "messenger",
+ "@mozilla.org/system-message-internal;1",
+ "nsISystemMessagesInternal");
+
+this.NetworkStatsService = {
+ init: function() {
+ debug("Service started");
+
+ Services.obs.addObserver(this, "xpcom-shutdown", false);
+ Services.obs.addObserver(this, TOPIC_CONNECTION_STATE_CHANGED, false);
+ Services.obs.addObserver(this, TOPIC_BANDWIDTH_CONTROL, false);
+ Services.obs.addObserver(this, "profile-after-change", false);
+
+ this.timer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
+
+ // Object to store network interfaces, each network interface is composed
+ // by a network object (network type and network Id) and a interfaceName
+ // that contains the name of the physical interface (wlan0, rmnet0, etc.).
+ // The network type can be 0 for wifi or 1 for mobile. On the other hand,
+ // the network id is '0' for wifi or the iccid for mobile (SIM).
+ // Each networkInterface is placed in the _networks object by the index of
+ // 'networkId + networkType'.
+ //
+ // _networks object allows to map available network interfaces at low level
+ // (wlan0, rmnet0, etc.) to a network. It's not mandatory to have a
+ // networkInterface per network but can't exist a networkInterface not
+ // being mapped to a network.
+
+ this._networks = Object.create(null);
+
+ // There is no way to know a priori if wifi connection is available,
+ // just when the wifi driver is loaded, but it is unloaded when
+ // wifi is switched off. So wifi connection is hardcoded
+ let netId = this.getNetworkId('0', NET_TYPE_WIFI);
+ this._networks[netId] = { network: { id: '0',
+ type: NET_TYPE_WIFI },
+ interfaceName: null,
+ status: NETWORK_STATUS_STANDBY };
+
+ this.messages = ["NetworkStats:Get",
+ "NetworkStats:Clear",
+ "NetworkStats:ClearAll",
+ "NetworkStats:SetAlarm",
+ "NetworkStats:GetAlarms",
+ "NetworkStats:RemoveAlarms",
+ "NetworkStats:GetAvailableNetworks",
+ "NetworkStats:GetAvailableServiceTypes",
+ "NetworkStats:SampleRate",
+ "NetworkStats:MaxStorageAge"];
+
+ this.messages.forEach(function(aMsgName) {
+ ppmm.addMessageListener(aMsgName, this);
+ }, this);
+
+ this._db = new NetworkStatsDB();
+
+ // Stats for all interfaces are updated periodically
+ this.timer.initWithCallback(this, this._db.sampleRate,
+ Ci.nsITimer.TYPE_REPEATING_PRECISE);
+
+ // Stats not from netd are firstly stored in the cached.
+ this.cachedStats = Object.create(null);
+ this.cachedStatsDate = new Date();
+
+ this.updateQueue = [];
+ this.isQueueRunning = false;
+
+ this._currentAlarms = {};
+ this.initAlarms();
+ },
+
+ receiveMessage: function(aMessage) {
+ if (!aMessage.target.assertPermission("networkstats-manage")) {
+ return;
+ }
+
+ debug("receiveMessage " + aMessage.name);
+
+ let mm = aMessage.target;
+ let msg = aMessage.json;
+
+ switch (aMessage.name) {
+ case "NetworkStats:Get":
+ this.getSamples(mm, msg);
+ break;
+ case "NetworkStats:Clear":
+ this.clearInterfaceStats(mm, msg);
+ break;
+ case "NetworkStats:ClearAll":
+ this.clearDB(mm, msg);
+ break;
+ case "NetworkStats:SetAlarm":
+ this.setAlarm(mm, msg);
+ break;
+ case "NetworkStats:GetAlarms":
+ this.getAlarms(mm, msg);
+ break;
+ case "NetworkStats:RemoveAlarms":
+ this.removeAlarms(mm, msg);
+ break;
+ case "NetworkStats:GetAvailableNetworks":
+ this.getAvailableNetworks(mm, msg);
+ break;
+ case "NetworkStats:GetAvailableServiceTypes":
+ this.getAvailableServiceTypes(mm, msg);
+ break;
+ case "NetworkStats:SampleRate":
+ // This message is sync.
+ return this._db.sampleRate;
+ case "NetworkStats:MaxStorageAge":
+ // This message is sync.
+ return this._db.maxStorageSamples * this._db.sampleRate;
+ }
+ },
+
+ observe: function observe(aSubject, aTopic, aData) {
+ switch (aTopic) {
+ case TOPIC_CONNECTION_STATE_CHANGED:
+
+ // If new interface is registered (notified from NetworkService),
+ // the stats are updated for the new interface without waiting to
+ // complete the updating period.
+
+ let network = aSubject.QueryInterface(Ci.nsINetworkInterface);
+ debug("Network " + network.name + " of type " + network.type + " status change");
+
+ let netId = this.convertNetworkInterface(network);
+ if (!netId) {
+ break;
+ }
+
+ this._updateCurrentAlarm(netId);
+
+ debug("NetId: " + netId);
+ this.updateStats(netId);
+ break;
+
+ case TOPIC_BANDWIDTH_CONTROL:
+ debug("Bandwidth message from netd: " + JSON.stringify(aData));
+
+ let interfaceName = aData.substring(aData.lastIndexOf(" ") + 1);
+ for (let networkId in this._networks) {
+ if (interfaceName == this._networks[networkId].interfaceName) {
+ let currentAlarm = this._currentAlarms[networkId];
+ if (Object.getOwnPropertyNames(currentAlarm).length !== 0) {
+ this._fireAlarm(currentAlarm.alarm);
+ }
+ break;
+ }
+ }
+ break;
+
+ case "xpcom-shutdown":
+ debug("Service shutdown");
+
+ this.messages.forEach(function(aMsgName) {
+ ppmm.removeMessageListener(aMsgName, this);
+ }, this);
+
+ Services.obs.removeObserver(this, "xpcom-shutdown");
+ Services.obs.removeObserver(this, "profile-after-change");
+ Services.obs.removeObserver(this, TOPIC_CONNECTION_STATE_CHANGED);
+ Services.obs.removeObserver(this, TOPIC_BANDWIDTH_CONTROL);
+
+ this.timer.cancel();
+ this.timer = null;
+
+ // Update stats before shutdown
+ this.updateAllStats();
+ break;
+ }
+ },
+
+ /*
+ * nsITimerCallback
+ * Timer triggers the update of all stats
+ */
+ notify: function(aTimer) {
+ this.updateAllStats();
+ },
+
+ /*
+ * nsINetworkStatsService
+ */
+ getRilNetworks: function() {
+ let networks = {};
+ let numRadioInterfaces = gRil.numRadioInterfaces;
+ for (let i = 0; i < numRadioInterfaces; i++) {
+ let radioInterface = gRil.getRadioInterface(i);
+ if (radioInterface.rilContext.iccInfo) {
+ let netId = this.getNetworkId(radioInterface.rilContext.iccInfo.iccid,
+ NET_TYPE_MOBILE);
+ networks[netId] = { id : radioInterface.rilContext.iccInfo.iccid,
+ type: NET_TYPE_MOBILE };
+ }
+ }
+ return networks;
+ },
+
+ convertNetworkInterface: function(aNetwork) {
+ if (aNetwork.type != NET_TYPE_MOBILE &&
+ aNetwork.type != NET_TYPE_WIFI) {
+ return null;
+ }
+
+ let id = '0';
+ if (aNetwork.type == NET_TYPE_MOBILE) {
+ if (!(aNetwork instanceof Ci.nsIRilNetworkInterface)) {
+ debug("Error! Mobile network should be an nsIRilNetworkInterface!");
+ return null;
+ }
+
+ let rilNetwork = aNetwork.QueryInterface(Ci.nsIRilNetworkInterface);
+ id = rilNetwork.iccId;
+ }
+
+ let netId = this.getNetworkId(id, aNetwork.type);
+
+ if (!this._networks[netId]) {
+ this._networks[netId] = Object.create(null);
+ this._networks[netId].network = { id: id,
+ type: aNetwork.type };
+ }
+
+ this._networks[netId].status = NETWORK_STATUS_READY;
+ this._networks[netId].interfaceName = aNetwork.name;
+ return netId;
+ },
+
+ getNetworkId: function getNetworkId(aIccId, aNetworkType) {
+ return aIccId + '' + aNetworkType;
+ },
+
+ /* Function to ensure that one network is valid. The network is valid if its status is
+ * NETWORK_STATUS_READY, NETWORK_STATUS_STANDBY or NETWORK_STATUS_AWAY.
+ *
+ * The result is |netId| or null in case of a non-valid network
+ * aCallback is signatured as |function(netId)|.
+ */
+ validateNetwork: function validateNetwork(aNetwork, aCallback) {
+ let netId = this.getNetworkId(aNetwork.id, aNetwork.type);
+
+ if (this._networks[netId]) {
+ aCallback(netId);
+ return;
+ }
+
+ // Check if network is valid (RIL entry) but has not established a connection yet.
+ // If so add to networks list with empty interfaceName.
+ let rilNetworks = this.getRilNetworks();
+ if (rilNetworks[netId]) {
+ this._networks[netId] = Object.create(null);
+ this._networks[netId].network = rilNetworks[netId];
+ this._networks[netId].status = NETWORK_STATUS_STANDBY;
+ this._currentAlarms[netId] = Object.create(null);
+ aCallback(netId);
+ return;
+ }
+
+ // Check if network is available in the DB.
+ this._db.isNetworkAvailable(aNetwork, function(aError, aResult) {
+ if (aResult) {
+ this._networks[netId] = Object.create(null);
+ this._networks[netId].network = aNetwork;
+ this._networks[netId].status = NETWORK_STATUS_AWAY;
+ this._currentAlarms[netId] = Object.create(null);
+ aCallback(netId);
+ return;
+ }
+
+ aCallback(null);
+ }.bind(this));
+ },
+
+ getAvailableNetworks: function getAvailableNetworks(mm, msg) {
+ let self = this;
+ let rilNetworks = this.getRilNetworks();
+ this._db.getAvailableNetworks(function onGetNetworks(aError, aResult) {
+
+ // Also return the networks that are valid but have not
+ // established connections yet.
+ for (let netId in rilNetworks) {
+ let found = false;
+ for (let i = 0; i < aResult.length; i++) {
+ if (netId == self.getNetworkId(aResult[i].id, aResult[i].type)) {
+ found = true;
+ break;
+ }
+ }
+ if (!found) {
+ aResult.push(rilNetworks[netId]);
+ }
+ }
+
+ mm.sendAsyncMessage("NetworkStats:GetAvailableNetworks:Return",
+ { id: msg.id, error: aError, result: aResult });
+ });
+ },
+
+ getAvailableServiceTypes: function getAvailableServiceTypes(mm, msg) {
+ this._db.getAvailableServiceTypes(function onGetServiceTypes(aError, aResult) {
+ mm.sendAsyncMessage("NetworkStats:GetAvailableServiceTypes:Return",
+ { id: msg.id, error: aError, result: aResult });
+ });
+ },
+
+ initAlarms: function initAlarms() {
+ debug("Init usage alarms");
+ let self = this;
+
+ for (let netId in this._networks) {
+ this._currentAlarms[netId] = Object.create(null);
+
+ this._db.getFirstAlarm(netId, function getResult(error, result) {
+ if (!error && result) {
+ self._setAlarm(result, function onSet(error, success) {
+ if (error == "InvalidStateError") {
+ self._fireAlarm(result);
+ }
+ });
+ }
+ });
+ }
+ },
+
+ /*
+ * Function called from manager to get stats from database.
+ * In order to return updated stats, first is performed a call to
+ * updateAllStats function, which will get last stats from netd
+ * and update the database.
+ * Then, depending on the request (stats per appId or total stats)
+ * it retrieve them from database and return to the manager.
+ */
+ getSamples: function getSamples(mm, msg) {
+ let network = msg.network;
+ let netId = this.getNetworkId(network.id, network.type);
+
+ let appId = 0;
+ let appManifestURL = msg.appManifestURL;
+ if (appManifestURL) {
+ appId = appsService.getAppLocalIdByManifestURL(appManifestURL);
+
+ if (!appId) {
+ mm.sendAsyncMessage("NetworkStats:Get:Return",
+ { id: msg.id,
+ error: "Invalid appManifestURL", result: null });
+ return;
+ }
+ }
+
+ let browsingTrafficOnly = msg.browsingTrafficOnly || false;
+ let serviceType = msg.serviceType || "";
+
+ let start = new Date(msg.start);
+ let end = new Date(msg.end);
+
+ let callback = (function (aError, aResult) {
+ this._db.find(function onStatsFound(aError, aResult) {
+ mm.sendAsyncMessage("NetworkStats:Get:Return",
+ { id: msg.id, error: aError, result: aResult });
+ }, appId, browsingTrafficOnly, serviceType, network, start, end, appManifestURL);
+ }).bind(this);
+
+ this.validateNetwork(network, function onValidateNetwork(aNetId) {
+ if (!aNetId) {
+ mm.sendAsyncMessage("NetworkStats:Get:Return",
+ { id: msg.id, error: "Invalid connectionType", result: null });
+ return;
+ }
+
+ // If network is currently active we need to update the cached stats first before
+ // retrieving stats from the DB.
+ if (this._networks[aNetId].status == NETWORK_STATUS_READY) {
+ debug("getstats for network " + network.id + " of type " + network.type);
+ debug("appId: " + appId + " from appManifestURL: " + appManifestURL);
+ debug("browsingTrafficOnly: " + browsingTrafficOnly);
+ debug("serviceType: " + serviceType);
+
+ if (appId || serviceType) {
+ this.updateCachedStats(callback);
+ return;
+ }
+
+ this.updateStats(aNetId, function onStatsUpdated(aResult, aMessage) {
+ this.updateCachedStats(callback);
+ }.bind(this));
+ return;
+ }
+
+ // Network not active, so no need to update
+ this._db.find(function onStatsFound(aError, aResult) {
+ mm.sendAsyncMessage("NetworkStats:Get:Return",
+ { id: msg.id, error: aError, result: aResult });
+ }, appId, browsingTrafficOnly, serviceType, network, start, end, appManifestURL);
+ }.bind(this));
+ },
+
+ clearInterfaceStats: function clearInterfaceStats(mm, msg) {
+ let self = this;
+ let network = msg.network;
+
+ debug("clear stats for network " + network.id + " of type " + network.type);
+
+ this.validateNetwork(network, function onValidateNetwork(aNetId) {
+ if (!aNetId) {
+ mm.sendAsyncMessage("NetworkStats:Clear:Return",
+ { id: msg.id, error: "Invalid connectionType", result: null });
+ return;
+ }
+
+ network = {network: network, networkId: aNetId};
+ self.updateStats(aNetId, function onUpdate(aResult, aMessage) {
+ if (!aResult) {
+ mm.sendAsyncMessage("NetworkStats:Clear:Return",
+ { id: msg.id, error: aMessage, result: null });
+ return;
+ }
+
+ self._db.clearInterfaceStats(network, function onDBCleared(aError, aResult) {
+ self._updateCurrentAlarm(aNetId);
+ mm.sendAsyncMessage("NetworkStats:Clear:Return",
+ { id: msg.id, error: aError, result: aResult });
+ });
+ });
+ });
+ },
+
+ clearDB: function clearDB(mm, msg) {
+ let self = this;
+ this._db.getAvailableNetworks(function onGetNetworks(aError, aResult) {
+ if (aError) {
+ mm.sendAsyncMessage("NetworkStats:ClearAll:Return",
+ { id: msg.id, error: aError, result: aResult });
+ return;
+ }
+
+ let networks = aResult;
+ networks.forEach(function(network, index) {
+ networks[index] = {network: network, networkId: self.getNetworkId(network.id, network.type)};
+ }, self);
+
+ self.updateAllStats(function onUpdate(aResult, aMessage){
+ if (!aResult) {
+ mm.sendAsyncMessage("NetworkStats:ClearAll:Return",
+ { id: msg.id, error: aMessage, result: null });
+ return;
+ }
+
+ self._db.clearStats(networks, function onDBCleared(aError, aResult) {
+ networks.forEach(function(network, index) {
+ self._updateCurrentAlarm(network.networkId);
+ }, self);
+ mm.sendAsyncMessage("NetworkStats:ClearAll:Return",
+ { id: msg.id, error: aError, result: aResult });
+ });
+ });
+ });
+ },
+
+ updateAllStats: function updateAllStats(aCallback) {
+ let elements = [];
+ let lastElement = null;
+ let callback = (function (success, message) {
+ this.updateCachedStats(aCallback);
+ }).bind(this);
+
+ // For each connectionType create an object containning the type
+ // and the 'queueIndex', the 'queueIndex' is an integer representing
+ // the index of a connection type in the global queue array. So, if
+ // the connection type is already in the queue it is not appended again,
+ // else it is pushed in 'elements' array, which later will be pushed to
+ // the queue array.
+ for (let netId in this._networks) {
+ if (this._networks[netId].status != NETWORK_STATUS_READY) {
+ continue;
+ }
+
+ lastElement = { netId: netId,
+ queueIndex: this.updateQueueIndex(netId) };
+
+ if (lastElement.queueIndex == -1) {
+ elements.push({ netId: lastElement.netId,
+ callbacks: [],
+ queueType: QUEUE_TYPE_UPDATE_STATS });
+ }
+ }
+
+ if (!lastElement) {
+ // No elements need to be updated, probably because status is different than
+ // NETWORK_STATUS_READY.
+ if (aCallback) {
+ aCallback(true, "OK");
+ }
+ return;
+ }
+
+ if (elements.length > 0) {
+ // If length of elements is greater than 0, callback is set to
+ // the last element.
+ elements[elements.length - 1].callbacks.push(callback);
+ this.updateQueue = this.updateQueue.concat(elements);
+ } else {
+ // Else, it means that all connection types are already in the queue to
+ // be updated, so callback for this request is added to
+ // the element in the main queue with the index of the last 'lastElement'.
+ // But before is checked that element is still in the queue because it can
+ // be processed while generating 'elements' array.
+ let element = this.updateQueue[lastElement.queueIndex];
+ if (aCallback &&
+ (!element || element.netId != lastElement.netId)) {
+ aCallback();
+ return;
+ }
+
+ this.updateQueue[lastElement.queueIndex].callbacks.push(callback);
+ }
+
+ // Call the function that process the elements of the queue.
+ this.processQueue();
+
+ if (DEBUG) {
+ this.logAllRecords();
+ }
+ },
+
+ updateStats: function updateStats(aNetId, aCallback) {
+ // Check if the connection is in the main queue, push a new element
+ // if it is not being processed or add a callback if it is.
+ let index = this.updateQueueIndex(aNetId);
+ if (index == -1) {
+ this.updateQueue.push({ netId: aNetId,
+ callbacks: [aCallback],
+ queueType: QUEUE_TYPE_UPDATE_STATS });
+ } else {
+ this.updateQueue[index].callbacks.push(aCallback);
+ return;
+ }
+
+ // Call the function that process the elements of the queue.
+ this.processQueue();
+ },
+
+ /*
+ * Find if a connection is in the main queue array and return its
+ * index, if it is not in the array return -1.
+ */
+ updateQueueIndex: function updateQueueIndex(aNetId) {
+ return this.updateQueue.map(function(e) { return e.netId; }).indexOf(aNetId);
+ },
+
+ /*
+ * Function responsible of process all requests in the queue.
+ */
+ processQueue: function processQueue(aResult, aMessage) {
+ // If aResult is not undefined, the caller of the function is the result
+ // of processing an element, so remove that element and call the callbacks
+ // it has.
+ let self = this;
+
+ if (aResult != undefined) {
+ let item = this.updateQueue.shift();
+ for (let callback of item.callbacks) {
+ if (callback) {
+ callback(aResult, aMessage);
+ }
+ }
+ } else {
+ // The caller is a function that has pushed new elements to the queue,
+ // if isQueueRunning is false it means there is no processing currently
+ // being done, so start.
+ if (this.isQueueRunning) {
+ return;
+ } else {
+ this.isQueueRunning = true;
+ }
+ }
+
+ // Check length to determine if queue is empty and stop processing.
+ if (this.updateQueue.length < 1) {
+ this.isQueueRunning = false;
+ return;
+ }
+
+ // Process the next item as soon as possible.
+ setTimeout(function () {
+ self.run(self.updateQueue[0]);
+ }, 0);
+ },
+
+ run: function run(item) {
+ switch (item.queueType) {
+ case QUEUE_TYPE_UPDATE_STATS:
+ this.update(item.netId, this.processQueue.bind(this));
+ break;
+ case QUEUE_TYPE_UPDATE_CACHE:
+ this.updateCache(this.processQueue.bind(this));
+ break;
+ case QUEUE_TYPE_WRITE_CACHE:
+ this.writeCache(item.stats, this.processQueue.bind(this));
+ break;
+ }
+ },
+
+ update: function update(aNetId, aCallback) {
+ // Check if connection type is valid.
+ if (!this._networks[aNetId]) {
+ if (aCallback) {
+ aCallback(false, "Invalid network " + aNetId);
+ }
+ return;
+ }
+
+ let interfaceName = this._networks[aNetId].interfaceName;
+ debug("Update stats for " + interfaceName);
+
+ // Request stats to NetworkService, which will get stats from netd, passing
+ // 'networkStatsAvailable' as a callback.
+ if (interfaceName) {
+ networkService.getNetworkInterfaceStats(interfaceName,
+ this.networkStatsAvailable.bind(this, aCallback, aNetId));
+ return;
+ }
+
+ if (aCallback) {
+ aCallback(true, "ok");
+ }
+ },
+
+ /*
+ * Callback of request stats. Store stats in database.
+ */
+ networkStatsAvailable: function networkStatsAvailable(aCallback, aNetId,
+ aResult, aRxBytes,
+ aTxBytes, aTimestamp) {
+ if (!aResult) {
+ if (aCallback) {
+ aCallback(false, "Netd IPC error");
+ }
+ return;
+ }
+
+ let stats = { appId: 0,
+ isInBrowser: false,
+ serviceType: "",
+ networkId: this._networks[aNetId].network.id,
+ networkType: this._networks[aNetId].network.type,
+ date: new Date(aTimestamp),
+ rxBytes: aTxBytes,
+ txBytes: aRxBytes,
+ isAccumulative: true };
+
+ debug("Update stats for: " + JSON.stringify(stats));
+
+ this._db.saveStats(stats, function onSavedStats(aError, aResult) {
+ if (aCallback) {
+ if (aError) {
+ aCallback(false, aError);
+ return;
+ }
+
+ aCallback(true, "OK");
+ }
+ });
+ },
+
+ /*
+ * Function responsible for receiving stats which are not from netd.
+ */
+ saveStats: function saveStats(aAppId, aIsInBrowser, aServiceType, aNetwork,
+ aTimeStamp, aRxBytes, aTxBytes, aIsAccumulative,
+ aCallback) {
+ let netId = this.convertNetworkInterface(aNetwork);
+ if (!netId) {
+ if (aCallback) {
+ aCallback(false, "Invalid network type");
+ }
+ return;
+ }
+
+ // Check if |aConnectionType|, |aAppId| and |aServiceType| are valid.
+ // There are two invalid cases for the combination of |aAppId| and
+ // |aServiceType|:
+ // a. Both |aAppId| is non-zero and |aServiceType| is non-empty.
+ // b. Both |aAppId| is zero and |aServiceType| is empty.
+ if (!this._networks[netId] || (aAppId && aServiceType) ||
+ (!aAppId && !aServiceType)) {
+ debug("Invalid network interface, appId or serviceType");
+ return;
+ }
+
+ let stats = { appId: aAppId,
+ isInBrowser: aIsInBrowser,
+ serviceType: aServiceType,
+ networkId: this._networks[netId].network.id,
+ networkType: this._networks[netId].network.type,
+ date: new Date(aTimeStamp),
+ rxBytes: aRxBytes,
+ txBytes: aTxBytes,
+ isAccumulative: aIsAccumulative };
+
+ this.updateQueue.push({ stats: stats,
+ callbacks: [aCallback],
+ queueType: QUEUE_TYPE_WRITE_CACHE });
+
+ this.processQueue();
+ },
+
+ /*
+ *
+ */
+ writeCache: function writeCache(aStats, aCallback) {
+ debug("saveStats: " + aStats.appId + " " + aStats.isInBrowser + " " +
+ aStats.serviceType + " " + aStats.networkId + " " +
+ aStats.networkType + " " + aStats.date + " " +
+ aStats.rxBytes + " " + aStats.txBytes);
+
+ // Generate an unique key from |appId|, |isInBrowser|, |serviceType| and
+ // |netId|, which is used to retrieve data in |cachedStats|.
+ let netId = this.getNetworkId(aStats.networkId, aStats.networkType);
+ let key = aStats.appId + "" + aStats.isInBrowser + "" +
+ aStats.serviceType + "" + netId;
+
+ // |cachedStats| only keeps the data with the same date.
+ // If the incoming date is different from |cachedStatsDate|,
+ // both |cachedStats| and |cachedStatsDate| will get updated.
+ let diff = (this._db.normalizeDate(aStats.date) -
+ this._db.normalizeDate(this.cachedStatsDate)) /
+ this._db.sampleRate;
+ if (diff != 0) {
+ this.updateCache(function onUpdated(success, message) {
+ this.cachedStatsDate = aStats.date;
+ this.cachedStats[key] = aStats;
+
+ if (aCallback) {
+ aCallback(true, "ok");
+ }
+ }.bind(this));
+ return;
+ }
+
+ // Try to find the matched row in the cached by |appId| and |connectionType|.
+ // If not found, save the incoming data into the cached.
+ let cachedStats = this.cachedStats[key];
+ if (!cachedStats) {
+ this.cachedStats[key] = aStats;
+ if (aCallback) {
+ aCallback(true, "ok");
+ }
+ return;
+ }
+
+ // Find matched row, accumulate the traffic amount.
+ cachedStats.rxBytes += aStats.rxBytes;
+ cachedStats.txBytes += aStats.txBytes;
+
+ // If new rxBytes or txBytes exceeds MAX_CACHED_TRAFFIC
+ // the corresponding row will be saved to indexedDB.
+ // Then, the row will be removed from the cached.
+ if (cachedStats.rxBytes > MAX_CACHED_TRAFFIC ||
+ cachedStats.txBytes > MAX_CACHED_TRAFFIC) {
+ this._db.saveStats(cachedStats, function (error, result) {
+ debug("Application stats inserted in indexedDB");
+ if (aCallback) {
+ aCallback(true, "ok");
+ }
+ });
+ delete this.cachedStats[key];
+ return;
+ }
+
+ if (aCallback) {
+ aCallback(true, "ok");
+ }
+ },
+
+ updateCachedStats: function updateCachedStats(aCallback) {
+ this.updateQueue.push({ callbacks: [aCallback],
+ queueType: QUEUE_TYPE_UPDATE_CACHE });
+
+ this.processQueue();
+ },
+
+ updateCache: function updateCache(aCallback) {
+ debug("updateCache: " + this.cachedStatsDate);
+
+ let stats = Object.keys(this.cachedStats);
+ if (stats.length == 0) {
+ // |cachedStats| is empty, no need to update.
+ if (aCallback) {
+ aCallback(true, "no need to update");
+ }
+ return;
+ }
+
+ let index = 0;
+ this._db.saveStats(this.cachedStats[stats[index]],
+ function onSavedStats(error, result) {
+ debug("Application stats inserted in indexedDB");
+
+ // Clean up the |cachedStats| after updating.
+ if (index == stats.length - 1) {
+ this.cachedStats = Object.create(null);
+
+ if (aCallback) {
+ aCallback(true, "ok");
+ }
+ return;
+ }
+
+ // Update is not finished, keep updating.
+ index += 1;
+ this._db.saveStats(this.cachedStats[stats[index]],
+ onSavedStats.bind(this, error, result));
+ }.bind(this));
+ },
+
+ get maxCachedTraffic () {
+ return MAX_CACHED_TRAFFIC;
+ },
+
+ logAllRecords: function logAllRecords() {
+ this._db.logAllRecords(function onResult(aError, aResult) {
+ if (aError) {
+ debug("Error: " + aError);
+ return;
+ }
+
+ debug("===== LOG =====");
+ debug("There are " + aResult.length + " items");
+ debug(JSON.stringify(aResult));
+ });
+ },
+
+ getAlarms: function getAlarms(mm, msg) {
+ let self = this;
+ let network = msg.data.network;
+ let manifestURL = msg.data.manifestURL;
+
+ if (network) {
+ this.validateNetwork(network, function onValidateNetwork(aNetId) {
+ if (!aNetId) {
+ mm.sendAsyncMessage("NetworkStats:GetAlarms:Return",
+ { id: msg.id, error: "InvalidInterface", result: null });
+ return;
+ }
+
+ self._getAlarms(mm, msg, aNetId, manifestURL);
+ });
+ return;
+ }
+
+ this._getAlarms(mm, msg, null, manifestURL);
+ },
+
+ _getAlarms: function _getAlarms(mm, msg, aNetId, aManifestURL) {
+ let self = this;
+ this._db.getAlarms(aNetId, aManifestURL, function onCompleted(error, result) {
+ if (error) {
+ mm.sendAsyncMessage("NetworkStats:GetAlarms:Return",
+ { id: msg.id, error: error, result: result });
+ return;
+ }
+
+ let alarms = []
+ // NetworkStatsManager must return the network instead of the networkId.
+ for (let i = 0; i < result.length; i++) {
+ let alarm = result[i];
+ alarms.push({ id: alarm.id,
+ network: self._networks[alarm.networkId].network,
+ threshold: alarm.absoluteThreshold,
+ data: alarm.data });
+ }
+
+ mm.sendAsyncMessage("NetworkStats:GetAlarms:Return",
+ { id: msg.id, error: null, result: alarms });
+ });
+ },
+
+ removeAlarms: function removeAlarms(mm, msg) {
+ let alarmId = msg.data.alarmId;
+ let manifestURL = msg.data.manifestURL;
+
+ let self = this;
+ let callback = function onRemove(error, result) {
+ if (error) {
+ mm.sendAsyncMessage("NetworkStats:RemoveAlarms:Return",
+ { id: msg.id, error: error, result: result });
+ return;
+ }
+
+ for (let i in self._currentAlarms) {
+ let currentAlarm = self._currentAlarms[i].alarm;
+ if (currentAlarm && ((alarmId == currentAlarm.id) ||
+ (alarmId == -1 && currentAlarm.manifestURL == manifestURL))) {
+
+ self._updateCurrentAlarm(currentAlarm.networkId);
+ }
+ }
+
+ mm.sendAsyncMessage("NetworkStats:RemoveAlarms:Return",
+ { id: msg.id, error: error, result: true });
+ };
+
+ if (alarmId == -1) {
+ this._db.removeAlarms(manifestURL, callback);
+ } else {
+ this._db.removeAlarm(alarmId, manifestURL, callback);
+ }
+ },
+
+ /*
+ * Function called from manager to set an alarm.
+ */
+ setAlarm: function setAlarm(mm, msg) {
+ let options = msg.data;
+ let network = options.network;
+ let threshold = options.threshold;
+
+ debug("Set alarm at " + threshold + " for " + JSON.stringify(network));
+
+ if (threshold < 0) {
+ mm.sendAsyncMessage("NetworkStats:SetAlarm:Return",
+ { id: msg.id, error: "InvalidThresholdValue", result: null });
+ return;
+ }
+
+ let self = this;
+ this.validateNetwork(network, function onValidateNetwork(aNetId) {
+ if (!aNetId) {
+ mm.sendAsyncMessage("NetworkStats:SetAlarm:Return",
+ { id: msg.id, error: "InvalidiConnectionType", result: null });
+ return;
+ }
+
+ let newAlarm = {
+ id: null,
+ networkId: aNetId,
+ absoluteThreshold: threshold,
+ relativeThreshold: null,
+ startTime: options.startTime,
+ data: options.data,
+ pageURL: options.pageURL,
+ manifestURL: options.manifestURL
+ };
+
+ self._getAlarmQuota(newAlarm, function onUpdate(error, quota) {
+ if (error) {
+ mm.sendAsyncMessage("NetworkStats:SetAlarm:Return",
+ { id: msg.id, error: error, result: null });
+ return;
+ }
+
+ self._db.addAlarm(newAlarm, function addSuccessCb(error, newId) {
+ if (error) {
+ mm.sendAsyncMessage("NetworkStats:SetAlarm:Return",
+ { id: msg.id, error: error, result: null });
+ return;
+ }
+
+ newAlarm.id = newId;
+ self._setAlarm(newAlarm, function onSet(error, success) {
+ mm.sendAsyncMessage("NetworkStats:SetAlarm:Return",
+ { id: msg.id, error: error, result: newId });
+
+ if (error == "InvalidStateError") {
+ self._fireAlarm(newAlarm);
+ }
+ });
+ });
+ });
+ });
+ },
+
+ _setAlarm: function _setAlarm(aAlarm, aCallback) {
+ let currentAlarm = this._currentAlarms[aAlarm.networkId];
+ if ((Object.getOwnPropertyNames(currentAlarm).length !== 0 &&
+ aAlarm.relativeThreshold > currentAlarm.alarm.relativeThreshold) ||
+ this._networks[aAlarm.networkId].status != NETWORK_STATUS_READY) {
+ aCallback(null, true);
+ return;
+ }
+
+ let self = this;
+
+ this._getAlarmQuota(aAlarm, function onUpdate(aError, aQuota) {
+ if (aError) {
+ aCallback(aError, null);
+ return;
+ }
+
+ let callback = function onAlarmSet(aError) {
+ if (aError) {
+ debug("Set alarm error: " + aError);
+ aCallback("netdError", null);
+ return;
+ }
+
+ self._currentAlarms[aAlarm.networkId].alarm = aAlarm;
+
+ aCallback(null, true);
+ };
+
+ debug("Set alarm " + JSON.stringify(aAlarm));
+ let interfaceName = self._networks[aAlarm.networkId].interfaceName;
+ if (interfaceName) {
+ networkService.setNetworkInterfaceAlarm(interfaceName,
+ aQuota,
+ callback);
+ return;
+ }
+
+ aCallback(null, true);
+ });
+ },
+
+ _getAlarmQuota: function _getAlarmQuota(aAlarm, aCallback) {
+ let self = this;
+ this.updateStats(aAlarm.networkId, function onStatsUpdated(aResult, aMessage) {
+ self._db.getCurrentStats(self._networks[aAlarm.networkId].network,
+ aAlarm.startTime,
+ function onStatsFound(error, result) {
+ if (error) {
+ debug("Error getting stats for " +
+ JSON.stringify(self._networks[aAlarm.networkId]) + ": " + error);
+ aCallback(error, result);
+ return;
+ }
+
+ let quota = aAlarm.absoluteThreshold - result.rxBytes - result.txBytes;
+
+ // Alarm set to a threshold lower than current rx/tx bytes.
+ if (quota <= 0) {
+ aCallback("InvalidStateError", null);
+ return;
+ }
+
+ aAlarm.relativeThreshold = aAlarm.startTime
+ ? result.rxTotalBytes + result.txTotalBytes + quota
+ : aAlarm.absoluteThreshold;
+
+ aCallback(null, quota);
+ });
+ });
+ },
+
+ _fireAlarm: function _fireAlarm(aAlarm) {
+ debug("Fire alarm");
+
+ let self = this;
+ this._db.removeAlarm(aAlarm.id, null, function onRemove(aError, aResult){
+ if (!aError && !aResult) {
+ return;
+ }
+
+ self._fireSystemMessage(aAlarm);
+ self._updateCurrentAlarm(aAlarm.networkId);
+ });
+ },
+
+ _updateCurrentAlarm: function _updateCurrentAlarm(aNetworkId) {
+ this._currentAlarms[aNetworkId] = Object.create(null);
+
+ let self = this;
+ this._db.getFirstAlarm(aNetworkId, function onGet(error, result){
+ if (error) {
+ debug("Error getting the first alarm");
+ return;
+ }
+
+ if (!result) {
+ let interfaceName = self._networks[aNetworkId].interfaceName;
+ networkService.setNetworkInterfaceAlarm(interfaceName, -1,
+ function onComplete(){});
+ return;
+ }
+
+ self._setAlarm(result, function onSet(error, success){
+ if (error == "InvalidStateError") {
+ self._fireAlarm(result);
+ return;
+ }
+ });
+ });
+ },
+
+ _fireSystemMessage: function _fireSystemMessage(aAlarm) {
+ debug("Fire system message: " + JSON.stringify(aAlarm));
+
+ let manifestURI = Services.io.newURI(aAlarm.manifestURL, null, null);
+ let pageURI = Services.io.newURI(aAlarm.pageURL, null, null);
+
+ let alarm = { "id": aAlarm.id,
+ "threshold": aAlarm.absoluteThreshold,
+ "data": aAlarm.data };
+ messenger.sendMessage("networkstats-alarm", alarm, pageURI, manifestURI);
+ }
+};
+
+NetworkStatsService.init();
diff --git a/dom/network/NetworkStatsServiceProxy.js b/dom/network/NetworkStatsServiceProxy.js
new file mode 100644
index 000000000..2990c1f34
--- /dev/null
+++ b/dom/network/NetworkStatsServiceProxy.js
@@ -0,0 +1,90 @@
+/* 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/. */
+
+"use strict";
+
+const DEBUG = false;
+function debug(s) { dump("-*- NetworkStatsServiceProxy: " + s + "\n"); }
+
+const {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components;
+
+this.EXPORTED_SYMBOLS = ["NetworkStatsServiceProxy"];
+
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+Cu.import("resource://gre/modules/NetworkStatsService.jsm");
+
+const NETWORKSTATSSERVICEPROXY_CONTRACTID = "@mozilla.org/networkstatsServiceProxy;1";
+const NETWORKSTATSSERVICEPROXY_CID = Components.ID("98fd8f69-784e-4626-aa59-56d6436a3c24");
+const nsINetworkStatsServiceProxy = Ci.nsINetworkStatsServiceProxy;
+
+function NetworkStatsServiceProxy() {
+ if (DEBUG) {
+ debug("Proxy started");
+ }
+}
+
+NetworkStatsServiceProxy.prototype = {
+ /*
+ * Function called in the protocol layer (HTTP, FTP, WebSocket ...etc)
+ * to pass the per-app stats to NetworkStatsService.
+ */
+ saveAppStats: function saveAppStats(aAppId, aIsInBrowser, aNetwork, aTimeStamp,
+ aRxBytes, aTxBytes, aIsAccumulative,
+ aCallback) {
+ if (!aNetwork) {
+ if (DEBUG) {
+ debug("|aNetwork| is not specified. Failed to save stats. Returning.");
+ }
+ return;
+ }
+
+ if (DEBUG) {
+ debug("saveAppStats: " + aAppId + " " + aIsInBrowser + " " +
+ aNetwork.type + " " + aTimeStamp + " " +
+ aRxBytes + " " + aTxBytes + " " + aIsAccumulative);
+ }
+
+ if (aCallback) {
+ aCallback = aCallback.notify;
+ }
+
+ NetworkStatsService.saveStats(aAppId, aIsInBrowser, "", aNetwork,
+ aTimeStamp, aRxBytes, aTxBytes,
+ aIsAccumulative, aCallback);
+ },
+
+ /*
+ * Function called in the points of different system services
+ * to pass the per-service stats to NetworkStatsService.
+ */
+ saveServiceStats: function saveServiceStats(aServiceType, aNetwork,
+ aTimeStamp, aRxBytes, aTxBytes,
+ aIsAccumulative, aCallback) {
+ if (!aNetwork) {
+ if (DEBUG) {
+ debug("|aNetwork| is not specified. Failed to save stats. Returning.");
+ }
+ return;
+ }
+
+ if (DEBUG) {
+ debug("saveServiceStats: " + aServiceType + " " + aNetwork.type + " " +
+ aTimeStamp + " " + aRxBytes + " " + aTxBytes + " " +
+ aIsAccumulative);
+ }
+
+ if (aCallback) {
+ aCallback = aCallback.notify;
+ }
+
+ NetworkStatsService.saveStats(0, false, aServiceType ,aNetwork, aTimeStamp,
+ aRxBytes, aTxBytes, aIsAccumulative,
+ aCallback);
+ },
+
+ classID : NETWORKSTATSSERVICEPROXY_CID,
+ QueryInterface : XPCOMUtils.generateQI([nsINetworkStatsServiceProxy]),
+}
+
+this.NSGetFactory = XPCOMUtils.generateNSGetFactory([NetworkStatsServiceProxy]);
diff --git a/dom/network/NetworkStatsServiceProxy.manifest b/dom/network/NetworkStatsServiceProxy.manifest
new file mode 100644
index 000000000..24f09f088
--- /dev/null
+++ b/dom/network/NetworkStatsServiceProxy.manifest
@@ -0,0 +1,2 @@
+component {98fd8f69-784e-4626-aa59-56d6436a3c24} NetworkStatsServiceProxy.js
+contract @mozilla.org/networkstatsServiceProxy;1 {98fd8f69-784e-4626-aa59-56d6436a3c24}
diff --git a/dom/network/src/PTCPSocket.ipdl b/dom/network/PTCPServerSocket.ipdl
index 765f9e7c0..abb870124 100644
--- a/dom/network/src/PTCPSocket.ipdl
+++ b/dom/network/PTCPServerSocket.ipdl
@@ -6,48 +6,29 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
include protocol PNecko;
+include protocol PTCPSocket;
include "mozilla/net/NeckoMessageUtils.h";
-using mozilla::void_t;
-
-struct TCPError {
- nsString name;
-};
-
-union SendableData {
- uint8_t[];
- nsString;
-};
-
-union CallbackData {
- void_t;
- SendableData;
- TCPError;
-};
-
namespace mozilla {
namespace net {
//-------------------------------------------------------------------
-protocol PTCPSocket
+protocol PTCPServerSocket
{
manager PNecko;
parent:
- Data(SendableData data);
- Suspend();
- Resume();
Close();
RequestDelete();
child:
- Callback(nsString type, CallbackData data,
- nsString readyState, uint32_t bufferedAmount);
+ CallbackAccept(PTCPSocket socket);
+ CallbackError(nsString message, nsString filename,
+ uint32_t lineNumber, uint32_t columnNumber);
__delete__();
};
-
} // namespace net
} // namespace mozilla
diff --git a/dom/network/PTCPSocket.ipdl b/dom/network/PTCPSocket.ipdl
new file mode 100644
index 000000000..c3fd0940e
--- /dev/null
+++ b/dom/network/PTCPSocket.ipdl
@@ -0,0 +1,76 @@
+/* -*- 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";
+
+using struct mozilla::void_t from "ipc/IPCMessageUtils.h";
+
+struct TCPError {
+ nsString name;
+};
+
+union SendableData {
+ uint8_t[];
+ nsString;
+};
+
+union CallbackData {
+ void_t;
+ SendableData;
+ TCPError;
+};
+
+namespace mozilla {
+namespace net {
+
+//-------------------------------------------------------------------
+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).
+ Open(nsString host, uint16_t port, bool useSSL, nsString binaryType);
+
+ // When child's send() is called, this message requrests parent to send
+ // data and update it's trackingNumber.
+ Data(SendableData data, uint32_t trackingNumber);
+
+ // Forward calling to child's upgradeToSecure() method to parent.
+ StartTLS();
+
+ // Forward calling to child's send() method to parent.
+ Suspend();
+
+ // Forward calling to child's resume() method to parent.
+ Resume();
+
+ // Forward calling to child's close() method to parent.
+ Close();
+
+child:
+ // Forward events that are dispatched by parent.
+ Callback(nsString type, CallbackData data, nsString 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().
+ UpdateBufferedAmount(uint32_t bufferedAmount, uint32_t trackingNumber);
+
+both:
+ RequestDelete();
+ __delete__();
+};
+
+
+} // namespace net
+} // namespace mozilla
+
diff --git a/dom/network/PUDPSocket.ipdl b/dom/network/PUDPSocket.ipdl
new file mode 100644
index 000000000..215d8f4e4
--- /dev/null
+++ b/dom/network/PUDPSocket.ipdl
@@ -0,0 +1,65 @@
+/* -*- 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 PBlob;
+include InputStreamParams;
+
+include "mozilla/net/NeckoMessageUtils.h";
+include "mozilla/net/DNS.h";
+include "prio.h";
+
+using mozilla::net::NetAddr from "mozilla/net/DNS.h";
+using struct mozilla::void_t from "ipc/IPCMessageUtils.h";
+
+struct UDPAddressInfo {
+ nsCString addr;
+ uint16_t port;
+};
+
+union UDPSocketAddr {
+ UDPAddressInfo;
+ NetAddr;
+};
+
+union UDPData {
+ uint8_t[];
+ InputStreamParams;
+};
+
+namespace mozilla {
+namespace net {
+
+//-------------------------------------------------------------------
+protocol PUDPSocket
+{
+ manager PNecko;
+
+parent:
+ Bind(UDPAddressInfo addressInfo, bool addressReuse, bool loopback);
+
+ OutgoingData(UDPData data, UDPSocketAddr addr);
+
+ JoinMulticast(nsCString multicastAddress, nsCString iface);
+ LeaveMulticast(nsCString multicastAddress, nsCString iface);
+
+ Close();
+
+ RequestDelete();
+
+child:
+ CallbackOpened(UDPAddressInfo addressInfo);
+ CallbackClosed();
+ CallbackReceivedData(UDPAddressInfo addressInfo, uint8_t[] data);
+ CallbackError(nsCString message, nsCString filename, uint32_t lineNumber);
+ __delete__();
+};
+
+
+} // namespace net
+} // namespace mozilla
+
diff --git a/dom/network/TCPServerSocket.js b/dom/network/TCPServerSocket.js
new file mode 100644
index 000000000..bea8308b7
--- /dev/null
+++ b/dom/network/TCPServerSocket.js
@@ -0,0 +1,187 @@
+/* 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/. */
+
+"use strict";
+
+const Cc = Components.classes;
+const Ci = Components.interfaces;
+const Cu = Components.utils;
+const Cr = Components.results;
+const CC = Components.Constructor;
+
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+
+const ServerSocket = CC(
+ '@mozilla.org/network/server-socket;1', 'nsIServerSocket', 'init'),
+ TCPSocketInternal = Cc[
+ '@mozilla.org/tcp-socket;1'].createInstance(Ci.nsITCPSocketInternal);
+
+/*
+ * Debug logging function
+ */
+
+let debug = true;
+function LOG(msg) {
+ if (debug) {
+ dump("TCPServerSocket: " + msg + "\n");
+ }
+}
+
+/*
+ * nsIDOMTCPServerSocket object
+ */
+
+function TCPServerSocket() {
+ this._localPort = 0;
+ this._binaryType = null;
+
+ this._onconnect = null;
+ this._onerror = null;
+
+ this._inChild = false;
+ this._neckoTCPServerSocket = null;
+ this._serverBridge = null;
+ this.useWin = null;
+}
+
+// When this API moves to WebIDL and these __exposedProps__ go away, remove
+// this call here and remove the API from XPConnect.
+Cu.skipCOWCallableChecks();
+
+TCPServerSocket.prototype = {
+ __exposedProps__: {
+ localPort: 'r',
+ onconnect: 'rw',
+ onerror: 'rw'
+ },
+ get localPort() {
+ return this._localPort;
+ },
+ get onconnect() {
+ return this._onconnect;
+ },
+ set onconnect(f) {
+ this._onconnect = f;
+ },
+ get onerror() {
+ return this._onerror;
+ },
+ set onerror(f) {
+ this._onerror = f;
+ },
+
+ _callListenerAcceptCommon: function tss_callListenerAcceptCommon(socket) {
+ if (this._onconnect) {
+ try {
+ this["onconnect"].call(null, socket);
+ } catch (e) {
+ socket.close();
+ }
+ }
+ else {
+ socket.close();
+ dump("Received unexpected connection!");
+ }
+ },
+ init: function tss_init(aWindowObj) {
+ this.useWin = aWindowObj;
+ },
+
+ /* nsITCPServerSocketInternal method */
+ listen: function tss_listen(localPort, options, backlog) {
+ this._inChild = Cc["@mozilla.org/xre/app-info;1"].getService(Ci.nsIXULRuntime)
+ .processType != Ci.nsIXULRuntime.PROCESS_TYPE_DEFAULT;
+ this._binaryType = options.binaryType;
+
+ if (this._inChild) {
+ if (this._serverBridge == null) {
+ this._serverBridge = Cc["@mozilla.org/tcp-server-socket-child;1"]
+ .createInstance(Ci.nsITCPServerSocketChild);
+ this._serverBridge.listen(this, localPort, backlog, options.binaryType);
+ }
+ else {
+ throw new Error("Child TCPServerSocket has already listening. \n");
+ }
+ }
+ else {
+ if (this._neckoTCPServerSocket == null) {
+ this._neckoTCPServerSocket = new ServerSocket(localPort, false, backlog);
+ this._localPort = this._neckoTCPServerSocket.port;
+ this._neckoTCPServerSocket.asyncListen(this);
+ }
+ else {
+ throw new Error("Parent TCPServerSocket has already listening. \n");
+ }
+ }
+ },
+
+ callListenerAccept: function tss_callListenerSocket(socketChild) {
+ // this method is called at child process when the socket is accepted at parent process.
+ let socket = TCPSocketInternal.createAcceptedChild(socketChild, this._binaryType, this.useWin);
+ this._callListenerAcceptCommon(socket);
+ },
+
+ callListenerError: function tss_callListenerError(message, filename, lineNumber, columnNumber) {
+ if (this._onerror) {
+ var type = "error";
+ var error = new Error(message, filename, lineNumber, columnNumber);
+
+ this["onerror"].call(null, new TCPSocketEvent(type, this, error));
+ }
+ },
+ /* end nsITCPServerSocketInternal method */
+
+ close: function tss_close() {
+ if (this._inChild) {
+ this._serverBridge.close();
+ return;
+ }
+
+ /* Close ServerSocket */
+ if (this._neckoTCPServerSocket) {
+ this._neckoTCPServerSocket.close();
+ }
+ },
+
+ // nsIServerSocketListener (Triggered by _neckoTCPServerSocket.asyncListen)
+ onSocketAccepted: function tss_onSocketAccepted(server, trans) {
+ // precondition: this._inChild == false
+ try {
+ let that = TCPSocketInternal.createAcceptedParent(trans, this._binaryType,
+ this.useWin);
+ this._callListenerAcceptCommon(that);
+ }
+ catch(e) {
+ trans.close(Cr.NS_BINDING_ABORTED);
+ }
+ },
+
+ // nsIServerSocketListener (Triggered by _neckoTCPServerSocket.asyncListen)
+ onStopListening: function tss_onStopListening(server, status) {
+ if (status != Cr.NS_BINDING_ABORTED) {
+ throw new Error("Server socket was closed by unexpected reason.");
+ }
+ this._neckoTCPServerSocket = null;
+ },
+
+ classID: Components.ID("{73065eae-27dc-11e2-895a-000c29987aa2}"),
+
+ classInfo: XPCOMUtils.generateCI({
+ classID: Components.ID("{73065eae-27dc-11e2-895a-000c29987aa2}"),
+ classDescription: "Server TCP Socket",
+ interfaces: [
+ Ci.nsIDOMTCPServerSocket,
+ Ci.nsISupportsWeakReference
+ ],
+ flags: Ci.nsIClassInfo.DOM_OBJECT,
+ }),
+
+ QueryInterface: XPCOMUtils.generateQI([
+ Ci.nsIDOMTCPServerSocket,
+ Ci.nsITCPServerSocketInternal,
+ Ci.nsISupportsWeakReference
+ ])
+}
+
+this.NSGetFactory = XPCOMUtils.generateNSGetFactory([TCPServerSocket]);
diff --git a/dom/network/TCPServerSocketChild.cpp b/dom/network/TCPServerSocketChild.cpp
new file mode 100644
index 000000000..05bccbef1
--- /dev/null
+++ b/dom/network/TCPServerSocketChild.cpp
@@ -0,0 +1,115 @@
+/* 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 "mozilla/net/NeckoChild.h"
+#include "mozilla/dom/PBrowserChild.h"
+#include "mozilla/dom/TabChild.h"
+#include "nsIDOMTCPSocket.h"
+#include "nsJSUtils.h"
+#include "jsfriendapi.h"
+
+using mozilla::net::gNeckoChild;
+
+namespace mozilla {
+namespace 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(nsITCPServerSocketChild)
+ NS_INTERFACE_MAP_ENTRY(nsISupports)
+NS_INTERFACE_MAP_END
+
+TCPServerSocketChildBase::TCPServerSocketChildBase()
+: mIPCOpen(false)
+{
+}
+
+TCPServerSocketChildBase::~TCPServerSocketChildBase()
+{
+}
+
+NS_IMETHODIMP_(MozExternalRefCountType) TCPServerSocketChild::Release(void)
+{
+ nsrefcnt refcnt = TCPServerSocketChildBase::Release();
+ if (refcnt == 1 && mIPCOpen) {
+ PTCPServerSocketChild::SendRequestDelete();
+ return 1;
+ }
+ return refcnt;
+}
+
+TCPServerSocketChild::TCPServerSocketChild()
+{
+}
+
+NS_IMETHODIMP
+TCPServerSocketChild::Listen(nsITCPServerSocketInternal* aServerSocket, uint16_t aLocalPort,
+ uint16_t aBacklog, const nsAString & aBinaryType, JSContext* aCx)
+{
+ mServerSocket = aServerSocket;
+ AddIPDLReference();
+ gNeckoChild->SendPTCPServerSocketConstructor(this, aLocalPort, aBacklog, nsString(aBinaryType));
+ return NS_OK;
+}
+
+void
+TCPServerSocketChildBase::ReleaseIPDLReference()
+{
+ MOZ_ASSERT(mIPCOpen);
+ mIPCOpen = false;
+ this->Release();
+}
+
+void
+TCPServerSocketChildBase::AddIPDLReference()
+{
+ MOZ_ASSERT(!mIPCOpen);
+ mIPCOpen = true;
+ this->AddRef();
+}
+
+TCPServerSocketChild::~TCPServerSocketChild()
+{
+}
+
+bool
+TCPServerSocketChild::RecvCallbackAccept(PTCPSocketChild *psocket)
+{
+ TCPSocketChild* socket = static_cast<TCPSocketChild*>(psocket);
+
+ nsresult rv = mServerSocket->CallListenerAccept(static_cast<nsITCPSocketChild*>(socket));
+ if (NS_FAILED(rv)) {
+ NS_WARNING("CallListenerAccept threw exception.");
+ }
+ return true;
+}
+
+bool
+TCPServerSocketChild::RecvCallbackError(const nsString& aMessage,
+ const nsString& aFilename,
+ const uint32_t& aLineNumber,
+ const uint32_t& aColumnNumber)
+{
+ nsresult rv = mServerSocket->CallListenerError(aMessage, aFilename,
+ aLineNumber, aColumnNumber);
+ if (NS_FAILED(rv)) {
+ NS_WARNING("CallListenerError threw exception.");
+ }
+ return true;
+}
+
+NS_IMETHODIMP
+TCPServerSocketChild::Close()
+{
+ SendClose();
+ return NS_OK;
+}
+
+} // namespace dom
+} // namespace mozilla
diff --git a/dom/network/TCPServerSocketChild.h b/dom/network/TCPServerSocketChild.h
new file mode 100644
index 000000000..be20a68d6
--- /dev/null
+++ b/dom/network/TCPServerSocketChild.h
@@ -0,0 +1,52 @@
+/* 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/net/PTCPServerSocketChild.h"
+#include "nsITCPServerSocketChild.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 {
+namespace dom {
+
+class TCPServerSocketChildBase : public nsITCPServerSocketChild {
+public:
+ NS_DECL_CYCLE_COLLECTION_CLASS(TCPServerSocketChildBase)
+ NS_DECL_CYCLE_COLLECTING_ISUPPORTS
+
+ void AddIPDLReference();
+ void ReleaseIPDLReference();
+
+protected:
+ TCPServerSocketChildBase();
+ virtual ~TCPServerSocketChildBase();
+
+ nsCOMPtr<nsITCPServerSocketInternal> mServerSocket;
+ bool mIPCOpen;
+};
+
+class TCPServerSocketChild : public mozilla::net::PTCPServerSocketChild
+ , public TCPServerSocketChildBase
+{
+public:
+ NS_DECL_NSITCPSERVERSOCKETCHILD
+ NS_IMETHOD_(MozExternalRefCountType) Release() override;
+
+ TCPServerSocketChild();
+ ~TCPServerSocketChild();
+
+ virtual bool RecvCallbackAccept(PTCPSocketChild *socket) override;
+ virtual bool RecvCallbackError(const nsString& aMessage,
+ const nsString& aFilename,
+ const uint32_t& aLineNumber,
+ const uint32_t& aColumnNumber) override;
+};
+
+} // namespace dom
+} // namespace mozilla
diff --git a/dom/network/TCPServerSocketParent.cpp b/dom/network/TCPServerSocketParent.cpp
new file mode 100644
index 000000000..5d8459236
--- /dev/null
+++ b/dom/network/TCPServerSocketParent.cpp
@@ -0,0 +1,175 @@
+/* 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 "TCPServerSocketParent.h"
+#include "nsJSUtils.h"
+#include "TCPSocketParent.h"
+#include "mozilla/unused.h"
+#include "mozilla/AppProcessChecker.h"
+#include "mozilla/dom/ContentParent.h"
+#include "mozilla/dom/TabParent.h"
+
+namespace mozilla {
+namespace dom {
+
+static void
+FireInteralError(mozilla::net::PTCPServerSocketParent* aActor,
+ uint32_t aLineNo)
+{
+ mozilla::unused <<
+ aActor->SendCallbackError(NS_LITERAL_STRING("Internal error"),
+ NS_LITERAL_STRING(__FILE__), aLineNo, 0);
+}
+
+NS_IMPL_CYCLE_COLLECTION(TCPServerSocketParent, mServerSocket, mIntermediary)
+NS_IMPL_CYCLE_COLLECTING_ADDREF(TCPServerSocketParent)
+NS_IMPL_CYCLE_COLLECTING_RELEASE(TCPServerSocketParent)
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(TCPServerSocketParent)
+ NS_INTERFACE_MAP_ENTRY(nsITCPServerSocketParent)
+ 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();
+}
+
+bool
+TCPServerSocketParent::Init(PNeckoParent* neckoParent, const uint16_t& aLocalPort,
+ const uint16_t& aBacklog, const nsString& aBinaryType)
+{
+ mNeckoParent = neckoParent;
+
+ nsresult rv;
+ mIntermediary = do_CreateInstance("@mozilla.org/tcp-socket-intermediary;1", &rv);
+ if (NS_FAILED(rv)) {
+ FireInteralError(this, __LINE__);
+ return true;
+ }
+
+ rv = mIntermediary->Listen(this, aLocalPort, aBacklog, aBinaryType, GetAppId(),
+ GetInBrowser(), getter_AddRefs(mServerSocket));
+ if (NS_FAILED(rv) || !mServerSocket) {
+ FireInteralError(this, __LINE__);
+ return true;
+ }
+ return true;
+}
+
+uint32_t
+TCPServerSocketParent::GetAppId()
+{
+ uint32_t appId = nsIScriptSecurityManager::UNKNOWN_APP_ID;
+ const PContentParent *content = Manager()->Manager();
+ const InfallibleTArray<PBrowserParent*>& browsers = content->ManagedPBrowserParent();
+ if (browsers.Length() > 0) {
+ TabParent *tab = TabParent::GetFrom(browsers[0]);
+ appId = tab->OwnAppId();
+ }
+ return appId;
+};
+
+bool
+TCPServerSocketParent::GetInBrowser()
+{
+ bool inBrowser = false;
+ const PContentParent *content = Manager()->Manager();
+ const InfallibleTArray<PBrowserParent*>& browsers = content->ManagedPBrowserParent();
+ if (browsers.Length() > 0) {
+ TabParent *tab = TabParent::GetFrom(browsers[0]);
+ inBrowser = tab->IsBrowserElement();
+ }
+ return inBrowser;
+}
+
+NS_IMETHODIMP
+TCPServerSocketParent::SendCallbackAccept(nsITCPSocketParent *socket)
+{
+ TCPSocketParent* _socket = static_cast<TCPSocketParent*>(socket);
+ PTCPSocketParent* _psocket = static_cast<PTCPSocketParent*>(_socket);
+
+ _socket->AddIPDLReference();
+
+ 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(_psocket, host, port)) {
+ mozilla::unused << PTCPServerSocketParent::SendCallbackAccept(_psocket);
+ }
+ else {
+ NS_ERROR("Sending data from PTCPSocketParent was failed.");
+ };
+ }
+ else {
+ NS_ERROR("The member value for NeckoParent is wrong.");
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+TCPServerSocketParent::SendCallbackError(const nsAString& message,
+ const nsAString& filename,
+ uint32_t lineNumber,
+ uint32_t columnNumber)
+{
+ mozilla::unused <<
+ PTCPServerSocketParent::SendCallbackError(nsString(message), nsString(filename),
+ lineNumber, columnNumber);
+ return NS_OK;
+}
+
+bool
+TCPServerSocketParent::RecvClose()
+{
+ NS_ENSURE_TRUE(mServerSocket, true);
+ mServerSocket->Close();
+ return true;
+}
+
+void
+TCPServerSocketParent::ActorDestroy(ActorDestroyReason why)
+{
+ if (mServerSocket) {
+ mServerSocket->Close();
+ mServerSocket = nullptr;
+ }
+ mNeckoParent = nullptr;
+ mIntermediary = nullptr;
+}
+
+bool
+TCPServerSocketParent::RecvRequestDelete()
+{
+ mozilla::unused << Send__delete__(this);
+ return true;
+}
+
+} // namespace dom
+} // namespace mozilla
diff --git a/dom/network/TCPServerSocketParent.h b/dom/network/TCPServerSocketParent.h
new file mode 100644
index 000000000..db9512f0a
--- /dev/null
+++ b/dom/network/TCPServerSocketParent.h
@@ -0,0 +1,52 @@
+/* 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/net/PNeckoParent.h"
+#include "mozilla/net/PTCPServerSocketParent.h"
+#include "nsITCPSocketParent.h"
+#include "nsITCPServerSocketParent.h"
+#include "nsCycleCollectionParticipant.h"
+#include "nsCOMPtr.h"
+#include "nsIDOMTCPSocket.h"
+
+namespace mozilla {
+namespace dom {
+
+class PBrowserParent;
+
+class TCPServerSocketParent : public mozilla::net::PTCPServerSocketParent
+ , public nsITCPServerSocketParent
+{
+public:
+ NS_DECL_CYCLE_COLLECTION_CLASS(TCPServerSocketParent)
+ NS_DECL_CYCLE_COLLECTING_ISUPPORTS
+ NS_DECL_NSITCPSERVERSOCKETPARENT
+
+ TCPServerSocketParent() : mNeckoParent(nullptr), mIPCOpen(false) {}
+
+ bool Init(PNeckoParent* neckoParent, const uint16_t& aLocalPort, const uint16_t& aBacklog,
+ const nsString& aBinaryType);
+
+ virtual bool RecvClose() override;
+ virtual bool RecvRequestDelete() override;
+
+ uint32_t GetAppId();
+ bool GetInBrowser();
+
+ void AddIPDLReference();
+ void ReleaseIPDLReference();
+
+private:
+ ~TCPServerSocketParent() {}
+
+ virtual void ActorDestroy(ActorDestroyReason why) override;
+
+ PNeckoParent* mNeckoParent;
+ nsCOMPtr<nsITCPSocketIntermediary> mIntermediary;
+ nsCOMPtr<nsIDOMTCPServerSocket> mServerSocket;
+ bool mIPCOpen;
+};
+
+} // namespace dom
+} // namespace mozilla
diff --git a/dom/network/src/TCPSocket.js b/dom/network/TCPSocket.js
index 6ab48f1b8..9a8107175 100644
--- a/dom/network/src/TCPSocket.js
+++ b/dom/network/TCPSocket.js
@@ -27,13 +27,17 @@ const InputStreamPump = CC(
'@mozilla.org/io/arraybuffer-input-stream;1', 'nsIArrayBufferInputStream'),
MultiplexInputStream = CC(
'@mozilla.org/io/multiplex-input-stream;1', 'nsIMultiplexInputStream');
+const TCPServerSocket = CC(
+ "@mozilla.org/tcp-server-socket;1", "nsITCPServerSocketInternal", "init");
const kCONNECTING = 'connecting';
const kOPEN = 'open';
const kCLOSING = 'closing';
const kCLOSED = 'closed';
+const kRESUME_ERROR = 'Calling resume() on a connection that was not suspended.';
const BUFFER_SIZE = 65536;
+const NETWORK_STATS_THRESHOLD = 65536;
// XXX we have no TCPError implementation right now because it's really hard to
// do on b2g18. On mozilla-central we want a proper TCPError that ideally
@@ -65,11 +69,21 @@ function TCPSocketEvent(type, sock, data) {
this._data = data;
}
+// When this API moves to WebIDL and these __exposedProps__ go away, remove
+// this call here and remove the API from XPConnect.
+Cu.skipCOWCallableChecks();
+
TCPSocketEvent.prototype = {
__exposedProps__: {
type: 'r',
target: 'r',
- data: 'r'
+ data: 'r',
+ // Promise::ResolveInternal tries to check if the thing being resolved is
+ // itself a promise through the presence of "then". Accordingly, we list
+ // it as an exposed property, although we return undefined for it.
+ // Bug 882123 covers making TCPSocket be a proper event target with proper
+ // events.
+ then: 'r'
},
get type() {
return this._type;
@@ -79,6 +93,9 @@ TCPSocketEvent.prototype = {
},
get data() {
return this._data;
+ },
+ get then() {
+ return undefined;
}
}
@@ -117,6 +134,7 @@ TCPSocket.prototype = {
send: 'r',
readyState: 'r',
binaryType: 'r',
+ listen: 'r',
onopen: 'rw',
ondrain: 'rw',
ondata: 'rw',
@@ -153,6 +171,23 @@ TCPSocket.prototype = {
// IPC socket actor
_socketBridge: null,
+ // StartTLS
+ _waitingForStartTLS: false,
+ _pendingDataAfterStartTLS: [],
+
+ // Used to notify when update bufferedAmount is updated.
+ _onUpdateBufferedAmount: null,
+ _trackingNumber: 0,
+
+#ifdef MOZ_WIDGET_GONK
+ // Network statistics (Gonk-specific feature)
+ _txBytes: 0,
+ _rxBytes: 0,
+ _appId: Ci.nsIScriptSecurityManager.NO_APP_ID,
+ _inBrowser: false,
+ _activeNetwork: null,
+#endif
+
// Public accessors.
get readyState() {
return this._readyState;
@@ -206,19 +241,29 @@ TCPSocket.prototype = {
this._onclose = f;
},
+ _activateTLS: function() {
+ let securityInfo = this._transport.securityInfo
+ .QueryInterface(Ci.nsISSLSocketControl);
+ securityInfo.StartTLS();
+ },
+
// Helper methods.
_createTransport: function ts_createTransport(host, port, sslMode) {
- let options, optlen;
- if (sslMode) {
- options = [sslMode];
- optlen = 1;
+ let options;
+ if (sslMode === 'ssl') {
+ options = ['ssl'];
} else {
- options = null;
- optlen = 0;
+ options = ['starttls'];
}
return Cc["@mozilla.org/network/socket-transport-service;1"]
.getService(Ci.nsISocketTransportService)
- .createTransport(options, optlen, host, port, null);
+ .createTransport(options, 1, host, port, null);
+ },
+
+ _sendBufferedAmount: function ts_sendBufferedAmount() {
+ if (this._onUpdateBufferedAmount) {
+ this._onUpdateBufferedAmount(this.bufferedAmount, this._trackingNumber);
+ }
},
_ensureCopying: function ts_ensureCopying() {
@@ -233,6 +278,7 @@ TCPSocket.prototype = {
onStopRequest: function ts_output_onStopRequest(request, context, status) {
self._asyncCopierActive = false;
self._multiplexStream.removeStream(0);
+ self._sendBufferedAmount();
if (!Components.isSuccessCode(status)) {
// Note that we can/will get an error here as well as in the
@@ -244,7 +290,24 @@ TCPSocket.prototype = {
if (self._multiplexStream.count) {
self._ensureCopying();
} else {
- if (self._waitingForDrain) {
+ // If we are waiting for initiating starttls, we can begin to
+ // activate tls now.
+ if (self._waitingForStartTLS && self._readyState == kOPEN) {
+ self._activateTLS();
+ self._waitingForStartTLS = false;
+ // If we have pending data, we should send them, or fire
+ // a drain event if we are waiting for it.
+ if (self._pendingDataAfterStartTLS.length > 0) {
+ while (self._pendingDataAfterStartTLS.length)
+ self._multiplexStream.appendStream(self._pendingDataAfterStartTLS.shift());
+ self._ensureCopying();
+ return;
+ }
+ }
+
+ // If we have a callback to update bufferedAmount, we let child to
+ // decide whether ondrain should be dispatched.
+ if (self._waitingForDrain && !self._onUpdateBufferedAmount) {
self._waitingForDrain = false;
self.callListener("drain");
}
@@ -258,6 +321,68 @@ TCPSocket.prototype = {
}, null);
},
+ _initStream: function ts_initStream(binaryType) {
+ this._binaryType = binaryType;
+ this._socketInputStream = this._transport.openInputStream(0, 0, 0);
+ this._socketOutputStream = this._transport.openOutputStream(
+ Ci.nsITransport.OPEN_UNBUFFERED, 0, 0);
+
+ // If the other side is not listening, we will
+ // get an onInputStreamReady callback where available
+ // raises to indicate the connection was refused.
+ this._socketInputStream.asyncWait(
+ this, this._socketInputStream.WAIT_CLOSURE_ONLY, 0, Services.tm.currentThread);
+
+ if (this._binaryType === "arraybuffer") {
+ this._inputStreamBinary = new BinaryInputStream(this._socketInputStream);
+ } else {
+ this._inputStreamScriptable = new ScriptableInputStream(this._socketInputStream);
+ }
+
+ this._multiplexStream = new MultiplexInputStream();
+
+ this._multiplexStreamCopier = new AsyncStreamCopier(
+ this._multiplexStream,
+ this._socketOutputStream,
+ // (nsSocketTransport uses gSocketTransportService)
+ Cc["@mozilla.org/network/socket-transport-service;1"]
+ .getService(Ci.nsIEventTarget),
+ /* source buffered */ true, /* sink buffered */ false,
+ BUFFER_SIZE, /* close source*/ false, /* close sink */ false);
+ },
+
+#ifdef MOZ_WIDGET_GONK
+ // Helper method for collecting network statistics.
+ // Note this method is Gonk-specific.
+ _saveNetworkStats: function ts_saveNetworkStats(enforce) {
+ if (this._txBytes <= 0 && this._rxBytes <= 0) {
+ // There is no traffic at all. No need to save statistics.
+ return;
+ }
+
+ // If "enforce" is false, the traffic amount is saved to NetworkStatsServiceProxy
+ // only when the total amount exceeds the predefined threshold value.
+ // The purpose is to avoid too much overhead for collecting statistics.
+ let totalBytes = this._txBytes + this._rxBytes;
+ if (!enforce && totalBytes < NETWORK_STATS_THRESHOLD) {
+ return;
+ }
+
+ let nssProxy = Cc["@mozilla.org/networkstatsServiceProxy;1"]
+ .getService(Ci.nsINetworkStatsServiceProxy);
+ if (!nssProxy) {
+ LOG("Error: Ci.nsINetworkStatsServiceProxy service is not available.");
+ return;
+ }
+ nssProxy.saveAppStats(this._appId, this._inBrowser, this._activeNetwork,
+ Date.now(), this._rxBytes, this._txBytes, false);
+
+ // Reset the counters once the statistics is saved to NetworkStatsServiceProxy.
+ this._txBytes = this._rxBytes = 0;
+ },
+ // End of helper method for network statistics.
+#endif
+
callListener: function ts_callListener(type, data) {
if (!this["on" + type])
return;
@@ -284,14 +409,115 @@ TCPSocket.prototype = {
this.callListener(type);
},
- updateReadyStateAndBuffered: function ts_setReadyState(readyState, bufferedAmount) {
+ /**
+ * This method is expected to be called by TCPSocketChild to update child's
+ * readyState.
+ */
+ updateReadyState: function ts_updateReadyState(readyState) {
+ if (!this._inChild) {
+ LOG("Calling updateReadyState in parent, which should only be called " +
+ "in child");
+ return;
+ }
this._readyState = readyState;
+ },
+
+ updateBufferedAmount: function ts_updateBufferedAmount(bufferedAmount, trackingNumber) {
+ if (trackingNumber != this._trackingNumber) {
+ LOG("updateBufferedAmount is called but trackingNumber is not matched " +
+ "parent's trackingNumber: " + trackingNumber + ", child's trackingNumber: " +
+ this._trackingNumber);
+ return;
+ }
this._bufferedAmount = bufferedAmount;
+ if (bufferedAmount == 0) {
+ if (this._waitingForDrain) {
+ this._waitingForDrain = false;
+ this.callListener("drain");
+ }
+ } else {
+ LOG("bufferedAmount is updated but haven't reaches zero. bufferedAmount: " +
+ bufferedAmount);
+ }
+ },
+
+ createAcceptedParent: function ts_createAcceptedParent(transport, binaryType, windowObject) {
+ let that = new TCPSocket();
+ that._transport = transport;
+ that._initStream(binaryType);
+
+ // ReadyState is kOpen since accepted transport stream has already been connected
+ that._readyState = kOPEN;
+ that._inputStreamPump = new InputStreamPump(that._socketInputStream, -1, -1, 0, 0, false);
+ that._inputStreamPump.asyncRead(that, null);
+
+ // Grab host/port from SocketTransport.
+ that._host = transport.host;
+ that._port = transport.port;
+ that.useWin = windowObject;
+
+ return that;
+ },
+
+ createAcceptedChild: function ts_createAcceptedChild(socketChild, binaryType, windowObject) {
+ let that = new TCPSocket();
+
+ that._binaryType = binaryType;
+ that._inChild = true;
+ that._readyState = kOPEN;
+ socketChild.setSocketAndWindow(that, windowObject);
+ that._socketBridge = socketChild;
+ that._host = socketChild.host;
+ that._port = socketChild.port;
+ that.useWin = windowObject;
+
+ return that;
},
+
+ setAppId: function ts_setAppId(appId) {
+#ifdef MOZ_WIDGET_GONK
+ this._appId = appId;
+#else
+ // Do nothing because _appId only exists on Gonk-specific platform.
+#endif
+ },
+
+ setInBrowser: function ts_setInBrowser(inBrowser) {
+#ifdef MOZ_WIDGET_GONK
+ this._inBrowser = inBrowser;
+#else
+ // Do nothing.
+#endif
+ },
+
+ setOnUpdateBufferedAmountHandler: function(aFunction) {
+ if (typeof(aFunction) == 'function') {
+ this._onUpdateBufferedAmount = aFunction;
+ } else {
+ throw new Error("only function can be passed to " +
+ "setOnUpdateBufferedAmountHandler");
+ }
+ },
+
+ /**
+ * Handle the requst of sending data and update trackingNumber from
+ * child.
+ * This function is expected to be called by TCPSocketChild.
+ */
+ onRecvSendFromChild: function(data, byteOffset, byteLength, trackingNumber) {
+ this._trackingNumber = trackingNumber;
+ this.send(data, byteOffset, byteLength);
+ },
+
/* end nsITCPSocketInternal methods */
initWindowless: function ts_initWindowless() {
- return Services.prefs.getBoolPref("dom.mozTCPSocket.enabled");
+ try {
+ return Services.prefs.getBoolPref("dom.mozTCPSocket.enabled");
+ } catch (e) {
+ // no pref means return false
+ return false;
+ }
},
init: function ts_init(aWindow) {
@@ -342,9 +568,6 @@ TCPSocket.prototype = {
// nsIDOMTCPSocket
open: function ts_open(host, port, options) {
- if (!this.initWindowless())
- return null;
-
this._inChild = Cc["@mozilla.org/xre/app-info;1"].getService(Ci.nsIXULRuntime)
.processType != Ci.nsIXULRuntime.PROCESS_TYPE_DEFAULT;
LOG("content process: " + (this._inChild ? "true" : "false"));
@@ -370,7 +593,7 @@ TCPSocket.prototype = {
that._host = host;
that._port = port;
if (options !== undefined) {
- if (options.useSSL) {
+ if (options.useSecureTransport) {
that._ssl = 'ssl';
} else {
that._ssl = false;
@@ -383,41 +606,62 @@ TCPSocket.prototype = {
if (this._inChild) {
that._socketBridge = Cc["@mozilla.org/tcp-socket-child;1"]
.createInstance(Ci.nsITCPSocketChild);
- that._socketBridge.open(that, host, port, !!that._ssl,
- that._binaryType, this.useWin, this.useWin || this);
+ that._socketBridge.sendOpen(that, host, port, !!that._ssl,
+ that._binaryType, this.useWin, this.useWin || this);
return that;
}
let transport = that._transport = this._createTransport(host, port, that._ssl);
transport.setEventSink(that, Services.tm.currentThread);
+ that._initStream(that._binaryType);
+
+#ifdef MOZ_WIDGET_GONK
+ // Set _activeNetwork, which is only required for network statistics.
+ // Note that nsINetworkManager, as well as nsINetworkStatsServiceProxy, is
+ // Gonk-specific.
+ let networkManager = Cc["@mozilla.org/network/manager;1"].getService(Ci.nsINetworkManager);
+ if (networkManager) {
+ that._activeNetwork = networkManager.active;
+ }
+#endif
- that._socketInputStream = transport.openInputStream(0, 0, 0);
- that._socketOutputStream = transport.openOutputStream(
- Ci.nsITransport.OPEN_UNBUFFERED, 0, 0);
+ return that;
+ },
- // If the other side is not listening, we will
- // get an onInputStreamReady callback where available
- // raises to indicate the connection was refused.
- that._socketInputStream.asyncWait(
- that, that._socketInputStream.WAIT_CLOSURE_ONLY, 0, Services.tm.currentThread);
+ upgradeToSecure: function ts_upgradeToSecure() {
+ if (this._readyState !== kOPEN) {
+ throw new Error("Socket not open.");
+ }
+ if (this._ssl == 'ssl') {
+ // Already SSL
+ return;
+ }
- if (that._binaryType === "arraybuffer") {
- that._inputStreamBinary = new BinaryInputStream(that._socketInputStream);
- } else {
- that._inputStreamScriptable = new ScriptableInputStream(that._socketInputStream);
+ this._ssl = 'ssl';
+
+ if (this._inChild) {
+ this._socketBridge.sendStartTLS();
+ return;
}
- that._multiplexStream = new MultiplexInputStream();
+ if (this._multiplexStream.count == 0) {
+ this._activateTLS();
+ } else {
+ this._waitingForStartTLS = true;
+ }
+ },
- that._multiplexStreamCopier = new AsyncStreamCopier(
- that._multiplexStream,
- that._socketOutputStream,
- // (nsSocketTransport uses gSocketTransportService)
- Cc["@mozilla.org/network/socket-transport-service;1"]
- .getService(Ci.nsIEventTarget),
- /* source buffered */ true, /* sink buffered */ false,
- BUFFER_SIZE, /* close source*/ false, /* close sink */ false);
+ listen: function ts_listen(localPort, options, backlog) {
+ // in the testing case, init won't be called and
+ // hasPrivileges will be null. We want to proceed to test.
+ if (this._hasPrivileges !== true && this._hasPrivileges !== null) {
+ throw new Error("TCPSocket does not have permission in this context.\n");
+ }
+ let that = new TCPServerSocket(this.useWin);
+ options = options || { binaryType : this.binaryType };
+ backlog = backlog || -1;
+ that.listen(localPort, options, backlog);
return that;
},
@@ -429,7 +673,7 @@ TCPSocket.prototype = {
this._readyState = kCLOSING;
if (this._inChild) {
- this._socketBridge.close();
+ this._socketBridge.sendClose();
return;
}
@@ -449,15 +693,27 @@ TCPSocket.prototype = {
}
if (this._inChild) {
- this._socketBridge.send(data, byteOffset, byteLength);
+ this._socketBridge.sendSend(data, byteOffset, byteLength, ++this._trackingNumber);
}
let length = this._binaryType === "arraybuffer" ? byteLength : data.length;
+ let newBufferedAmount = this.bufferedAmount + length;
+ let 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.
+ this._waitingForDrain = true;
+ }
- var newBufferedAmount = this.bufferedAmount + length;
- var bufferNotFull = newBufferedAmount < BUFFER_SIZE;
if (this._inChild) {
- return bufferNotFull;
+ // In child, we just add buffer length to our bufferedAmount and let
+ // parent to update our bufferedAmount when data have been sent.
+ this._bufferedAmount = newBufferedAmount;
+ return !bufferFull;
}
let new_stream;
@@ -468,24 +724,29 @@ TCPSocket.prototype = {
new_stream = new StringInputStream();
new_stream.setData(data, length);
}
- this._multiplexStream.appendStream(new_stream);
- if (newBufferedAmount >= BUFFER_SIZE) {
- // 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.
- this._waitingForDrain = true;
+ if (this._waitingForStartTLS) {
+ // When we are waiting for starttls, new_stream is added to pendingData
+ // and will be appended to multiplexStream after tls had been set up.
+ this._pendingDataAfterStartTLS.push(new_stream);
+ } else {
+ this._multiplexStream.appendStream(new_stream);
}
this._ensureCopying();
- return bufferNotFull;
+
+#ifdef MOZ_WIDGET_GONK
+ // Collect transmitted amount for network statistics.
+ this._txBytes += length;
+ this._saveNetworkStats(false);
+#endif
+
+ return !bufferFull;
},
suspend: function ts_suspend() {
if (this._inChild) {
- this._socketBridge.suspend();
+ this._socketBridge.sendSuspend();
return;
}
@@ -498,18 +759,26 @@ TCPSocket.prototype = {
resume: function ts_resume() {
if (this._inChild) {
- this._socketBridge.resume();
+ this._socketBridge.sendResume();
return;
}
if (this._inputStreamPump) {
this._inputStreamPump.resume();
+ } else if (this._suspendCount < 1) {
+ throw new Error(kRESUME_ERROR);
} else {
--this._suspendCount;
}
},
_maybeReportErrorAndCloseIfOpen: function(status) {
+#ifdef MOZ_WIDGET_GONK
+ // Save network statistics once the connection is closed.
+ // For now this function is Gonk-specific.
+ this._saveNetworkStats(true);
+#endif
+
// If we're closed, we've already reported the error or just don't need to
// report the error.
if (this._readyState === kCLOSED)
@@ -702,6 +971,12 @@ TCPSocket.prototype = {
} else {
this.callListener("data", this._inputStreamScriptable.read(count));
}
+
+#ifdef MOZ_WIDGET_GONK
+ // Collect received amount for network statistics.
+ this._rxBytes += count;
+ this._saveNetworkStats(false);
+#endif
},
classID: Components.ID("{cda91b22-6472-11e1-aa11-834fec09cd0a}"),
diff --git a/dom/network/src/TCPSocket.manifest b/dom/network/TCPSocket.manifest
index 0d22400ea..3a77e37a0 100644
--- a/dom/network/src/TCPSocket.manifest
+++ b/dom/network/TCPSocket.manifest
@@ -6,3 +6,7 @@ category JavaScript-navigator-property mozTCPSocket @mozilla.org/tcp-socket;1
# TCPSocketParentIntermediary.js
component {afa42841-a6cb-4a91-912f-93099f6a3d18} TCPSocketParentIntermediary.js
contract @mozilla.org/tcp-socket-intermediary;1 {afa42841-a6cb-4a91-912f-93099f6a3d18}
+
+# TCPServerSocket.js
+component {73065eae-27dc-11e2-895a-000c29987aa2} TCPServerSocket.js
+contract @mozilla.org/tcp-server-socket;1 {73065eae-27dc-11e2-895a-000c29987aa2}
diff --git a/dom/network/src/TCPSocketChild.cpp b/dom/network/TCPSocketChild.cpp
index 1e70561d5..0d33ee8b5 100644
--- a/dom/network/src/TCPSocketChild.cpp
+++ b/dom/network/TCPSocketChild.cpp
@@ -2,7 +2,10 @@
* 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/unused.h"
+#include "mozilla/UniquePtr.h"
#include "mozilla/net/NeckoChild.h"
#include "mozilla/dom/PBrowserChild.h"
#include "mozilla/dom/TabChild.h"
@@ -25,14 +28,17 @@ DeserializeArrayBuffer(JS::Handle<JSObject*> aObj,
mozilla::AutoSafeJSContext cx;
JSAutoCompartment ac(cx, aObj);
- JS::Rooted<JSObject*> obj(cx, JS_NewArrayBuffer(cx, aBuffer.Length()));
- if (!obj)
- return false;
- uint8_t* data = JS_GetArrayBufferData(obj);
+ mozilla::UniquePtr<uint8_t[], JS::FreePolicy> data(js_pod_malloc<uint8_t>(aBuffer.Length()));
if (!data)
- return false;
- memcpy(data, aBuffer.Elements(), aBuffer.Length());
- aVal.set(OBJECT_TO_JSVAL(obj));
+ return false;
+ memcpy(data.get(), aBuffer.Elements(), aBuffer.Length());
+
+ JSObject* obj = JS_NewArrayBufferWithContents(cx, aBuffer.Length(), data.get());
+ if (!obj)
+ return false;
+ data.release();
+
+ aVal.setObject(*obj);
return true;
}
@@ -41,7 +47,7 @@ DeserializeArrayBuffer(JS::Handle<JSObject*> aObj,
namespace mozilla {
namespace dom {
-NS_IMPL_CYCLE_COLLECTION_1(TCPSocketChildBase, mSocket)
+NS_IMPL_CYCLE_COLLECTION(TCPSocketChildBase, mSocket)
NS_IMPL_CYCLE_COLLECTING_ADDREF(TCPSocketChildBase)
NS_IMPL_CYCLE_COLLECTING_RELEASE(TCPSocketChildBase)
@@ -59,7 +65,7 @@ TCPSocketChildBase::~TCPSocketChildBase()
{
}
-NS_IMETHODIMP_(nsrefcnt) TCPSocketChild::Release(void)
+NS_IMETHODIMP_(MozExternalRefCountType) TCPSocketChild::Release(void)
{
nsrefcnt refcnt = TCPSocketChildBase::Release();
if (refcnt == 1 && mIPCOpen) {
@@ -70,26 +76,35 @@ NS_IMETHODIMP_(nsrefcnt) TCPSocketChild::Release(void)
}
TCPSocketChild::TCPSocketChild()
-: mSocketObj(nullptr)
+: mWindowObj(nullptr)
+, mHost()
+, mPort(0)
{
}
+void TCPSocketChild::Init(const nsString& aHost, const uint16_t& aPort) {
+ mHost = aHost;
+ mPort = aPort;
+}
+
NS_IMETHODIMP
-TCPSocketChild::Open(nsITCPSocketInternal* aSocket, const nsAString& aHost,
- uint16_t aPort, bool aUseSSL, const nsAString& aBinaryType,
- nsIDOMWindow* aWindow, const JS::Value& aSocketObj,
- JSContext* aCx)
+TCPSocketChild::SendOpen(nsITCPSocketInternal* aSocket,
+ const nsAString& aHost, uint16_t aPort,
+ bool aUseSSL, const nsAString& aBinaryType,
+ nsIDOMWindow* aWindow, JS::Handle<JS::Value> aWindowObj,
+ JSContext* aCx)
{
mSocket = aSocket;
- MOZ_ASSERT(aSocketObj.isObject());
- mSocketObj = js::CheckedUnwrap(&aSocketObj.toObject());
- if (!mSocketObj) {
+
+ MOZ_ASSERT(aWindowObj.isObject());
+ mWindowObj = js::CheckedUnwrap(&aWindowObj.toObject());
+ if (!mWindowObj) {
return NS_ERROR_FAILURE;
}
AddIPDLReference();
- gNeckoChild->SendPTCPSocketConstructor(this, nsString(aHost), aPort,
- aUseSSL, nsString(aBinaryType),
- GetTabChildFrom(aWindow));
+ gNeckoChild->SendPTCPSocketConstructor(this, nsString(aHost), aPort);
+ PTCPSocketChild::SendOpen(nsString(aHost), aPort,
+ aUseSSL, nsString(aBinaryType));
return NS_OK;
}
@@ -114,12 +129,21 @@ TCPSocketChild::~TCPSocketChild()
}
bool
+TCPSocketChild::RecvUpdateBufferedAmount(const uint32_t& aBuffered,
+ const uint32_t& aTrackingNumber)
+{
+ if (NS_FAILED(mSocket->UpdateBufferedAmount(aBuffered, aTrackingNumber))) {
+ NS_ERROR("Shouldn't fail!");
+ }
+ return true;
+}
+
+bool
TCPSocketChild::RecvCallback(const nsString& aType,
const CallbackData& aData,
- const nsString& aReadyState,
- const uint32_t& aBuffered)
+ const nsString& aReadyState)
{
- if (NS_FAILED(mSocket->UpdateReadyStateAndBuffered(aReadyState, aBuffered)))
+ if (NS_FAILED(mSocket->UpdateReadyState(aReadyState)))
NS_ERROR("Shouldn't fail!");
nsresult rv = NS_ERROR_FAILURE;
@@ -135,9 +159,10 @@ TCPSocketChild::RecvCallback(const nsString& aType,
if (data.type() == SendableData::TArrayOfuint8_t) {
JSContext* cx = nsContentUtils::GetSafeJSContext();
+ JSAutoRequest ar(cx);
JS::Rooted<JS::Value> val(cx);
- JS::Rooted<JSObject*> socket(cx, mSocketObj);
- bool ok = IPC::DeserializeArrayBuffer(socket, data.get_ArrayOfuint8_t(), &val);
+ JS::Rooted<JSObject*> window(cx, mWindowObj);
+ bool ok = IPC::DeserializeArrayBuffer(window, data.get_ArrayOfuint8_t(), &val);
NS_ENSURE_TRUE(ok, true);
rv = mSocket->CallListenerArrayBuffer(aType, val);
@@ -145,50 +170,57 @@ TCPSocketChild::RecvCallback(const nsString& aType,
rv = mSocket->CallListenerData(aType, data.get_nsString());
} else {
- MOZ_NOT_REACHED("Invalid callback data type!");
+ MOZ_CRASH("Invalid callback data type!");
}
} else {
- MOZ_NOT_REACHED("Invalid callback type!");
+ MOZ_CRASH("Invalid callback type!");
}
NS_ENSURE_SUCCESS(rv, true);
return true;
}
NS_IMETHODIMP
-TCPSocketChild::Suspend()
+TCPSocketChild::SendStartTLS()
{
- SendSuspend();
+ PTCPSocketChild::SendStartTLS();
return NS_OK;
}
NS_IMETHODIMP
-TCPSocketChild::Resume()
+TCPSocketChild::SendSuspend()
{
- SendResume();
+ PTCPSocketChild::SendSuspend();
return NS_OK;
}
NS_IMETHODIMP
-TCPSocketChild::Close()
+TCPSocketChild::SendResume()
{
- SendClose();
+ PTCPSocketChild::SendResume();
return NS_OK;
}
NS_IMETHODIMP
-TCPSocketChild::Send(const JS::Value& aData,
- uint32_t aByteOffset,
- uint32_t aByteLength,
- JSContext* aCx)
+TCPSocketChild::SendClose()
+{
+ PTCPSocketChild::SendClose();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+TCPSocketChild::SendSend(JS::Handle<JS::Value> aData,
+ uint32_t aByteOffset,
+ uint32_t aByteLength,
+ uint32_t aTrackingNumber,
+ JSContext* aCx)
{
if (aData.isString()) {
JSString* jsstr = aData.toString();
- nsDependentJSString str;
+ nsAutoJSString str;
bool ok = str.init(aCx, jsstr);
NS_ENSURE_TRUE(ok, NS_ERROR_FAILURE);
- SendData(str);
-
+ SendData(str, aTrackingNumber);
} else {
NS_ENSURE_TRUE(aData.isObject(), NS_ERROR_FAILURE);
JS::Rooted<JSObject*> obj(aCx, &aData.toObject());
@@ -196,20 +228,58 @@ TCPSocketChild::Send(const JS::Value& aData,
uint32_t buflen = JS_GetArrayBufferByteLength(obj);
aByteOffset = std::min(buflen, aByteOffset);
uint32_t nbytes = std::min(buflen - aByteOffset, aByteLength);
- uint8_t* data = JS_GetArrayBufferData(obj);
- if (!data) {
- return NS_ERROR_OUT_OF_MEMORY;
- }
FallibleTArray<uint8_t> fallibleArr;
- if (!fallibleArr.InsertElementsAt(0, data, nbytes)) {
- return NS_ERROR_OUT_OF_MEMORY;
+ {
+ JS::AutoCheckCannotGC nogc;
+ uint8_t* data = JS_GetArrayBufferData(obj, nogc);
+ if (!data) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+ if (!fallibleArr.InsertElementsAt(0, data + aByteOffset, nbytes)) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
}
InfallibleTArray<uint8_t> arr;
arr.SwapElements(fallibleArr);
- SendData(arr);
+ SendData(arr, aTrackingNumber);
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+TCPSocketChild::SetSocketAndWindow(nsITCPSocketInternal *aSocket,
+ JS::Handle<JS::Value> aWindowObj,
+ JSContext* aCx)
+{
+ mSocket = aSocket;
+ MOZ_ASSERT(aWindowObj.isObject());
+ mWindowObj = js::CheckedUnwrap(&aWindowObj.toObject());
+ if (!mWindowObj) {
+ return NS_ERROR_FAILURE;
}
return NS_OK;
}
+NS_IMETHODIMP
+TCPSocketChild::GetHost(nsAString& aHost)
+{
+ aHost = mHost;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+TCPSocketChild::GetPort(uint16_t* aPort)
+{
+ *aPort = mPort;
+ return NS_OK;
+}
+
+bool
+TCPSocketChild::RecvRequestDelete()
+{
+ mozilla::unused << Send__delete__(this);
+ return true;
+}
+
} // namespace dom
} // namespace mozilla
diff --git a/dom/network/src/TCPSocketChild.h b/dom/network/TCPSocketChild.h
index 17fbf44d5..5e3e697eb 100644
--- a/dom/network/src/TCPSocketChild.h
+++ b/dom/network/TCPSocketChild.h
@@ -2,17 +2,19 @@
* 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/net/PTCPSocketChild.h"
#include "nsITCPSocketChild.h"
#include "nsCycleCollectionParticipant.h"
#include "nsCOMPtr.h"
+#include "js/TypeDecls.h"
#define TCPSOCKETCHILD_CID \
{ 0xa589d96f, 0x7e09, 0x4edf, { 0xa0, 0x1a, 0xeb, 0x49, 0x51, 0xf4, 0x2f, 0x37 } }
class nsITCPSocketInternal;
-struct JSContext;
-class JSObject;
namespace mozilla {
namespace dom {
@@ -38,18 +40,26 @@ class TCPSocketChild : public mozilla::net::PTCPSocketChild
{
public:
NS_DECL_NSITCPSOCKETCHILD
- NS_IMETHOD_(nsrefcnt) Release() MOZ_OVERRIDE;
+ NS_IMETHOD_(MozExternalRefCountType) Release() override;
TCPSocketChild();
~TCPSocketChild();
+ void Init(const nsString& aHost, const uint16_t& aPort);
+
virtual bool RecvCallback(const nsString& aType,
const CallbackData& aData,
- const nsString& aReadyState,
- const uint32_t& aBuffered) MOZ_OVERRIDE;
+ const nsString& aReadyState) override;
+ virtual bool RecvRequestDelete() override;
+ virtual bool RecvUpdateBufferedAmount(const uint32_t& aBufferred,
+ const uint32_t& aTrackingNumber) override;
private:
- JSObject* mSocketObj;
+ JSObject* mWindowObj;
+ nsString mHost;
+ uint16_t mPort;
};
} // namespace dom
} // namespace mozilla
+
+#endif
diff --git a/dom/network/TCPSocketParent.cpp b/dom/network/TCPSocketParent.cpp
new file mode 100644
index 000000000..1ddd76759
--- /dev/null
+++ b/dom/network/TCPSocketParent.cpp
@@ -0,0 +1,410 @@
+/* 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 "nsIDOMTCPSocket.h"
+#include "mozilla/unused.h"
+#include "mozilla/AppProcessChecker.h"
+#include "mozilla/net/NeckoCommon.h"
+#include "mozilla/net/PNeckoParent.h"
+#include "mozilla/dom/ContentParent.h"
+#include "mozilla/dom/ScriptSettings.h"
+#include "mozilla/dom/TabParent.h"
+#include "mozilla/HoldDropJSObjects.h"
+#include "nsIScriptSecurityManager.h"
+
+namespace IPC {
+
+//Defined in TCPSocketChild.cpp
+extern bool
+DeserializeArrayBuffer(JS::Handle<JSObject*> aObj,
+ const InfallibleTArray<uint8_t>& aBuffer,
+ JS::MutableHandle<JS::Value> aVal);
+
+}
+
+namespace mozilla {
+namespace dom {
+
+static void
+FireInteralError(mozilla::net::PTCPSocketParent* aActor, uint32_t aLineNo)
+{
+ mozilla::unused <<
+ aActor->SendCallback(NS_LITERAL_STRING("onerror"),
+ TCPError(NS_LITERAL_STRING("InvalidStateError")),
+ NS_LITERAL_STRING("connecting"));
+}
+
+NS_IMPL_CYCLE_COLLECTION_CLASS(TCPSocketParentBase)
+
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(TCPSocketParentBase)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE_SCRIPT_OBJECTS
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mSocket)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mIntermediary)
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
+
+NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(TCPSocketParentBase)
+ tmp->mIntermediaryObj = nullptr;
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mSocket)
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mIntermediary)
+NS_IMPL_CYCLE_COLLECTION_UNLINK_END
+
+NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN(TCPSocketParentBase)
+ NS_IMPL_CYCLE_COLLECTION_TRACE_JS_MEMBER_CALLBACK(mIntermediaryObj)
+NS_IMPL_CYCLE_COLLECTION_TRACE_END
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(TCPSocketParentBase)
+ NS_INTERFACE_MAP_ENTRY(nsITCPSocketParent)
+ NS_INTERFACE_MAP_ENTRY(nsISupports)
+NS_INTERFACE_MAP_END
+
+NS_IMPL_CYCLE_COLLECTING_ADDREF(TCPSocketParentBase)
+NS_IMPL_CYCLE_COLLECTING_RELEASE(TCPSocketParentBase)
+
+TCPSocketParentBase::TCPSocketParentBase()
+: mIPCOpen(false)
+{
+ mObserver = new mozilla::net::OfflineObserver(this);
+ mozilla::HoldJSObjects(this);
+}
+
+TCPSocketParentBase::~TCPSocketParentBase()
+{
+ if (mObserver) {
+ mObserver->RemoveObserver();
+ }
+ mozilla::DropJSObjects(this);
+}
+
+uint32_t
+TCPSocketParent::GetAppId()
+{
+ uint32_t appId = nsIScriptSecurityManager::UNKNOWN_APP_ID;
+ const PContentParent *content = Manager()->Manager();
+ const InfallibleTArray<PBrowserParent*>& browsers = content->ManagedPBrowserParent();
+ if (browsers.Length() > 0) {
+ TabParent *tab = TabParent::GetFrom(browsers[0]);
+ appId = tab->OwnAppId();
+ }
+ return appId;
+};
+
+bool
+TCPSocketParent::GetInBrowser()
+{
+ bool inBrowser = false;
+ const PContentParent *content = Manager()->Manager();
+ const InfallibleTArray<PBrowserParent*>& browsers = content->ManagedPBrowserParent();
+ if (browsers.Length() > 0) {
+ TabParent *tab = TabParent::GetFrom(browsers[0]);
+ inBrowser = tab->IsBrowserElement();
+ }
+ return inBrowser;
+}
+
+nsresult
+TCPSocketParent::OfflineNotification(nsISupports *aSubject)
+{
+ nsCOMPtr<nsIAppOfflineInfo> info(do_QueryInterface(aSubject));
+ if (!info) {
+ return NS_OK;
+ }
+
+ uint32_t targetAppId = nsIScriptSecurityManager::UNKNOWN_APP_ID;
+ info->GetAppId(&targetAppId);
+
+ // Obtain App ID
+ uint32_t appId = GetAppId();
+ if (appId != targetAppId) {
+ return NS_OK;
+ }
+
+ // If the app is offline, close the socket
+ if (mSocket && NS_IsAppOffline(appId)) {
+ mSocket->Close();
+ mSocket = nullptr;
+ mIntermediaryObj = nullptr;
+ mIntermediary = nullptr;
+ }
+
+ return NS_OK;
+}
+
+
+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;
+}
+
+bool
+TCPSocketParent::RecvOpen(const nsString& aHost, const uint16_t& aPort, const bool& aUseSSL,
+ const nsString& aBinaryType)
+{
+ // We don't have browser actors in xpcshell, and hence can't run automated
+ // tests without this loophole.
+ if (net::UsingNeckoIPCSecurity() &&
+ !AssertAppProcessPermission(Manager()->Manager(), "tcp-socket")) {
+ FireInteralError(this, __LINE__);
+ return true;
+ }
+
+ // Obtain App ID
+ uint32_t appId = GetAppId();
+ bool inBrowser = GetInBrowser();
+
+ if (NS_IsAppOffline(appId)) {
+ NS_ERROR("Can't open socket because app is offline");
+ FireInteralError(this, __LINE__);
+ return true;
+ }
+
+ nsresult rv;
+ mIntermediary = do_CreateInstance("@mozilla.org/tcp-socket-intermediary;1", &rv);
+ if (NS_FAILED(rv)) {
+ FireInteralError(this, __LINE__);
+ return true;
+ }
+
+ rv = mIntermediary->Open(this, aHost, aPort, aUseSSL, aBinaryType, appId,
+ inBrowser, getter_AddRefs(mSocket));
+ if (NS_FAILED(rv) || !mSocket) {
+ FireInteralError(this, __LINE__);
+ return true;
+ }
+
+ return true;
+}
+
+NS_IMETHODIMP
+TCPSocketParent::InitJS(JS::Handle<JS::Value> aIntermediary, JSContext* aCx)
+{
+ MOZ_ASSERT(aIntermediary.isObject());
+ mIntermediaryObj = &aIntermediary.toObject();
+ return NS_OK;
+}
+
+bool
+TCPSocketParent::RecvStartTLS()
+{
+ NS_ENSURE_TRUE(mSocket, true);
+ nsresult rv = mSocket->UpgradeToSecure();
+ NS_ENSURE_SUCCESS(rv, true);
+ return true;
+}
+
+bool
+TCPSocketParent::RecvSuspend()
+{
+ NS_ENSURE_TRUE(mSocket, true);
+ nsresult rv = mSocket->Suspend();
+ NS_ENSURE_SUCCESS(rv, true);
+ return true;
+}
+
+bool
+TCPSocketParent::RecvResume()
+{
+ NS_ENSURE_TRUE(mSocket, true);
+ nsresult rv = mSocket->Resume();
+ NS_ENSURE_SUCCESS(rv, true);
+ return true;
+}
+
+bool
+TCPSocketParent::RecvData(const SendableData& aData,
+ const uint32_t& aTrackingNumber)
+{
+ NS_ENSURE_TRUE(mIntermediary, true);
+
+ nsresult rv;
+ switch (aData.type()) {
+ case SendableData::TArrayOfuint8_t: {
+ AutoSafeJSContext cx;
+ JSAutoRequest ar(cx);
+ JS::Rooted<JS::Value> val(cx);
+ JS::Rooted<JSObject*> obj(cx, mIntermediaryObj);
+ IPC::DeserializeArrayBuffer(obj, aData.get_ArrayOfuint8_t(), &val);
+ rv = mIntermediary->OnRecvSendArrayBuffer(val, aTrackingNumber);
+ NS_ENSURE_SUCCESS(rv, true);
+ break;
+ }
+
+ case SendableData::TnsString:
+ rv = mIntermediary->OnRecvSendString(aData.get_nsString(), aTrackingNumber);
+ NS_ENSURE_SUCCESS(rv, true);
+ break;
+
+ default:
+ MOZ_CRASH("unexpected SendableData type");
+ }
+ return true;
+}
+
+bool
+TCPSocketParent::RecvClose()
+{
+ NS_ENSURE_TRUE(mSocket, true);
+ nsresult rv = mSocket->Close();
+ NS_ENSURE_SUCCESS(rv, true);
+ return true;
+}
+
+NS_IMETHODIMP
+TCPSocketParent::SendEvent(const nsAString& aType, JS::Handle<JS::Value> aDataVal,
+ const nsAString& aReadyState, JSContext* aCx)
+{
+ if (!mIPCOpen) {
+ NS_WARNING("Dropping callback due to no IPC connection");
+ return NS_OK;
+ }
+
+ CallbackData data;
+ if (aDataVal.isString()) {
+ JSString* jsstr = aDataVal.toString();
+ nsAutoJSString str;
+ if (!str.init(aCx, jsstr)) {
+ FireInteralError(this, __LINE__);
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+ data = SendableData(str);
+
+ } else if (aDataVal.isUndefined() || aDataVal.isNull()) {
+ data = mozilla::void_t();
+
+ } else if (aDataVal.isObject()) {
+ JS::Rooted<JSObject *> obj(aCx, &aDataVal.toObject());
+ if (JS_IsArrayBufferObject(obj)) {
+ FallibleTArray<uint8_t> fallibleArr;
+ uint32_t errLine = 0;
+ do {
+ JS::AutoCheckCannotGC nogc;
+ uint32_t nbytes = JS_GetArrayBufferByteLength(obj);
+ uint8_t* buffer = JS_GetArrayBufferData(obj, nogc);
+ if (!buffer) {
+ errLine = __LINE__;
+ break;
+ }
+ if (!fallibleArr.InsertElementsAt(0, buffer, nbytes)) {
+ errLine = __LINE__;
+ break;
+ }
+ } while (false);
+
+ if (errLine) {
+ FireInteralError(this, errLine);
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ InfallibleTArray<uint8_t> arr;
+ arr.SwapElements(fallibleArr);
+ data = SendableData(arr);
+
+ } else {
+ nsAutoJSString name;
+
+ JS::Rooted<JS::Value> val(aCx);
+ if (!JS_GetProperty(aCx, obj, "name", &val)) {
+ NS_ERROR("No name property on supposed error object");
+ } else if (val.isString()) {
+ if (!name.init(aCx, val.toString())) {
+ NS_WARNING("couldn't initialize string");
+ }
+ }
+
+ data = TCPError(name);
+ }
+ } else {
+ NS_ERROR("Unexpected JS value encountered");
+ FireInteralError(this, __LINE__);
+ return NS_ERROR_FAILURE;
+ }
+ mozilla::unused <<
+ PTCPSocketParent::SendCallback(nsString(aType), data,
+ nsString(aReadyState));
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+TCPSocketParent::SetSocketAndIntermediary(nsIDOMTCPSocket *socket,
+ nsITCPSocketIntermediary *intermediary,
+ JSContext* cx)
+{
+ mSocket = socket;
+ mIntermediary = intermediary;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+TCPSocketParent::SendUpdateBufferedAmount(uint32_t aBufferedAmount,
+ uint32_t aTrackingNumber)
+{
+ mozilla::unused << PTCPSocketParent::SendUpdateBufferedAmount(aBufferedAmount,
+ aTrackingNumber);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+TCPSocketParent::GetHost(nsAString& aHost)
+{
+ if (!mSocket) {
+ NS_ERROR("No internal socket instance mSocket!");
+ return NS_ERROR_FAILURE;
+ }
+ return mSocket->GetHost(aHost);
+}
+
+NS_IMETHODIMP
+TCPSocketParent::GetPort(uint16_t* aPort)
+{
+ if (!mSocket) {
+ NS_ERROR("No internal socket instance mSocket!");
+ return NS_ERROR_FAILURE;
+ }
+ return mSocket->GetPort(aPort);
+}
+
+void
+TCPSocketParent::ActorDestroy(ActorDestroyReason why)
+{
+ if (mSocket) {
+ mSocket->Close();
+ }
+ mSocket = nullptr;
+ mIntermediaryObj = nullptr;
+ mIntermediary = nullptr;
+}
+
+bool
+TCPSocketParent::RecvRequestDelete()
+{
+ mozilla::unused << Send__delete__(this);
+ return true;
+}
+
+} // namespace dom
+} // namespace mozilla
diff --git a/dom/network/TCPSocketParent.h b/dom/network/TCPSocketParent.h
new file mode 100644
index 000000000..8a543eaac
--- /dev/null
+++ b/dom/network/TCPSocketParent.h
@@ -0,0 +1,75 @@
+/* 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/net/PTCPSocketParent.h"
+#include "nsITCPSocketParent.h"
+#include "nsCycleCollectionParticipant.h"
+#include "nsCOMPtr.h"
+#include "nsIDOMTCPSocket.h"
+#include "js/TypeDecls.h"
+#include "mozilla/net/OfflineObserver.h"
+
+#define TCPSOCKETPARENT_CID \
+ { 0x4e7246c6, 0xa8b3, 0x426d, { 0x9c, 0x17, 0x76, 0xda, 0xb1, 0xe1, 0xe1, 0x4a } }
+
+namespace mozilla {
+namespace dom {
+
+class PBrowserParent;
+
+class TCPSocketParentBase : public nsITCPSocketParent
+ , public mozilla::net::DisconnectableParent
+{
+public:
+ NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(TCPSocketParentBase)
+ NS_DECL_CYCLE_COLLECTING_ISUPPORTS
+
+ void AddIPDLReference();
+ void ReleaseIPDLReference();
+
+protected:
+ TCPSocketParentBase();
+ virtual ~TCPSocketParentBase();
+
+ JS::Heap<JSObject*> mIntermediaryObj;
+ nsCOMPtr<nsITCPSocketIntermediary> mIntermediary;
+ nsCOMPtr<nsIDOMTCPSocket> mSocket;
+ nsRefPtr<mozilla::net::OfflineObserver> mObserver;
+ bool mIPCOpen;
+};
+
+class TCPSocketParent : public mozilla::net::PTCPSocketParent
+ , public TCPSocketParentBase
+{
+public:
+ NS_DECL_NSITCPSOCKETPARENT
+ NS_IMETHOD_(MozExternalRefCountType) Release() override;
+
+ TCPSocketParent() {}
+
+ virtual bool RecvOpen(const nsString& aHost, const uint16_t& aPort,
+ const bool& useSSL, const nsString& aBinaryType) override;
+
+ virtual bool RecvStartTLS() override;
+ virtual bool RecvSuspend() override;
+ virtual bool RecvResume() override;
+ virtual bool RecvClose() override;
+ virtual bool RecvData(const SendableData& aData,
+ const uint32_t& aTrackingNumber) override;
+ virtual bool RecvRequestDelete() override;
+ virtual nsresult OfflineNotification(nsISupports *) override;
+ virtual uint32_t GetAppId() override;
+ bool GetInBrowser();
+
+private:
+ virtual void ActorDestroy(ActorDestroyReason why) override;
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif
diff --git a/dom/network/TCPSocketParentIntermediary.js b/dom/network/TCPSocketParentIntermediary.js
new file mode 100644
index 000000000..08e72037e
--- /dev/null
+++ b/dom/network/TCPSocketParentIntermediary.js
@@ -0,0 +1,117 @@
+/* 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/. */
+
+"use strict";
+
+const Cc = Components.classes;
+const Ci = Components.interfaces;
+const Cu = Components.utils;
+
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+
+function TCPSocketParentIntermediary() {
+}
+
+TCPSocketParentIntermediary.prototype = {
+ _setCallbacks: function(aParentSide, socket) {
+ aParentSide.initJS(this);
+ this._socket = socket;
+
+ // Create handlers for every possible callback that attempt to trigger
+ // corresponding callbacks on the child object.
+ // ondrain event is not forwarded, since the decision of firing ondrain
+ // is made in child.
+ ["open", "data", "error", "close"].forEach(
+ function(p) {
+ socket["on" + p] = function(data) {
+ aParentSide.sendEvent(p, data.data, socket.readyState,
+ socket.bufferedAmount);
+ };
+ }
+ );
+ },
+
+ _onUpdateBufferedAmountHandler: function(aParentSide, aBufferedAmount, aTrackingNumber) {
+ aParentSide.sendUpdateBufferedAmount(aBufferedAmount, aTrackingNumber);
+ },
+
+ open: function(aParentSide, aHost, aPort, aUseSSL, aBinaryType,
+ aAppId, aInBrowser) {
+ let baseSocket = Cc["@mozilla.org/tcp-socket;1"].createInstance(Ci.nsIDOMTCPSocket);
+ let socket = baseSocket.open(aHost, aPort, {useSecureTransport: aUseSSL, binaryType: aBinaryType});
+ if (!socket)
+ return null;
+
+ let socketInternal = socket.QueryInterface(Ci.nsITCPSocketInternal);
+ socketInternal.setAppId(aAppId);
+ socketInternal.setInBrowser(aInBrowser);
+
+ // Handle parent's request to update buffered amount.
+ socketInternal.setOnUpdateBufferedAmountHandler(
+ this._onUpdateBufferedAmountHandler.bind(this, aParentSide));
+
+ // Handlers are set to the JS-implemented socket object on the parent side.
+ this._setCallbacks(aParentSide, socket);
+ return socket;
+ },
+
+ listen: function(aTCPServerSocketParent, aLocalPort, aBacklog, aBinaryType,
+ aAppId, aInBrowser) {
+ let baseSocket = Cc["@mozilla.org/tcp-socket;1"].createInstance(Ci.nsIDOMTCPSocket);
+ let serverSocket = baseSocket.listen(aLocalPort, { binaryType: aBinaryType }, aBacklog);
+ if (!serverSocket)
+ return null;
+
+ let localPort = serverSocket.localPort;
+
+ serverSocket["onconnect"] = function(socket) {
+ var socketParent = Cc["@mozilla.org/tcp-socket-parent;1"]
+ .createInstance(Ci.nsITCPSocketParent);
+ var intermediary = new TCPSocketParentIntermediary();
+
+ let socketInternal = socket.QueryInterface(Ci.nsITCPSocketInternal);
+ socketInternal.setAppId(aAppId);
+ socketInternal.setInBrowser(aInBrowser);
+ socketInternal.setOnUpdateBufferedAmountHandler(
+ intermediary._onUpdateBufferedAmountHandler.bind(intermediary, socketParent));
+
+ // Handlers are set to the JS-implemented socket object on the parent side,
+ // so that the socket parent object can communicate data
+ // with the corresponding socket child object through IPC.
+ intermediary._setCallbacks(socketParent, socket);
+ // The members in the socket parent object are set with arguments,
+ // so that the socket parent object can communicate data
+ // with the JS socket object on the parent side via the intermediary object.
+ socketParent.setSocketAndIntermediary(socket, intermediary);
+ aTCPServerSocketParent.sendCallbackAccept(socketParent);
+ };
+
+ serverSocket["onerror"] = function(data) {
+ var error = data.data;
+
+ aTCPServerSocketParent.sendCallbackError(error.message, error.filename,
+ error.lineNumber, error.columnNumber);
+ };
+
+ return serverSocket;
+ },
+
+ onRecvSendString: function(aData, aTrackingNumber) {
+ let socketInternal = this._socket.QueryInterface(Ci.nsITCPSocketInternal);
+ return socketInternal.onRecvSendFromChild(aData, 0, 0, aTrackingNumber);
+ },
+
+ onRecvSendArrayBuffer: function(aData, aTrackingNumber) {
+ let socketInternal = this._socket.QueryInterface(Ci.nsITCPSocketInternal);
+ return socketInternal.onRecvSendFromChild(aData, 0, aData.byteLength,
+ aTrackingNumber);
+ },
+
+ classID: Components.ID("{afa42841-a6cb-4a91-912f-93099f6a3d18}"),
+ QueryInterface: XPCOMUtils.generateQI([
+ Ci.nsITCPSocketIntermediary
+ ])
+};
+
+this.NSGetFactory = XPCOMUtils.generateNSGetFactory([TCPSocketParentIntermediary]);
diff --git a/dom/network/src/Types.h b/dom/network/Types.h
index ee2b5e516..ee2b5e516 100644
--- a/dom/network/src/Types.h
+++ b/dom/network/Types.h
diff --git a/dom/network/UDPSocket.cpp b/dom/network/UDPSocket.cpp
new file mode 100644
index 000000000..3ec6f1359
--- /dev/null
+++ b/dom/network/UDPSocket.cpp
@@ -0,0 +1,723 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
+/* 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/UDPMessageEvent.h"
+#include "mozilla/dom/UDPSocketBinding.h"
+#include "mozilla/dom/UnionTypes.h"
+#include "mozilla/net/DNS.h"
+#include "nsComponentManagerUtils.h"
+#include "nsContentUtils.h"
+#include "nsINetAddr.h"
+#include "nsStringStream.h"
+
+namespace mozilla {
+namespace 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_INHERITED(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<nsPIDOMWindow> 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()) {
+ remoteAddress = NS_ConvertUTF16toUTF8(aOptions.mRemoteAddress.Value());
+ } 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);
+ PRNetAddr prAddr;
+ PRStatus status = PR_StringToNetAddr(address.BeginReading(), &prAddr);
+ if (status != PR_SUCCESS) {
+ 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;
+ }
+ }
+
+ nsRefPtr<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(nsPIDOMWindow* aOwner,
+ const nsCString& aRemoteAddress,
+ const Nullable<uint16_t>& aRemotePort)
+ : DOMEventTargetHelper(aOwner)
+ , mRemoteAddress(aRemoteAddress)
+ , mRemotePort(aRemotePort)
+ , mReadyState(SocketReadyState::Opening)
+{
+ MOZ_ASSERT(aOwner);
+ MOZ_ASSERT(aOwner->IsInnerWindow());
+
+ nsIDocument* aDoc = aOwner->GetExtantDoc();
+ if (aDoc) {
+ aDoc->DisallowBFCaching();
+ }
+}
+
+UDPSocket::~UDPSocket()
+{
+ CloseWithReason(NS_OK);
+}
+
+JSObject*
+UDPSocket::WrapObject(JSContext* aCx)
+{
+ return UDPSocketBinding::Wrap(aCx, this);
+}
+
+void
+UDPSocket::DisconnectFromOwner()
+{
+ DOMEventTargetHelper::DisconnectFromOwner();
+ CloseWithReason(NS_OK);
+}
+
+already_AddRefed<Promise>
+UDPSocket::Close()
+{
+ MOZ_ASSERT(mClosed);
+
+ nsRefPtr<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->MaybeResolve(JS::UndefinedHandleValue);
+ } 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, EmptyCString());
+ NS_WARN_IF(aRv.Failed());
+
+ return;
+ }
+
+ MOZ_ASSERT(mSocketChild);
+
+ aRv = mSocketChild->JoinMulticast(address, EmptyCString());
+ NS_WARN_IF(aRv.Failed());
+}
+
+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, EmptyCString());
+ NS_WARN_IF(aRv.Failed());
+ return;
+ }
+
+ MOZ_ASSERT(mSocketChild);
+
+ aRv = mSocketChild->LeaveMulticast(address, EmptyCString());
+ NS_WARN_IF(aRv.Failed());
+}
+
+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.ErrorCode();
+ }
+ }
+
+ 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()) {
+ remoteAddress = NS_ConvertUTF16toUTF8(aRemoteAddress.Value());
+ } else if (!mRemoteAddress.IsVoid()) {
+ remoteAddress = mRemoteAddress;
+ } 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()) {
+ File& blob = aData.GetAsBlob();
+
+ aRv = blob.GetInternalStream(getter_AddRefs(stream));
+ 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.ComputeLengthAndData();
+ aRv = strStream->SetData(reinterpret_cast<const char*>(data.Data()), data.Length());
+ } else {
+ const ArrayBufferView& data = aData.GetAsArrayBufferView();
+ data.ComputeLengthAndData();
+ 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;
+ }
+
+ if (aLocalAddress.IsEmpty()) {
+ rv = sock->Init(aLocalPort, /* loopback = */ false, mAddressReuse, /* optionalArgc = */ 1);
+ } else {
+ PRNetAddr prAddr;
+ PR_InitializeNetAddr(PR_IpAddrAny, aLocalPort, &prAddr);
+ PR_StringToNetAddr(NS_ConvertUTF16toUTF8(aLocalAddress).BeginReading(), &prAddr);
+
+ mozilla::net::NetAddr addr;
+ PRNetAddrToNetAddr(&prAddr, &addr);
+ rv = sock->InitWithAddress(&addr, 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;
+ }
+ mLocalAddress = NS_ConvertUTF8toUTF16(localAddress);
+
+ 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->MaybeResolve(JS::UndefinedHandleValue);
+
+ return NS_OK;
+}
+
+nsresult
+UDPSocket::InitRemote(const nsAString& aLocalAddress,
+ const uint16_t& aLocalPort)
+{
+ nsresult rv;
+
+ nsCOMPtr<nsIUDPSocketChild> sock =
+ do_CreateInstance("@mozilla.org/udp-socket-child;1", &rv);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ mListenerProxy = new ListenerProxy(this);
+
+ rv = sock->Bind(mListenerProxy,
+ NS_ConvertUTF16toUTF8(aLocalAddress),
+ aLocalPort,
+ mAddressReuse,
+ mLoopback);
+
+ 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.ErrorCode();
+ }
+
+ mClosed = Promise::Create(global, rv);
+ if (NS_WARN_IF(rv.Failed())) {
+ return rv.ErrorCode();
+ }
+
+ class OpenSocketRunnable final : public nsRunnable
+ {
+ public:
+ explicit OpenSocketRunnable(UDPSocket* aSocket) : 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_GetProcessType() != GoannaProcessType_Default) {
+ 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:
+ nsRefPtr<UDPSocket> mSocket;
+ };
+
+ nsCOMPtr<nsIRunnable> runnable = new OpenSocketRunnable(this);
+
+ return NS_DispatchToMainThread(runnable);
+}
+
+void
+UDPSocket::HandleReceivedData(const nsACString& aRemoteAddress,
+ const uint16_t& aRemotePort,
+ const uint8_t* aData,
+ const uint32_t& aDataLength)
+{
+ if (mReadyState != SocketReadyState::Open) {
+ return;
+ }
+
+ if (NS_FAILED(CheckInnerWindowCorrectness())) {
+ return;
+ }
+
+ if (NS_FAILED(DispatchReceivedData(aRemoteAddress, aRemotePort, aData, aDataLength))) {
+ CloseWithReason(NS_ERROR_TYPE_ERR);
+ }
+}
+
+nsresult
+UDPSocket::DispatchReceivedData(const nsACString& aRemoteAddress,
+ const uint16_t& aRemotePort,
+ const uint8_t* aData,
+ const uint32_t& aDataLength)
+{
+ 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, aDataLength, aData));
+
+ 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);
+ init.mRemoteAddress = NS_ConvertUTF8toUTF16(aRemoteAddress);
+ init.mRemotePort = aRemotePort;
+ init.mData = jsData;
+
+ nsRefPtr<UDPMessageEvent> udpEvent =
+ UDPMessageEvent::Constructor(this, NS_LITERAL_STRING("message"), init);
+
+ if (NS_WARN_IF(!udpEvent)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ udpEvent->SetTrusted(true);
+
+ nsRefPtr<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.Elements(), buffer.Length());
+ 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 uint8_t* aData,
+ uint32_t aDataLength)
+{
+ HandleReceivedData(aRemoteAddress, aRemotePort, aData, aDataLength);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+UDPSocket::CallListenerOpened()
+{
+ if (mReadyState != SocketReadyState::Opening) {
+ return NS_OK;
+ }
+
+ MOZ_ASSERT(mSocketChild);
+
+ // Get real local address and local port
+ nsCString localAddress;
+ mSocketChild->GetLocalAddress(localAddress);
+ mLocalAddress = NS_ConvertUTF8toUTF16(localAddress);
+
+ uint16_t localPort;
+ mSocketChild->GetLocalPort(&localPort);
+ mLocalPort.SetValue(localPort);
+
+ mReadyState = SocketReadyState::Open;
+ nsresult rv = DoPendingMcastCommand();
+
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ CloseWithReason(rv);
+ return NS_OK;
+ }
+
+ mOpened->MaybeResolve(JS::UndefinedHandleValue);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+UDPSocket::CallListenerClosed()
+{
+ CloseWithReason(NS_OK);
+
+ return NS_OK;
+}
+
+} // namespace dom
+} // namespace mozilla
diff --git a/dom/network/UDPSocket.h b/dom/network/UDPSocket.h
new file mode 100644
index 000000000..f041e1235
--- /dev/null
+++ b/dom/network/UDPSocket.h
@@ -0,0 +1,222 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
+/* 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/ErrorResult.h"
+#include "mozilla/dom/Promise.h"
+#include "mozilla/dom/SocketCommonBinding.h"
+#include "nsIUDPSocket.h"
+#include "nsIUDPSocketChild.h"
+#include "nsTArray.h"
+
+struct JSContext;
+
+namespace mozilla {
+namespace dom {
+
+struct UDPOptions;
+class StringOrBlobOrArrayBufferOrArrayBufferView;
+
+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
+ NS_REALLY_FORWARD_NSIDOMEVENTTARGET(DOMEventTargetHelper)
+
+public:
+ nsPIDOMWindow*
+ GetParentObject() const
+ {
+ return GetOwner();
+ }
+
+ virtual JSObject*
+ WrapObject(JSContext* aCx) 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;
+ }
+
+ aRetVal = NS_ConvertUTF8toUTF16(mRemoteAddress);
+ }
+
+ 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() {}
+
+ UDPSocket* mSocket;
+ };
+
+ UDPSocket(nsPIDOMWindow* 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 uint8_t* aData,
+ const uint32_t& aDataLength);
+
+ nsresult
+ DispatchReceivedData(const nsACString& aRemoteAddress,
+ const uint16_t& aRemotePort,
+ const uint8_t* aData,
+ const uint32_t& aDataLength);
+
+ void
+ CloseWithReason(nsresult aReason);
+
+ nsresult
+ DoPendingMcastCommand();
+
+ nsString mLocalAddress;
+ Nullable<uint16_t> mLocalPort;
+ nsCString mRemoteAddress;
+ Nullable<uint16_t> mRemotePort;
+ bool mAddressReuse;
+ bool mLoopback;
+ SocketReadyState mReadyState;
+ nsRefPtr<Promise> mOpened;
+ nsRefPtr<Promise> mClosed;
+
+ nsCOMPtr<nsIUDPSocket> mSocket;
+ nsCOMPtr<nsIUDPSocketChild> mSocketChild;
+ nsRefPtr<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 000000000..eb6a86f50
--- /dev/null
+++ b/dom/network/UDPSocketChild.cpp
@@ -0,0 +1,259 @@
+/* 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 "mozilla/unused.h"
+#include "mozilla/ipc/InputStreamUtils.h"
+#include "mozilla/net/NeckoChild.h"
+
+using mozilla::net::gNeckoChild;
+
+namespace mozilla {
+namespace dom {
+
+NS_IMPL_ISUPPORTS(UDPSocketChildBase, nsIUDPSocketChild)
+
+UDPSocketChildBase::UDPSocketChildBase()
+: mIPCOpen(false)
+{
+}
+
+UDPSocketChildBase::~UDPSocketChildBase()
+{
+}
+
+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()
+:mLocalPort(0)
+{
+}
+
+UDPSocketChild::~UDPSocketChild()
+{
+}
+
+// nsIUDPSocketChild Methods
+
+NS_IMETHODIMP
+UDPSocketChild::Bind(nsIUDPSocketInternal* aSocket,
+ const nsACString& aHost,
+ uint16_t aPort,
+ bool aAddressReuse,
+ bool aLoopback)
+{
+ NS_ENSURE_ARG(aSocket);
+
+ mSocket = aSocket;
+ AddIPDLReference();
+
+ gNeckoChild->SendPUDPSocketConstructor(this, mFilterName);
+
+ SendBind(UDPAddressInfo(nsCString(aHost), aPort), aAddressReuse, aLoopback);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+UDPSocketChild::Close()
+{
+ SendClose();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+UDPSocketChild::Send(const nsACString& aHost,
+ uint16_t aPort,
+ const uint8_t* aData,
+ uint32_t aByteLength)
+{
+ NS_ENSURE_ARG(aData);
+
+ return SendDataInternal(UDPSocketAddr(UDPAddressInfo(nsCString(aHost), aPort)),
+ aData, aByteLength);
+}
+
+NS_IMETHODIMP
+UDPSocketChild::SendWithAddr(nsINetAddr* aAddr,
+ const uint8_t* aData,
+ uint32_t aByteLength)
+{
+ NS_ENSURE_ARG(aAddr);
+ NS_ENSURE_ARG(aData);
+
+ NetAddr addr;
+ aAddr->GetNetAddr(&addr);
+
+ return SendDataInternal(UDPSocketAddr(addr), aData, aByteLength);
+}
+
+NS_IMETHODIMP
+UDPSocketChild::SendWithAddress(const NetAddr* aAddr,
+ const uint8_t* aData,
+ uint32_t aByteLength)
+{
+ NS_ENSURE_ARG(aAddr);
+ NS_ENSURE_ARG(aData);
+
+ 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)) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ InfallibleTArray<uint8_t> array;
+ array.SwapElements(fallibleArray);
+
+ SendOutgoingData(array, aAddr);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+UDPSocketChild::SendBinaryStream(const nsACString& aHost,
+ uint16_t aPort,
+ nsIInputStream* aStream)
+{
+ NS_ENSURE_ARG(aStream);
+
+ OptionalInputStreamParams stream;
+ nsTArray<mozilla::ipc::FileDescriptor> fds;
+ SerializeInputStream(aStream, stream, fds);
+
+ MOZ_ASSERT(fds.IsEmpty());
+
+ SendOutgoingData(UDPData(stream), UDPSocketAddr(UDPAddressInfo(nsCString(aHost), aPort)));
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+UDPSocketChild::JoinMulticast(const nsACString& aMulticastAddress,
+ const nsACString& aInterface)
+{
+ SendJoinMulticast(nsCString(aMulticastAddress), nsCString(aInterface));
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+UDPSocketChild::LeaveMulticast(const nsACString& aMulticastAddress,
+ const nsACString& aInterface)
+{
+ SendLeaveMulticast(nsCString(aMulticastAddress), nsCString(aInterface));
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+UDPSocketChild::GetLocalPort(uint16_t* aLocalPort)
+{
+ NS_ENSURE_ARG_POINTER(aLocalPort);
+
+ *aLocalPort = mLocalPort;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+UDPSocketChild::GetLocalAddress(nsACString& aLocalAddress)
+{
+ aLocalAddress = mLocalAddress;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+UDPSocketChild::SetFilterName(const nsACString& aFilterName)
+{
+ if (!mFilterName.IsEmpty()) {
+ // filter name can only be set once.
+ return NS_ERROR_FAILURE;
+ }
+ mFilterName = aFilterName;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+UDPSocketChild::GetFilterName(nsACString& aFilterName)
+{
+ aFilterName = mFilterName;
+ return NS_OK;
+}
+
+// PUDPSocketChild Methods
+bool
+UDPSocketChild::RecvCallbackOpened(const UDPAddressInfo& aAddressInfo)
+{
+ mLocalAddress = aAddressInfo.addr();
+ mLocalPort = aAddressInfo.port();
+
+ nsresult rv = mSocket->CallListenerOpened();
+ mozilla::unused << NS_WARN_IF(NS_FAILED(rv));
+
+ return true;
+}
+
+bool
+UDPSocketChild::RecvCallbackClosed()
+{
+ nsresult rv = mSocket->CallListenerClosed();
+ mozilla::unused << NS_WARN_IF(NS_FAILED(rv));
+
+ return true;
+}
+
+bool
+UDPSocketChild::RecvCallbackReceivedData(const UDPAddressInfo& aAddressInfo,
+ InfallibleTArray<uint8_t>&& aData)
+{
+ nsresult rv = mSocket->CallListenerReceivedData(aAddressInfo.addr(), aAddressInfo.port(),
+ aData.Elements(), aData.Length());
+ mozilla::unused << NS_WARN_IF(NS_FAILED(rv));
+
+ return true;
+}
+
+bool
+UDPSocketChild::RecvCallbackError(const nsCString& aMessage,
+ const nsCString& aFilename,
+ const uint32_t& aLineNumber)
+{
+ nsresult rv = mSocket->CallListenerError(aMessage, aFilename, aLineNumber);
+ mozilla::unused << NS_WARN_IF(NS_FAILED(rv));
+
+ return true;
+}
+
+} // namespace dom
+} // namespace mozilla
diff --git a/dom/network/UDPSocketChild.h b/dom/network/UDPSocketChild.h
new file mode 100644
index 000000000..9f44ec05a
--- /dev/null
+++ b/dom/network/UDPSocketChild.h
@@ -0,0 +1,64 @@
+/* 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 "nsIUDPSocketChild.h"
+#include "nsCycleCollectionParticipant.h"
+#include "nsCOMPtr.h"
+
+#define UDPSOCKETCHILD_CID \
+ {0xb47e5a0f, 0xd384, 0x48ef, { 0x88, 0x85, 0x42, 0x59, 0x79, 0x3d, 0x9c, 0xf0 }}
+
+namespace mozilla {
+namespace dom {
+
+class UDPSocketChildBase : public nsIUDPSocketChild {
+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_DECL_NSIUDPSOCKETCHILD
+ NS_IMETHOD_(MozExternalRefCountType) Release() override;
+
+ UDPSocketChild();
+ virtual ~UDPSocketChild();
+
+ virtual bool RecvCallbackOpened(const UDPAddressInfo& aAddressInfo) override;
+ virtual bool RecvCallbackClosed() override;
+ virtual bool RecvCallbackReceivedData(const UDPAddressInfo& aAddressInfo,
+ InfallibleTArray<uint8_t>&& aData) override;
+ virtual bool RecvCallbackError(const nsCString& aMessage,
+ const nsCString& aFilename,
+ const uint32_t& aLineNumber) override;
+
+private:
+ nsresult SendDataInternal(const UDPSocketAddr& aAddr,
+ const uint8_t* aData,
+ const uint32_t aByteLength);
+
+ uint16_t mLocalPort;
+ nsCString mLocalAddress;
+ nsCString mFilterName;
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif // !defined(mozilla_dom_UDPSocketChild_h__)
diff --git a/dom/network/UDPSocketParent.cpp b/dom/network/UDPSocketParent.cpp
new file mode 100644
index 000000000..dc742013f
--- /dev/null
+++ b/dom/network/UDPSocketParent.cpp
@@ -0,0 +1,429 @@
+/* -*- 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 "nsIServiceManager.h"
+#include "UDPSocketParent.h"
+#include "nsComponentManagerUtils.h"
+#include "nsIUDPSocket.h"
+#include "nsINetAddr.h"
+#include "mozilla/AppProcessChecker.h"
+#include "mozilla/unused.h"
+#include "mozilla/ipc/InputStreamUtils.h"
+#include "mozilla/net/DNS.h"
+#include "mozilla/net/NeckoCommon.h"
+#include "mozilla/net/PNeckoParent.h"
+#include "nsNetUtil.h"
+#include "mozilla/dom/ContentParent.h"
+#include "mozilla/dom/TabParent.h"
+
+namespace mozilla {
+namespace dom {
+
+NS_IMPL_ISUPPORTS(UDPSocketParent, nsIUDPSocketListener)
+
+UDPSocketParent::UDPSocketParent()
+ : mIPCOpen(true)
+{
+ mObserver = new mozilla::net::OfflineObserver(this);
+}
+
+UDPSocketParent::~UDPSocketParent()
+{
+ if (mObserver) {
+ mObserver->RemoveObserver();
+ }
+}
+
+nsresult
+UDPSocketParent::OfflineNotification(nsISupports *aSubject)
+{
+ nsCOMPtr<nsIAppOfflineInfo> info(do_QueryInterface(aSubject));
+ if (!info) {
+ return NS_OK;
+ }
+
+ uint32_t targetAppId = nsIScriptSecurityManager::UNKNOWN_APP_ID;
+ info->GetAppId(&targetAppId);
+
+ // Obtain App ID
+ uint32_t appId = GetAppId();
+ if (appId != targetAppId) {
+ return NS_OK;
+ }
+
+ // If the app is offline, close the socket
+ if (mSocket && NS_IsAppOffline(appId)) {
+ mSocket->Close();
+ }
+
+ return NS_OK;
+}
+
+uint32_t
+UDPSocketParent::GetAppId()
+{
+ uint32_t appId = nsIScriptSecurityManager::UNKNOWN_APP_ID;
+ const PContentParent *content = Manager()->Manager();
+ const InfallibleTArray<PBrowserParent*>& browsers = content->ManagedPBrowserParent();
+ if (browsers.Length() > 0) {
+ TabParent *tab = TabParent::GetFrom(browsers[0]);
+ appId = tab->OwnAppId();
+ }
+ return appId;
+}
+
+bool
+UDPSocketParent::Init(const nsACString& aFilter)
+{
+ if (!aFilter.IsEmpty()) {
+ nsAutoCString contractId(NS_NETWORK_UDP_SOCKET_FILTER_HANDLER_PREFIX);
+ contractId.Append(aFilter);
+ nsCOMPtr<nsIUDPSocketFilterHandler> 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
+
+bool
+UDPSocketParent::RecvBind(const UDPAddressInfo& aAddressInfo,
+ const bool& aAddressReuse, const bool& aLoopback)
+{
+ // We don't have browser actors in xpcshell, and hence can't run automated
+ // tests without this loophole.
+ if (net::UsingNeckoIPCSecurity() && !mFilter &&
+ !AssertAppProcessPermission(Manager()->Manager(), "udp-socket")) {
+ FireInternalError(__LINE__);
+ return false;
+ }
+
+ if (NS_FAILED(BindInternal(aAddressInfo.addr(), aAddressInfo.port(), aAddressReuse, aLoopback))) {
+ FireInternalError(__LINE__);
+ return true;
+ }
+
+ nsCOMPtr<nsINetAddr> localAddr;
+ mSocket->GetLocalAddr(getter_AddRefs(localAddr));
+
+ nsCString addr;
+ if (NS_FAILED(localAddr->GetAddress(addr))) {
+ FireInternalError(__LINE__);
+ return true;
+ }
+
+ uint16_t port;
+ if (NS_FAILED(localAddr->GetPort(&port))) {
+ FireInternalError(__LINE__);
+ return true;
+ }
+
+ mozilla::unused << SendCallbackOpened(UDPAddressInfo(addr, port));
+
+ return true;
+}
+
+nsresult
+UDPSocketParent::BindInternal(const nsCString& aHost, const uint16_t& aPort,
+ const bool& aAddressReuse, const bool& aLoopback)
+{
+ nsresult rv;
+
+ 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, 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;
+ PRNetAddrToNetAddr(&prAddr, &addr);
+ rv = sock->InitWithAddress(&addr, aAddressReuse, /* optional_argc = */ 1);
+ }
+
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = sock->SetMulticastLoopback(aLoopback);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ // register listener
+ rv = sock->AsyncListen(this);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ mSocket = sock;
+
+ return NS_OK;
+}
+
+bool
+UDPSocketParent::RecvOutgoingData(const UDPData& aData,
+ const UDPSocketAddr& aAddr)
+{
+ MOZ_ASSERT(mSocket);
+
+ nsresult rv;
+ if (mFilter) {
+ // TODO, Bug 933102, filter packets that are sent with hostname.
+ // Until then we simply throw away packets that are sent to a hostname.
+ if (aAddr.type() != UDPSocketAddr::TNetAddr) {
+ return true;
+ }
+
+ // TODO, Packet filter doesn't support input stream yet.
+ if (aData.type() != UDPData::TArrayOfuint8_t) {
+ return true;
+ }
+
+ bool allowed;
+ const InfallibleTArray<uint8_t>& data(aData.get_ArrayOfuint8_t());
+ rv = mFilter->FilterPacket(&aAddr.get_NetAddr(), data.Elements(),
+ data.Length(), nsIUDPSocketFilter::SF_OUTGOING,
+ &allowed);
+
+ // Sending unallowed data, kill content.
+ if (NS_WARN_IF(NS_FAILED(rv)) || !allowed) {
+ return false;
+ }
+ }
+
+ switch(aData.type()) {
+ case UDPData::TArrayOfuint8_t:
+ Send(aData.get_ArrayOfuint8_t(), aAddr);
+ break;
+ case UDPData::TInputStreamParams:
+ Send(aData.get_InputStreamParams(), aAddr);
+ break;
+ default:
+ MOZ_ASSERT(false, "Invalid data type!");
+ return true;
+ }
+
+ return true;
+}
+
+void
+UDPSocketParent::Send(const InfallibleTArray<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.Elements(), aData.Length(), &count);
+ break;
+ }
+ case UDPSocketAddr::TNetAddr: {
+ const NetAddr& addr(aAddr.get_NetAddr());
+ rv = mSocket->SendWithAddress(&addr, aData.Elements(),
+ aData.Length(), &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 InputStreamParams& aStream,
+ const UDPSocketAddr& aAddr)
+{
+ nsTArray<mozilla::ipc::FileDescriptor> fds;
+ nsCOMPtr<nsIInputStream> stream = DeserializeInputStream(aStream, fds);
+
+ 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__);
+ }
+}
+
+bool
+UDPSocketParent::RecvJoinMulticast(const nsCString& aMulticastAddress,
+ const nsCString& aInterface)
+{
+ nsresult rv = mSocket->JoinMulticast(aMulticastAddress, aInterface);
+
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ FireInternalError(__LINE__);
+ }
+
+ return true;
+}
+
+bool
+UDPSocketParent::RecvLeaveMulticast(const nsCString& aMulticastAddress,
+ const nsCString& aInterface)
+{
+ nsresult rv = mSocket->LeaveMulticast(aMulticastAddress, aInterface);
+
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ FireInternalError(__LINE__);
+ }
+
+ return true;
+}
+
+bool
+UDPSocketParent::RecvClose()
+{
+ if (!mSocket) {
+ return true;
+ }
+
+ nsresult rv = mSocket->Close();
+ mSocket = nullptr;
+
+ mozilla::unused << NS_WARN_IF(NS_FAILED(rv));
+
+ return true;
+}
+
+bool
+UDPSocketParent::RecvRequestDelete()
+{
+ mozilla::unused << Send__delete__(this);
+ return true;
+}
+
+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();
+
+ if (mFilter) {
+ bool allowed;
+ mozilla::net::NetAddr addr;
+ fromAddr->GetNetAddr(&addr);
+ nsresult rv = mFilter->FilterPacket(&addr,
+ (const uint8_t*)buffer, len,
+ nsIUDPSocketFilter::SF_INCOMING,
+ &allowed);
+ // Receiving unallowed data, drop.
+ if (NS_WARN_IF(NS_FAILED(rv)) || !allowed) {
+ return NS_OK;
+ }
+ }
+
+ FallibleTArray<uint8_t> fallibleArray;
+ if (!fallibleArray.InsertElementsAt(0, buffer, len)) {
+ FireInternalError(__LINE__);
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+ InfallibleTArray<uint8_t> infallibleArray;
+ infallibleArray.SwapElements(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(NS_LITERAL_CSTRING("Internal error"),
+ NS_LITERAL_CSTRING(__FILE__), aLineNo);
+}
+
+} // namespace dom
+} // namespace mozilla
diff --git a/dom/network/UDPSocketParent.h b/dom/network/UDPSocketParent.h
new file mode 100644
index 000000000..9ac55e0e9
--- /dev/null
+++ b/dom/network/UDPSocketParent.h
@@ -0,0 +1,65 @@
+/* -*- 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 "nsIUDPSocketFilter.h"
+#include "mozilla/net/OfflineObserver.h"
+
+namespace mozilla {
+namespace dom {
+
+class UDPSocketParent : public mozilla::net::PUDPSocketParent
+ , public nsIUDPSocketListener
+ , public mozilla::net::DisconnectableParent
+{
+public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSIUDPSOCKETLISTENER
+
+ UDPSocketParent();
+
+ bool Init(const nsACString& aFilter);
+
+ virtual bool RecvBind(const UDPAddressInfo& aAddressInfo,
+ const bool& aAddressReuse, const bool& aLoopback) override;
+
+ virtual bool RecvOutgoingData(const UDPData& aData, const UDPSocketAddr& aAddr) override;
+
+ virtual bool RecvClose() override;
+ virtual bool RecvRequestDelete() override;
+ virtual bool RecvJoinMulticast(const nsCString& aMulticastAddress,
+ const nsCString& aInterface) override;
+ virtual bool RecvLeaveMulticast(const nsCString& aMulticastAddress,
+ const nsCString& aInterface) override;
+ virtual nsresult OfflineNotification(nsISupports *) override;
+ virtual uint32_t GetAppId() override;
+
+private:
+ virtual ~UDPSocketParent();
+
+ virtual void ActorDestroy(ActorDestroyReason why) override;
+ void Send(const InfallibleTArray<uint8_t>& aData, const UDPSocketAddr& aAddr);
+ void Send(const InputStreamParams& aStream, const UDPSocketAddr& aAddr);
+ nsresult BindInternal(const nsCString& aHost, const uint16_t& aPort,
+ const bool& aAddressReuse, const bool& aLoopback);
+
+ void FireInternalError(uint32_t aLineNo);
+
+ bool mIPCOpen;
+ nsCOMPtr<nsIUDPSocket> mSocket;
+ nsCOMPtr<nsIUDPSocketFilter> mFilter;
+ nsRefPtr<mozilla::net::OfflineObserver> mObserver;
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif // !defined(mozilla_dom_UDPSocketParent_h__)
diff --git a/dom/network/interfaces/Makefile.in b/dom/network/interfaces/Makefile.in
deleted file mode 100644
index 143590c47..000000000
--- a/dom/network/interfaces/Makefile.in
+++ /dev/null
@@ -1,15 +0,0 @@
-# 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/.
-
-DEPTH = @DEPTH@
-topsrcdir = @top_srcdir@
-srcdir = @srcdir@
-VPATH = @srcdir@
-
-include $(DEPTH)/config/autoconf.mk
-
-include $(topsrcdir)/dom/dom-config.mk
-
-include $(topsrcdir)/config/rules.mk
-
diff --git a/dom/network/interfaces/moz.build b/dom/network/interfaces/moz.build
index 0b90873af..a078c7b1b 100644
--- a/dom/network/interfaces/moz.build
+++ b/dom/network/interfaces/moz.build
@@ -5,29 +5,20 @@
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
XPIDL_SOURCES += [
- 'nsIDOMConnection.idl',
- 'nsIDOMDataErrorEvent.idl',
- 'nsIDOMNavigatorNetwork.idl',
+ 'nsIDOMTCPServerSocket.idl',
'nsIDOMTCPSocket.idl',
- 'nsIDOMUSSDReceivedEvent.idl',
+ 'nsIMozNavigatorNetwork.idl',
+ 'nsITCPServerSocketChild.idl',
+ 'nsITCPServerSocketParent.idl',
'nsITCPSocketChild.idl',
'nsITCPSocketParent.idl',
+ 'nsIUDPSocketChild.idl',
]
-if CONFIG['MOZ_B2G_RIL']:
+if CONFIG['MOZ_WIDGET_TOOLKIT'] == 'gonk':
XPIDL_SOURCES += [
- 'nsIDOMCFStateChangeEvent.idl',
- 'nsIDOMMobileConnection.idl',
- 'nsIDOMNetworkStats.idl',
'nsIDOMNetworkStatsManager.idl',
- 'nsIMobileConnectionProvider.idl',
- 'nsINavigatorMobileConnection.idl',
+ 'nsINetworkStatsServiceProxy.idl',
]
XPIDL_MODULE = 'dom_network'
-
-XPIDL_FLAGS += [
- '-I$(topsrcdir)/dom/interfaces/base',
- '-I$(topsrcdir)/dom/interfaces/events',
-]
-
diff --git a/dom/network/interfaces/nsIDOMCFStateChangeEvent.idl b/dom/network/interfaces/nsIDOMCFStateChangeEvent.idl
deleted file mode 100644
index 0b8d9fd93..000000000
--- a/dom/network/interfaces/nsIDOMCFStateChangeEvent.idl
+++ /dev/null
@@ -1,74 +0,0 @@
-/* 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 "nsIDOMEvent.idl"
-
-[scriptable, builtinclass, uuid(905348f1-3d06-443c-8762-567e7e4b6696)]
-interface nsIDOMCFStateChangeEvent : nsIDOMEvent
-{
- /**
- * Indicates about errors while setting up the Call forwarding rule.
- */
- readonly attribute boolean success;
-
- /**
- * Indicates what to do with the rule.
- *
- * One of the CALL_FORWARD_ACTION_* constants. It will be either disable (0),
- * enable (1), query status (2), registration (3), or erasure (4).
- *
- * @see 3GPP nsIDOMMozMobileCFInfo.CALL_FORWARD_ACTION_* values.
- * @see 3GPP TS 27.007 7.11 "mode".
- */
- readonly attribute unsigned short action;
-
- /**
- * Indicates the reason the call is being forwarded.
- *
- * One of the CALL_FORWARD_REASON_* constants. It will be either
- * unconditional (0), mobile busy (1), no reply (2), not reachable (3),
- * all call forwarding (4), or all conditional call forwarding (5).
- *
- * @see 3GPP nsIDOMMozMobileCFInfo.CALL_FORWARD_REASON_* values.
- * @see 3GPP TS 27.007 7.11 "reason".
- */
- readonly attribute unsigned short reason;
-
- /**
- * Phone number of forwarding address.
- */
- readonly attribute DOMString number;
-
- /**
- * When "no reply" is enabled or queried, this gives the time in
- * seconds to wait before call is forwarded.
- */
- readonly attribute unsigned short timeSeconds;
-
- /**
- * Service for which the call forward is set up. It should be one of the
- * nsIDOMMozMobileConnectionInfo.ICC_SERVICE_CLASS_* values.
- */
- readonly attribute unsigned short serviceClass;
-
- [noscript] void initCFStateChangeEvent(in DOMString aType,
- in boolean aCanBubble,
- in boolean aCancelable,
- in boolean aSuccess,
- in unsigned short aAction,
- in unsigned short aReason,
- in DOMString aNumber,
- in unsigned short aTimeSeconds,
- in unsigned short aServiceClass);
-};
-
-dictionary CFStateChangeEventInit : EventInit
-{
- boolean success;
- unsigned short action;
- unsigned short reason;
- DOMString number;
- unsigned short timeSeconds;
- unsigned short serviceClass;
-};
diff --git a/dom/network/interfaces/nsIDOMConnection.idl b/dom/network/interfaces/nsIDOMConnection.idl
deleted file mode 100644
index 7a4c2b95e..000000000
--- a/dom/network/interfaces/nsIDOMConnection.idl
+++ /dev/null
@@ -1,16 +0,0 @@
-/* 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"
-
-interface nsIDOMEventListener;
-
-[scriptable, uuid(a0eb16f3-5fa2-4cbd-bf7a-4ce7704b13ea)]
-interface nsIDOMMozConnection : nsISupports
-{
- readonly attribute double bandwidth;
- readonly attribute boolean metered;
-
- [implicit_jscontext] attribute jsval onchange;
-};
diff --git a/dom/network/interfaces/nsIDOMDataErrorEvent.idl b/dom/network/interfaces/nsIDOMDataErrorEvent.idl
deleted file mode 100644
index 740ada0ae..000000000
--- a/dom/network/interfaces/nsIDOMDataErrorEvent.idl
+++ /dev/null
@@ -1,21 +0,0 @@
-/* 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 "nsIDOMEvent.idl"
-
-[scriptable, builtinclass, uuid(8b4ed443-a6b0-4445-aab0-9aa9fd5f6c1e)]
-interface nsIDOMDataErrorEvent : nsIDOMEvent
-{
- readonly attribute DOMString message;
-
- [noscript] void initDataErrorEvent(in DOMString aType,
- in boolean aCanBubble,
- in boolean aCancelable,
- in DOMString aMessage);
-};
-
-dictionary DataErrorEventInit : EventInit
-{
- DOMString message;
-};
diff --git a/dom/network/interfaces/nsIDOMMobileConnection.idl b/dom/network/interfaces/nsIDOMMobileConnection.idl
deleted file mode 100644
index 02c4ee6e1..000000000
--- a/dom/network/interfaces/nsIDOMMobileConnection.idl
+++ /dev/null
@@ -1,533 +0,0 @@
-/* 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 "nsIDOMEventTarget.idl"
-
-interface nsIDOMEventListener;
-interface nsIDOMDOMRequest;
-interface nsIDOMMozMobileICCInfo;
-interface nsIDOMMozMobileConnectionInfo;
-interface nsIDOMMozMobileNetworkInfo;
-interface nsIDOMMozMobileCellInfo;
-interface nsIDOMMozMobileCFInfo;
-
-[scriptable, builtinclass, uuid(c7fdf0f0-a740-11e2-9e96-0800200c9a66)]
-interface nsIDOMMozMobileConnection : nsIDOMEventTarget
-{
- const long ICC_SERVICE_CLASS_VOICE = (1 << 0);
- const long ICC_SERVICE_CLASS_DATA = (1 << 1);
- const long ICC_SERVICE_CLASS_FAX = (1 << 2);
- const long ICC_SERVICE_CLASS_SMS = (1 << 3);
- const long ICC_SERVICE_CLASS_DATA_SYNC = (1 << 4);
- const long ICC_SERVICE_CLASS_DATA_ASYNC = (1 << 5);
- const long ICC_SERVICE_CLASS_PACKET = (1 << 6);
- const long ICC_SERVICE_CLASS_PAD = (1 << 7);
- const long ICC_SERVICE_CLASS_MAX = (1 << 7);
-
- /**
- * Call barring program.
- *
- * (0) all outgoing.
- * (1) outgoing international.
- * (2) outgoing international except to home country.
- * (3) all incoming.
- * (4) incoming when roaming outside the home country.
- */
- const long CALL_BARRING_PROGRAM_ALL_OUTGOING = 0;
- const long CALL_BARRING_PROGRAM_OUTGOING_INTERNATIONAL = 1;
- const long CALL_BARRING_PROGRAM_OUTGOING_INTERNATIONAL_EXCEPT_HOME = 2;
- const long CALL_BARRING_PROGRAM_ALL_INCOMING = 3;
- const long CALL_BARRING_PROGRAM_INCOMING_ROAMING = 4;
-
- /**
- * These two fields can be accessed by privileged applications with the
- * 'mobilenetwork' permission.
- */
- readonly attribute DOMString lastKnownNetwork;
- readonly attribute DOMString lastKnownHomeNetwork;
-
- /**
- * Indicates the state of the device's ICC card.
- *
- * Possible values: null, 'unknown', 'absent', 'pinRequired', 'pukRequired',
- * 'networkLocked', 'corporateLocked', 'serviceProviderLocked', 'ready'.
- */
- readonly attribute DOMString cardState;
-
- /**
- * Indicates the number of retries remaining when cardState equals 'pinRequired'
- * or 'pukRequired'. 0 denotes the retry count is unavailable.
- *
- * Value is undefined for other cardState values.
- */
- readonly attribute long retryCount;
-
- /**
- * Information stored in the device's ICC card.
- */
- readonly attribute nsIDOMMozMobileICCInfo iccInfo;
-
- /**
- * Information about the voice connection.
- */
- readonly attribute nsIDOMMozMobileConnectionInfo voice;
-
- /**
- * Information about the data connection.
- */
- readonly attribute nsIDOMMozMobileConnectionInfo data;
-
- /**
- * The selection mode of the voice and data networks.
- *
- * Possible values: null (unknown), 'automatic', 'manual'
- */
- readonly attribute DOMString networkSelectionMode;
-
- /**
- * Search for available networks.
- *
- * If successful, the request's onsuccess will be called, and the request's
- * result will be an array of nsIDOMMozMobileNetworkInfo.
- *
- * Otherwise, the request's onerror will be called, and the request's error
- * will be either 'RadioNotAvailable', 'RequestNotSupported',
- * or 'GenericFailure'.
- */
- nsIDOMDOMRequest getNetworks();
-
- /**
- * Manually selects the passed in network, overriding the radio's current
- * selection.
- *
- * If successful, the request's onsuccess will be called.
- * Note: If the network was actually changed by this request,
- * the 'voicechange' and 'datachange' events will also be fired.
- *
- * Otherwise, the request's onerror will be called, and the request's error
- * will be either 'RadioNotAvailable', 'RequestNotSupported',
- * 'IllegalSIMorME', or 'GenericFailure'
- */
- nsIDOMDOMRequest selectNetwork(in nsIDOMMozMobileNetworkInfo network);
-
- /**
- * Tell the radio to automatically select a network.
- *
- * If successful, the request's onsuccess will be called.
- * Note: If the network was actually changed by this request, the
- * 'voicechange' and 'datachange' events will also be fired.
- *
- * Otherwise, the request's onerror will be called, and the request's error
- * will be either 'RadioNotAvailable', 'RequestNotSupported',
- * 'IllegalSIMorME', or 'GenericFailure'
- */
- nsIDOMDOMRequest selectNetworkAutomatically();
-
- /**
- * Send a MMI message.
- *
- * @param mmi
- * DOMString containing an MMI string that can be associated to a
- * USSD request or other RIL functionality.
- *
- * @return a nsIDOMDOMRequest
- * The request's result will be an object containing information
- * about the operation.
- *
- * In case that the MMI code requires sending an USSD request, the DOMrequest
- * 'success' event means that the RIL has successfully processed and sent the
- * USSD request to the network. The network reply will be reported via
- * 'onussdreceived' event. If the MMI code is not associated to a USSD but to
- * other RIL request its result, if one is needed, will be notified via the
- * returned DOMRequest 'success' or 'error' event.
- */
- nsIDOMDOMRequest sendMMI(in DOMString mmi);
-
- /**
- * Cancel the current MMI request if one exists.
- */
- nsIDOMDOMRequest cancelMMI();
-
- /**
- * Configures call forward options.
- *
- * @param CFInfo
- * An object containing the call forward rule to set.
- *
- * If successful, the request's onsuccess will be called.
- *
- * Otherwise, the request's onerror will be called, and the request's error
- * will be either 'RadioNotAvailable', 'RequestNotSupported',
- * 'IllegalSIMorME', or 'GenericFailure'
- */
- nsIDOMDOMRequest setCallForwardingOption(in nsIDOMMozMobileCFInfo CFInfo);
-
- /**
- * Queries current call forward options.
- *
- * @param reason
- * Indicates the reason the call is being forwarded. It will be either
- * unconditional (0), mobile busy (1), no reply (2), not reachable (3),
- * all call forwarding (4), or all conditional call forwarding (5).
- *
- * If successful, the request's onsuccess will be called, and the request's
- * result will be an array of nsIDOMMozMobileCFInfo.
- *
- * Otherwise, the request's onerror will be called, and the request's error
- * will be either 'RadioNotAvailable', 'RequestNotSupported',
- * or 'GenericFailure'.
- */
- nsIDOMDOMRequest getCallForwardingOption(in unsigned short reason);
-
- /**
- * Configures call barring option.
- *
- * @param option
- * An object containing the call barring rule to set.
- * @see MozCallBarringOption for the detail of info.
- *
- * If successful, the request's onsuccess will be called.
- *
- * Otherwise, the request's onerror will be called, and the request's error
- * will be either 'RadioNotAvailable', 'RequestNotSupported',
- * 'IllegalSIMorME', 'InvalidCallBarringOption' or 'GenericFailure'
- */
- nsIDOMDOMRequest setCallBarringOption(in jsval option);
-
- /**
- * Queries current call barring status.
- *
- * @param info
- * An object containing the call barring rule to query. No need to
- * specify 'enabled' property.
- * @see MozCallBarringOption for the detail of info.
- *
- * If successful, the request's onsuccess will be called, and the request's
- * result will be an object of MozCallBarringOption with correct 'enabled'
- * property indicating the status of this rule.
- *
- * Otherwise, the request's onerror will be called, and the request's error
- * will be either 'RadioNotAvailable', 'RequestNotSupported',
- * 'InvalidCallBarringOption' or 'GenericFailure'.
- */
- nsIDOMDOMRequest getCallBarringOption(in jsval option);
-
- /**
- * Configures call waiting options.
- *
- * @param enabled
- * Value containing the desired call waiting status.
- *
- * If successful, the request's onsuccess will be called.
- *
- * Otherwise, the request's onerror will be called, and the request's error
- * will be either 'RadioNotAvailable', 'RequestNotSupported',
- * 'IllegalSIMorME', or 'GenericFailure'
- */
- nsIDOMDOMRequest setCallWaitingOption(in bool enabled);
-
- /**
- * Queries current call waiting options.
- *
- * If successful, the request's onsuccess will be called, and the request's
- * result will be a boolean indicating the call waiting status.
- *
- *
- * Otherwise, the request's onerror will be called, and the request's error
- * will be either 'RadioNotAvailable', 'RequestNotSupported',
- * or 'GenericFailure'.
- */
- nsIDOMDOMRequest getCallWaitingOption();
-
- /**
- * The 'cardstatechange' event is notified when the 'cardState' attribute
- * changes value.
- */
- [implicit_jscontext] attribute jsval oncardstatechange;
-
- /**
- * The 'iccinfochange' event is notified whenever the icc info object
- * changes.
- */
- [implicit_jscontext] attribute jsval oniccinfochange;
-
- /**
- * The 'voicechange' event is notified whenever the voice connection object
- * changes.
- */
- [implicit_jscontext] attribute jsval onvoicechange;
-
- /**
- * The 'datachange' event is notified whenever the data connection object
- * changes values.
- */
- [implicit_jscontext] attribute jsval ondatachange;
-
- /**
- * The 'ussdreceived' event is notified whenever a new USSD message is
- * received.
- */
- [implicit_jscontext] attribute jsval onussdreceived;
-
- /**
- * The 'dataerror' event is notified whenever the data connection object
- * receives an error from the RIL
- */
- [implicit_jscontext] attribute jsval ondataerror;
-
- /**
- * The 'oncfstatechange' event is notified whenever the call forwarding
- * state changes.
- */
- [implicit_jscontext] attribute jsval oncfstatechange;
-};
-
-[scriptable, uuid(c9d9ff61-a2f0-41cd-b478-9cefa7b31f31)]
-interface nsIDOMMozMobileConnectionInfo : nsISupports
-{
- /**
- * State of the connection.
- *
- * Possible values: 'notSearching', 'searching', 'denied', 'registered'.
- * null if the state is unknown.
- */
- readonly attribute DOMString state;
-
- /**
- * Indicates whether the connection is ready. This may be different
- */
- readonly attribute bool connected;
-
- /**
- * Indicates whether only emergency calls are possible.
- *
- * This flag is only relevant to voice connections and when 'connected' is
- * false.
- */
- readonly attribute bool emergencyCallsOnly;
-
- /**
- * Indicates whether the connection is going through a foreign operator
- * (roaming) or not.
- */
- readonly attribute bool roaming;
-
- /**
- * Network operator
- */
- readonly attribute nsIDOMMozMobileNetworkInfo network;
-
- /**
- * Mobile Country Code (MCC) of last known network operator.
- */
- readonly attribute DOMString lastKnownMcc;
-
- /**
- * Type of connection.
- *
- * Possible values: 'gsm', 'cdma', gprs', 'edge', 'umts', 'hsdpa', 'evdo0',
- * 'evdoa', 'evdob', etc.
- */
- readonly attribute DOMString type;
-
- /**
- * Signal strength in dBm, or null if no service is available.
- */
- readonly attribute jsval signalStrength;
-
- /**
- * Signal strength, represented linearly as a number between 0 (weakest
- * signal) and 100 (full signal).
- */
- readonly attribute jsval relSignalStrength;
-
- /**
- * Cell location.
- */
- readonly attribute nsIDOMMozMobileCellInfo cell;
-
-};
-
-[scriptable, uuid(40018fc7-4c42-47b6-8de6-3591a9c622bc)]
-interface nsIDOMMozMobileNetworkInfo: nsISupports
-{
- /**
- * Short name of the network operator
- */
- readonly attribute DOMString shortName;
-
- /**
- * Long name of the network operator
- */
- readonly attribute DOMString longName;
-
- /**
- * Mobile Country Code (MCC) of the network operator
- */
- readonly attribute DOMString mcc;
-
- /**
- * Mobile Network Code (MNC) of the network operator
- */
- readonly attribute DOMString mnc;
-
- /**
- * State of this network operator.
- *
- * Possible values: 'available', 'connected', 'forbidden', or null (unknown)
- */
- readonly attribute DOMString state;
-};
-
-[scriptable, uuid(aa546788-4f34-488b-8c3e-2786e02ab992)]
-interface nsIDOMMozMobileCellInfo: nsISupports
-{
- /**
- * Mobile Location Area Code (LAC) for GSM/WCDMA networks.
- */
- readonly attribute unsigned short gsmLocationAreaCode;
-
- /**
- * Mobile Cell ID for GSM/WCDMA networks.
- */
- readonly attribute unsigned long gsmCellId;
-};
-
-[scriptable, uuid(10d5c5a2-d43f-4f94-8657-cf7ccabbab6e)]
-interface nsIDOMMozMobileICCInfo : nsISupports
-{
- /**
- * Integrated Circuit Card Identifier.
- */
- readonly attribute DOMString iccid;
-
- /**
- * Mobile Country Code (MCC) of the subscriber's home network.
- */
- readonly attribute DOMString mcc;
-
- /**
- * Mobile Network Code (MNC) of the subscriber's home network.
- */
- readonly attribute DOMString mnc;
-
- /**
- * Service Provider Name (SPN) of the subscriber's home network.
- */
- readonly attribute DOMString spn;
-
- /**
- * Network name must be a part of displayed carrier name.
- */
- readonly attribute boolean isDisplayNetworkNameRequired;
-
- /**
- * Service provider name must be a part of displayed carrier name.
- */
- readonly attribute boolean isDisplaySpnRequired;
-
- /**
- * Mobile Station ISDN Number (MSISDN) of the subscriber's, aka
- * his phone number.
- */
- readonly attribute DOMString msisdn;
-};
-
-[scriptable, uuid(d1b35ad8-99aa-47cc-ab49-2e72b00e39df)]
-interface nsIDOMMozMobileCFInfo : nsISupports
-{
- /**
- * Call forwarding rule status.
- *
- * It will be either not active (false), or active (true).
- *
- * Note: Unused for setting call forwarding options. It reports
- * the status of the rule when getting how the rule is
- * configured.
- *
- * @see 3GPP TS 27.007 7.11 "status".
- */
- readonly attribute bool active;
-
- const long CALL_FORWARD_ACTION_DISABLE = 0;
- const long CALL_FORWARD_ACTION_ENABLE = 1;
- const long CALL_FORWARD_ACTION_QUERY_STATUS = 2;
- const long CALL_FORWARD_ACTION_REGISTRATION = 3;
- const long CALL_FORWARD_ACTION_ERASURE = 4;
-
- /**
- * Indicates what to do with the rule.
- *
- * One of the CALL_FORWARD_ACTION_* constants. It will be either disable (0),
- * enable (1), query status (2), registration (3), or erasure (4).
- *
- * @see 3GPP TS 27.007 7.11 "mode".
- */
- readonly attribute unsigned short action;
-
- const long CALL_FORWARD_REASON_UNCONDITIONAL = 0;
- const long CALL_FORWARD_REASON_MOBILE_BUSY = 1;
- const long CALL_FORWARD_REASON_NO_REPLY = 2;
- const long CALL_FORWARD_REASON_NOT_REACHABLE = 3;
- const long CALL_FORWARD_REASON_ALL_CALL_FORWARDING = 4;
- const long CALL_FORWARD_REASON_ALL_CONDITIONAL_CALL_FORWARDING = 5;
-
- /**
- * Indicates the reason the call is being forwarded.
- *
- * One of the CALL_FORWARD_REASON_* constants. It will be either
- * unconditional (0), mobile busy (1), no reply (2), not reachable (3),
- * all call forwarding (4), or all conditional call forwarding (5).
- *
- * @see 3GPP TS 27.007 7.11 "reason".
- */
- readonly attribute unsigned short reason;
-
- /**
- * Phone number of forwarding address.
- */
- readonly attribute DOMString number;
-
- /**
- * When "no reply" is enabled or queried, this gives the time in
- * seconds to wait before call is forwarded.
- */
- readonly attribute unsigned short timeSeconds;
-
- /**
- * Service for which the call forward is set up. It should be one of the
- * nsIDOMMozMobileConnection.ICC_SERVICE_CLASS_* values.
- */
- readonly attribute unsigned short serviceClass;
-};
-
-
-dictionary MozCallBarringOption
-{
- /**
- * Indicates the program the call is being barred.
- *
- * It shall be one of the nsIDOMMozMobileConnection.CALL_BARRING_PROGRAM_*
- * values.
- */
- unsigned short program;
-
- /**
- * Enable or disable the call barring program.
- */
- boolean enabled;
-
- /**
- * Barring password. Use "" if no password specified.
- */
- DOMString password;
-
- /**
- * Service for which the call barring is set up.
- *
- * It shall be one of the nsIDOMMozMobileConnection.ICC_SERVICE_CLASS_*
- * values.
- */
- unsigned short serviceClass;
-};
diff --git a/dom/network/interfaces/nsIDOMNetworkStats.idl b/dom/network/interfaces/nsIDOMNetworkStats.idl
deleted file mode 100644
index ae9fb9f3b..000000000
--- a/dom/network/interfaces/nsIDOMNetworkStats.idl
+++ /dev/null
@@ -1,35 +0,0 @@
-/* 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"
-
-[scriptable, builtinclass, uuid(3b16fe17-5583-483a-b486-b64a3243221c)]
-interface nsIDOMMozNetworkStatsData : nsISupports
-{
- readonly attribute unsigned long rxBytes; // Received bytes.
- readonly attribute unsigned long txBytes; // Sent bytes.
- readonly attribute jsval date; // Date.
-};
-
-[scriptable, builtinclass, uuid(037435a6-f563-48f3-99b3-a0106d8ba5bd)]
-interface nsIDOMMozNetworkStats : nsISupports
-{
- /**
- * Can be 'mobile', 'wifi' or null.
- * If null, stats for both mobile and wifi are returned.
- */
- readonly attribute DOMString connectionType;
-
- /**
- * Stats for connectionType
- */
- readonly attribute jsval data; // array of NetworkStatsData.
- // one element per day.
-
- /**
- * Dates
- */
- readonly attribute jsval start; // Date.
- readonly attribute jsval end; // Date.
-};
diff --git a/dom/network/interfaces/nsIDOMNetworkStatsManager.idl b/dom/network/interfaces/nsIDOMNetworkStatsManager.idl
index 4da6e151c..fd663e9a9 100644
--- a/dom/network/interfaces/nsIDOMNetworkStatsManager.idl
+++ b/dom/network/interfaces/nsIDOMNetworkStatsManager.idl
@@ -6,51 +6,91 @@
interface nsIDOMDOMRequest;
-dictionary NetworkStatsOptions
+[scriptable, uuid(ceb874cd-cc1a-4e65-b404-cc2d3e42425f)]
+interface nsIDOMMozNetworkStatsManager : nsISupports
{
/**
- * Connection type used to filter which network stats will be returned:
- * 'mobile', 'wifi' or null.
- * If null, stats for both mobile and wifi are returned.
+ * Constants for known interface types.
*/
- DOMString connectionType;
- jsval start; // date
- jsval end; // date
-};
+ const long WIFI = 0;
+ const long MOBILE = 1;
-[scriptable, uuid(87529a6c-aef6-11e1-a595-4f034275cfa6)]
-interface nsIDOMMozNetworkStatsManager : nsISupports
-{
/**
- * Query network interface statistics.
+ * Find samples between two dates start and end, both included.
*
- * If options.connectionType is not provided, return statistics for all known
- * network interfaces.
+ * If options is provided, per-app or per-system service usage will be
+ * retrieved; otherwise the target will be overall system usage.
*
- * If successful, the request result will be an nsIDOMMozNetworkStats object.
+ * If success, the request result will be an nsIDOMMozNetworkStats object.
+ */
+ nsIDOMDOMRequest getSamples(in nsISupports network,
+ in jsval start,
+ in jsval end,
+ [optional] in jsval options /* NetworkStatsGetOptions */);
+
+ /**
+ * Install an alarm on a network. The network must be in the return of
+ * getAvailableNetworks() otherwise an "InvalidNetwork" exception will
+ * be raised.
+ *
+ * When total data usage reaches threshold bytes, a "networkstats-alarm"
+ * system message is sent to the application, where the optional parameter
+ * |data| must be a cloneable object.
*
- * If network stats are not available for some dates, then rxBytes &
- * txBytes are undefined for those dates.
+ * If success, the |result| field of the DOMRequest keeps the alarm Id.
+ */
+ nsIDOMDOMRequest addAlarm(in nsISupports network,
+ in long threshold,
+ [optional] in jsval options /* NetworkStatsAlarmOptions */);
+
+ /**
+ * Obtain all alarms for those networks returned by getAvailableNetworks().
+ * If a network is provided, only retrieves the alarms for that network.
+ * The network must be one of those returned by getAvailebleNetworks() or an
+ * "InvalidNetwork" exception will be raised.
+ *
+ * Each alarm object has the same fields as that in the system message:
+ * - alarmId
+ * - network
+ * - threshold
+ * - data
+ */
+ nsIDOMDOMRequest getAllAlarms([optional] in nsISupports network);
+
+ /**
+ * Remove all network alarms. If an |alarmId| is provided, then only that
+ * alarm is removed.
+ */
+ nsIDOMDOMRequest removeAlarms([optional] in long alarmId);
+
+ /**
+ * Remove all stats related with the provided network from DB.
+ */
+ nsIDOMDOMRequest clearStats(in nsISupports network);
+
+ /**
+ * Remove all stats in the database.
*/
- nsIDOMDOMRequest getNetworkStats(in jsval options);
+ nsIDOMDOMRequest clearAllStats();
/**
- * Return available connection types.
+ * Return available networks that used to be saved in the database.
*/
- readonly attribute jsval connectionTypes; // array of DOMStrings.
+ nsIDOMDOMRequest getAvailableNetworks(); // array of MozNetworkStatsInterface.
/**
- * Clear all stats from DB.
+ * Return available service types that used to be saved in the database.
*/
- nsIDOMDOMRequest clearAllData();
+ nsIDOMDOMRequest getAvailableServiceTypes(); // array of string.
/**
- * Time in seconds between samples stored in database.
+ * Minimum time in milliseconds between samples stored in the database.
*/
- readonly attribute long sampleRate;
+ readonly attribute long sampleRate;
/**
- * Maximum number of samples stored in the database per connection type.
+ * Time in milliseconds recorded by the API until present time. All samples
+ * older than maxStorageAge from now are deleted.
*/
- readonly attribute long maxStorageSamples;
+ readonly attribute long long maxStorageAge;
};
diff --git a/dom/network/interfaces/nsIDOMTCPServerSocket.idl b/dom/network/interfaces/nsIDOMTCPServerSocket.idl
new file mode 100644
index 000000000..9f214c973
--- /dev/null
+++ b/dom/network/interfaces/nsIDOMTCPServerSocket.idl
@@ -0,0 +1,92 @@
+/* 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 "domstubs.idl"
+#include "nsITCPSocketChild.idl"
+
+// Bug 797561 - Expose a server tcp socket API to web applications
+/**
+ * nsIDOMTCPServerSocket
+ *
+ * An interface to a server socket that can accept incoming connections for gaia apps.
+ */
+[scriptable, uuid(821638a1-5327-416d-8031-668764f2ec04)]
+interface nsIDOMTCPServerSocket : nsISupports
+{
+ /**
+ * The port of this server socket object.
+ */
+ readonly attribute unsigned short localPort;
+
+ /**
+ * The onconnect event handler is called when a client connection is accepted.
+ * The data attribute of the event passed to the onconnect handler will be a TCPSocket
+ * instance, which is used for communication between client and server.
+ */
+ attribute jsval onconnect;
+
+ /**
+ * The onerror handler will be called when the listen of a server socket is aborted.
+ * The data attribute of the event passed to the onerror handler will have a
+ * description of the kind of error.
+ */
+ attribute jsval onerror;
+
+ /**
+ * Close the server socket.
+ */
+ void close();
+};
+
+/**
+ * Internal interfaces for use in cross-process server-socket implementation.
+ * Needed to account for multiple possible types that can be provided to
+ * the socket callbacks as arguments.
+ *
+ * These interfaces are for calling each method from the server socket object
+ * on the parent and child side for an IPC protocol implementation.
+ */
+
+[scriptable, uuid(b64b1e68-4efa-497c-b0d8-69f067ad5ec8)]
+interface nsITCPServerSocketInternal : nsISupports
+{
+ /**
+ * Initialization after creating a TCP server socket object.
+ *
+ * @param windowVal
+ * An object to create ArrayBuffer for this window. See Bug 831107.
+ */
+ void init(in jsval windowVal);
+
+ /**
+ * Listen on a port
+ *
+ * @param localPort
+ * The port of the server socket. Pass -1 to indicate no preference,
+ * and a port will be selected automatically.
+ * @param options
+ * An object specifying one or more parameters which
+ * determine the details of the socket.
+ *
+ * binaryType: "arraybuffer" to use UInt8 array
+ * instances in the ondata callback and as the argument
+ * to send. Defaults to "string", to use JavaScript strings.
+ * @param backlog
+ * The maximum length the queue of pending connections may grow to.
+ * This parameter may be silently limited by the operating system.
+ * Pass -1 to use the default value.
+ */
+ void listen(in unsigned short localPort, in jsval options, in unsigned short backlog);
+
+ /**
+ * Listener for receiving an accepted socket.
+ */
+ void callListenerAccept(in nsITCPSocketChild socketChild);
+
+ /**
+ * Listener for handling an error caused in chrome process.
+ */
+ void callListenerError(in DOMString message, in DOMString filename,
+ in uint32_t lineNumber, in uint32_t columnNumber);
+};
diff --git a/dom/network/interfaces/nsIDOMTCPSocket.idl b/dom/network/interfaces/nsIDOMTCPSocket.idl
index 70ac57698..1054cdd34 100644
--- a/dom/network/interfaces/nsIDOMTCPSocket.idl
+++ b/dom/network/interfaces/nsIDOMTCPSocket.idl
@@ -3,7 +3,7 @@
* You can obtain one at http://mozilla.org/MPL/2.0/. */
/**
- * MozTCPSocket exposes a TCP client socket (no server sockets yet)
+ * 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.
@@ -11,6 +11,10 @@
#include "domstubs.idl"
#include "nsIDOMEvent.idl"
+#include "nsITCPSocketChild.idl"
+#include "nsIDOMTCPServerSocket.idl"
+
+interface nsISocketTransport;
// Bug 731746 - Allow chrome JS object to implement nsIDOMEventTarget
// nsITCPSocket should be an nsIEventTarget but js objects
@@ -23,9 +27,12 @@
// Once bug 723206 will be fixed, this method could be replaced by
// arguments when instantiating a TCPSocket object. For example it will
// be possible to do (similarly to the WebSocket API):
-// var s = new MozTCPSocket(host, port);
+// var s = new MozTCPSocket(host, port);
+
+// Bug 797561 - Expose a server tcp socket API to web applications
+
-[scriptable, uuid(1f99bc6f-73d3-44db-9dbf-5fc441704a7c)]
+[scriptable, uuid(65f6d2c8-4be6-4695-958d-0735e8935289)]
interface nsIDOMTCPSocket : nsISupports
{
/**
@@ -37,7 +44,7 @@ interface nsIDOMTCPSocket : nsISupports
* @param options An object specifying one or more parameters which
* determine the details of the socket.
*
- * useSSL: true to create an SSL socket. Defaults to false.
+ * useSecureTransport: true to create an SSL socket. Defaults to false.
*
* binaryType: "arraybuffer" to use ArrayBuffer
* instances in the ondata callback and as the argument
@@ -48,6 +55,31 @@ interface nsIDOMTCPSocket : nsISupports
nsIDOMTCPSocket open(in DOMString host, in unsigned short port, [optional] in jsval options);
/**
+ * Listen on a port
+ *
+ * @param localPort The port of the server socket. Pass -1 to indicate no preference,
+ * and a port will be selected automatically.
+ * @param options An object specifying one or more parameters which
+ * determine the details of the socket.
+ *
+ * binaryType: "arraybuffer" to use ArrayBuffer
+ * instances in the ondata callback and as the argument
+ * to send. Defaults to "string", to use JavaScript strings.
+ * @param backlog The maximum length the queue of pending connections may grow to.
+ * This parameter may be silently limited by the operating system.
+ * Pass -1 to use the default value.
+ *
+ * @return The new TCPServerSocket instance.
+ */
+ nsIDOMTCPServerSocket listen(in unsigned short localPort, [optional] in jsval options,
+ [optional] in unsigned short backlog);
+
+ /**
+ * Enable secure on channel.
+ */
+ void upgradeToSecure();
+
+ /**
* The host of this socket object.
*/
readonly attribute DOMString host;
@@ -179,11 +211,12 @@ interface nsIDOMTCPSocket : nsISupports
};
/*
- * Internal interfaces for use in cross-process socket implementation.
+ * This interface is implemented in TCPSocket.js as an internal interfaces
+ * 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(322193a3-da17-4ca5-ad26-3539c519ea4b)]
+[scriptable, uuid(ac2c4b69-cb79-4767-b1ce-bcf62945cd39)]
interface nsITCPSocketInternal : nsISupports {
// Trigger the callback for |type| and provide a DOMError() object with the given data
void callListenerError(in DOMString type, in DOMString name);
@@ -197,8 +230,66 @@ interface nsITCPSocketInternal : nsISupports {
// Trigger the callback for |type| with no argument
void callListenerVoid(in DOMString type);
- // Update the DOM object's readyState and bufferedAmount values with the provided data
- void updateReadyStateAndBuffered(in DOMString readyState, in uint32_t bufferedAmount);
+ // Update the DOM object's readyState.
+ // @param readyState
+ // new ready state to be set to TCPSocket.
+ void updateReadyState(in DOMString readyState);
+
+ // Update the DOM object's bufferedAmount value with a tracking number to
+ // ensure the update request is sent after child's send() invocation.
+ // @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);
+
+ // Create a socket object on the parent side.
+ // This is called in accepting any open request on the parent side.
+ //
+ // @param transport
+ // The accepted socket transport.
+ // @param binaryType
+ // "arraybuffer" to use ArrayBuffer instances
+ // in the ondata callback and as the argument to send.
+ // @param window
+ // An object to create ArrayBuffer for this window. See Bug 831107.
+ nsIDOMTCPSocket createAcceptedParent(in nsISocketTransport transport,
+ in DOMString binaryType,
+ in nsIDOMWindow window);
+
+ // Create a DOM socket on the child side
+ // This is called when the socket is accepted on the parent side.
+ //
+ // @param socketChild
+ // The socket child object for the IPC implementation.
+ // @param binaryType
+ // "arraybuffer" to use ArrayBuffer instances
+ // in the ondata callback and as the argument to send.
+ // @param window
+ // An object to create ArrayBuffer for this window. See Bug 831107.
+ nsIDOMTCPSocket createAcceptedChild(in nsITCPSocketChild socketChild,
+ in DOMString binaryType,
+ in nsIDOMWindow window);
+
+ // Set App ID.
+ void setAppId(in unsigned long appId);
+
+ // Set inBrowser.
+ void setInBrowser(in boolean inBrowser);
+
+ // Set a callback that handles the request from a TCP socket parent when that
+ // socket parent wants to notify that its bufferedAmount is updated.
+ void setOnUpdateBufferedAmountHandler(in jsval handler);
+
+ // Providing child process with ability to pass more arguments to parent's
+ // send() function.
+ // @param trackingNumber
+ // To ensure the request to update bufferedAmount in child is after
+ // lastest send() invocation from child.
+ void onRecvSendFromChild(in jsval data, in unsigned long byteOffset,
+ in unsigned long byteLength, in unsigned long trackingNumber);
};
/**
diff --git a/dom/network/interfaces/nsIDOMUSSDReceivedEvent.idl b/dom/network/interfaces/nsIDOMUSSDReceivedEvent.idl
deleted file mode 100644
index b859890ad..000000000
--- a/dom/network/interfaces/nsIDOMUSSDReceivedEvent.idl
+++ /dev/null
@@ -1,24 +0,0 @@
-/* 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 "nsIDOMEvent.idl"
-
-[scriptable, builtinclass, uuid(d26880c8-0468-4abb-94a6-9143defb5545)]
-interface nsIDOMUSSDReceivedEvent : nsIDOMEvent
-{
- readonly attribute DOMString message;
- [infallible] readonly attribute boolean sessionEnded;
-
- [noscript] void initUSSDReceivedEvent(in DOMString aType,
- in boolean aCanBubble,
- in boolean aCancelable,
- in DOMString aMessage,
- in boolean aSessionEnded);
-};
-
-dictionary USSDReceivedEventInit : EventInit
-{
- DOMString? message;
- boolean sessionEnded;
-};
diff --git a/dom/network/interfaces/nsIMobileConnectionProvider.idl b/dom/network/interfaces/nsIMobileConnectionProvider.idl
deleted file mode 100644
index ddba41fb3..000000000
--- a/dom/network/interfaces/nsIMobileConnectionProvider.idl
+++ /dev/null
@@ -1,74 +0,0 @@
-/* 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"
-
-interface nsIDOMMozMobileICCInfo;
-interface nsIDOMMozMobileConnectionInfo;
-interface nsIDOMMozMobileNetworkInfo;
-interface nsIDOMMozMobileCFInfo;
-interface nsIDOMDOMRequest;
-interface nsIDOMWindow;
-
-[scriptable, uuid(d09099b0-a25a-11e2-9e96-0800200c9a66)]
-interface nsIMobileConnectionListener : nsISupports
-{
- void notifyVoiceChanged();
- void notifyDataChanged();
- void notifyCardStateChanged();
- void notifyIccInfoChanged();
- void notifyUssdReceived(in DOMString message,
- in boolean sessionEnded);
- void notifyDataError(in DOMString message);
- void notifyCFStateChange(in boolean success,
- in unsigned short action,
- in unsigned short reason,
- in DOMString number,
- in unsigned short timeSeconds,
- in unsigned short serviceClass);
-};
-
-/**
- * XPCOM component (in the content process) that provides the mobile
- * network information.
- */
-[scriptable, uuid(b9605230-a25a-11e2-9e96-0800200c9a66)]
-interface nsIMobileConnectionProvider : nsISupports
-{
- /**
- * Called when a content process registers receiving unsolicited messages from
- * RadioInterfaceLayer in the chrome process. Only a content process that has
- * the 'mobileconnection' permission is allowed to register.
- */
- void registerMobileConnectionMsg(in nsIMobileConnectionListener listener);
- void unregisterMobileConnectionMsg(in nsIMobileConnectionListener listener);
-
- readonly attribute DOMString cardState;
- readonly attribute long retryCount;
- readonly attribute nsIDOMMozMobileICCInfo iccInfo;
- readonly attribute nsIDOMMozMobileConnectionInfo voiceConnectionInfo;
- readonly attribute nsIDOMMozMobileConnectionInfo dataConnectionInfo;
- readonly attribute DOMString networkSelectionMode;
-
- nsIDOMDOMRequest getNetworks(in nsIDOMWindow window);
- nsIDOMDOMRequest selectNetwork(in nsIDOMWindow window, in nsIDOMMozMobileNetworkInfo network);
- nsIDOMDOMRequest selectNetworkAutomatically(in nsIDOMWindow window);
-
- nsIDOMDOMRequest sendMMI(in nsIDOMWindow window, in DOMString mmi);
- nsIDOMDOMRequest cancelMMI(in nsIDOMWindow window);
-
- nsIDOMDOMRequest getCallForwardingOption(in nsIDOMWindow window,
- in unsigned short reason);
- nsIDOMDOMRequest setCallForwardingOption(in nsIDOMWindow window,
- in nsIDOMMozMobileCFInfo CFInfo);
-
- nsIDOMDOMRequest getCallBarringOption(in nsIDOMWindow window,
- in jsval option);
- nsIDOMDOMRequest setCallBarringOption(in nsIDOMWindow window,
- in jsval option);
-
- nsIDOMDOMRequest setCallWaitingOption(in nsIDOMWindow window,
- in bool enabled);
- nsIDOMDOMRequest getCallWaitingOption(in nsIDOMWindow window);
-};
diff --git a/dom/network/interfaces/nsIDOMNavigatorNetwork.idl b/dom/network/interfaces/nsIMozNavigatorNetwork.idl
index bb66cbbef..1d667aada 100644
--- a/dom/network/interfaces/nsIDOMNavigatorNetwork.idl
+++ b/dom/network/interfaces/nsIMozNavigatorNetwork.idl
@@ -4,10 +4,10 @@
#include "nsISupports.idl"
-interface nsIDOMMozConnection;
+interface nsINetworkProperties;
-[scriptable, uuid(c1685d27-f2e2-4ed9-998f-ff5b1442058f)]
-interface nsIDOMMozNavigatorNetwork : nsISupports
+[uuid(7956523b-631e-4f80-94a5-3883bcfd6bf3)]
+interface nsIMozNavigatorNetwork : nsISupports
{
- readonly attribute nsIDOMMozConnection mozConnection;
+ readonly attribute nsINetworkProperties properties;
};
diff --git a/dom/network/interfaces/nsINavigatorMobileConnection.idl b/dom/network/interfaces/nsINavigatorMobileConnection.idl
deleted file mode 100644
index d6a279210..000000000
--- a/dom/network/interfaces/nsINavigatorMobileConnection.idl
+++ /dev/null
@@ -1,13 +0,0 @@
-/* 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"
-
-interface nsIDOMMozMobileConnection;
-
-[scriptable, uuid(d8672694-3334-4d0d-a4c3-38fa70c265d0)]
-interface nsIMozNavigatorMobileConnection : nsISupports
-{
- readonly attribute nsIDOMMozMobileConnection mozMobileConnection;
-};
diff --git a/dom/network/interfaces/nsINetworkStatsServiceProxy.idl b/dom/network/interfaces/nsINetworkStatsServiceProxy.idl
new file mode 100644
index 000000000..897b357ba
--- /dev/null
+++ b/dom/network/interfaces/nsINetworkStatsServiceProxy.idl
@@ -0,0 +1,59 @@
+/* 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"
+
+interface nsINetworkInterface;
+
+[scriptable, function, uuid(5f821529-1d80-4ab5-a933-4e1b3585b6bc)]
+interface nsINetworkStatsServiceProxyCallback : nsISupports
+{
+ /*
+ * @param aResult callback result with boolean value
+ * @param aMessage message
+ */
+ void notify(in boolean aResult, in jsval aMessage);
+};
+
+[scriptable, uuid(98fd8f69-784e-4626-aa59-56d6436a3c24)]
+interface nsINetworkStatsServiceProxy : nsISupports
+{
+ /*
+ * An interface used to record per-app traffic data.
+ * @param aAppId app id
+ * @param aIsInBrowser true if the iframe element is mozbrowser
+ * @param aNetworkInterface network
+ * @param aTimeStamp time stamp
+ * @param aRxBytes received data amount
+ * @param aTxBytes transmitted data amount
+ * @param aIsAccumulative is stats accumulative
+ * @param aCallback an optional callback
+ */
+ void saveAppStats(in unsigned long aAppId,
+ in boolean aIsInBrowser,
+ in nsINetworkInterface aNetwork,
+ in unsigned long long aTimeStamp,
+ in unsigned long long aRxBytes,
+ in unsigned long long aTxBytes,
+ in boolean aIsAccumulative,
+ [optional] in nsINetworkStatsServiceProxyCallback aCallback);
+
+ /*
+ * An interface used to record per-system service traffic data.
+ * @param aServiceType system service type
+ * @param aNetworkInterface network
+ * @param aTimeStamp time stamp
+ * @param aRxBytes received data amount
+ * @param aTxBytes transmitted data amount
+ * @param aIsAccumulative is stats accumulative
+ * @param aCallback an optional callback
+ */
+ void saveServiceStats(in string aServiceType,
+ in nsINetworkInterface aNetwork,
+ in unsigned long long aTimeStamp,
+ in unsigned long long aRxBytes,
+ in unsigned long long aTxBytes,
+ in boolean aIsAccumulative,
+ [optional] in nsINetworkStatsServiceProxyCallback aCallback);
+};
diff --git a/dom/network/interfaces/nsITCPServerSocketChild.idl b/dom/network/interfaces/nsITCPServerSocketChild.idl
new file mode 100644
index 000000000..bdaaaa965
--- /dev/null
+++ b/dom/network/interfaces/nsITCPServerSocketChild.idl
@@ -0,0 +1,39 @@
+/* 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 "domstubs.idl"
+#include "nsIDOMTCPServerSocket.idl"
+
+interface nsITCPServerSocketInternal;
+
+/**
+ * Interface to allow the content process server socket to reach the IPC bridge.
+ * It is used in the server socket implementation on the child side.
+ */
+
+[scriptable, uuid(41a77ec8-fd86-409e-aea9-af2ca407ef8e)]
+interface nsITCPServerSocketChild : nsISupports
+{
+ /**
+ * Tell the chrome process to listen on the port with the given parameters.
+ *
+ * @param serverSocket
+ * The server socket generated in the listen of nsIDOMTCPSocket
+ * on the child side.
+ * @param port
+ * The port of the server socket.
+ * @param backlog
+ * The maximum length the queue of pending connections may grow to.
+ * @param binaryType
+ * "arraybuffer" to use UInt8 array instances or "string" to use String.
+ */
+ [implicit_jscontext]
+ void listen(in nsITCPServerSocketInternal serverSocket, in unsigned short port,
+ in unsigned short backlog, in DOMString binaryType);
+
+ /**
+ * Tell the chrome process to close the server socket.
+ */
+ void close();
+};
diff --git a/dom/network/interfaces/nsITCPServerSocketParent.idl b/dom/network/interfaces/nsITCPServerSocketParent.idl
new file mode 100644
index 000000000..bc864ecbf
--- /dev/null
+++ b/dom/network/interfaces/nsITCPServerSocketParent.idl
@@ -0,0 +1,42 @@
+/* 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 "domstubs.idl"
+#include "nsITCPSocketParent.idl"
+
+interface nsIDOMTCPServerSocket;
+
+/**
+ * Interface required to allow the TCP server-socket object in the parent process
+ * to talk to the parent IPC actor.
+ * It is used in the server socket implementation on the parent side.
+ */
+[scriptable, uuid(161ffc9f-54d3-4f21-a536-4166003d0e1d)]
+interface nsITCPServerSocketParent : nsISupports
+{
+ /**
+ * Trigger a callback in the content process when the socket accepts any request.
+ *
+ * @param socket
+ * The socket generated in accepting any open request on the parent side.
+ */
+ void sendCallbackAccept(in nsITCPSocketParent socket);
+
+ /**
+ * Trigger a callback in the content process when an error occurs.
+ *
+ * @param message
+ * The error message.
+ * @param filename
+ * The file name in which the error occured.
+ * @param lineNumber
+ * The line number in which the error occured.
+ * @param columnNumber
+ * The column number in which the error occured.
+ */
+ void sendCallbackError(in DOMString message,
+ in DOMString filename,
+ in uint32_t lineNumber,
+ in uint32_t columnNumber);
+};
diff --git a/dom/network/interfaces/nsITCPSocketChild.idl b/dom/network/interfaces/nsITCPSocketChild.idl
index 83963207d..4409f172d 100644
--- a/dom/network/interfaces/nsITCPSocketChild.idl
+++ b/dom/network/interfaces/nsITCPSocketChild.idl
@@ -8,18 +8,43 @@ interface nsITCPSocketInternal;
interface nsIDOMWindow;
// Interface to allow the content process socket to reach the IPC bridge.
-[scriptable, uuid(bdc91763-e9a1-4122-9c2f-8f17505c8c7a)]
+// Implemented in C++ as TCPSocketChild, referenced as _socketBridge in TCPSocket.js
+[scriptable, uuid(4277aff0-4c33-11e3-8f96-0800200c9a66)]
interface nsITCPSocketChild : nsISupports
{
// Tell the chrome process to open a corresponding connection with the given parameters
[implicit_jscontext]
- void open(in nsITCPSocketInternal socket, in DOMString host,
- in unsigned short port, in boolean ssl, in DOMString binaryType,
- in nsIDOMWindow window, in jsval socketVal);
+ void sendOpen(in nsITCPSocketInternal socket, in DOMString host,
+ in unsigned short port, in boolean ssl, in DOMString binaryType,
+ in nsIDOMWindow window, in jsval windowVal);
+
+ // Tell the chrome process to perform send and update the tracking number.
+ [implicit_jscontext]
+ void sendSend(in jsval data, in unsigned long byteOffset,
+ in unsigned long byteLength, in unsigned long trackingNumber);
// Tell the chrome process to perform equivalent operations to all following methods
- [implicit_jscontext] void send(in jsval data, in unsigned long byteOffset, in unsigned long byteLength);
- void resume();
- void suspend();
- void close();
+ void sendResume();
+ void sendSuspend();
+ void sendClose();
+ void sendStartTLS();
+
+ /**
+ * Initialize the TCP socket on the child side for IPC. It is called from the child side,
+ * which is generated in receiving a notification of accepting any open request
+ * on the parent side. We use single implementation that works on a child process
+ * as well as in the single process model.
+ *
+ * @param socket
+ * The TCP socket on the child side.
+ * This instance is connected with the child IPC side of the IPC bridge.
+ * @param windowVal
+ * The window object on the child side to create data
+ * as "jsval" for deserialization.
+ */
+ [implicit_jscontext]
+ void setSocketAndWindow(in nsITCPSocketInternal socket, in jsval windowVal);
+
+ readonly attribute DOMString host;
+ readonly attribute unsigned short port;
};
diff --git a/dom/network/interfaces/nsITCPSocketParent.idl b/dom/network/interfaces/nsITCPSocketParent.idl
index 346e11a24..d23e90a8e 100644
--- a/dom/network/interfaces/nsITCPSocketParent.idl
+++ b/dom/network/interfaces/nsITCPSocketParent.idl
@@ -5,35 +5,85 @@
#include "domstubs.idl"
interface nsIDOMTCPSocket;
+interface nsIDOMTCPServerSocket;
+interface nsITCPServerSocketParent;
+interface nsITCPSocketIntermediary;
-// Interface required to allow the TCP socket object in the parent process
-// to talk to the parent IPC actor
-[scriptable, uuid(4e7246c6-a8b3-426d-9c17-76dab1e1e14a)]
+// Interface required to allow the TCP socket object (TCPSocket.js) in the
+// parent process to talk to the parent IPC actor, TCPSocketParent, which
+// is written in C++.
+[scriptable, uuid(6f040bf0-6852-11e3-949a-0800200c9a66)]
interface nsITCPSocketParent : nsISupports
{
[implicit_jscontext] void initJS(in jsval intermediary);
// Trigger a callback in the content process for |type|, providing a serialized
- // argument of |data|, and update the child's readyState and bufferedAmount values
- // with the given values.
- [implicit_jscontext] void sendCallback(in DOMString type,
- in jsval data,
- in DOMString readyState,
- in uint32_t bufferedAmount);
+ // argument of |data|, and update the child's readyState value with the given
+ // values.
+ //
+ // @param type
+ // Event type: 'onopen', 'ondata', 'onerror' or 'onclose'. 'odrain' is
+ // controlled by child.
+ // @param data
+ // Serialized data that is passed to event handler.
+ // @param readyState
+ // Current ready state.
+ [implicit_jscontext] void sendEvent(in DOMString type,
+ in jsval data,
+ in DOMString readyState);
+
+ // Initialize a parent socket object. It is called from the parent side socket,
+ // which is generated in accepting any open request on the parent side.
+ // The socket after being initialized will be established.
+ //
+ // @param socket
+ // The socket on the parent side.
+ // @param intermediary
+ // Intermediate class object. See nsITCPSocketIntermediary.
+ [implicit_jscontext] void setSocketAndIntermediary(in nsIDOMTCPSocket socket,
+ in nsITCPSocketIntermediary intermediary);
+
+ // When parent's buffered amount is updated and it wants to inform child to
+ // update the bufferedAmount as well.
+ //
+ // @param bufferedAmount
+ // The new value of bufferedAmount that is going to be set to child's
+ // bufferedAmount.
+ // @param trackingNumber
+ // Parent's current tracking number, reflecting the number of calls to
+ // send() on the child process. This number is sent back to the child
+ // to make sure the bufferedAmount updated on the child will correspond
+ // to the latest call of send().
+ void sendUpdateBufferedAmount(in uint32_t bufferedAmount, in uint32_t trackingNumber);
+
+ readonly attribute DOMString host;
+ readonly attribute unsigned short port;
};
// Intermediate class to handle sending multiple possible data types
// and kicking off the chrome process socket object's connection.
-[scriptable, uuid(afa42841-a6cb-4a91-912f-93099f6a3d18)]
+// This interface is the bridge of TCPSocketParent, which is written in C++,
+// and TCPSocket, which is written in Javascript. TCPSocketParentIntermediary
+// implements nsITCPSocketIntermediary in Javascript.
+[scriptable, uuid(aa9bd46d-26bf-4ba8-9c18-ba02482c02f0)]
interface nsITCPSocketIntermediary : nsISupports {
// Open the connection to the server with the given parameters
nsIDOMTCPSocket open(in nsITCPSocketParent parent,
in DOMString host, in unsigned short port,
- in boolean useSSL, in DOMString binaryType);
+ in boolean useSSL, in DOMString binaryType,
+ in unsigned long appId,
+ in boolean inBrowser);
+
+ // Listen on a port
+ nsIDOMTCPServerSocket listen(in nsITCPServerSocketParent parent,
+ in unsigned short port, in unsigned short backlog,
+ in DOMString binaryType,
+ in unsigned long appId,
+ in boolean inBrowser);
- // Send a basic string along the connection
- void sendString(in DOMString data);
+ // Called when received a child request to send a string.
+ void onRecvSendString(in DOMString data, in uint32_t trackingNumber);
- // Send a typed array
- void sendArrayBuffer(in jsval data);
+ // Called when received a child request to send an array buffer.
+ void onRecvSendArrayBuffer(in jsval data, in uint32_t trackingNumber);
};
diff --git a/dom/network/interfaces/nsIUDPSocketChild.idl b/dom/network/interfaces/nsIUDPSocketChild.idl
new file mode 100644
index 000000000..773c02d05
--- /dev/null
+++ b/dom/network/interfaces/nsIUDPSocketChild.idl
@@ -0,0 +1,67 @@
+/* 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;
+
+%{ C++
+namespace mozilla {
+namespace net {
+union NetAddr;
+}
+}
+%}
+native NetAddr(mozilla::net::NetAddr);
+[ptr] native NetAddrPtr(mozilla::net::NetAddr);
+
+[scriptable, uuid(36ec5264-6a58-4cf2-ad9a-185292e0d1d1)]
+interface nsIUDPSocketChild : nsISupports
+{
+ readonly attribute unsigned short localPort;
+ readonly attribute AUTF8String localAddress;
+ attribute AUTF8String filterName;
+
+ // Tell the chrome process to bind the UDP socket to a given local host and port
+ void bind(in nsIUDPSocketInternal socket, in AUTF8String host, in unsigned short port,
+ in bool addressReuse, in bool loopback);
+
+ // Tell the chrome process to perform equivalent operations to all following methods
+ void send(in AUTF8String host, in unsigned short port,
+ [const, array, size_is(byteLength)] in uint8_t bytes,
+ in unsigned long byteLength);
+ // Send without DNS query
+ void sendWithAddr(in nsINetAddr addr,
+ [const, array, size_is(byteLength)] in uint8_t bytes,
+ in unsigned long byteLength);
+ [noscript] void sendWithAddress([const] in NetAddrPtr addr,
+ [const, array, size_is(byteLength)] in uint8_t bytes,
+ in unsigned long byteLength);
+ // Send input stream. This must be a buffered stream implementation.
+ void sendBinaryStream(in AUTF8String host, in unsigned short port, in nsIInputStream stream);
+
+ void close();
+ void joinMulticast(in AUTF8String multicastAddress, in AUTF8String iface);
+ void leaveMulticast(in AUTF8String multicastAddress, in AUTF8String iface);
+};
+
+/*
+ * Internal interface for callback from chrome process
+ */
+[scriptable, uuid(44cd9ad5-d574-4169-baf9-e1af0648a143)]
+interface nsIUDPSocketInternal : nsISupports
+{
+ // callback while socket is opened. localPort and localAddress is ready until this time.
+ void callListenerOpened();
+ // callback while socket is closed.
+ void callListenerClosed();
+ // callback while incoming packet is received.
+ void callListenerReceivedData(in AUTF8String host, in unsigned short port,
+ [const, array, size_is(dataLength)] in uint8_t data,
+ in unsigned long dataLength);
+ // 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
index 6834f77cb..ee6ac73e7 100644
--- a/dom/network/moz.build
+++ b/dom/network/moz.build
@@ -4,5 +4,83 @@
# 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/.
-PARALLEL_DIRS += ['interfaces', 'src']
-TEST_DIRS += ['tests']
+DIRS += ['interfaces']
+
+XPCSHELL_TESTS_MANIFESTS += [
+ 'tests/unit/xpcshell.ini',
+ 'tests/unit_ipc/xpcshell.ini',
+]
+
+if CONFIG['MOZ_B2G_RIL']:
+ XPCSHELL_TESTS_MANIFESTS += ['tests/unit_stats/xpcshell.ini']
+
+MOCHITEST_MANIFESTS += ['tests/mochitest.ini']
+
+EXPORTS.mozilla.dom += [
+ 'UDPSocket.h',
+]
+
+EXPORTS.mozilla.dom.network += [
+ 'Connection.h',
+ 'Constants.h',
+ 'TCPServerSocketChild.h',
+ 'TCPServerSocketParent.h',
+ 'TCPSocketChild.h',
+ 'TCPSocketParent.h',
+ 'Types.h',
+ 'UDPSocketChild.h',
+ 'UDPSocketParent.h',
+]
+
+UNIFIED_SOURCES += [
+ 'Connection.cpp',
+ 'TCPServerSocketChild.cpp',
+ 'TCPServerSocketParent.cpp',
+ 'TCPSocketChild.cpp',
+ 'TCPSocketParent.cpp',
+ 'UDPSocket.cpp',
+ 'UDPSocketChild.cpp',
+ 'UDPSocketParent.cpp',
+]
+
+if CONFIG['MOZ_WIDGET_TOOLKIT'] == 'gonk':
+ EXTRA_JS_MODULES += [
+ 'NetworkStatsDB.jsm',
+ 'NetworkStatsService.jsm',
+ ]
+
+EXTRA_COMPONENTS += [
+ 'TCPServerSocket.js',
+ 'TCPSocket.manifest',
+ 'TCPSocketParentIntermediary.js',
+]
+
+EXTRA_PP_COMPONENTS += [
+ 'TCPSocket.js',
+]
+
+if CONFIG['MOZ_WIDGET_TOOLKIT'] == 'gonk':
+ EXTRA_COMPONENTS += [
+ 'NetworkStatsManager.js',
+ 'NetworkStatsManager.manifest',
+ 'NetworkStatsServiceProxy.js',
+ 'NetworkStatsServiceProxy.manifest',
+ ]
+ EXPORTS.mozilla.dom.network += [
+ 'NetUtils.h',
+ ]
+ UNIFIED_SOURCES += [
+ 'NetUtils.cpp',
+ ]
+
+IPDL_SOURCES += [
+ 'PTCPServerSocket.ipdl',
+ 'PTCPSocket.ipdl',
+ 'PUDPSocket.ipdl',
+]
+
+FAIL_ON_WARNINGS = True
+
+include('/ipc/chromium/chromium-config.mozbuild')
+
+FINAL_LIBRARY = 'xul'
diff --git a/dom/network/src/Connection.cpp b/dom/network/src/Connection.cpp
deleted file mode 100644
index c77bb3d6d..000000000
--- a/dom/network/src/Connection.cpp
+++ /dev/null
@@ -1,117 +0,0 @@
-/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
-/* 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 "Connection.h"
-#include "nsIDOMClassInfo.h"
-#include "mozilla/Preferences.h"
-#include "nsDOMEvent.h"
-#include "Constants.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 NS_LITERAL_STRING("change")
-
-DOMCI_DATA(MozConnection, mozilla::dom::network::Connection)
-
-namespace mozilla {
-namespace dom {
-namespace network {
-
-const char* Connection::sMeteredPrefName = "dom.network.metered";
-const bool Connection::sMeteredDefaultValue = false;
-
-NS_INTERFACE_MAP_BEGIN(Connection)
- NS_INTERFACE_MAP_ENTRY(nsIDOMMozConnection)
- NS_DOM_INTERFACE_MAP_ENTRY_CLASSINFO(MozConnection)
-NS_INTERFACE_MAP_END_INHERITING(nsDOMEventTargetHelper)
-
-// Don't use |Connection| alone, since that confuses nsTraceRefcnt since
-// we're not the only class with that name.
-NS_IMPL_ADDREF_INHERITED(dom::network::Connection, nsDOMEventTargetHelper)
-NS_IMPL_RELEASE_INHERITED(dom::network::Connection, nsDOMEventTargetHelper)
-
-NS_IMPL_EVENT_HANDLER(Connection, change)
-
-Connection::Connection()
- : mCanBeMetered(kDefaultCanBeMetered)
- , mBandwidth(kDefaultBandwidth)
-{
-}
-
-void
-Connection::Init(nsPIDOMWindow* aWindow)
-{
- BindToOwner(aWindow);
-
- hal::RegisterNetworkObserver(this);
-
- hal::NetworkInformation networkInfo;
- hal::GetCurrentNetworkInformation(&networkInfo);
-
- UpdateFromNetworkInfo(networkInfo);
-}
-
-void
-Connection::Shutdown()
-{
- hal::UnregisterNetworkObserver(this);
-}
-
-NS_IMETHODIMP
-Connection::GetBandwidth(double* aBandwidth)
-{
- if (mBandwidth == kDefaultBandwidth) {
- *aBandwidth = std::numeric_limits<double>::infinity();
- return NS_OK;
- }
-
- *aBandwidth = mBandwidth;
- return NS_OK;
-}
-
-NS_IMETHODIMP
-Connection::GetMetered(bool* aMetered)
-{
- if (!mCanBeMetered) {
- *aMetered = false;
- return NS_OK;
- }
-
- *aMetered = Preferences::GetBool(sMeteredPrefName,
- sMeteredDefaultValue);
- return NS_OK;
-}
-
-void
-Connection::UpdateFromNetworkInfo(const hal::NetworkInformation& aNetworkInfo)
-{
- mBandwidth = aNetworkInfo.bandwidth();
- mCanBeMetered = aNetworkInfo.canBeMetered();
-}
-
-void
-Connection::Notify(const hal::NetworkInformation& aNetworkInfo)
-{
- double previousBandwidth = mBandwidth;
- bool previousCanBeMetered = mCanBeMetered;
-
- UpdateFromNetworkInfo(aNetworkInfo);
-
- if (previousBandwidth == mBandwidth &&
- previousCanBeMetered == mCanBeMetered) {
- return;
- }
-
- DispatchTrustedEvent(CHANGE_EVENT_NAME);
-}
-
-} // namespace network
-} // namespace dom
-} // namespace mozilla
-
diff --git a/dom/network/src/Makefile.in b/dom/network/src/Makefile.in
deleted file mode 100644
index 057f04638..000000000
--- a/dom/network/src/Makefile.in
+++ /dev/null
@@ -1,44 +0,0 @@
-# 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/.
-
-DEPTH = @DEPTH@
-topsrcdir = @top_srcdir@
-srcdir = @srcdir@
-VPATH = $(srcdir)
-
-include $(DEPTH)/config/autoconf.mk
-
-LIBRARY_NAME = dom_network_s
-LIBXUL_LIBRARY = 1
-FORCE_STATIC_LIB = 1
-FAIL_ON_WARNINGS := 1
-
-DISABLED_EXTRA_COMPONENTS = \
- TCPSocket.js \
- TCPSocketParentIntermediary.js \
- TCPSocket.manifest \
- $(NULL)
-
-ifdef MOZ_B2G_RIL
-DISABLED_EXTRA_COMPONENTS += \
- NetworkStatsManager.manifest \
- NetworkStatsManager.js \
- $(NULL)
-endif
-
-include $(topsrcdir)/dom/dom-config.mk
-
-LOCAL_INCLUDES = \
- -I$(topsrcdir)/content/events/src \
- $(NULL)
-
-ifdef MOZ_B2G_RIL
-LOCAL_INCLUDES += \
- -I$(topsrcdir)/dom/icc/src \
- $(NULL)
-endif
-
-include $(topsrcdir)/config/config.mk
-include $(topsrcdir)/ipc/chromium/chromium-config.mk
-include $(topsrcdir)/config/rules.mk
diff --git a/dom/network/src/MobileConnection.cpp b/dom/network/src/MobileConnection.cpp
deleted file mode 100644
index 134836638..000000000
--- a/dom/network/src/MobileConnection.cpp
+++ /dev/null
@@ -1,512 +0,0 @@
-/* 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 "MobileConnection.h"
-#include "nsIDOMDOMRequest.h"
-#include "nsIDOMClassInfo.h"
-#include "nsDOMEvent.h"
-#include "nsIDOMUSSDReceivedEvent.h"
-#include "nsIDOMDataErrorEvent.h"
-#include "nsIDOMCFStateChangeEvent.h"
-#include "GeneratedEvents.h"
-#include "mozilla/Preferences.h"
-#include "nsIPermissionManager.h"
-
-#include "nsContentUtils.h"
-#include "nsJSUtils.h"
-#include "nsJSON.h"
-#include "jsapi.h"
-#include "mozilla/Services.h"
-
-#define NS_RILCONTENTHELPER_CONTRACTID "@mozilla.org/ril/content-helper;1"
-
-using namespace mozilla::dom::network;
-
-class MobileConnection::Listener : public nsIMobileConnectionListener
-{
- MobileConnection* mMobileConnection;
-
-public:
- NS_DECL_ISUPPORTS
- NS_FORWARD_SAFE_NSIMOBILECONNECTIONLISTENER(mMobileConnection)
-
- Listener(MobileConnection* aMobileConnection)
- : mMobileConnection(aMobileConnection)
- {
- MOZ_ASSERT(mMobileConnection);
- }
-
- void Disconnect()
- {
- MOZ_ASSERT(mMobileConnection);
- mMobileConnection = nullptr;
- }
-};
-
-NS_IMPL_ISUPPORTS1(MobileConnection::Listener, nsIMobileConnectionListener)
-
-DOMCI_DATA(MozMobileConnection, MobileConnection)
-
-NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(MobileConnection,
- nsDOMEventTargetHelper)
- // Don't traverse mListener because it doesn't keep any reference to
- // MobileConnection but a raw pointer instead. Neither does mProvider because
- // it's an xpcom service and is only released at shutting down.
-NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
-
-NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(MobileConnection,
- nsDOMEventTargetHelper)
-NS_IMPL_CYCLE_COLLECTION_UNLINK_END
-
-NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(MobileConnection)
- NS_INTERFACE_MAP_ENTRY(nsIDOMMozMobileConnection)
- NS_DOM_INTERFACE_MAP_ENTRY_CLASSINFO(MozMobileConnection)
-NS_INTERFACE_MAP_END_INHERITING(nsDOMEventTargetHelper)
-
-NS_IMPL_ADDREF_INHERITED(MobileConnection, nsDOMEventTargetHelper)
-NS_IMPL_RELEASE_INHERITED(MobileConnection, nsDOMEventTargetHelper)
-
-NS_IMPL_EVENT_HANDLER(MobileConnection, cardstatechange)
-NS_IMPL_EVENT_HANDLER(MobileConnection, iccinfochange)
-NS_IMPL_EVENT_HANDLER(MobileConnection, voicechange)
-NS_IMPL_EVENT_HANDLER(MobileConnection, datachange)
-NS_IMPL_EVENT_HANDLER(MobileConnection, ussdreceived)
-NS_IMPL_EVENT_HANDLER(MobileConnection, dataerror)
-NS_IMPL_EVENT_HANDLER(MobileConnection, cfstatechange)
-
-MobileConnection::MobileConnection()
-{
- mProvider = do_GetService(NS_RILCONTENTHELPER_CONTRACTID);
- mWindow = nullptr;
-
- // Not being able to acquire the provider isn't fatal since we check
- // for it explicitly below.
- if (!mProvider) {
- NS_WARNING("Could not acquire nsIMobileConnectionProvider!");
- return;
- }
-}
-
-void
-MobileConnection::Init(nsPIDOMWindow* aWindow)
-{
- BindToOwner(aWindow);
-
- mWindow = do_GetWeakReference(aWindow);
- mListener = new Listener(this);
-
- if (!CheckPermission("mobilenetwork") &&
- CheckPermission("mobileconnection")) {
- DebugOnly<nsresult> rv = mProvider->RegisterMobileConnectionMsg(mListener);
- NS_WARN_IF_FALSE(NS_SUCCEEDED(rv),
- "Failed registering mobile connection messages with provider");
-
- printf_stderr("MobileConnection initialized");
- }
-}
-
-void
-MobileConnection::Shutdown()
-{
- if (mProvider && mListener) {
- mListener->Disconnect();
- mProvider->UnregisterMobileConnectionMsg(mListener);
- mProvider = nullptr;
- mListener = nullptr;
- }
-}
-
-// nsIDOMMozMobileConnection
-
-NS_IMETHODIMP
-MobileConnection::GetLastKnownNetwork(nsAString& network)
-{
- network.SetIsVoid(true);
-
- if (!CheckPermission("mobilenetwork")) {
- return NS_OK;
- }
-
- network = mozilla::Preferences::GetString("ril.lastKnownNetwork");
- return NS_OK;
-}
-
-NS_IMETHODIMP
-MobileConnection::GetLastKnownHomeNetwork(nsAString& network)
-{
- network.SetIsVoid(true);
-
- if (!CheckPermission("mobilenetwork")) {
- return NS_OK;
- }
-
- network = mozilla::Preferences::GetString("ril.lastKnownHomeNetwork");
- return NS_OK;
-}
-
-// All fields below require the "mobileconnection" permission.
-
-bool
-MobileConnection::CheckPermission(const char* type)
-{
- nsCOMPtr<nsPIDOMWindow> window = do_QueryReferent(mWindow);
- NS_ENSURE_TRUE(window, false);
-
- nsCOMPtr<nsIPermissionManager> permMgr =
- do_GetService(NS_PERMISSIONMANAGER_CONTRACTID);
- NS_ENSURE_TRUE(permMgr, false);
-
- uint32_t permission = nsIPermissionManager::DENY_ACTION;
- permMgr->TestPermissionFromWindow(window, type, &permission);
- return permission == nsIPermissionManager::ALLOW_ACTION;
-}
-
-NS_IMETHODIMP
-MobileConnection::GetCardState(nsAString& cardState)
-{
- cardState.SetIsVoid(true);
-
- if (!mProvider || !CheckPermission("mobileconnection")) {
- return NS_OK;
- }
- return mProvider->GetCardState(cardState);
-}
-
-NS_IMETHODIMP
-MobileConnection::GetRetryCount(int32_t* retryCount)
-{
- *retryCount = 0;
-
- if (!mProvider || !CheckPermission("mobileconnection")) {
- return NS_OK;
- }
- return mProvider->GetRetryCount(retryCount);
-}
-
-NS_IMETHODIMP
-MobileConnection::GetIccInfo(nsIDOMMozMobileICCInfo** aIccInfo)
-{
- *aIccInfo = nullptr;
-
- if (!mProvider || !CheckPermission("mobileconnection")) {
- return NS_OK;
- }
- return mProvider->GetIccInfo(aIccInfo);
-}
-
-NS_IMETHODIMP
-MobileConnection::GetVoice(nsIDOMMozMobileConnectionInfo** voice)
-{
- *voice = nullptr;
-
- if (!mProvider || !CheckPermission("mobileconnection")) {
- return NS_OK;
- }
- return mProvider->GetVoiceConnectionInfo(voice);
-}
-
-NS_IMETHODIMP
-MobileConnection::GetData(nsIDOMMozMobileConnectionInfo** data)
-{
- *data = nullptr;
-
- if (!mProvider || !CheckPermission("mobileconnection")) {
- return NS_OK;
- }
- return mProvider->GetDataConnectionInfo(data);
-}
-
-NS_IMETHODIMP
-MobileConnection::GetNetworkSelectionMode(nsAString& networkSelectionMode)
-{
- networkSelectionMode.SetIsVoid(true);
-
- if (!mProvider || !CheckPermission("mobileconnection")) {
- return NS_OK;
- }
- return mProvider->GetNetworkSelectionMode(networkSelectionMode);
-}
-
-NS_IMETHODIMP
-MobileConnection::GetNetworks(nsIDOMDOMRequest** request)
-{
- *request = nullptr;
-
- if (!CheckPermission("mobileconnection")) {
- return NS_OK;
- }
-
- if (!mProvider) {
- return NS_ERROR_FAILURE;
- }
-
- return mProvider->GetNetworks(GetOwner(), request);
-}
-
-NS_IMETHODIMP
-MobileConnection::SelectNetwork(nsIDOMMozMobileNetworkInfo* network, nsIDOMDOMRequest** request)
-{
- *request = nullptr;
-
- if (!CheckPermission("mobileconnection")) {
- return NS_OK;
- }
-
- if (!mProvider) {
- return NS_ERROR_FAILURE;
- }
-
- return mProvider->SelectNetwork(GetOwner(), network, request);
-}
-
-NS_IMETHODIMP
-MobileConnection::SelectNetworkAutomatically(nsIDOMDOMRequest** request)
-{
- *request = nullptr;
-
- if (!CheckPermission("mobileconnection")) {
- return NS_OK;
- }
-
- if (!mProvider) {
- return NS_ERROR_FAILURE;
- }
-
- return mProvider->SelectNetworkAutomatically(GetOwner(), request);
-}
-
-NS_IMETHODIMP
-MobileConnection::SendMMI(const nsAString& aMMIString,
- nsIDOMDOMRequest** request)
-{
- if (!CheckPermission("mobileconnection")) {
- return NS_OK;
- }
-
- if (!mProvider) {
- return NS_ERROR_FAILURE;
- }
-
- return mProvider->SendMMI(GetOwner(), aMMIString, request);
-}
-
-NS_IMETHODIMP
-MobileConnection::CancelMMI(nsIDOMDOMRequest** request)
-{
- if (!CheckPermission("mobileconnection")) {
- return NS_OK;
- }
-
- if (!mProvider) {
- return NS_ERROR_FAILURE;
- }
-
- return mProvider->CancelMMI(GetOwner(), request);
-}
-
-NS_IMETHODIMP
-MobileConnection::GetCallForwardingOption(uint16_t aReason,
- nsIDOMDOMRequest** aRequest)
-{
- *aRequest = nullptr;
-
- if (!CheckPermission("mobileconnection")) {
- return NS_OK;
- }
-
- if (!mProvider) {
- return NS_ERROR_FAILURE;
- }
-
- return mProvider->GetCallForwardingOption(GetOwner(), aReason, aRequest);
-}
-
-NS_IMETHODIMP
-MobileConnection::SetCallForwardingOption(nsIDOMMozMobileCFInfo* aCFInfo,
- nsIDOMDOMRequest** aRequest)
-{
- *aRequest = nullptr;
-
- if (!CheckPermission("mobileconnection")) {
- return NS_OK;
- }
-
- if (!mProvider) {
- return NS_ERROR_FAILURE;
- }
-
- return mProvider->SetCallForwardingOption(GetOwner(), aCFInfo, aRequest);
-}
-
-NS_IMETHODIMP
-MobileConnection::GetCallBarringOption(const JS::Value& aOption,
- nsIDOMDOMRequest** aRequest)
-{
- *aRequest = nullptr;
-
- if (!CheckPermission("mobileconnection")) {
- return NS_OK;
- }
-
- if (!mProvider) {
- return NS_ERROR_FAILURE;
- }
-
- return mProvider->GetCallBarringOption(GetOwner(), aOption, aRequest);
-}
-
-NS_IMETHODIMP
-MobileConnection::SetCallBarringOption(const JS::Value& aOption,
- nsIDOMDOMRequest** aRequest)
-{
- *aRequest = nullptr;
-
- if (!CheckPermission("mobileconnection")) {
- return NS_OK;
- }
-
- if (!mProvider) {
- return NS_ERROR_FAILURE;
- }
-
- return mProvider->SetCallBarringOption(GetOwner(), aOption, aRequest);
-}
-
-NS_IMETHODIMP
-MobileConnection::GetCallWaitingOption(nsIDOMDOMRequest** aRequest)
-{
- *aRequest = nullptr;
-
- if (!CheckPermission("mobileconnection")) {
- return NS_OK;
- }
-
- if (!mProvider) {
- return NS_ERROR_FAILURE;
- }
-
- return mProvider->GetCallWaitingOption(GetOwner(), aRequest);
-}
-
-NS_IMETHODIMP
-MobileConnection::SetCallWaitingOption(bool aEnabled,
- nsIDOMDOMRequest** aRequest)
-{
- *aRequest = nullptr;
-
- if (!CheckPermission("mobileconnection")) {
- return NS_OK;
- }
-
- if (!mProvider) {
- return NS_ERROR_FAILURE;
- }
-
- return mProvider->SetCallWaitingOption(GetOwner(), aEnabled, aRequest);
-}
-
-// nsIMobileConnectionListener
-
-NS_IMETHODIMP
-MobileConnection::NotifyVoiceChanged()
-{
- if (!CheckPermission("mobileconnection")) {
- return NS_OK;
- }
-
- return DispatchTrustedEvent(NS_LITERAL_STRING("voicechange"));
-}
-
-NS_IMETHODIMP
-MobileConnection::NotifyDataChanged()
-{
- if (!CheckPermission("mobileconnection")) {
- return NS_OK;
- }
-
- return DispatchTrustedEvent(NS_LITERAL_STRING("datachange"));
-}
-
-NS_IMETHODIMP
-MobileConnection::NotifyCardStateChanged()
-{
- if (!CheckPermission("mobileconnection")) {
- return NS_OK;
- }
-
- return DispatchTrustedEvent(NS_LITERAL_STRING("cardstatechange"));
-}
-
-NS_IMETHODIMP
-MobileConnection::NotifyIccInfoChanged()
-{
- if (!CheckPermission("mobileconnection")) {
- return NS_OK;
- }
-
- return DispatchTrustedEvent(NS_LITERAL_STRING("iccinfochange"));
-}
-
-NS_IMETHODIMP
-MobileConnection::NotifyUssdReceived(const nsAString& aMessage,
- bool aSessionEnded)
-{
- if (!CheckPermission("mobileconnection")) {
- return NS_OK;
- }
-
- nsCOMPtr<nsIDOMEvent> event;
- NS_NewDOMUSSDReceivedEvent(getter_AddRefs(event), this, nullptr, nullptr);
-
- nsCOMPtr<nsIDOMUSSDReceivedEvent> ce = do_QueryInterface(event);
- nsresult rv = ce->InitUSSDReceivedEvent(NS_LITERAL_STRING("ussdreceived"),
- false, false,
- aMessage, aSessionEnded);
- NS_ENSURE_SUCCESS(rv, rv);
-
- return DispatchTrustedEvent(ce);
-}
-
-NS_IMETHODIMP
-MobileConnection::NotifyDataError(const nsAString& aMessage)
-{
- if (!CheckPermission("mobileconnection")) {
- return NS_OK;
- }
-
- nsCOMPtr<nsIDOMEvent> event;
- NS_NewDOMDataErrorEvent(getter_AddRefs(event), this, nullptr, nullptr);
-
- nsCOMPtr<nsIDOMDataErrorEvent> ce = do_QueryInterface(event);
- nsresult rv = ce->InitDataErrorEvent(NS_LITERAL_STRING("dataerror"),
- false, false, aMessage);
- NS_ENSURE_SUCCESS(rv, rv);
-
- return DispatchTrustedEvent(ce);
-}
-
-NS_IMETHODIMP
-MobileConnection::NotifyCFStateChange(bool aSuccess,
- unsigned short aAction,
- unsigned short aReason,
- const nsAString& aNumber,
- unsigned short aSeconds,
- unsigned short aServiceClass)
-{
- if (!CheckPermission("mobileconnection")) {
- return NS_OK;
- }
-
- nsCOMPtr<nsIDOMEvent> event;
- NS_NewDOMCFStateChangeEvent(getter_AddRefs(event), this, nullptr, nullptr);
-
- nsCOMPtr<nsIDOMCFStateChangeEvent> ce = do_QueryInterface(event);
- nsresult rv = ce->InitCFStateChangeEvent(NS_LITERAL_STRING("cfstatechange"),
- false, false,
- aSuccess, aAction, aReason, aNumber,
- aSeconds, aServiceClass);
- NS_ENSURE_SUCCESS(rv, rv);
-
- return DispatchTrustedEvent(ce);
-}
diff --git a/dom/network/src/MobileConnection.h b/dom/network/src/MobileConnection.h
deleted file mode 100644
index 3dfc63b99..000000000
--- a/dom/network/src/MobileConnection.h
+++ /dev/null
@@ -1,57 +0,0 @@
-/* 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_MobileConnection_h
-#define mozilla_dom_network_MobileConnection_h
-
-#include "nsIDOMMobileConnection.h"
-#include "nsIMobileConnectionProvider.h"
-#include "nsDOMEventTargetHelper.h"
-#include "nsCycleCollectionParticipant.h"
-
-namespace mozilla {
-namespace dom {
-namespace network {
-
-class MobileConnection : public nsDOMEventTargetHelper
- , public nsIDOMMozMobileConnection
-{
- /**
- * Class MobileConnection doesn't actually inherit
- * nsIMobileConnectionListener. Instead, it owns an
- * nsIMobileConnectionListener derived instance mListener and passes it to
- * nsIMobileConnectionProvider. The onreceived events are first delivered to
- * mListener and then forwarded to its owner, MobileConnection. See also bug
- * 775997 comment #51.
- */
- class Listener;
-
-public:
- NS_DECL_ISUPPORTS_INHERITED
- NS_DECL_NSIDOMMOZMOBILECONNECTION
- NS_DECL_NSIMOBILECONNECTIONLISTENER
-
- NS_REALLY_FORWARD_NSIDOMEVENTTARGET(nsDOMEventTargetHelper)
-
- MobileConnection();
-
- void Init(nsPIDOMWindow *aWindow);
- void Shutdown();
-
- NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(MobileConnection,
- nsDOMEventTargetHelper)
-
-private:
- nsCOMPtr<nsIMobileConnectionProvider> mProvider;
- nsRefPtr<Listener> mListener;
- nsWeakPtr mWindow;
-
- bool CheckPermission(const char* type);
-};
-
-} // namespace network
-} // namespace dom
-} // namespace mozilla
-
-#endif // mozilla_dom_network_MobileConnection_h
diff --git a/dom/network/src/NetworkStatsDB.jsm b/dom/network/src/NetworkStatsDB.jsm
deleted file mode 100644
index c7dd3e475..000000000
--- a/dom/network/src/NetworkStatsDB.jsm
+++ /dev/null
@@ -1,389 +0,0 @@
-/* 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/. */
-
-"use strict";
-
-this.EXPORTED_SYMBOLS = ['NetworkStatsDB'];
-
-const DEBUG = false;
-function debug(s) { dump("-*- NetworkStatsDB: " + s + "\n"); }
-
-const {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components;
-
-Cu.import("resource://gre/modules/Services.jsm");
-Cu.import("resource://gre/modules/IndexedDBHelper.jsm");
-
-const DB_NAME = "net_stats";
-const DB_VERSION = 1;
-const STORE_NAME = "net_stats";
-
-// Constant defining the maximum values allowed per interface. If more, older
-// will be erased.
-const VALUES_MAX_LENGTH = 6 * 30;
-
-// Constant defining the rate of the samples. Daily.
-const SAMPLE_RATE = 1000 * 60 * 60 * 24;
-
-this.NetworkStatsDB = function NetworkStatsDB(aGlobal, aConnectionTypes) {
- if (DEBUG) {
- debug("Constructor");
- }
- this._connectionTypes = aConnectionTypes;
- this.initDBHelper(DB_NAME, DB_VERSION, [STORE_NAME], aGlobal);
-}
-
-NetworkStatsDB.prototype = {
- __proto__: IndexedDBHelper.prototype,
-
- dbNewTxn: function dbNewTxn(txn_type, callback, txnCb) {
- function successCb(result) {
- txnCb(null, result);
- }
- function errorCb(error) {
- txnCb(error, null);
- }
- return this.newTxn(txn_type, STORE_NAME, callback, successCb, errorCb);
- },
-
- upgradeSchema: function upgradeSchema(aTransaction, aDb, aOldVersion, aNewVersion) {
- if (DEBUG) {
- debug("upgrade schema from: " + aOldVersion + " to " + aNewVersion + " called!");
- }
- let db = aDb;
- let objectStore;
- for (let currVersion = aOldVersion; currVersion < aNewVersion; currVersion++) {
- if (currVersion == 0) {
- /**
- * Create the initial database schema.
- */
-
- objectStore = db.createObjectStore(STORE_NAME, { keyPath: ["connectionType", "timestamp"] });
- objectStore.createIndex("connectionType", "connectionType", { unique: false });
- objectStore.createIndex("timestamp", "timestamp", { unique: false });
- objectStore.createIndex("rxBytes", "rxBytes", { unique: false });
- objectStore.createIndex("txBytes", "txBytes", { unique: false });
- objectStore.createIndex("rxTotalBytes", "rxTotalBytes", { unique: false });
- objectStore.createIndex("txTotalBytes", "txTotalBytes", { unique: false });
- if (DEBUG) {
- debug("Created object stores and indexes");
- }
-
- // There could be a time delay between the point when the network
- // interface comes up and the point when the database is initialized.
- // In this short interval some traffic data are generated but are not
- // registered by the first sample. The initialization of the database
- // should make up the missing sample.
- let stats = [];
- for (let connection in this._connectionTypes) {
- let connectionType = this._connectionTypes[connection].name;
- let timestamp = this.normalizeDate(new Date());
- stats.push({ connectionType: connectionType,
- timestamp: timestamp,
- rxBytes: 0,
- txBytes: 0,
- rxTotalBytes: 0,
- txTotalBytes: 0 });
- }
- this._saveStats(aTransaction, objectStore, stats);
- if (DEBUG) {
- debug("Database initialized");
- }
- }
- }
- },
-
- normalizeDate: function normalizeDate(aDate) {
- // Convert to UTC according to timezone and
- // filter timestamp to get SAMPLE_RATE precission
- let timestamp = aDate.getTime() - (new Date()).getTimezoneOffset() * 60 * 1000;
- timestamp = Math.floor(timestamp / SAMPLE_RATE) * SAMPLE_RATE;
- return timestamp;
- },
-
- saveStats: function saveStats(stats, aResultCb) {
- let timestamp = this.normalizeDate(stats.date);
-
- stats = {connectionType: stats.connectionType,
- timestamp: timestamp,
- rxBytes: 0,
- txBytes: 0,
- rxTotalBytes: stats.rxBytes,
- txTotalBytes: stats.txBytes};
-
- this.dbNewTxn("readwrite", function(txn, store) {
- if (DEBUG) {
- debug("Filtered time: " + new Date(timestamp));
- debug("New stats: " + JSON.stringify(stats));
- }
-
- let request = store.index("connectionType").openCursor(stats.connectionType, "prev");
- request.onsuccess = function onsuccess(event) {
- let cursor = event.target.result;
- if (!cursor) {
- // Empty, so save first element.
- this._saveStats(txn, store, stats);
- return;
- }
-
- // There are old samples
- if (DEBUG) {
- debug("Last value " + JSON.stringify(cursor.value));
- }
-
- // Remove stats previous to now - VALUE_MAX_LENGTH
- this._removeOldStats(txn, store, stats.connectionType, stats.timestamp);
-
- // Process stats before save
- this._processSamplesDiff(txn, store, cursor, stats);
- }.bind(this);
- }.bind(this), aResultCb);
- },
-
- /*
- * This function check that stats are saved in the database following the sample rate.
- * In this way is easier to find elements when stats are requested.
- */
- _processSamplesDiff: function _processSamplesDiff(txn, store, lastSampleCursor, newSample) {
- let lastSample = lastSampleCursor.value;
-
- // Get difference between last and new sample.
- let diff = (newSample.timestamp - lastSample.timestamp) / SAMPLE_RATE;
- if (diff % 1) {
- // diff is decimal, so some error happened because samples are stored as a multiple
- // of SAMPLE_RATE
- txn.abort();
- throw new Error("Error processing samples");
- }
-
- if (DEBUG) {
- debug("New: " + newSample.timestamp + " - Last: " + lastSample.timestamp + " - diff: " + diff);
- }
-
- let rxDiff = newSample.rxTotalBytes - lastSample.rxTotalBytes;
- let txDiff = newSample.txTotalBytes - lastSample.txTotalBytes;
- if (rxDiff < 0 || txDiff < 0) {
- rxDiff = newSample.rxTotalBytes;
- txDiff = newSample.txTotalBytes;
- }
- newSample.rxBytes = rxDiff;
- newSample.txBytes = txDiff;
-
- if (diff == 1) {
- // New element.
- this._saveStats(txn, store, newSample);
- return;
- }
- if (diff > 1) {
- // Some samples lost. Device off during one or more samplerate periods.
- // Time or timezone changed
- // Add lost samples with 0 bytes and the actual one.
- if (diff > VALUES_MAX_LENGTH) {
- diff = VALUES_MAX_LENGTH;
- }
-
- let data = [];
- for (let i = diff - 2; i >= 0; i--) {
- let time = newSample.timestamp - SAMPLE_RATE * (i + 1);
- let sample = {connectionType: newSample.connectionType,
- timestamp: time,
- rxBytes: 0,
- txBytes: 0,
- rxTotalBytes: lastSample.rxTotalBytes,
- txTotalBytes: lastSample.txTotalBytes};
- data.push(sample);
- }
-
- data.push(newSample);
- this._saveStats(txn, store, data);
- return;
- }
- if (diff == 0 || diff < 0) {
- // New element received before samplerate period.
- // It means that device has been restarted (or clock / timezone change).
- // Update element.
-
- // If diff < 0, clock or timezone changed back. Place data in the last sample.
-
- lastSample.rxBytes += rxDiff;
- lastSample.txBytes += txDiff;
- lastSample.rxTotalBytes = newSample.rxTotalBytes;
- lastSample.txTotalBytes = newSample.txTotalBytes;
- if (DEBUG) {
- debug("Update: " + JSON.stringify(lastSample));
- }
- let req = lastSampleCursor.update(lastSample);
- }
- },
-
- _saveStats: function _saveStats(txn, store, networkStats) {
- if (DEBUG) {
- debug("_saveStats: " + JSON.stringify(networkStats));
- }
-
- if (Array.isArray(networkStats)) {
- let len = networkStats.length - 1;
- for (let i = 0; i <= len; i++) {
- store.put(networkStats[i]);
- }
- } else {
- store.put(networkStats);
- }
- },
-
- _removeOldStats: function _removeOldStats(txn, store, connType, date) {
- // Callback function to remove old items when new ones are added.
- let filterDate = date - (SAMPLE_RATE * VALUES_MAX_LENGTH - 1);
- let lowFilter = [connType, 0];
- let upFilter = [connType, filterDate];
- let range = this.dbGlobal.IDBKeyRange.bound(lowFilter, upFilter, false, false);
- store.openCursor(range).onsuccess = function(event) {
- var cursor = event.target.result;
- if (cursor) {
- cursor.delete();
- cursor.continue();
- }
- }.bind(this);
- },
-
- clear: function clear(aResultCb) {
- this.dbNewTxn("readwrite", function(txn, store) {
- if (DEBUG) {
- debug("Going to clear all!");
- }
- store.clear();
- }, aResultCb);
- },
-
- find: function find(aResultCb, aOptions) {
- let offset = (new Date()).getTimezoneOffset() * 60 * 1000;
- let start = this.normalizeDate(aOptions.start);
- let end = this.normalizeDate(aOptions.end);
-
- if (DEBUG) {
- debug("Find: connectionType:" + aOptions.connectionType + " start: " + start + " end: " + end);
- debug("Start time: " + new Date(start));
- debug("End time: " + new Date(end));
- }
-
- this.dbNewTxn("readonly", function(txn, store) {
- let lowFilter = [aOptions.connectionType, start];
- let upFilter = [aOptions.connectionType, end];
- let range = this.dbGlobal.IDBKeyRange.bound(lowFilter, upFilter, false, false);
-
- let data = [];
-
- if (!txn.result) {
- txn.result = {};
- }
-
- let request = store.openCursor(range).onsuccess = function(event) {
- var cursor = event.target.result;
- if (cursor){
- data.push({ rxBytes: cursor.value.rxBytes,
- txBytes: cursor.value.txBytes,
- date: new Date(cursor.value.timestamp + offset) });
- cursor.continue();
- return;
- }
-
- // When requested samples (start / end) are not in the range of now and
- // now - VALUES_MAX_LENGTH, fill with empty samples.
- this.fillResultSamples(start + offset, end + offset, data);
-
- txn.result.connectionType = aOptions.connectionType;
- txn.result.start = aOptions.start;
- txn.result.end = aOptions.end;
- txn.result.data = data;
- }.bind(this);
- }.bind(this), aResultCb);
- },
-
- findAll: function findAll(aResultCb, aOptions) {
- let offset = (new Date()).getTimezoneOffset() * 60 * 1000;
- let start = this.normalizeDate(aOptions.start);
- let end = this.normalizeDate(aOptions.end);
-
- if (DEBUG) {
- debug("FindAll: start: " + start + " end: " + end + "\n");
- }
-
- let self = this;
- this.dbNewTxn("readonly", function(txn, store) {
- let lowFilter = start;
- let upFilter = end;
- let range = this.dbGlobal.IDBKeyRange.bound(lowFilter, upFilter, false, false);
-
- let data = [];
-
- if (!txn.result) {
- txn.result = {};
- }
-
- let request = store.index("timestamp").openCursor(range).onsuccess = function(event) {
- var cursor = event.target.result;
- if (cursor) {
- if (data.length > 0 &&
- data[data.length - 1].date.getTime() == cursor.value.timestamp + offset) {
- // Time is the same, so add values.
- data[data.length - 1].rxBytes += cursor.value.rxBytes;
- data[data.length - 1].txBytes += cursor.value.txBytes;
- } else {
- data.push({ rxBytes: cursor.value.rxBytes,
- txBytes: cursor.value.txBytes,
- date: new Date(cursor.value.timestamp + offset) });
- }
- cursor.continue();
- return;
- }
-
- this.fillResultSamples(start + offset, end + offset, data);
-
- txn.result.connectionType = aOptions.connectionType;
- txn.result.start = aOptions.start;
- txn.result.end = aOptions.end;
- txn.result.data = data;
- }.bind(this);
- }.bind(this), aResultCb);
- },
-
- /*
- * Fill data array (samples from database) with empty samples to match
- * requested start / end dates.
- */
- fillResultSamples: function fillResultSamples(aStart, aEnd, aData) {
- if (aData.length == 0) {
- aData.push({ rxBytes: undefined,
- txBytes: undefined,
- date: new Date(aStart) });
- }
-
- while (aStart < aData[0].date.getTime()) {
- aData.unshift({ rxBytes: undefined,
- txBytes: undefined,
- date: new Date(aData[0].date.getTime() - SAMPLE_RATE) });
- }
-
- while (aEnd > aData[aData.length - 1].date.getTime()) {
- aData.push({ rxBytes: undefined,
- txBytes: undefined,
- date: new Date(aData[aData.length - 1].date.getTime() + SAMPLE_RATE) });
- }
- },
-
- get sampleRate () {
- return SAMPLE_RATE;
- },
-
- get maxStorageSamples () {
- return VALUES_MAX_LENGTH;
- },
-
- logAllRecords: function logAllRecords(aResultCb) {
- this.dbNewTxn("readonly", function(txn, store) {
- store.mozGetAll().onsuccess = function onsuccess(event) {
- txn.result = event.target.result;
- };
- }, aResultCb);
- },
-};
diff --git a/dom/network/src/NetworkStatsManager.js b/dom/network/src/NetworkStatsManager.js
deleted file mode 100644
index 9912d2fbc..000000000
--- a/dom/network/src/NetworkStatsManager.js
+++ /dev/null
@@ -1,246 +0,0 @@
-/* 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/. */
-
-"use strict";
-
-const DEBUG = false;
-function debug(s) { dump("-*- NetworkStatsManager: " + s + "\n"); }
-
-const {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components;
-
-Cu.import("resource://gre/modules/XPCOMUtils.jsm");
-Cu.import("resource://gre/modules/Services.jsm");
-Cu.import("resource://gre/modules/DOMRequestHelper.jsm");
-Cu.import("resource://gre/modules/ObjectWrapper.jsm");
-
-// Ensure NetworkStatsService and NetworkStatsDB are loaded in the parent process
-// to receive messages from the child processes.
-let appInfo = Cc["@mozilla.org/xre/app-info;1"];
-let isParentProcess = !appInfo || appInfo.getService(Ci.nsIXULRuntime)
- .processType == Ci.nsIXULRuntime.PROCESS_TYPE_DEFAULT;
-if (isParentProcess) {
- Cu.import("resource://gre/modules/NetworkStatsService.jsm");
-}
-
-XPCOMUtils.defineLazyServiceGetter(this, "cpmm",
- "@mozilla.org/childprocessmessagemanager;1",
- "nsISyncMessageSender");
-
-// NetworkStatsData
-const nsIClassInfo = Ci.nsIClassInfo;
-const NETWORKSTATSDATA_CID = Components.ID("{3b16fe17-5583-483a-b486-b64a3243221c}");
-const nsIDOMMozNetworkStatsData = Components.interfaces.nsIDOMMozNetworkStatsData;
-
-function NetworkStatsData(aData) {
- this.rxBytes = aData.rxBytes;
- this.txBytes = aData.txBytes;
- this.date = aData.date;
-}
-
-NetworkStatsData.prototype = {
- __exposedProps__: {
- rxBytes: 'r',
- txBytes: 'r',
- date: 'r',
- },
-
- classID : NETWORKSTATSDATA_CID,
- classInfo : XPCOMUtils.generateCI({classID: NETWORKSTATSDATA_CID,
- contractID:"@mozilla.org/networkstatsdata;1",
- classDescription: "NetworkStatsData",
- interfaces: [nsIDOMMozNetworkStatsData],
- flags: nsIClassInfo.DOM_OBJECT}),
-
- QueryInterface : XPCOMUtils.generateQI([nsIDOMMozNetworkStatsData])
-};
-
-// NetworkStats
-const NETWORKSTATS_CONTRACTID = "@mozilla.org/networkstats;1";
-const NETWORKSTATS_CID = Components.ID("{037435a6-f563-48f3-99b3-a0106d8ba5bd}");
-const nsIDOMMozNetworkStats = Components.interfaces.nsIDOMMozNetworkStats;
-
-function NetworkStats(aWindow, aStats) {
- if (DEBUG) {
- debug("NetworkStats Constructor");
- }
- this.connectionType = aStats.connectionType || null;
- this.start = aStats.start || null;
- this.end = aStats.end || null;
-
- let samples = this.data = Cu.createArrayIn(aWindow);
- for (let i = 0; i < aStats.data.length; i++) {
- samples.push(new NetworkStatsData(aStats.data[i]));
- }
-}
-
-NetworkStats.prototype = {
- __exposedProps__: {
- connectionType: 'r',
- start: 'r',
- end: 'r',
- data: 'r',
- },
-
- classID : NETWORKSTATS_CID,
- classInfo : XPCOMUtils.generateCI({classID: NETWORKSTATS_CID,
- contractID: NETWORKSTATS_CONTRACTID,
- classDescription: "NetworkStats",
- interfaces: [nsIDOMMozNetworkStats],
- flags: nsIClassInfo.DOM_OBJECT}),
-
- QueryInterface : XPCOMUtils.generateQI([nsIDOMMozNetworkStats,
- nsIDOMMozNetworkStatsData])
-}
-
-// NetworkStatsManager
-
-const NETWORKSTATSMANAGER_CONTRACTID = "@mozilla.org/networkStatsManager;1";
-const NETWORKSTATSMANAGER_CID = Components.ID("{87529a6c-aef6-11e1-a595-4f034275cfa6}");
-const nsIDOMMozNetworkStatsManager = Components.interfaces.nsIDOMMozNetworkStatsManager;
-
-function NetworkStatsManager() {
- if (DEBUG) {
- debug("Constructor");
- }
-}
-
-NetworkStatsManager.prototype = {
- __proto__: DOMRequestIpcHelper.prototype,
-
- checkPrivileges: function checkPrivileges() {
- if (!this.hasPrivileges) {
- throw Components.Exception("Permission denied", Cr.NS_ERROR_FAILURE);
- }
- },
-
- getNetworkStats: function getNetworkStats(aOptions) {
- this.checkPrivileges();
-
- if (!aOptions.start || !aOptions.end ||
- aOptions.start > aOptions.end) {
- throw Components.results.NS_ERROR_INVALID_ARG;
- }
-
- let request = this.createRequest();
- cpmm.sendAsyncMessage("NetworkStats:Get",
- {data: aOptions, id: this.getRequestId(request)});
- return request;
- },
-
- clearAllData: function clearAllData() {
- this.checkPrivileges();
-
- let request = this.createRequest();
- cpmm.sendAsyncMessage("NetworkStats:Clear",
- {id: this.getRequestId(request)});
- return request;
- },
-
- get connectionTypes() {
- this.checkPrivileges();
- return ObjectWrapper.wrap(cpmm.sendSyncMessage("NetworkStats:Types")[0], this._window);
- },
-
- get sampleRate() {
- this.checkPrivileges();
- return cpmm.sendSyncMessage("NetworkStats:SampleRate")[0] / 1000;
- },
-
- get maxStorageSamples() {
- this.checkPrivileges();
- return cpmm.sendSyncMessage("NetworkStats:MaxStorageSamples")[0];
- },
-
- receiveMessage: function(aMessage) {
- if (DEBUG) {
- debug("NetworkStatsmanager::receiveMessage: " + aMessage.name);
- }
- let msg = aMessage.json;
-
- let req = this.takeRequest(msg.id);
- if (!req) {
- if (DEBUG) {
- debug("No request stored with id " + msg.id);
- }
- return;
- }
-
- switch (aMessage.name) {
- case "NetworkStats:Get:Return":
- if (msg.error) {
- Services.DOMRequest.fireError(req, msg.error);
- return;
- }
-
- let result = new NetworkStats(this._window, msg.result);
- if (DEBUG) {
- debug("result: " + JSON.stringify(result));
- }
- Services.DOMRequest.fireSuccess(req, result);
- break;
-
- case "NetworkStats:Clear:Return":
- if (msg.error) {
- Services.DOMRequest.fireError(req, msg.error);
- return;
- }
-
- Services.DOMRequest.fireSuccess(req, true);
- break;
-
- default:
- if (DEBUG) {
- debug("Wrong message: " + aMessage.name);
- }
- }
- },
-
- init: function(aWindow) {
- // Set navigator.mozNetworkStats to null.
- if (!Services.prefs.getBoolPref("dom.mozNetworkStats.enabled")) {
- return null;
- }
-
- let principal = aWindow.document.nodePrincipal;
- let secMan = Services.scriptSecurityManager;
- let perm = principal == secMan.getSystemPrincipal() ?
- Ci.nsIPermissionManager.ALLOW_ACTION :
- Services.perms.testExactPermissionFromPrincipal(principal,
- "networkstats-manage");
-
- // Only pages with perm set can use the netstats.
- this.hasPrivileges = perm == Ci.nsIPermissionManager.ALLOW_ACTION;
- if (DEBUG) {
- debug("has privileges: " + this.hasPrivileges);
- }
-
- if (!this.hasPrivileges) {
- return null;
- }
-
- this.initHelper(aWindow, ["NetworkStats:Get:Return",
- "NetworkStats:Clear:Return"]);
- },
-
- // Called from DOMRequestIpcHelper
- uninit: function uninit() {
- if (DEBUG) {
- debug("uninit call");
- }
- },
-
- classID : NETWORKSTATSMANAGER_CID,
- QueryInterface : XPCOMUtils.generateQI([nsIDOMMozNetworkStatsManager,
- Ci.nsIDOMGlobalPropertyInitializer]),
-
- classInfo : XPCOMUtils.generateCI({classID: NETWORKSTATSMANAGER_CID,
- contractID: NETWORKSTATSMANAGER_CONTRACTID,
- classDescription: "NetworkStatsManager",
- interfaces: [nsIDOMMozNetworkStatsManager],
- flags: nsIClassInfo.DOM_OBJECT})
-}
-
-this.NSGetFactory = XPCOMUtils.generateNSGetFactory([NetworkStatsData,
- NetworkStats,
- NetworkStatsManager]);
diff --git a/dom/network/src/NetworkStatsManager.manifest b/dom/network/src/NetworkStatsManager.manifest
deleted file mode 100644
index 58d1c2363..000000000
--- a/dom/network/src/NetworkStatsManager.manifest
+++ /dev/null
@@ -1,9 +0,0 @@
-component {3b16fe17-5583-483a-b486-b64a3243221c} NetworkStatsManager.js
-contract @mozilla.org/networkStatsdata;1 {3b16fe17-5583-483a-b486-b64a3243221c}
-
-component {037435a6-f563-48f3-99b3-a0106d8ba5bd} NetworkStatsManager.js
-contract @mozilla.org/networkStats;1 {037435a6-f563-48f3-99b3-a0106d8ba5bd}
-
-component {87529a6c-aef6-11e1-a595-4f034275cfa6} NetworkStatsManager.js
-contract @mozilla.org/networkStatsManager;1 {87529a6c-aef6-11e1-a595-4f034275cfa6}
-category JavaScript-navigator-property mozNetworkStats @mozilla.org/networkStatsManager;1
diff --git a/dom/network/src/NetworkStatsService.jsm b/dom/network/src/NetworkStatsService.jsm
deleted file mode 100644
index da6458091..000000000
--- a/dom/network/src/NetworkStatsService.jsm
+++ /dev/null
@@ -1,394 +0,0 @@
-/* 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/. */
-
-"use strict";
-
-const DEBUG = false;
-function debug(s) { dump("-*- NetworkStatsService: " + s + "\n"); }
-
-const {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components;
-
-this.EXPORTED_SYMBOLS = ["NetworkStatsService"];
-
-Cu.import("resource://gre/modules/XPCOMUtils.jsm");
-Cu.import("resource://gre/modules/Services.jsm");
-Cu.import("resource://gre/modules/NetworkStatsDB.jsm");
-
-const NET_NETWORKSTATSSERVICE_CONTRACTID = "@mozilla.org/network/netstatsservice;1";
-const NET_NETWORKSTATSSERVICE_CID = Components.ID("{18725604-e9ac-488a-8aa0-2471e7f6c0a4}");
-
-const TOPIC_INTERFACE_REGISTERED = "network-interface-registered";
-const TOPIC_INTERFACE_UNREGISTERED = "network-interface-unregistered";
-const NET_TYPE_WIFI = Ci.nsINetworkInterface.NETWORK_TYPE_WIFI;
-const NET_TYPE_MOBILE = Ci.nsINetworkInterface.NETWORK_TYPE_MOBILE;
-
-XPCOMUtils.defineLazyServiceGetter(this, "gIDBManager",
- "@mozilla.org/dom/indexeddb/manager;1",
- "nsIIndexedDatabaseManager");
-
-XPCOMUtils.defineLazyServiceGetter(this, "ppmm",
- "@mozilla.org/parentprocessmessagemanager;1",
- "nsIMessageListenerManager");
-
-XPCOMUtils.defineLazyServiceGetter(this, "networkManager",
- "@mozilla.org/network/manager;1",
- "nsINetworkManager");
-
-let myGlobal = this;
-
-this.NetworkStatsService = {
- init: function() {
- if (DEBUG) {
- debug("Service started");
- }
-
- Services.obs.addObserver(this, "xpcom-shutdown", false);
- Services.obs.addObserver(this, TOPIC_INTERFACE_REGISTERED, false);
- Services.obs.addObserver(this, TOPIC_INTERFACE_UNREGISTERED, false);
- Services.obs.addObserver(this, "profile-after-change", false);
-
- this.timer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
-
- this._connectionTypes = Object.create(null);
- this._connectionTypes[NET_TYPE_WIFI] = { name: "wifi",
- network: Object.create(null) };
- this._connectionTypes[NET_TYPE_MOBILE] = { name: "mobile",
- network: Object.create(null) };
-
-
- this.messages = ["NetworkStats:Get",
- "NetworkStats:Clear",
- "NetworkStats:Types",
- "NetworkStats:SampleRate",
- "NetworkStats:MaxStorageSamples"];
-
- this.messages.forEach(function(msgName) {
- ppmm.addMessageListener(msgName, this);
- }, this);
-
- gIDBManager.initWindowless(myGlobal);
- this._db = new NetworkStatsDB(myGlobal, this._connectionTypes);
-
- // Stats for all interfaces are updated periodically
- this.timer.initWithCallback(this, this._db.sampleRate,
- Ci.nsITimer.TYPE_REPEATING_PRECISE_CAN_SKIP);
-
- this.updateQueue = [];
- this.isQueueRunning = false;
- },
-
- receiveMessage: function(aMessage) {
- if (!aMessage.target.assertPermission("networkstats-manage")) {
- return;
- }
-
- if (DEBUG) {
- debug("receiveMessage " + aMessage.name);
- }
- let mm = aMessage.target;
- let msg = aMessage.json;
-
- switch (aMessage.name) {
- case "NetworkStats:Get":
- this.getStats(mm, msg);
- break;
- case "NetworkStats:Clear":
- this.clearDB(mm, msg);
- break;
- case "NetworkStats:Types":
- // This message is sync.
- let types = [];
- for (let i in this._connectionTypes) {
- types.push(this._connectionTypes[i].name);
- }
- return types;
- case "NetworkStats:SampleRate":
- // This message is sync.
- return this._db.sampleRate;
- case "NetworkStats:MaxStorageSamples":
- // This message is sync.
- return this._db.maxStorageSamples;
- }
- },
-
- observe: function observe(subject, topic, data) {
- switch (topic) {
- case TOPIC_INTERFACE_REGISTERED:
- case TOPIC_INTERFACE_UNREGISTERED:
- // If new interface is registered (notified from NetworkManager),
- // the stats are updated for the new interface without waiting to
- // complete the updating period
- let network = subject.QueryInterface(Ci.nsINetworkInterface);
- if (DEBUG) {
- debug("Network " + network.name + " of type " + network.type + " status change");
- }
- if (this._connectionTypes[network.type]) {
- this._connectionTypes[network.type].network = network;
- this.updateStats(network.type);
- }
- break;
- case "xpcom-shutdown":
- if (DEBUG) {
- debug("Service shutdown");
- }
-
- this.messages.forEach(function(msgName) {
- ppmm.removeMessageListener(msgName, this);
- }, this);
-
- Services.obs.removeObserver(this, "xpcom-shutdown");
- Services.obs.removeObserver(this, "profile-after-change");
- Services.obs.removeObserver(this, TOPIC_INTERFACE_REGISTERED);
- Services.obs.removeObserver(this, TOPIC_INTERFACE_UNREGISTERED);
-
- this.timer.cancel();
- this.timer = null;
-
- // Update stats before shutdown
- this.updateAllStats();
- break;
- }
- },
-
- /*
- * nsITimerCallback
- * Timer triggers the update of all stats
- */
- notify: function(timer) {
- this.updateAllStats();
- },
-
- /*
- * Function called from manager to get stats from database.
- * In order to return updated stats, first is performed a call to
- * updateAllStats function, which will get last stats from netd
- * and update the database.
- * Then, depending on the request (stats per interface or total stats)
- * it retrieve them from database and return to the manager.
- */
- getStats: function getStats(mm, msg) {
- this.updateAllStats(function onStatsUpdated(aResult, aMessage) {
-
- let options = msg.data;
- if (DEBUG) {
- debug("getstats for: - " + options.connectionType + " -");
- }
-
- if (!options.connectionType || options.connectionType.length == 0) {
- this._db.findAll(function onStatsFound(error, result) {
- mm.sendAsyncMessage("NetworkStats:Get:Return",
- { id: msg.id, error: error, result: result });
- }, options);
- return;
- }
-
- for (let i in this._connectionTypes) {
- if (this._connectionTypes[i].name == options.connectionType) {
- this._db.find(function onStatsFound(error, result) {
- mm.sendAsyncMessage("NetworkStats:Get:Return",
- { id: msg.id, error: error, result: result });
- }, options);
- return;
- }
- }
-
- mm.sendAsyncMessage("NetworkStats:Get:Return",
- { id: msg.id, error: "Invalid connectionType", result: null });
-
- }.bind(this));
- },
-
- clearDB: function clearDB(mm, msg) {
- this._db.clear(function onDBCleared(error, result) {
- mm.sendAsyncMessage("NetworkStats:Clear:Return",
- { id: msg.id, error: error, result: result });
- });
- },
-
- updateAllStats: function updateAllStats(callback) {
- let elements = [];
- let lastElement;
-
- // For each connectionType create an object containning the type
- // and the 'queueIndex', the 'queueIndex' is an integer representing
- // the index of a connection type in the global queue array. So, if
- // the connection type is already in the queue it is not appended again,
- // else it is pushed in 'elements' array, which later will be pushed to
- // the queue array.
- for (let i in this._connectionTypes) {
- lastElement = { type: i,
- queueIndex: this.updateQueueIndex(i)};
- if (lastElement.queueIndex == -1) {
- elements.push({type: lastElement.type, callbacks: []});
- }
- }
-
- if (elements.length > 0) {
- // If length of elements is greater than 0, callback is set to
- // the last element.
- elements[elements.length - 1].callbacks.push(callback);
- this.updateQueue = this.updateQueue.concat(elements);
- } else {
- // Else, it means that all connection types are already in the queue to
- // be updated, so callback for this request is added to
- // the element in the main queue with the index of the last 'lastElement'.
- // But before is checked that element is still in the queue because it can
- // be processed while generating 'elements' array.
-
- if (!this.updateQueue[lastElement.queueIndex] ||
- this.updateQueue[lastElement.queueIndex].type != lastElement.queueIndex) {
- if (callback) {
- callback();
- }
- return;
- }
-
- this.updateQueue[lastElement.queueIndex].callbacks.push(callback);
- }
-
- // Call the function that process the elements of the queue.
- this.processQueue();
-
- if (DEBUG) {
- this.logAllRecords();
- }
- },
-
- updateStats: function updateStats(connectionType, callback) {
- // Check if the connection type is in the main queue, push a new element
- // if it is not being processed or add a callback if it is.
- let index = this.updateQueueIndex(connectionType);
- if (index == -1) {
- this.updateQueue.push({type: connectionType, callbacks: [callback]});
- } else {
- this.updateQueue[index].callbacks.push(callback);
- }
-
- // Call the function that process the elements of the queue.
- this.processQueue();
- },
-
- /*
- * Find if a connection type is in the main queue array and return its
- * index, if it is not in the array return -1.
- */
- updateQueueIndex: function updateQueueIndex(type) {
- for (let i in this.updateQueue) {
- if (this.updateQueue[i].type == type) {
- return i;
- }
- }
- return -1;
- },
-
- /*
- * Function responsible of process all requests in the queue.
- */
- processQueue: function processQueue(aResult, aMessage) {
- // If aResult is not undefined, the caller of the function is the result
- // of processing an element, so remove that element and call the callbacks
- // it has.
- if (aResult != undefined) {
- let item = this.updateQueue.shift();
- for (let callback of item.callbacks) {
- if(callback) {
- callback(aResult, aMessage);
- }
- }
- } else {
- // The caller is a function that has pushed new elements to the queue,
- // if isQueueRunning is false it means there is no processing currently being
- // done, so start.
- if (this.isQueueRunning) {
- if(this.updateQueue.length > 1) {
- return;
- }
- } else {
- this.isQueueRunning = true;
- }
- }
-
- // Check length to determine if queue is empty and stop processing.
- if (this.updateQueue.length < 1) {
- this.isQueueRunning = false;
- return;
- }
-
- // Call the update function for the next element.
- this.update(this.updateQueue[0].type, this.processQueue.bind(this));
- },
-
- update: function update(connectionType, callback) {
- // Check if connection type is valid.
- if (!this._connectionTypes[connectionType]) {
- if (callback) {
- callback(false, "Invalid network type " + connectionType);
- }
- return;
- }
-
- if (DEBUG) {
- debug("Update stats for " + this._connectionTypes[connectionType].name);
- }
-
- // Request stats to NetworkManager, which will get stats from netd, passing
- // 'networkStatsAvailable' as a callback.
- let networkName = this._connectionTypes[connectionType].network.name;
- if (networkName) {
- networkManager.getNetworkInterfaceStats(networkName,
- this.networkStatsAvailable.bind(this, callback, connectionType));
- return;
- }
- if (callback) {
- callback(true, "ok");
- }
- },
-
- /*
- * Callback of request stats. Store stats in database.
- */
- networkStatsAvailable: function networkStatsAvailable(callback, connType, result, rxBytes, txBytes, date) {
- if (!result) {
- if (callback) {
- callback(false, "Netd IPC error");
- }
- return;
- }
-
- let stats = { connectionType: this._connectionTypes[connType].name,
- date: date,
- rxBytes: txBytes,
- txBytes: rxBytes};
-
- if (DEBUG) {
- debug("Update stats for " + stats.connectionType + ": rx=" + stats.rxBytes +
- " tx=" + stats.txBytes + " timestamp=" + stats.date);
- }
- this._db.saveStats(stats, function onSavedStats(error, result) {
- if (callback) {
- if (error) {
- callback(false, error);
- return;
- }
-
- callback(true, "OK");
- }
- });
- },
-
- logAllRecords: function logAllRecords() {
- this._db.logAllRecords(function onResult(error, result) {
- if (error) {
- debug("Error: " + error);
- return;
- }
-
- debug("===== LOG =====");
- debug("There are " + result.length + " items");
- debug(JSON.stringify(result));
- });
- }
-};
-
-NetworkStatsService.init();
diff --git a/dom/network/src/TCPSocketParent.cpp b/dom/network/src/TCPSocketParent.cpp
deleted file mode 100644
index 2fd00f092..000000000
--- a/dom/network/src/TCPSocketParent.cpp
+++ /dev/null
@@ -1,217 +0,0 @@
-/* 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 "nsIDOMTCPSocket.h"
-#include "nsContentUtils.h"
-#include "nsCxPusher.h"
-#include "mozilla/unused.h"
-#include "mozilla/AppProcessChecker.h"
-
-namespace IPC {
-
-//Defined in TCPSocketChild.cpp
-extern bool
-DeserializeArrayBuffer(JS::Handle<JSObject*> aObj,
- const InfallibleTArray<uint8_t>& aBuffer,
- JS::MutableHandle<JS::Value> aVal);
-
-}
-
-namespace mozilla {
-namespace dom {
-
-static void
-FireInteralError(mozilla::net::PTCPSocketParent* aActor, uint32_t aLineNo)
-{
- mozilla::unused <<
- aActor->SendCallback(NS_LITERAL_STRING("onerror"),
- TCPError(NS_LITERAL_STRING("InvalidStateError")),
- NS_LITERAL_STRING("connecting"), 0);
-}
-
-NS_IMPL_CYCLE_COLLECTION_2(TCPSocketParent, mSocket, mIntermediary)
-NS_IMPL_CYCLE_COLLECTING_ADDREF(TCPSocketParent)
-NS_IMPL_CYCLE_COLLECTING_RELEASE(TCPSocketParent)
-
-NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(TCPSocketParent)
- NS_INTERFACE_MAP_ENTRY(nsITCPSocketParent)
- NS_INTERFACE_MAP_ENTRY(nsISupports)
-NS_INTERFACE_MAP_END
-
-bool
-TCPSocketParent::Init(const nsString& aHost, const uint16_t& aPort, const bool& aUseSSL,
- const nsString& aBinaryType)
-{
- nsresult rv;
- mIntermediary = do_CreateInstance("@mozilla.org/tcp-socket-intermediary;1", &rv);
- if (NS_FAILED(rv)) {
- FireInteralError(this, __LINE__);
- return true;
- }
-
- rv = mIntermediary->Open(this, aHost, aPort, aUseSSL, aBinaryType, getter_AddRefs(mSocket));
- if (NS_FAILED(rv) || !mSocket) {
- FireInteralError(this, __LINE__);
- return true;
- }
-
- return true;
-}
-
-NS_IMETHODIMP
-TCPSocketParent::InitJS(const JS::Value& aIntermediary, JSContext* aCx)
-{
- MOZ_ASSERT(aIntermediary.isObject());
- mIntermediaryObj = &aIntermediary.toObject();
- return NS_OK;
-}
-
-bool
-TCPSocketParent::RecvSuspend()
-{
- NS_ENSURE_TRUE(mSocket, true);
- nsresult rv = mSocket->Suspend();
- NS_ENSURE_SUCCESS(rv, true);
- return true;
-}
-
-bool
-TCPSocketParent::RecvResume()
-{
- NS_ENSURE_TRUE(mSocket, true);
- nsresult rv = mSocket->Resume();
- NS_ENSURE_SUCCESS(rv, true);
- return true;
-}
-
-bool
-TCPSocketParent::RecvData(const SendableData& aData)
-{
- NS_ENSURE_TRUE(mIntermediary, true);
-
- nsresult rv;
- switch (aData.type()) {
- case SendableData::TArrayOfuint8_t: {
- AutoSafeJSContext cx;
- JS::Rooted<JS::Value> val(cx);
- JS::Rooted<JSObject*> obj(cx, mIntermediaryObj);
- IPC::DeserializeArrayBuffer(obj, aData.get_ArrayOfuint8_t(), &val);
- rv = mIntermediary->SendArrayBuffer(val);
- NS_ENSURE_SUCCESS(rv, true);
- break;
- }
-
- case SendableData::TnsString:
- rv = mIntermediary->SendString(aData.get_nsString());
- NS_ENSURE_SUCCESS(rv, true);
- break;
-
- default:
- MOZ_NOT_REACHED("unexpected SendableData type");
- return false;
- }
- return true;
-}
-
-bool
-TCPSocketParent::RecvClose()
-{
- NS_ENSURE_TRUE(mSocket, true);
- nsresult rv = mSocket->Close();
- NS_ENSURE_SUCCESS(rv, true);
- return true;
-}
-
-NS_IMETHODIMP
-TCPSocketParent::SendCallback(const nsAString& aType, const JS::Value& aDataVal,
- const nsAString& aReadyState, uint32_t aBuffered,
- JSContext* aCx)
-{
- if (!mIPCOpen) {
- NS_WARNING("Dropping callback due to no IPC connection");
- return NS_OK;
- }
-
- CallbackData data;
- if (aDataVal.isString()) {
- JSString* jsstr = aDataVal.toString();
- nsDependentJSString str;
- if (!str.init(aCx, jsstr)) {
- FireInteralError(this, __LINE__);
- return NS_ERROR_OUT_OF_MEMORY;
- }
- data = SendableData(str);
-
- } else if (aDataVal.isUndefined() || aDataVal.isNull()) {
- data = mozilla::void_t();
-
- } else if (aDataVal.isObject()) {
- JSObject* obj = &aDataVal.toObject();
- if (JS_IsArrayBufferObject(obj)) {
- uint32_t nbytes = JS_GetArrayBufferByteLength(obj);
- uint8_t* buffer = JS_GetArrayBufferData(obj);
- if (!buffer) {
- FireInteralError(this, __LINE__);
- return NS_ERROR_OUT_OF_MEMORY;
- }
- FallibleTArray<uint8_t> fallibleArr;
- if (!fallibleArr.InsertElementsAt(0, buffer, nbytes)) {
- FireInteralError(this, __LINE__);
- return NS_ERROR_OUT_OF_MEMORY;
- }
- InfallibleTArray<uint8_t> arr;
- arr.SwapElements(fallibleArr);
- data = SendableData(arr);
-
- } else {
- nsDependentJSString name;
-
- JS::Rooted<JS::Value> val(aCx);
- if (!JS_GetProperty(aCx, obj, "name", val.address())) {
- NS_ERROR("No name property on supposed error object");
- } else if (JSVAL_IS_STRING(val)) {
- if (!name.init(aCx, JSVAL_TO_STRING(val))) {
- NS_WARNING("couldn't initialize string");
- }
- }
-
- data = TCPError(name);
- }
- } else {
- NS_ERROR("Unexpected JS value encountered");
- FireInteralError(this, __LINE__);
- return NS_ERROR_FAILURE;
- }
- mozilla::unused <<
- PTCPSocketParent::SendCallback(nsString(aType), data,
- nsString(aReadyState), aBuffered);
- return NS_OK;
-}
-
-void
-TCPSocketParent::ActorDestroy(ActorDestroyReason why)
-{
- MOZ_ASSERT(mIPCOpen);
- mIPCOpen = false;
- if (mSocket) {
- mSocket->Close();
- }
- mSocket = nullptr;
- mIntermediaryObj = nullptr;
- mIntermediary = nullptr;
-}
-
-bool
-TCPSocketParent::RecvRequestDelete()
-{
- mozilla::unused << Send__delete__(this);
- return true;
-}
-
-} // namespace dom
-} // namespace mozilla
diff --git a/dom/network/src/TCPSocketParent.h b/dom/network/src/TCPSocketParent.h
deleted file mode 100644
index 82c4f80a2..000000000
--- a/dom/network/src/TCPSocketParent.h
+++ /dev/null
@@ -1,48 +0,0 @@
-/* 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/net/PTCPSocketParent.h"
-#include "nsITCPSocketParent.h"
-#include "nsCycleCollectionParticipant.h"
-#include "nsCOMPtr.h"
-#include "nsIDOMTCPSocket.h"
-
-struct JSContext;
-class JSObject;
-
-namespace mozilla {
-namespace dom {
-
-class PBrowserParent;
-
-class TCPSocketParent : public mozilla::net::PTCPSocketParent
- , public nsITCPSocketParent
-{
-public:
- NS_DECL_CYCLE_COLLECTION_CLASS(TCPSocketParent)
- NS_DECL_CYCLE_COLLECTING_ISUPPORTS
- NS_DECL_NSITCPSOCKETPARENT
-
- TCPSocketParent() : mIntermediaryObj(nullptr), mIPCOpen(true) {}
-
- bool Init(const nsString& aHost, const uint16_t& aPort,
- const bool& useSSL, const nsString& aBinaryType);
-
- virtual bool RecvSuspend() MOZ_OVERRIDE;
- virtual bool RecvResume() MOZ_OVERRIDE;
- virtual bool RecvClose() MOZ_OVERRIDE;
- virtual bool RecvData(const SendableData& aData) MOZ_OVERRIDE;
- virtual bool RecvRequestDelete() MOZ_OVERRIDE;
-
-private:
- virtual void ActorDestroy(ActorDestroyReason why) MOZ_OVERRIDE;
-
- nsCOMPtr<nsITCPSocketIntermediary> mIntermediary;
- nsCOMPtr<nsIDOMTCPSocket> mSocket;
- JSObject* mIntermediaryObj;
- bool mIPCOpen;
-};
-
-} // namespace dom
-} // namespace mozilla
diff --git a/dom/network/src/TCPSocketParentIntermediary.js b/dom/network/src/TCPSocketParentIntermediary.js
deleted file mode 100644
index 0ab1e0cf5..000000000
--- a/dom/network/src/TCPSocketParentIntermediary.js
+++ /dev/null
@@ -1,55 +0,0 @@
-/* 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/. */
-
-"use strict";
-
-const Cc = Components.classes;
-const Ci = Components.interfaces;
-const Cu = Components.utils;
-
-Cu.import("resource://gre/modules/XPCOMUtils.jsm");
-
-function TCPSocketParentIntermediary() {
-}
-
-TCPSocketParentIntermediary.prototype = {
- open: function(aParentSide, aHost, aPort, aUseSSL, aBinaryType) {
- aParentSide.initJS(this);
-
- let baseSocket = Cc["@mozilla.org/tcp-socket;1"].createInstance(Ci.nsIDOMTCPSocket);
- let socket = this._socket = baseSocket.open(aHost, aPort,
- {useSSL: aUseSSL,
- binaryType: aBinaryType});
- if (!socket)
- return null;
-
- // Create handlers for every possible callback that attempt to trigger
- // corresponding callbacks on the child object.
- ["open", "drain", "data", "error", "close"].forEach(
- function(p) {
- socket["on" + p] = function(data) {
- aParentSide.sendCallback(p, data.data, socket.readyState,
- socket.bufferedAmount);
- };
- }
- );
-
- return socket;
- },
-
- sendString: function(aData) {
- return this._socket.send(aData);
- },
-
- sendArrayBuffer: function(aData) {
- return this._socket.send(aData, 0, aData.byteLength);
- },
-
- classID: Components.ID("{afa42841-a6cb-4a91-912f-93099f6a3d18}"),
- QueryInterface: XPCOMUtils.generateQI([
- Ci.nsITCPSocketIntermediary
- ])
-};
-
-this.NSGetFactory = XPCOMUtils.generateNSGetFactory([TCPSocketParentIntermediary]);
diff --git a/dom/network/src/Utils.cpp b/dom/network/src/Utils.cpp
deleted file mode 100644
index 0de2fc361..000000000
--- a/dom/network/src/Utils.cpp
+++ /dev/null
@@ -1,21 +0,0 @@
-/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
-/* 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 "Utils.h"
-#include "mozilla/Preferences.h"
-
-namespace mozilla {
-namespace dom {
-namespace network {
-
-/* extern */ bool
-IsAPIEnabled()
-{
- return Preferences::GetBool("dom.network.enabled", true);
-}
-
-} // namespace network
-} // namespace dom
-} // namespace mozilla
diff --git a/dom/network/src/Utils.h b/dom/network/src/Utils.h
deleted file mode 100644
index 697516ff0..000000000
--- a/dom/network/src/Utils.h
+++ /dev/null
@@ -1,24 +0,0 @@
-/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
-/* 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_Utils_h
-#define mozilla_dom_network_Utils_h
-
-namespace mozilla {
-namespace dom {
-namespace network {
-
-/**
- * Returns whether the Network API is enabled.
- * @return whether the Network API is enabled.
- */
-extern bool IsAPIEnabled();
-
-} // namespace network
-} // namespace dom
-} // namespace mozilla
-
-#endif // mozilla_dom_network_Utils_h
-
diff --git a/dom/network/src/ipdl.mk b/dom/network/src/ipdl.mk
deleted file mode 100644
index 6efad3144..000000000
--- a/dom/network/src/ipdl.mk
+++ /dev/null
@@ -1,7 +0,0 @@
-# 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/.
-
-IPDLSRCS = \
- PTCPSocket.ipdl \
- $(NULL)
diff --git a/dom/network/src/moz.build b/dom/network/src/moz.build
deleted file mode 100644
index bf6554006..000000000
--- a/dom/network/src/moz.build
+++ /dev/null
@@ -1,41 +0,0 @@
-# -*- Mode: python; c-basic-offset: 4; 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/.
-
-EXPORTS.mozilla.dom.network += [
- 'Constants.h',
- 'TCPSocketChild.h',
- 'TCPSocketParent.h',
- 'Types.h',
- 'Utils.h',
-]
-
-CPP_SOURCES += [
- 'Connection.cpp',
- 'TCPSocketChild.cpp',
- 'TCPSocketParent.cpp',
- 'Utils.cpp',
-]
-
-if CONFIG['MOZ_B2G_RIL']:
- CPP_SOURCES += [
- 'MobileConnection.cpp',
- ]
- EXTRA_JS_MODULES = [
- 'NetworkStatsDB.jsm',
- 'NetworkStatsService.jsm',
- ]
-
-EXTRA_COMPONENTS += [
- 'TCPSocket.js',
- 'TCPSocket.manifest',
- 'TCPSocketParentIntermediary.js',
-]
-
-if CONFIG['MOZ_B2G_RIL']:
- EXTRA_COMPONENTS += [
- 'NetworkStatsManager.js',
- 'NetworkStatsManager.manifest',
- ]
diff --git a/dom/network/tests/Makefile.in b/dom/network/tests/Makefile.in
deleted file mode 100644
index cc7c0341b..000000000
--- a/dom/network/tests/Makefile.in
+++ /dev/null
@@ -1,30 +0,0 @@
-# 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/.
-
-DEPTH = @DEPTH@
-topsrcdir = @top_srcdir@
-srcdir = @srcdir@
-VPATH = @srcdir@
-
-relativesrcdir = @relativesrcdir@
-
-include $(DEPTH)/config/autoconf.mk
-
-MOCHITEST_FILES = \
- test_network_basics.html \
- test_tcpsocket_default_permissions.html \
- test_tcpsocket_enabled_no_perm.html \
- test_tcpsocket_enabled_with_perm.html \
- $(NULL)
-
-ifdef MOZ_B2G_RIL
-MOCHITEST_FILES = \
- test_networkstats_basics.html \
- test_networkstats_disabled.html \
- test_networkstats_enabled_no_perm.html \
- test_networkstats_enabled_perm.html \
- $(NULL)
-endif
-
-include $(topsrcdir)/config/rules.mk
diff --git a/dom/network/tests/add_task.js b/dom/network/tests/add_task.js
new file mode 100644
index 000000000..3028afdb7
--- /dev/null
+++ b/dom/network/tests/add_task.js
@@ -0,0 +1,83 @@
+// Temporary implementation of add_task for mochitest-plain until bug 1078657 is
+// implemented.
+SimpleTest.waitForExplicitFinish();
+(function(scope) {
+ var pendingTasks = [];
+ var pendingPromise = null;
+
+ // Strict spawn function that takes a known generatorFunc and assumes that
+ // every yielded value will be a Promise. If nesting is desired, then yield*
+ // should be used!
+ function spawn(generatorFunc) {
+ return new Promise(function(resolve, reject) {
+ try {
+ var iterator = generatorFunc();
+ }
+ catch (ex) {
+ ok(false, 'Problem invoking generator func: ' + ex + ': ' + ex.stack);
+ return;
+ }
+ var stepResolved = function(result) {
+ try {
+ var iterStep = iterator.next(result);
+ }
+ catch (ex) {
+ ok(false, 'Problem invoking iterator step: ' + ex + ': ' + ex.stack);
+ return;
+ }
+ if (iterStep.done) {
+ resolve(iterStep.value);
+ return;
+ }
+ if (!iterStep.value || !iterStep.value.then) {
+ ok(false, 'Iterator step returned non-Promise: ' + iterStep.value);
+ }
+ iterStep.value.then(stepResolved, generalErrback);
+ };
+ stepResolved();
+ });
+ }
+
+ function maybeSpawn(promiseOrGenerator) {
+ if (promiseOrGenerator.then) {
+ return promiseOrGenerator;
+ }
+ return spawn(promiseOrGenerator);
+ }
+
+ scope.add_task = function(thing) {
+ pendingTasks.push(thing);
+ };
+
+ function generalErrback(ex) {
+ ok(false,
+ 'A rejection happened: ' +
+ (ex ? (ex + ': ' + ex.stack) : ''));
+ }
+
+ function runNextTask() {
+ if (pendingTasks.length) {
+ pendingPromise = maybeSpawn(pendingTasks.shift());
+ pendingPromise.then(runNextTask, generalErrback);
+ } else {
+ SimpleTest.finish();
+ }
+ }
+
+ // Trigger runNextTask after we think all JS files have been loaded.
+ // The primary goal is that we can call SimpleTest.finish() after all test
+ // code has been loaded and run. We gate this based on the document's
+ // readyState.
+ var running = false;
+ function maybeStartRunning() {
+ if (!running && document.readyState === 'complete') {
+ running = true;
+ document.removeEventListener('readystateChange', maybeStartRunning);
+ // Defer to a subsequent turn of the event loop to let micro-tasks and any
+ // other clever setTimeout(0) instances run first.
+ window.setTimeout(runNextTask, 0);
+ }
+ }
+ document.addEventListener('readystatechange', maybeStartRunning);
+ maybeStartRunning();
+})(this);
diff --git a/dom/network/tests/file_udpsocket_iframe.html b/dom/network/tests/file_udpsocket_iframe.html
new file mode 100644
index 000000000..1e124552d
--- /dev/null
+++ b/dom/network/tests/file_udpsocket_iframe.html
@@ -0,0 +1,23 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test UDPSocket BFCache</title>
+</head>
+<body>
+<script type="application/javascript;version=1.8">
+'use strict';
+window.addEventListener('load', function onload() {
+ window.removeEventListener('load', onload);
+ 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);
+ });
+});
+</script>
+</body>
+</html>
diff --git a/dom/network/tests/marionette/manifest.ini b/dom/network/tests/marionette/manifest.ini
deleted file mode 100644
index d61be3dc8..000000000
--- a/dom/network/tests/marionette/manifest.ini
+++ /dev/null
@@ -1,17 +0,0 @@
-[DEFAULT]
-b2g = true
-browser = false
-qemu = true
-
-[test_mobile_networks.js]
-disabled = Bug 808783
-[test_mobile_voice_state.js]
-[test_mobile_iccinfo.js]
-[test_mobile_operator_names.js]
-[test_mobile_preferred_network_type.js]
-disabled = Bug 808783
-[test_mobile_data_location.js]
-[test_mobile_data_state.js]
-[test_mobile_mmi.js]
-[test_call_barring_get_option.js]
-[test_call_barring_set_error.js]
diff --git a/dom/network/tests/marionette/test_call_barring_get_option.js b/dom/network/tests/marionette/test_call_barring_get_option.js
deleted file mode 100644
index 2259da352..000000000
--- a/dom/network/tests/marionette/test_call_barring_get_option.js
+++ /dev/null
@@ -1,31 +0,0 @@
-/* Any copyright is dedicated to the Public Domain.
- http://creativecommons.org/publicdomain/zero/1.0/ */
-
-MARIONETTE_TIMEOUT = 60000;
-
-SpecialPowers.addPermission("mobileconnection", true, document);
-
-let connection = navigator.mozMobileConnection;
-ok(connection instanceof MozMobileConnection,
- "connection is instanceof " + connection.constructor);
-
-function testGetCallBarringOption() {
- let option = {'program': 0, 'password': '', 'serviceClass': 0};
- let request = connection.getCallBarringOption(option);
- request.onsuccess = function() {
- ok(request.result);
- ok('enabled' in request.result, 'should have "enabled" field');
- cleanUp();
- };
- request.onerror = function() {
- // Call barring is not supported by current emulator.
- cleanUp();
- };
-}
-
-function cleanUp() {
- SpecialPowers.removePermission("mobileconnection", document);
- finish();
-}
-
-testGetCallBarringOption();
diff --git a/dom/network/tests/marionette/test_call_barring_set_error.js b/dom/network/tests/marionette/test_call_barring_set_error.js
deleted file mode 100644
index 2afcf297c..000000000
--- a/dom/network/tests/marionette/test_call_barring_set_error.js
+++ /dev/null
@@ -1,65 +0,0 @@
-/* Any copyright is dedicated to the Public Domain.
- http://creativecommons.org/publicdomain/zero/1.0/ */
-
-MARIONETTE_TIMEOUT = 60000;
-
-SpecialPowers.addPermission("mobileconnection", true, document);
-
-let connection = navigator.mozMobileConnection;
-ok(connection instanceof MozMobileConnection,
- "connection is instanceof " + connection.constructor);
-
-let caseId = 0;
-let options = [
- buildOption(5, true, '0000', 0), // invalid program.
-
- // test null.
- buildOption(null, true, '0000', 0),
- buildOption(0, null, '0000', 0),
- buildOption(0, true, null, 0),
- buildOption(0, true, '0000', null),
-
- // test undefined.
- {'enabled': true, 'password': '0000', 'serviceClass': 0},
- {'program': 0, 'password': '0000', 'serviceClass': 0},
- {'program': 0, 'enabled': true, 'serviceClass': 0},
- {'program': 0, 'enabled': true, 'password': '0000'},
-];
-
-function buildOption(program, enabled, password, serviceClass) {
- return {
- 'program': program,
- 'enabled': enabled,
- 'password': password,
- 'serviceClass': serviceClass
- };
-}
-
-function testSetCallBarringOptionError(option) {
- let request = connection.setCallBarringOption(option);
- request.onsuccess = function() {
- ok(false,
- 'should not fire onsuccess for invaild call barring option: '
- + JSON.stringify(option));
- };
- request.onerror = function() {
- nextTest();
- };
-}
-
-function nextTest() {
- if (caseId >= options.length) {
- cleanUp();
- } else {
- let option = options[caseId++];
- log('test for ' + JSON.stringify(option));
- testSetCallBarringOptionError(option);
- }
-}
-
-function cleanUp() {
- SpecialPowers.removePermission("mobileconnection", document);
- finish();
-}
-
-nextTest();
diff --git a/dom/network/tests/marionette/test_mobile_data_location.js b/dom/network/tests/marionette/test_mobile_data_location.js
deleted file mode 100644
index e39d87f05..000000000
--- a/dom/network/tests/marionette/test_mobile_data_location.js
+++ /dev/null
@@ -1,119 +0,0 @@
-/* Any copyright is dedicated to the Public Domain.
- * http://creativecommons.org/publicdomain/zero/1.0/ */
-
-MARIONETTE_TIMEOUT = 20000;
-
-SpecialPowers.addPermission("mobileconnection", true, document);
-
-let mobileConnection = navigator.mozMobileConnection;
-let emulatorStartLac = 0;
-let emulatorStartCid = 0;
-
-function verifyInitialState() {
- log("Verifying initial state.");
- ok(mobileConnection instanceof MozMobileConnection,
- "mobileConnection is instanceof " + mobileConnection.constructor);
- testStartingCellLocation();
-}
-
-function testStartingCellLocation() {
- // Get the current emulator data cell location
- log("Getting the starting GSM location from the emulator.");
-
- runEmulatorCmd("gsm location", function(result) {
- log("Emulator callback.");
- is(result[0].substring(0,3), "lac", "lac output");
- is(result[1].substring(0,2), "ci", "ci output");
- is(result[2], "OK", "emulator ok");
-
- emulatorStartLac = result[0].substring(5);
- log("Emulator GSM location LAC is '" + emulatorStartLac + "'.");
- emulatorStartCid = result[1].substring(4);
- log("Emulator GSM location CID is '" + emulatorStartCid + "'.");
-
- log("mobileConnection.data.cell.gsmLocationAreaCode is '"
- + mobileConnection.data.cell.gsmLocationAreaCode + "'.");
- log("mobileConnection.data.cell.gsmCellId is '"
- + mobileConnection.data.cell.gsmCellId + "'.");
-
- // Verify the mobileConnection.data.cell location matches emulator values
- if (emulatorStartLac == -1) {
- // Emulator initializes LAC to -1, corresponds to these values
- is(mobileConnection.data.cell.gsmLocationAreaCode,
- 65535, "starting LAC");
- } else {
- // A previous test changed the LAC, so verify API matches emulator
- is(mobileConnection.data.cell.gsmLocationAreaCode,
- emulatorStartLac, "starting LAC");
- }
- if (emulatorStartCid == -1) {
- // Emulator initializes CID to -1, corresponds to these values
- is(mobileConnection.data.cell.gsmCellId, 268435455, "starting CID");
- } else {
- // A previous test changed the CID, so verify API matches emulator
- is(mobileConnection.data.cell.gsmCellId,
- emulatorStartCid, "starting CID");
- }
-
- // Now test changing the GSM location
- testChangeCellLocation(emulatorStartLac, emulatorStartCid);
- });
-}
-
-function testChangeCellLocation() {
- // Change emulator GSM location and verify mobileConnection.data.cell values
- let newLac = 1000;
- let newCid = 2000;
- let gotCallback = false;
-
- // Ensure values will actually be changed
- if (newLac == emulatorStartLac) { newLac++; };
- if (newCid == emulatorStartCid) { newCid++; };
-
- // Setup 'ondatachange' event listener
- mobileConnection.addEventListener("datachange", function ondatachange() {
- mobileConnection.removeEventListener("datachange", ondatachange);
- log("Received 'ondatachange' event.");
- log("mobileConnection.data.cell.gsmLocationAreaCode is now '"
- + mobileConnection.data.cell.gsmLocationAreaCode + "'.");
- log("mobileConnection.data.cell.gsmCellId is now '"
- + mobileConnection.data.cell.gsmCellId + "'.");
- is(mobileConnection.data.cell.gsmLocationAreaCode, newLac,
- "data.cell.gsmLocationAreaCode");
- is(mobileConnection.data.cell.gsmCellId, newCid, "data.cell.gsmCellId");
- waitFor(restoreLocation, function() {
- return(gotCallback);
- });
- });
-
- // Use emulator command to change GSM location
- log("Changing emulator GSM location to '" + newLac + ", " + newCid
- + "' and waiting for 'ondatachange' event.");
- gotCallback = false;
- runEmulatorCmd("gsm location " + newLac + " " + newCid, function(result) {
- is(result[0], "OK");
- log("Emulator callback on location change.");
- gotCallback = true;
- });
-}
-
-function restoreLocation() {
- // Restore the emulator GSM location back to what it was originally
- log("Restoring emulator GSM location back to '" + emulatorStartLac + ", "
- + emulatorStartCid + "'.");
- runEmulatorCmd("gsm location " + emulatorStartLac + " " + emulatorStartCid,
- function(result) {
- log("Emulator callback on restore.");
- is(result[0], "OK");
- cleanUp();
- });
-}
-
-function cleanUp() {
- mobileConnection.ondatachange = null;
- SpecialPowers.removePermission("mobileconnection", document);
- finish();
-}
-
-// Start the test
-verifyInitialState();
diff --git a/dom/network/tests/marionette/test_mobile_data_state.js b/dom/network/tests/marionette/test_mobile_data_state.js
deleted file mode 100644
index ecae8de48..000000000
--- a/dom/network/tests/marionette/test_mobile_data_state.js
+++ /dev/null
@@ -1,121 +0,0 @@
-/* Any copyright is dedicated to the Public Domain.
- * http://creativecommons.org/publicdomain/zero/1.0/ */
-
-MARIONETTE_TIMEOUT = 30000;
-
-SpecialPowers.addPermission("mobileconnection", true, document);
-
-let mobileConnection = navigator.mozMobileConnection;
-
-function verifyInitialState() {
- log("Verifying initial state.");
- ok(mobileConnection instanceof MozMobileConnection,
- "mobileConnection is instanceof " + mobileConnection.constructor);
- // Want to start test with mobileConnection.data.state 'registered'
- // This is the default state; if it is not currently this value then set it
- log("Starting mobileConnection.data.state is: '"
- + mobileConnection.data.state + "'.");
- if (mobileConnection.data.state != "registered") {
- changeDataStateAndVerify("home", "registered", testUnregistered);
- } else {
- testUnregistered();
- }
-}
-
-function changeDataStateAndVerify(dataState, expected, nextFunction) {
- let gotCallback = false;
-
- // Change the mobileConnection.data.state via 'gsm data' command
- log("Changing emulator data state to '" + dataState
- + "' and waiting for 'ondatachange' event.");
-
- // Setup 'ondatachange' event handler
- mobileConnection.addEventListener("datachange", function ondatachange() {
- mobileConnection.removeEventListener("datachange", ondatachange);
- log("Received 'ondatachange' event.");
- log("mobileConnection.data.state is now '"
- + mobileConnection.data.state + "'.");
- is(mobileConnection.data.state, expected, "data.state");
- waitFor(nextFunction, function() {
- return(gotCallback);
- });
- });
-
- // Change the emulator data state
- gotCallback = false;
- runEmulatorCmd("gsm data " + dataState, function(result) {
- is(result[0], "OK");
- log("Emulator callback complete.");
- gotCallback = true;
- });
-}
-
-function testUnregistered() {
- log("Test 1: Unregistered.");
- // Set emulator data state to 'unregistered' and verify
- // Expect mobileConnection.data.state to be 'notsearching'
- changeDataStateAndVerify("unregistered", "notSearching", testRoaming);
-}
-
-function testRoaming() {
- log("Test 2: Roaming.");
- // Set emulator data state to 'roaming' and verify
- // Expect mobileConnection.data.state to be 'registered'
- changeDataStateAndVerify("roaming", "registered", testOff);
-}
-
-function testOff() {
- log("Test 3: Off.");
- // Set emulator data state to 'off' and verify
- // Expect mobileConnection.data.state to be 'notsearching'
- changeDataStateAndVerify("off", "notSearching", testSearching);
-}
-
-function testSearching() {
- log("Test 4: Searching.");
- // Set emulator data state to 'searching' and verify
-
- // Bug 819533: WebMobileConnection data/voice state incorrect when emulator
- // data state is 'searching'. So until fixed, expect 'registered'.
-
- // changeDataStateAndVerify("searching", "searching", testDenied);
- log("* When Bug 819533 is fixed, change this test to expect 'searching' *");
- changeDataStateAndVerify("searching", "registered", testDenied);
-}
-
-function testDenied() {
- log("Test 5: Denied.");
- // Set emulator data state to 'denied' and verify
- // Expect mobileConnection.data.state to be 'denied'
- changeDataStateAndVerify("denied", "denied", testOn);
-}
-
-function testOn() {
- log("Test 6: On.");
- // Set emulator data state to 'on' and verify
- // Expect mobileConnection.data.state to be 'registered'
- changeDataStateAndVerify("on", "registered", testOffAgain);
-}
-
-function testOffAgain() {
- log("Test 7: Off again.");
- // Set emulator data state to 'off' and verify
- // Expect mobileConnection.data.state to be 'notsearching'
- changeDataStateAndVerify("off", "notSearching", testHome);
-}
-
-function testHome() {
- log("Test 8: Home.");
- // Set emulator data state to 'home' and verify
- // Expect mobileConnection.data.state to be 'registered'
- changeDataStateAndVerify("home", "registered", cleanUp);
-}
-
-function cleanUp() {
- mobileConnection.ondatachange = null;
- SpecialPowers.removePermission("mobileconnection", document);
- finish();
-}
-
-// Start the test
-verifyInitialState();
diff --git a/dom/network/tests/marionette/test_mobile_iccinfo.js b/dom/network/tests/marionette/test_mobile_iccinfo.js
deleted file mode 100644
index b3c63bb53..000000000
--- a/dom/network/tests/marionette/test_mobile_iccinfo.js
+++ /dev/null
@@ -1,85 +0,0 @@
-/* Any copyright is dedicated to the Public Domain.
- http://creativecommons.org/publicdomain/zero/1.0/ */
-
-MARIONETTE_TIMEOUT = 30000;
-
-SpecialPowers.addPermission("mobileconnection", true, document);
-
-let connection = navigator.mozMobileConnection;
-ok(connection instanceof MozMobileConnection,
- "connection is instanceof " + connection.constructor);
-
-let emulatorCmdPendingCount = 0;
-function sendEmulatorCommand(cmd, callback) {
- emulatorCmdPendingCount++;
- runEmulatorCmd(cmd, function (result) {
- emulatorCmdPendingCount--;
- is(result[result.length - 1], "OK");
- callback(result);
- });
-}
-
-function setEmulatorMccMnc(mcc, mnc) {
- let cmd = "operator set 0 Android,Android," + mcc + mnc;
- sendEmulatorCommand(cmd, function (result) {
- let re = new RegExp("" + mcc + mnc + "$");
- ok(result[0].match(re), "MCC/MNC should be changed.");
- });
-}
-
-function waitForIccInfoChange(callback) {
- connection.addEventListener("iccinfochange", function handler() {
- connection.removeEventListener("iccinfochange", handler);
- callback();
- });
-}
-
-function finalize() {
- SpecialPowers.removePermission("mobileconnection", document);
- finish();
-}
-
-// The emulator's hard coded iccid value.
-// See it here {B2G_HOME}/external/qemu/telephony/sim_card.c#L299.
-is(connection.iccInfo.iccid, 89014103211118510720);
-
-// The emulator's hard coded mcc and mnc codes.
-// See it here {B2G_HOME}/external/qemu/telephony/android_modem.c#L2465.
-is(connection.iccInfo.mcc, 310);
-is(connection.iccInfo.mnc, 260);
-is(connection.iccInfo.spn, "Android");
-// Phone number is hardcoded in MSISDN
-// See {B2G_HOME}/external/qemu/telephony/sim_card.c, in asimcard_io()
-is(connection.iccInfo.msisdn, "15555215554");
-
-// Test display condition change.
-function testDisplayConditionChange(func, caseArray, oncomplete) {
- (function do_call(index) {
- let next = index < (caseArray.length - 1) ? do_call.bind(null, index + 1) : oncomplete;
- caseArray[index].push(next);
- func.apply(null, caseArray[index]);
- })(0);
-}
-
-function testSPN(mcc, mnc, expectedIsDisplayNetworkNameRequired,
- expectedIsDisplaySpnRequired, callback) {
- waitForIccInfoChange(function() {
- is(connection.iccInfo.isDisplayNetworkNameRequired,
- expectedIsDisplayNetworkNameRequired);
- is(connection.iccInfo.isDisplaySpnRequired,
- expectedIsDisplaySpnRequired);
- // operatorchange will be ignored if we send commands too soon.
- window.setTimeout(callback, 100);
- });
- setEmulatorMccMnc(mcc, mnc);
-}
-
-testDisplayConditionChange(testSPN, [
- // [MCC, MNC, isDisplayNetworkNameRequired, isDisplaySpnRequired]
- [123, 456, false, true], // Not in HPLMN.
- [234, 136, true, true], // Not in HPLMN, but in PLMN specified in SPDI.
- [123, 456, false, true], // Not in HPLMN. Triggering iccinfochange
- [466, 92, true, true], // Not in HPLMN, but in another PLMN specified in SPDI.
- [123, 456, false, true], // Not in HPLMN. Triggering iccinfochange
- [310, 260, true, true], // inside HPLMN.
-], finalize);
diff --git a/dom/network/tests/marionette/test_mobile_mmi.js b/dom/network/tests/marionette/test_mobile_mmi.js
deleted file mode 100644
index cc636e5a7..000000000
--- a/dom/network/tests/marionette/test_mobile_mmi.js
+++ /dev/null
@@ -1,76 +0,0 @@
-/* Any copyright is dedicated to the Public Domain.
- * http://creativecommons.org/publicdomain/zero/1.0/ */
-
-MARIONETTE_TIMEOUT = 20000;
-
-SpecialPowers.addPermission("mobileconnection", true, document);
-
-let mobileConnection = navigator.mozMobileConnection;
-
-let tasks = {
- // List of test functions. Each of them should call |tasks.next()| when
- // completed or |tasks.abort()| to jump to the last one.
- _tasks: [],
- _nextTaskIndex: 0,
-
- push: function push(func) {
- this._tasks.push(func);
- },
-
- next: function next() {
- let index = this._nextTaskIndex++;
- let task = this._tasks[index];
- try {
- task();
- } catch (ex) {
- ok(false, "test task[" + index + "] throws: " + ex);
- // Run last task as clean up if possible.
- if (index != this._tasks.length - 1) {
- this.abort();
- }
- }
- },
-
- abort: function abort() {
- this._tasks[this._tasks.length - 1]();
- },
-
- run: function run() {
- this.next();
- }
-};
-
-tasks.push(function verifyInitialState() {
- log("Verifying initial state.");
-
- ok(mobileConnection instanceof MozMobileConnection,
- "mobileConnection is instanceof " + mobileConnection.constructor);
-
- tasks.next();
-});
-
-tasks.push(function testGettingIMEI() {
- log("Test *#06# ...");
-
- let request = mobileConnection.sendMMI("*#06#");
- ok(request instanceof DOMRequest,
- "request is instanceof " + request.constructor);
-
- request.onsuccess = function onsuccess(event) {
- ok(true, "request success");
- is(event.target.result, "000000000000000", "Emulator IMEI");
- tasks.next();
- }
- request.onerror = function onerror() {
- ok(false, "request success");
- tasks.abort();
- };
-});
-
-// WARNING: All tasks should be pushed before this!!!
-tasks.push(function cleanUp() {
- SpecialPowers.removePermission("mobileconnection", document);
- finish();
-});
-
-tasks.run();
diff --git a/dom/network/tests/marionette/test_mobile_networks.js b/dom/network/tests/marionette/test_mobile_networks.js
deleted file mode 100644
index 40e0818f4..000000000
--- a/dom/network/tests/marionette/test_mobile_networks.js
+++ /dev/null
@@ -1,240 +0,0 @@
-/* Any copyright is dedicated to the Public Domain.
- http://creativecommons.org/publicdomain/zero/1.0/ */
-
-// getNetworks() can take some time..
-MARIONETTE_TIMEOUT = 60000;
-
-SpecialPowers.addPermission("mobileconnection", true, document);
-
-let connection = navigator.mozMobileConnection;
-ok(connection instanceof MozMobileConnection,
- "connection is instanceof " + connection.constructor);
-
-is(connection.networkSelectionMode, "automatic");
-
-let androidNetwork = null;
-let telkilaNetwork = null;
-
-function isAndroidNetwork(network) {
- is(network.longName, "Android");
- is(network.shortName, "Android");
- is(network.mcc, "310");
- is(network.mnc, "260");
-}
-
-function isTelkilaNetwork(network) {
- is(network.longName, "TelKila");
- is(network.shortName, "TelKila");
- is(network.mcc, "310");
- is(network.mnc, "295");
-}
-
-function testConnectionInfo() {
- let voice = connection.voice;
- is(voice.connected, true);
- is(voice.state, "registered");
- is(voice.emergencyCallsOnly, false);
- is(voice.roaming, false);
- isAndroidNetwork(voice.network);
- is(voice.lastKnownMcc, "310");
-
- let data = connection.data;
- // data.connected = true means there's an active data call which we
- // can't predict here.
- is(data.state, "registered");
- is(data.emergencyCallsOnly, false);
- is(data.roaming, false);
- isAndroidNetwork(data.network);
- is(data.lastKnownMcc, null);
-
- testGetNetworks();
-}
-
-function testGetNetworks() {
- let request = connection.getNetworks();
- ok(request instanceof DOMRequest,
- "request is instanceof " + request.constructor);
-
- request.onerror = function() {
- ok(false, request.error);
- setTimeout(testSelectNetwork, 0);
- };
-
- request.onsuccess = function() {
- ok('result' in request, "Request did not contain a result");
- let networks = request.result;
-
- // The emulator RIL server should always return 2 networks:
- // {"longName":"Android","shortName":"Android","mcc":310,"mnc":260,"state":"available"}
- // {"longName":"TelKila","shortName":"TelKila","mcc":310,"mnc":295,"state":"available"}
- is(networks.length, 2);
-
- let network1 = androidNetwork = networks[0];
- isAndroidNetwork(network1);
- is(network1.state, "available");
-
- let network2 = telkilaNetwork = networks[1];
- isTelkilaNetwork(network2);
- is(network2.state, "available");
-
- setTimeout(testSelectNetwork, 0);
- };
-}
-
-function testSelectNetwork() {
- let request = connection.selectNetwork(telkilaNetwork);
- ok(request instanceof DOMRequest,
- "request instanceof " + request.constructor);
-
- connection.addEventListener("voicechange", function voiceChange() {
- connection.removeEventListener("voicechange", voiceChange);
-
- isTelkilaNetwork(connection.voice.network);
- setTimeout(testSelectNetworkAutomatically, 0);
- });
-
- request.onsuccess = function() {
- is(connection.networkSelectionMode, "manual",
- "selectNetwork sets mode to: " + connection.networkSelectionMode);
- };
-
- request.onerror = function() {
- ok(false, request.error);
- setTimeout(testSelectNetworkAutomatically, 0);
- };
-}
-
-function testSelectNetworkAutomatically() {
- let request = connection.selectNetworkAutomatically();
- ok(request instanceof DOMRequest,
- "request instanceof " + request.constructor);
-
- connection.addEventListener("voicechange", function voiceChange() {
- connection.removeEventListener("voicechange", voiceChange);
-
- isAndroidNetwork(connection.voice.network);
- setTimeout(testSelectNetworkErrors, 0);
- });
-
- request.onsuccess = function() {
- is(connection.networkSelectionMode, "automatic",
- "selectNetworkAutomatically sets mode to: " +
- connection.networkSelectionMode);
- };
-
- request.onerror = function() {
- ok(false, request.error);
- setTimeout(testSelectNetworkErrors, 0);
- };
-}
-
-function throwsException(fn) {
- try {
- fn();
- ok(false, "function did not throw an exception: " + fn);
- } catch (e) {
- ok(true, "function successfully caught exception: " + e);
- }
-}
-
-function testSelectNetworkErrors() {
- throwsException(function() {
- connection.selectNetwork(null);
- });
-
- throwsException(function() {
- connection.selectNetwork({});
- });
-
- connection.addEventListener("voicechange", function voiceChange() {
- connection.removeEventListener("voicechange", voiceChange);
- setTimeout(testSelectExistingNetworkManual, 0);
- });
-
- let request1 = connection.selectNetwork(telkilaNetwork);
- request1.onerror = function() {
- ok(false, request.error);
- setTimeout(testSelectExistingNetworkManual, 0);
- };
-
- // attempt to selectNetwork while one request has already been sent
- throwsException(function() {
- connection.selectNetwork(androidNetwork);
- });
-}
-
-function testSelectExistingNetworkManual() {
- // When the current network is selected again, the DOMRequest's onsuccess
- // should be called, but the network shouldn't actually change
-
- // Telkila should be the currently selected network
- log("Selecting TelKila (should already be selected");
- let request = connection.selectNetwork(telkilaNetwork);
-
- let voiceChanged = false;
- connection.addEventListener("voicechange", function voiceChange() {
- connection.removeEventListener("voicechange", voiceChange);
- voiceChanged = true;
- });
-
- function nextTest() {
- // Switch back to automatic selection to setup the next test
- let autoRequest = connection.selectNetworkAutomatically();
- autoRequest.onsuccess = function() {
- setTimeout(testSelectExistingNetworkAuto, 0);
- };
- autoRequest.onerror = function() {
- ok(false, autoRequest.error);
- cleanUp();
- };
- }
-
- request.onsuccess = function() {
- // Give the voicechange event another opportunity to fire
- setTimeout(function() {
- is(voiceChanged, false,
- "voiceNetwork changed while manually selecting Telkila network? " +
- voiceChanged);
- nextTest();
- }, 0);
- };
-
- request.onerror = function() {
- ok(false, request.error);
- nextTest();
- };
-}
-
-function testSelectExistingNetworkAuto() {
- // Now try the same thing but using automatic selection
- log("Selecting automatically (should already be auto)");
- let request = connection.selectNetworkAutomatically();
-
- let voiceChanged = false;
- connection.addEventListener("voicechange", function voiceChange() {
- connection.removeEventListener("voicechange", voiceChange);
- voiceChanged = true;
- });
-
- request.onsuccess = function() {
- // Give the voicechange event another opportunity to fire
- setTimeout(function() {
- is(voiceChanged, false,
- "voiceNetwork changed while automatically selecting network? " +
- voiceChanged);
- cleanUp();
- }, 0);
- };
-
- request.onerror = function() {
- ok(false, request.error);
- cleanUp();
- };
-}
-
-function cleanUp() {
- SpecialPowers.removePermission("mobileconnection", document);
- finish();
-}
-
-testConnectionInfo();
diff --git a/dom/network/tests/marionette/test_mobile_operator_names.js b/dom/network/tests/marionette/test_mobile_operator_names.js
deleted file mode 100644
index db357d7fd..000000000
--- a/dom/network/tests/marionette/test_mobile_operator_names.js
+++ /dev/null
@@ -1,209 +0,0 @@
-/* Any copyright is dedicated to the Public Domain.
- http://creativecommons.org/publicdomain/zero/1.0/ */
-
-MARIONETTE_TIMEOUT = 60000;
-
-SpecialPowers.addPermission("mobileconnection", true, document);
-
-const OPERATOR_HOME = 0;
-const OPERATOR_ROAMING = 1;
-
-let connection = navigator.mozMobileConnection;
-ok(connection instanceof MozMobileConnection,
- "connection is instanceof " + connection.constructor);
-
-let voice = connection.voice;
-ok(voice, "voice connection valid");
-
-let network = voice.network;
-ok(network, "voice network info valid");
-
-let emulatorCmdPendingCount = 0;
-function sendEmulatorCommand(cmd, callback) {
- emulatorCmdPendingCount++;
- runEmulatorCmd(cmd, function (result) {
- emulatorCmdPendingCount--;
-
- is(result[result.length - 1], "OK");
-
- callback(result);
- });
-}
-
-function setEmulatorOperatorNamesAndMccMnc(which, longName, shortName,
- mcc, mnc, callback) {
- let cmd = "operator set " + which + " " + longName + "," +
- shortName + "," + mcc + mnc;
- sendEmulatorCommand(cmd, function (result) {
- let re = new RegExp("^" + longName + "," +
- shortName + "," + mcc + mnc);
- ok(result[which].match(re), "Long/short name and mcc/mnc should be changed.");
-
- if (callback) {
- window.setTimeout(callback, 0);
- }
- });
-}
-
-function setEmulatorOperatorNames(which, longName, shortName, callback) {
- let cmd = "operator set " + which + " " + longName + "," + shortName;
- sendEmulatorCommand(cmd, function (result) {
- let re = new RegExp("^" + longName + "," + shortName + ",");
- ok(result[which].match(re), "Long/short name should be changed.");
-
- if (callback) {
- window.setTimeout(callback, 0);
- }
- });
-}
-
-function setEmulatorRoaming(roaming, callback) {
- let cmd = "gsm voice " + (roaming ? "roaming" : "home");
- sendEmulatorCommand(cmd, function (result) {
- is(result[0], "OK");
-
- if (callback) {
- window.setTimeout(callback, 0);
- }
- });
-}
-
-function checkValidMccMnc() {
- is(network.mcc, "310", "network.mcc");
- is(network.mnc, "260", "network.mnc");
-}
-
-function waitForVoiceChange(callback) {
- connection.addEventListener("voicechange", function onvoicechange() {
- connection.removeEventListener("voicechange", onvoicechange);
- callback();
- });
-}
-
-function doTestMobileOperatorNames(longName, shortName, callback) {
- log("Testing '" + longName + "', '" + shortName + "':");
-
- checkValidMccMnc();
-
- waitForVoiceChange(function () {
- is(network.longName, longName, "network.longName");
- is(network.shortName, shortName, "network.shortName");
-
- checkValidMccMnc();
-
- window.setTimeout(callback, 0);
- });
-
- setEmulatorOperatorNames(OPERATOR_HOME, longName, shortName);
-}
-
-function testMobileOperatorNames() {
- doTestMobileOperatorNames("Mozilla", "B2G", function () {
- doTestMobileOperatorNames("Mozilla", "", function () {
- doTestMobileOperatorNames("", "B2G", function () {
- doTestMobileOperatorNames("", "", function () {
- doTestMobileOperatorNames("Android", "Android", testOperatorPLMNList);
- });
- });
- });
- });
-}
-
-function doTestOperatorPLMNList(mcc, mnc, expectedLongName,
- expectedShortName, callback) {
- log("Testing mcc = " + mcc + ", mnc = " + mnc + ":");
-
- waitForVoiceChange(function () {
- is(network.longName, expectedLongName, "network.longName");
- is(network.shortName, expectedShortName, "network.shortName");
- is(network.mcc, mcc, "network.mcc");
- is(network.mnc, mnc, "network.mnc");
- window.setTimeout(callback, 0);
- });
-
- setEmulatorOperatorNamesAndMccMnc(OPERATOR_HOME, "Android", "Android", mcc, mnc);
-}
-
-function testOperatorPLMNList() {
- doTestOperatorPLMNList("123", "456", "Android", "Android", function() {
- doTestOperatorPLMNList("310", "070", "AT&T", "", function() {
- doTestOperatorPLMNList("310", "260", "Android", "Android", testRoamingCheck);
- });
- });
-}
-
-// See bug 797972 - B2G RIL: False roaming situation
-//
-// Steps to test:
-// 1. set roaming operator names
-// 2. set emulator roaming
-// 3. wait for onvoicechange event and test passing conditions
-// 4. set emulator roaming back to false
-// 5. wait for onvoicechange event again and callback
-function doTestRoamingCheck(longName, shortName, callback) {
- log("Testing roaming check '" + longName + "', '" + shortName + "':");
-
- setEmulatorOperatorNames(OPERATOR_ROAMING, longName, shortName,
- window.setTimeout.bind(window, function () {
- let done = false;
- function resetRoaming() {
- if (!done) {
- window.setTimeout(resetRoaming, 100);
- return;
- }
-
- waitForVoiceChange(callback);
- setEmulatorRoaming(false);
- }
-
- waitForVoiceChange(function () {
- is(network.longName, longName, "network.longName");
- is(network.shortName, shortName, "network.shortName");
- is(voice.roaming, false, "voice.roaming");
-
- resetRoaming();
- });
-
- setEmulatorRoaming(true, function () {
- done = true;
- });
- }, 3000) // window.setTimeout.bind
- ); // setEmulatorOperatorNames
-}
-
-function testRoamingCheck() {
- // If Either long name or short name of current registered operator matches
- // SPN("Android"), then the `roaming` attribute should be set to false.
- doTestRoamingCheck("Android", "Android", function () {
- doTestRoamingCheck("Android", "android", function () {
- doTestRoamingCheck("Android", "Xxx", function () {
- doTestRoamingCheck("android", "Android", function () {
- doTestRoamingCheck("android", "android", function () {
- doTestRoamingCheck("android", "Xxx", function () {
- doTestRoamingCheck("Xxx", "Android", function () {
- doTestRoamingCheck("Xxx", "android", function () {
- setEmulatorOperatorNames(OPERATOR_ROAMING, "TelKila", "TelKila",
- window.setTimeout.bind(window, cleanUp, 3000));
- });
- });
- });
- });
- });
- });
- });
- });
-}
-
-function cleanUp() {
- if (emulatorCmdPendingCount > 0) {
- setTimeout(cleanUp, 100);
- return;
- }
-
- SpecialPowers.removePermission("mobileconnection", document);
- finish();
-}
-
-waitFor(testMobileOperatorNames, function () {
- return voice.connected;
-});
diff --git a/dom/network/tests/marionette/test_mobile_preferred_network_type.js b/dom/network/tests/marionette/test_mobile_preferred_network_type.js
deleted file mode 100644
index 4ea62f761..000000000
--- a/dom/network/tests/marionette/test_mobile_preferred_network_type.js
+++ /dev/null
@@ -1,65 +0,0 @@
-/* Any copyright is dedicated to the Public Domain.
- * http://creativecommons.org/publicdomain/zero/1.0/ */
-
-MARIONETTE_TIMEOUT = 60000;
-
-const KEY = "ril.radio.preferredNetworkType";
-
-SpecialPowers.addPermission("mobileconnection", true, document);
-SpecialPowers.addPermission("settings-read", true, document);
-SpecialPowers.addPermission("settings-write", true, document);
-
-let settings = window.navigator.mozSettings;
-
-function test_revert_previous_setting_on_invalid_value() {
- log("Testing reverting to previous setting on invalid value received");
-
- let getLock = settings.createLock();
- let getReq = getLock.get(KEY);
- getReq.addEventListener("success", function onGetSuccess() {
- let originalValue = getReq.result[KEY] || "wcdma/gsm";
-
- let setDone = false;
- settings.addObserver(KEY, function observer(setting) {
- // Mark if the invalid value has been set in db and wait.
- if (setting.settingValue == obj[KEY]) {
- setDone = true;
- return;
- }
-
- // Skip any change before marking but keep it as original value.
- if (!setDone) {
- originalValue = setting.settingValue;
- return;
- }
-
- settings.removeObserver(KEY, observer);
- is(setting.settingValue, originalValue, "Settings reverted");
- window.setTimeout(cleanUp, 0);
- });
-
- let obj = {};
- obj[KEY] = "AnInvalidValue";
- let setLock = settings.createLock();
- setLock.set(obj);
- setLock.addEventListener("error", function onSetError() {
- ok(false, "cannot set '" + KEY + "'");
- });
- });
- getReq.addEventListener("error", function onGetError() {
- ok(false, "cannot get default value of '" + KEY + "'");
- });
-}
-
-function cleanUp() {
- SpecialPowers.removePermission("mobileconnection", document);
- SpecialPowers.removePermission("settings-write", document);
- SpecialPowers.removePermission("settings-read", document);
-
- finish();
-}
-
-waitFor(test_revert_previous_setting_on_invalid_value, function () {
- return navigator.mozMobileConnection.voice.connected;
-});
-
diff --git a/dom/network/tests/marionette/test_mobile_voice_state.js b/dom/network/tests/marionette/test_mobile_voice_state.js
deleted file mode 100644
index 2fe45171b..000000000
--- a/dom/network/tests/marionette/test_mobile_voice_state.js
+++ /dev/null
@@ -1,153 +0,0 @@
-/* Any copyright is dedicated to the Public Domain.
- http://creativecommons.org/publicdomain/zero/1.0/ */
-
-MARIONETTE_TIMEOUT = 30000;
-
-SpecialPowers.addPermission("mobileconnection", true, document);
-
-let connection = navigator.mozMobileConnection;
-ok(connection instanceof MozMobileConnection,
- "connection is instanceof " + connection.constructor);
-
-let emulatorCmdPendingCount = 0;
-function setEmulatorVoiceState(state) {
- emulatorCmdPendingCount++;
- runEmulatorCmd("gsm voice " + state, function (result) {
- emulatorCmdPendingCount--;
- is(result[0], "OK");
- });
-}
-
-function setEmulatorGsmLocation(lac, cid) {
- emulatorCmdPendingCount++;
- runEmulatorCmd("gsm location " + lac + " " + cid, function (result) {
- emulatorCmdPendingCount--;
- is(result[0], "OK");
- });
-}
-
-function testConnectionInfo() {
- let voice = connection.voice;
- is(voice.connected, true);
- is(voice.state, "registered");
- is(voice.emergencyCallsOnly, false);
- is(voice.roaming, false);
-
- testCellLocation();
-}
-
-function testCellLocation() {
- let voice = connection.voice;
-
- // Emulator always reports valid lac/cid value because its AT command parser
- // insists valid value for every complete response. See source file
- // hardare/ril/reference-ril/at_tok.c, function at_tok_nexthexint().
- ok(voice.cell, "location available");
-
- // Initial LAC/CID. Android emulator initializes both value to -1.
- is(voice.cell.gsmLocationAreaCode, 65535);
- is(voice.cell.gsmCellId, 268435455);
-
- connection.addEventListener("voicechange", function onvoicechange() {
- connection.removeEventListener("voicechange", onvoicechange);
-
- is(voice.cell.gsmLocationAreaCode, 100);
- is(voice.cell.gsmCellId, 100);
-
- testUnregistered();
- });
-
- setEmulatorGsmLocation(100, 100);
-}
-
-function testUnregistered() {
- setEmulatorVoiceState("unregistered");
-
- connection.addEventListener("voicechange", function onvoicechange() {
- connection.removeEventListener("voicechange", onvoicechange);
-
- is(connection.voice.connected, false);
- is(connection.voice.state, "notSearching");
- is(connection.voice.emergencyCallsOnly, false);
- is(connection.voice.roaming, false);
-
- testSearching();
- });
-}
-
-function testSearching() {
- // For some reason, requesting the "searching" state puts the fake modem
- // into "registered"... Skipping this test for now.
- testDenied();
- return;
-
- setEmulatorVoiceState("searching");
-
- connection.addEventListener("voicechange", function onvoicechange() {
- connection.removeEventListener("voicechange", onvoicechange);
-
- is(connection.voice.connected, false);
- is(connection.voice.state, "searching");
- is(connection.voice.emergencyCallsOnly, false);
- is(connection.voice.roaming, false);
-
- testDenied();
- });
-}
-
-function testDenied() {
- setEmulatorVoiceState("denied");
-
- connection.addEventListener("voicechange", function onvoicechange() {
- connection.removeEventListener("voicechange", onvoicechange);
-
- is(connection.voice.connected, false);
- is(connection.voice.state, "denied");
- is(connection.voice.emergencyCallsOnly, false);
- is(connection.voice.roaming, false);
-
- testRoaming();
- });
-}
-
-function testRoaming() {
- setEmulatorVoiceState("roaming");
-
- connection.addEventListener("voicechange", function onvoicechange() {
- connection.removeEventListener("voicechange", onvoicechange);
-
- is(connection.voice.connected, true);
- is(connection.voice.state, "registered");
- is(connection.voice.emergencyCallsOnly, false);
- is(connection.voice.roaming, true);
-
- testHome();
- });
-}
-
-function testHome() {
- setEmulatorVoiceState("home");
-
- connection.addEventListener("voicechange", function onvoicechange() {
- connection.removeEventListener("voicechange", onvoicechange);
-
- is(connection.voice.connected, true);
- is(connection.voice.state, "registered");
- is(connection.voice.emergencyCallsOnly, false);
- is(connection.voice.roaming, false);
-
- cleanUp();
- });
-}
-
-function cleanUp() {
- if (emulatorCmdPendingCount > 0) {
- setTimeout(cleanUp, 100);
- return;
- }
-
- SpecialPowers.removePermission("mobileconnection", document);
- finish();
-}
-
-testConnectionInfo();
diff --git a/dom/network/tests/mochitest.ini b/dom/network/tests/mochitest.ini
new file mode 100644
index 000000000..8d03bd9f9
--- /dev/null
+++ b/dom/network/tests/mochitest.ini
@@ -0,0 +1,26 @@
+[DEFAULT]
+support-files =
+ add_task.js
+ file_udpsocket_iframe.html
+ test_tcpsocket_client_and_server_basics.js
+
+[test_network_basics.html]
+skip-if = toolkit == "gonk" || toolkit == 'android'
+[test_tcpsocket_client_and_server_basics.html]
+[test_tcpsocket_default_permissions.html]
+skip-if = toolkit == "gonk"
+[test_tcpsocket_enabled_no_perm.html]
+skip-if = toolkit == "gonk"
+[test_tcpsocket_enabled_with_perm.html]
+skip-if = toolkit == "gonk" || e10s
+[test_networkstats_alarms.html]
+skip-if = toolkit != "gonk"
+[test_networkstats_basics.html]
+skip-if = toolkit != "gonk"
+[test_networkstats_disabled.html]
+skip-if = toolkit != "gonk"
+[test_networkstats_enabled_no_perm.html]
+skip-if = toolkit != "gonk"
+[test_networkstats_enabled_perm.html]
+skip-if = toolkit != "gonk"
+[test_udpsocket.html]
diff --git a/dom/network/tests/moz.build b/dom/network/tests/moz.build
deleted file mode 100644
index 191a5d6ba..000000000
--- a/dom/network/tests/moz.build
+++ /dev/null
@@ -1,12 +0,0 @@
-# -*- Mode: python; c-basic-offset: 4; 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/.
-
-MODULE = 'test_dom_socket'
-
-XPCSHELL_TESTS_MANIFESTS += ['unit/xpcshell.ini', 'unit_ipc/xpcshell.ini']
-
-if CONFIG['MOZ_B2G_RIL']:
- XPCSHELL_TESTS_MANIFESTS += ['unit_stats/xpcshell.ini']
diff --git a/dom/network/tests/test_network_basics.html b/dom/network/tests/test_network_basics.html
index 91005e308..e3c3eb25d 100644
--- a/dom/network/tests/test_network_basics.html
+++ b/dom/network/tests/test_network_basics.html
@@ -12,33 +12,25 @@
<pre id="test">
<script type="application/javascript">
-/** Test for Network API **/
+/** Test for Network Information API **/
+function test() {
+ ok('connection' in navigator, "navigator.connection should exist");
-function checkInterface(aInterface) {
- ok(!(aInterface in window), aInterface + " should be prefixed");
- ok(("Moz" + aInterface) in window, aInterface + " should be prefixed");
-}
-
-ok('mozConnection' in navigator, "navigator.mozConnection should exist");
+ ok(navigator.connection, "navigator.connection returns an object");
-ok(navigator.mozConnection, "navigator.mozConnection returns an object");
+ ok(navigator.connection instanceof EventTarget,
+ "navigator.connection is a EventTarget object");
-ok(navigator.mozConnection instanceof MozConnection,
- "navigator.mozConnection is a MozConnection object");
-ok(navigator.mozConnection instanceof EventTarget,
- "navigator.mozConnection 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");
-checkInterface("Connection");
-
-ok('bandwidth' in navigator.mozConnection,
- "bandwidth should be a Connection attribute");
-is(navigator.mozConnection.bandwidth, Infinity,
- "By default connection.bandwidth is equals to Infinity");
+ SimpleTest.finish();
+}
-ok('metered' in navigator.mozConnection,
- "metered should be a Connection attribute");
-is(navigator.mozConnection.metered, false,
- "By default the connection is not metered");
+SimpleTest.waitForExplicitFinish();
+SpecialPowers.pushPrefEnv({'set': [["dom.netinfo.enabled", true]]}, test);
</script>
</pre>
diff --git a/dom/network/tests/test_networkstats_alarms.html b/dom/network/tests/test_networkstats_alarms.html
new file mode 100644
index 000000000..f74081eef
--- /dev/null
+++ b/dom/network/tests/test_networkstats_alarms.html
@@ -0,0 +1,194 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>Test for NetworkStats alarms</title>
+ <script type="application/javascript" 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">
+</div>
+<pre id="test">
+<script type="application/javascript">
+
+function test() {
+ ok(true, "Checking if no alarms are set.");
+
+ req = navigator.mozNetworkStats.getAllAlarms();
+
+ req.onsuccess = function () {
+ ok(true, "Succeeded to get alarms.");
+ ok(Array.isArray(req.result) && req.result.length == 0,
+ "There are no alarms set.");
+ next();
+ };
+
+ req.onerror = function () {
+ ok(false, "getAllAlarms() shouldn't fail!");
+ }
+}
+
+var req;
+var index = -1;
+
+var wifi = {'type': 0, 'id': '0'};
+var mobile = {'type': 1, 'id': '1'};
+
+var steps = [
+ function () {
+ ok(true, "Calling getAllAlarms() with invalid network.");
+
+ req = navigator.mozNetworkStats
+ .getAllAlarms(new window.MozNetworkStatsInterface(mobile));
+
+ req.onsuccess = function () {
+ ok(false, "getAllAlarms() shouldn't succeed!");
+ };
+
+ req.onerror = function () {
+ ok(req.error.name == "InvalidInterface", "Get InvalidInterface error");
+ next();
+ }
+ },
+ function () {
+ ok(true, "Calling addAlarm() with invalid network or parameters.");
+
+ try {
+ navigator.mozNetworkStats.addAlarm();
+ } catch(ex) {
+ ok(ex.result == SpecialPowers.Cr.NS_ERROR_XPC_NOT_ENOUGH_ARGS,
+ "addAlarm() throws NS_ERROR_XPC_NOT_ENOUGH_ARGS exception when no parameters");
+ }
+
+ try {
+ navigator.mozNetworkStats.addAlarm(100000);
+ } catch(ex) {
+ ok(ex.result == SpecialPowers.Cr.NS_ERROR_XPC_NOT_ENOUGH_ARGS,
+ "addAlarm() throws NS_ERROR_XPC_NOT_ENOUGH_ARGS exception when no network");
+ }
+
+ try {
+ navigator.mozNetworkStats.addAlarm(new window.MozNetworkStatsInterface(wifi));
+ } catch(ex) {
+ ok(ex.result == SpecialPowers.Cr.NS_ERROR_XPC_NOT_ENOUGH_ARGS,
+ "addAlarm() throws NS_ERROR_XPC_NOT_ENOUGH_ARGS exception when no threshold");
+ }
+
+ req = navigator.mozNetworkStats
+ .addAlarm(new window.MozNetworkStatsInterface(mobile), -100000);
+
+ req.onsuccess = function () {
+ ok(false, "addAlarm() shouldn't succeed with negative threshold.");
+ };
+
+ req.onerror = function () {
+ ok(req.error.name == "InvalidThresholdValue", "Get InvalidThresholdValue error");
+ next();
+ }
+ },
+ function () {
+ ok(true, "Calling addAlarm()");
+
+ req = navigator.mozNetworkStats
+ .addAlarm(new window.MozNetworkStatsInterface(wifi), 100000000);
+
+ req.onsuccess = function () {
+ ok(true, "Succeeded to add alarm. AlarmId: " + req.result);
+ next();
+ };
+ req.onerror = function () {
+ ok(false, "addAlarm() shouldn't fail.");
+ };
+ },
+ function () {
+ ok(true, "Calling getAllAlarms()");
+
+ req = navigator.mozNetworkStats
+ .getAllAlarms(new window.MozNetworkStatsInterface(wifi));
+
+ req.onsuccess = function () {
+ ok(req.result.length == 1, "Only one alarm");
+ ok(req.result[0].alarmId == 1, "Get correct alarmId");
+ next();
+ };
+
+ req.onerror = function () {
+ ok(false, "getAllAlarms() shouldn't fail.");
+ }
+ },
+ function () {
+ ok(true, "Calling removeAlarms() to remove alarms.");
+
+ req = navigator.mozNetworkStats.removeAlarms();
+
+ req.onsuccess = function () {
+ ok(req.result, "Succeeded to remove alarms.");
+ next();
+ };
+
+ req.onerror = function () {
+ ok(false, "removeAlarms() shouldn't fail.");
+ }
+ },
+ function () {
+ ok(true, "Checking if all alarms are removed.");
+
+ req = navigator.mozNetworkStats.getAllAlarms();
+
+ req.onsuccess = function () {
+ ok(Array.isArray(req.result) && req.result.length == 0,
+ "Succeeded to remove all alarms.");
+ next();
+ };
+
+ req.onerror = function () {
+ ok(false, "getAllAlarms() shouldn't fail.");
+ }
+ },
+ function () {
+ ok(true, "all done!\n");
+ SimpleTest.finish();
+ return;
+ }
+];
+
+function next() {
+ index += 1;
+ if (index >= steps.length) {
+ ok(false, "Shouldn't get here!");
+ return;
+ }
+ try {
+ steps[index]();
+ } catch(ex) {
+ ok(false, "Caught exception", ex);
+ }
+}
+
+SimpleTest.waitForExplicitFinish();
+
+SpecialPowers.pushPrefEnv({'set': [["dom.mozNetworkStats.enabled", true]]},
+ function() {
+ SpecialPowers.pushPermissions([{ 'type': 'networkstats-manage', 'allow': 1, 'context': window.document }],
+ function() {
+ ok(SpecialPowers.hasPermission("networkstats-manage", document),
+ "Has permission 'networkstats-manage'.");
+
+ ok(SpecialPowers.getBoolPref("dom.mozNetworkStats.enabled"),
+ "Preference 'dom.mozNetworkStats.enabled' is true.");
+
+ ok('mozNetworkStats' in navigator, "navigator.mozNetworkStats should exist");
+
+ ok(navigator.mozNetworkStats instanceof SpecialPowers.Ci.nsIDOMMozNetworkStatsManager,
+ "navigator.mozNetworkStats should be a nsIDOMMozNetworkStatsManager object");
+
+ test();
+ });
+});
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/network/tests/test_networkstats_basics.html b/dom/network/tests/test_networkstats_basics.html
index 1f76ba0bb..2f6df9bc9 100644
--- a/dom/network/tests/test_networkstats_basics.html
+++ b/dom/network/tests/test_networkstats_basics.html
@@ -12,45 +12,27 @@
<pre id="test">
<script type="application/javascript">
-// Test for NetworkStats
-function checkInterface(aInterface) {
- ok(!(aInterface in window), aInterface + " should be prefixed");
- ok(("Moz" + aInterface) in window, aInterface + " should be prefixed");
-}
-
function test() {
- // Test interfaces
- checkInterface("NetworkStatsManager");
- checkInterface("NetworkStats");
- checkInterface("NetworkStatsData");
-
- ok('mozNetworkStats' in navigator, "navigator.mozMozNetworkStats should exist");
- ok(navigator.mozNetworkStats, "navigator.mozNetworkStats returns an object");
-
- netStats = navigator.mozNetworkStats;
+ netStats = window.navigator.mozNetworkStats;
+ ok(netStats, "mozNetworkStats exists");
// Test IDL attributes
- ok('connectionTypes' in netStats,
- "connectionTypes should be a NetworkStats attribute");
- ok(Array.isArray(netStats.connectionTypes) && netStats.connectionTypes.length > 0,
- "connectionTypes is an array not empty.");
-
ok('sampleRate' in netStats,
"sampleRate should be a NetworkStats attribute");
ok(netStats.sampleRate > 0,
"sampleRate is greater than 0.");
- ok('maxStorageSamples' in netStats,
- "maxStorageSamples should be a NetworkStats attribute");
- ok(netStats.maxStorageSamples > 0,
- "maxStorageSamples is greater than 0.");
+ ok('maxStorageAge' in netStats,
+ "maxStorageAge should be a NetworkStats attribute");
+ ok(netStats.maxStorageAge > 0,
+ "maxStorageAge is greater than 0.");
// Test IDL methods
next();
return;
}
-function checkDataDates(data, start, end, sampleRate){
+function checkDataDates(data, start, end, sampleRate) {
var offset = (new Date()).getTimezoneOffset() * 60 * 1000;
start = Math.floor((start.getTime() - offset) / sampleRate) * sampleRate + offset;
end = Math.floor((end.getTime() - offset) / sampleRate) * sampleRate + offset;
@@ -71,209 +53,254 @@ function checkDataDates(data, start, end, sampleRate){
ok(success, "data result has correct dates");
}
+function compareNetworks(networkA, networkB) {
+ return (networkA.id == networkB.id &&
+ networkA.type == networkB.type);
+}
+
var req;
var index = -1;
var netStats = null;
var steps = [
function () {
- // Test clearAlldata
- req = netStats.clearAllData();
+ // Test getAvailableNetworks
+ req = netStats.getAvailableNetworks();
+ req.onsuccess = function () {
+ ok(true, "getAvailableNetworks request ok");
+ ok(Array.isArray(req.result) && req.result.length > 0,
+ "getAvailableNetworks returns an array not empty");
+ next();
+ };
+ req.onerror = function () {
+ ok(false, "getAvailableNetworks failure!");
+ }
+ },
+ function () {
+ // Test clearAllStats
+ req = netStats.clearAllStats();
+ req.onsuccess = function () {
+ ok(true, "clearAllStats deleted the database");
+ next();
+ };
+ req.onerror = function () {
+ ok(false, "clearAllStats deleted the database");
+ }
+ },
+ function () {
+ // Check if getSamples throws exception when start is greather than end
+
+ // Prepare get params
+ req = netStats.getAvailableNetworks();
req.onsuccess = function () {
- ok(true, "clearAllData deleted the database");
+ var network = req.result[0];
+
+ // Get dates
+ var endDate = new Date();
+ var startDate = new Date(endDate.getTime() + 1000);
+
+ try {
+ netStats.getSamples(network, startDate, endDate);
+ } catch(ex) {
+ ok(true, "getSamples throws exception when start is greater than end");
+ next();
+ return;
+ }
+
+ ok(false, "getSamples throws exception when start is greater than end");
next();
+ return;
};
req.onerror = function () {
- ok(false, "clearAllData deleted the database");
+ ok(false, "Error getting networks!");
}
},
function () {
- // Check if getNetworkStats launch exception when start is greather than end
+ // Test if call getSamples with network of type different than
+ // MozNetworkStatsInterface throws an exception
// Prepare get params
- var type = netStats.connectionTypes[0];
- // Get dates
+ var network = "wifi";
var endDate = new Date();
- var startDate = new Date(endDate.getTime() + 1000);
+ var startDate = new Date(endDate.getTime() - 1000);
try {
- netStats.getNetworkStats({start: startDate, end: endDate});
+ netStats.getSamples(network, new Date(), new Date());
} catch(ex) {
- ok(true, "getNetworkStats launch exception when start is greater than end");
+ ok(true, "getSamples throws exception if network is not " +
+ "a MozNetworkStatsInterface");
next();
return;
}
- ok(false, "getNetworkStats launch exceptionwhen start is greater than end");
- next();
- return;
+ ok(false, "getSamples throws exception if network is not " +
+ "a MozNetworkStatsInterface");
},
function () {
- // Test if call getNetworkStats with undefined start param launch an exception
+ // Test if call getSamples with start parameter type different than Date throws an exception
// Prepare get params
- var type = netStats.connectionTypes[0];
- setTimeout(function() {
+ req = netStats.getAvailableNetworks();
+ req.onsuccess = function () {
+ var network = req.result[0];
+
+ var endDate = new Date();
+ var startDate = new Date(endDate.getTime() - 1000);
+ startDate = startDate.toString();
+
try {
- netStats.getNetworkStats({end: new Date()});
+ netStats.getSamples(network, startDate, endDate);
} catch(ex) {
- ok(true, "getNetworkStats launch exception when start param does not exist");
+ ok(true, "getSamples throws exception when start param is not a Date");
next();
return;
}
- ok(false, "getNetworkStats launch exception when start param does not exist");
- }, 1000);
+ ok(false, "getSamples throws exception when start param is not a Date");
+ };
+ req.onerror = function () {
+ ok(false, "Error getting networks!");
+ };
},
function () {
- // Test if call getNetworkStats with undefined end param launch an exception
+ // Test if call getSamples with end parameter type different than Date throws an exception
// Prepare get params
- var type = netStats.connectionTypes[0];
- setTimeout(function() {
+ req = netStats.getAvailableNetworks();
+ req.onsuccess = function () {
+ var network = req.result[0];
+
+ var endDate = new Date();
+ var startDate = new Date(endDate.getTime() - 1000);
+ endDate = startDate.toString();
+
try {
- netStats.getNetworkStats({start: new Date()});
+ netStats.getSamples(network, startDate, endDate);
} catch(ex) {
- ok(true, "getNetworkStats launch exception when end param does not exist");
+ ok(true, "getSamples throws exception when end param is not a Date");
next();
return;
}
- ok(false, "getNetworkStats launch exception when end param does not exist");
- }, 1000);
+ ok(false, "getSamples throws exception when end param is not a Date");
+ };
+ req.onerror = function () {
+ ok(false, "Error getting networks!");
+ };
},
function () {
- ok(true, "Get stats for a connectionType and dates adapted to samplerate");
+ ok(true, "Get stats for a network and dates adapted to samplerate");
// Prepare get params
- var type = netStats.connectionTypes[0];
- var diff = 2;
- // Get samplerate in millis
- var sampleRate = netStats.sampleRate * 1000;
- // Get date with samplerate's precision
- var offset = new Date().getTimezoneOffset() * 60 * 1000;
- var endDate = new Date(Math.floor((new Date().getTime() - offset) / sampleRate)
- * sampleRate + offset);
- var startDate = new Date(endDate.getTime() - (sampleRate * diff));
- // Calculate the number of samples that should be returned based on the
- // the samplerate and including final and initial samples.
- var samples = (endDate.getTime() - startDate.getTime()) / sampleRate + 1;
-
- // Launch request
- req = netStats.getNetworkStats({start: startDate, end: endDate, connectionType: type});
+ req = netStats.getAvailableNetworks();
req.onsuccess = function () {
- ok(true, "Get stats request ok");
- ok(req.result.connectionType == type, "connectionTypes should be equals");
- ok(req.result.start.getTime() == startDate.getTime(), "starts should be equals");
- ok(req.result.end.getTime() == endDate.getTime(), "ends should be equals");
- var data = req.result.data;
- ok(Array.isArray(data) && data.length == samples,
- "data is an array of length " + samples);
- checkDataDates(data, startDate, endDate, sampleRate);
- next();
+ var network = req.result[0];
+ var diff = 2;
+ // Get samplerate in millis
+ var sampleRate = netStats.sampleRate;
+ // Get date with samplerate's precision
+ var offset = new Date().getTimezoneOffset() * 60 * 1000;
+ var endDate = new Date(Math.floor((new Date().getTime() - offset) / sampleRate)
+ * sampleRate + offset);
+ var startDate = new Date(endDate.getTime() - (sampleRate * diff));
+ // Calculate the number of samples that should be returned based on the
+ // the samplerate and including final and initial samples.
+ var samples = (endDate.getTime() - startDate.getTime()) / sampleRate + 1;
+
+ // Launch request
+ req = netStats.getSamples(network, startDate, endDate);
+ req.onsuccess = function () {
+ ok(true, "Get system stats request ok");
+ ok(req.result.manifestURL == null, "manifestURL should be null");
+ ok(compareNetworks(req.result.network, network), "networks should be equals");
+ ok(req.result.start.getTime() == startDate.getTime(), "starts should be equals");
+ ok(req.result.end.getTime() == endDate.getTime(), "ends should be equals");
+ var data = req.result.data;
+ ok(Array.isArray(data) && data.length == samples,
+ "data is an array of length " + samples);
+ checkDataDates(data, startDate, endDate, sampleRate);
+ next();
+ };
+ req.onerror = function () {
+ ok(false, "Get stats failure!");
+ }
};
req.onerror = function () {
- ok(false, "Get stats for a connectionType failure!");
- }
+ ok(false, "Error getting networks!");
+ };
},
function () {
- ok(true, "Get stats for all connectionTypes and dates adapted to samplerate");
+ ok(true, "Get system stats for a network and dates not adapted to samplerate");
// Prepare get params
- var diff = 2;
- // Get samplerate in millis
- var sampleRate = netStats.sampleRate * 1000;
- // Get date with samplerate's precision
- var offset = new Date().getTimezoneOffset() * 60 * 1000;
- var endDate = new Date(Math.floor((new Date().getTime() - offset) / sampleRate)
- * sampleRate + offset);
- var startDate = new Date(endDate.getTime() - (sampleRate * diff));
- // Calculate the number of samples that should be returned based on the
- // the samplerate and including final and initial samples.
- var samples = (endDate.getTime() - startDate.getTime()) / sampleRate + 1;
-
- // Launch request
- req = netStats.getNetworkStats({start: startDate, end: endDate});
+ req = netStats.getAvailableNetworks();
req.onsuccess = function () {
- ok(true, "Get stats request ok");
- ok(req.result.connectionType == null, "connectionTypes should be null");
- ok(req.result.start.getTime() == startDate.getTime(), "starts should be equals");
- ok(req.result.end.getTime() == endDate.getTime(), "ends should be equals");
- var data = req.result.data;
- ok(Array.isArray(data) && data.length == samples,
- "data is an array of length " + samples);
- checkDataDates(data, startDate, endDate, sampleRate);
- next();
+ var network = req.result[0];
+ var diff = 2;
+ // Get samplerate in millis
+ var sampleRate = netStats.sampleRate;
+ var endDate = new Date();
+ var startDate = new Date(endDate.getTime() - (sampleRate * diff));
+ // Calculate the number of samples that should be returned based on the
+ // the samplerate, including final and initial samples and taking into
+ // account that these will be filtered according to precision.
+ var samples = (Math.floor(endDate.getTime() / (sampleRate)) * sampleRate -
+ Math.floor(startDate.getTime() / (sampleRate)) * sampleRate) / sampleRate + 1;
+
+ // Launch request
+ req = netStats.getSamples(network, startDate, endDate);
+ req.onsuccess = function () {
+ ok(true, "Get stats request ok");
+ ok(req.result.manifestURL == null, "manifestURL should be null");
+ ok(compareNetworks(req.result.network, network), "networks should be equals");
+ ok(req.result.start.getTime() == startDate.getTime(), "starts should be equals");
+ ok(req.result.end.getTime() == endDate.getTime(), "ends should be equals");
+ var data = req.result.data;
+ ok(Array.isArray(data) && data.length == samples,
+ "data is an array of length " + samples);
+ checkDataDates(data, startDate, endDate, sampleRate);
+ next();
+ };
+ req.onerror = function () {
+ ok(false, "Get stats failure!");
+ }
};
req.onerror = function () {
- ok(false, "Get stats for all connectionTypes failure!");
- }
+ ok(false, "Error getting networks!");
+ };
},
function () {
- ok(true, "Get stats for a connectionType and dates not adapted to samplerate");
- // Prepare get params
- var type = netStats.connectionTypes[0];
- var diff = 2;
- // Get samplerate in millis
- var sampleRate = netStats.sampleRate * 1000;
- var endDate = new Date();
- var startDate = new Date(endDate.getTime() - (sampleRate * diff));
- // Calculate the number of samples that should be returned based on the
- // the samplerate, including final and initial samples and taking into
- // account that these will be filtered according to precision.
- var samples = (Math.floor(endDate.getTime() / (sampleRate)) * sampleRate -
- Math.floor(startDate.getTime() / (sampleRate)) * sampleRate) / sampleRate + 1;
-
- // Launch request
- req = netStats.getNetworkStats({start: startDate, end: endDate, connectionType: type});
+ // Test clearStats
+ req = netStats.getAvailableNetworks();
req.onsuccess = function () {
- ok(true, "Get stats request ok");
- ok(req.result.connectionType == type, "connectionTypes should be equals");
- ok(req.result.start.getTime() == startDate.getTime(), "starts should be equals");
- ok(req.result.end.getTime() == endDate.getTime(), "ends should be equals");
- var data = req.result.data;
- ok(Array.isArray(data) && data.length == samples,
- "data is an array of length " + samples);
- checkDataDates(data, startDate, endDate, sampleRate);
- next();
+ var network = req.result[0];
+
+ req = netStats.clearStats(network);
+ req.onsuccess = function () {
+ ok(true, "clearStats deleted the database");
+ next();
+ };
+ req.onerror = function () {
+ ok(false, "clearStats deleted the database");
+ }
};
req.onerror = function () {
- ok(false, "Get stats for a connectionType failure!");
- }
+ ok(false, "Error getting networks!");
+ };
},
function () {
- ok(true, "Get stats for all connectionTypes and dates not adapted to samplerate");
- // Prepare get params
- var diff = 2;
- // Get samplerate in millis
- var sampleRate = netStats.sampleRate * 1000;
- // Get date with samplerate's precision
- var endDate = new Date();
- var startDate = new Date(endDate.getTime() - (sampleRate * diff));
- // Calculate the number of samples that should be returned based on the
- // the samplerate, including final and initial samples and taking into
- // account that these will be filtered according to precision.
- var samples = (Math.floor(endDate.getTime() / (sampleRate)) * sampleRate -
- Math.floor(startDate.getTime() / (sampleRate)) * sampleRate) / sampleRate + 1;
-
- // Launch request
- req = netStats.getNetworkStats({start: startDate, end: endDate});
+ // Test getAvailableServiceTypes
+ req = netStats.getAvailableServiceTypes();
req.onsuccess = function () {
- ok(true, "Get stats request ok");
- ok(req.result.connectionType == null, "connectionTypes should be null");
- ok(req.result.start.getTime() == startDate.getTime(), "starts should be equals");
- ok(req.result.end.getTime() == endDate.getTime(), "ends should be equals");
- var data = req.result.data;
- ok(Array.isArray(data) && data.length == samples,
- "data is an array of length " + samples);
- checkDataDates(data, startDate, endDate, sampleRate);
+ ok(true, "getAvailableServiceTypes request ok");
+ ok(Array.isArray(req.result) && req.result.length == 0,
+ "getAvailableServiceTypes returns an empty array");
next();
};
req.onerror = function () {
- ok(false, "Get stats for all connectionType failure!");
+ ok(false, "getAvailableServiceTypes failure!");
}
},
function () {
ok(true, "all done!\n");
- SpecialPowers.removePermission("networkstats-manage", document);
SimpleTest.finish();
return;
}
@@ -294,8 +321,24 @@ function next() {
SimpleTest.waitForExplicitFinish();
-SpecialPowers.addPermission("networkstats-manage", true, document);
-SpecialPowers.pushPrefEnv({'set': [["dom.mozNetworkStats.enabled", true]]}, test);
+SpecialPowers.pushPrefEnv({'set': [["dom.mozNetworkStats.enabled", true]]},
+ function() {
+ SpecialPowers.pushPermissions([{ 'type': 'networkstats-manage', 'allow': 1, 'context': window.document }],
+ function() {
+ ok(SpecialPowers.hasPermission("networkstats-manage", document),
+ "Has permission 'networkstats-manage'.");
+
+ ok(SpecialPowers.getBoolPref("dom.mozNetworkStats.enabled"),
+ "Preference 'dom.mozNetworkStats.enabled' is true.");
+
+ ok('mozNetworkStats' in navigator, "navigator.mozNetworkStats should exist");
+
+ ok(navigator.mozNetworkStats instanceof SpecialPowers.Ci.nsIDOMMozNetworkStatsManager,
+ "navigator.mozNetworkStats should be a nsIDOMMozNetworkStatsManager object");
+
+ test();
+ });
+});
</script>
</pre>
diff --git a/dom/network/tests/test_networkstats_disabled.html b/dom/network/tests/test_networkstats_disabled.html
index 6cd05c315..f515d778c 100644
--- a/dom/network/tests/test_networkstats_disabled.html
+++ b/dom/network/tests/test_networkstats_disabled.html
@@ -17,10 +17,15 @@
SimpleTest.waitForExplicitFinish();
// Test to ensure NetworkStats is not accessible when it is disabled
-SpecialPowers.pushPrefEnv({'set': [["dom.mozNetworkStats.enabled", false]]}, function(){
+SpecialPowers.pushPrefEnv({'set': [["dom.mozNetworkStats.enabled", false]]},
+ function() {
+ ok(!SpecialPowers.getBoolPref("dom.mozNetworkStats.enabled"),
+ "Preference 'dom.mozNetworkStats.enabled' is false.");
ok('mozNetworkStats' in navigator, "navigator.mozNetworkStats should exist");
- is(navigator.mozNetworkStats, null, "mozNetworkStats should be null when not enabled.");
+
+ is(navigator.mozNetworkStats, null,
+ "mozNetworkStats should be null when not enabled.");
SimpleTest.finish();
});
diff --git a/dom/network/tests/test_networkstats_enabled_no_perm.html b/dom/network/tests/test_networkstats_enabled_no_perm.html
index dd911c506..93c3e3e4c 100644
--- a/dom/network/tests/test_networkstats_enabled_no_perm.html
+++ b/dom/network/tests/test_networkstats_enabled_no_perm.html
@@ -12,23 +12,40 @@
<pre id="test">
<script type="application/javascript">
-// Test to ensure NetworkStats is enabled but mozNetworkStats.connectionTypes
-// does not work in content.
+// Test to ensure NetworkStats is enabled but mozNetworkStats.getAvailableNetworks
+// does not work in content because mozNetworkStats is null when no permission.
+ SimpleTest.waitForExplicitFinish();
+ SpecialPowers.pushPrefEnv({"set": [['dom.mozNetworkStats.enabled', true]]},
+ function() {
+ SpecialPowers.pushPermissions([{ 'type': 'networkstats-manage', 'allow': 0, 'context': window.document }], runTest);
+ });
-SpecialPowers.setBoolPref("dom.mozNetworkStats.enabled", true);
-SpecialPowers.removePermission("networkstats-manage", document);
+ function runTest() {
+ ok(SpecialPowers.getBoolPref("dom.mozNetworkStats.enabled"),
+ "Preference 'dom.mozNetworkStats.enabled' is true.");
-ok('mozNetworkStats' in navigator, "navigator.mozNetworkStats should be accessible if dom.mozNetworkStats.enabled is true");
+ ok(!SpecialPowers.hasPermission("networkstats-manage", document),
+ "Has no permission 'networkstats-manage'.");
-var error;
-try {
- navigator.mozNetworkStats.connectionTypes;
- ok(false, "Accessing navigator.mozNetworkStats.connectionTypes should have thrown!");
-} catch (ex) {
- error = ex;
-}
-ok(error, "Got an exception accessing navigator.mozNetworkStats.connectionTypes");
+ ok('mozNetworkStats' in navigator, "navigator.mozNetworkStats should exist");
+
+ is(navigator.mozNetworkStats, null,
+ "mozNetworkStats should be null when no permission.");
+
+ var error;
+ try {
+ navigator.mozNetworkStats.getAvailableNetworks;
+ ok(false,
+ "Accessing navigator.mozNetworkStats.getAvailableNetworks should throw!");
+ } catch (ex) {
+ error = ex;
+ }
+
+ ok(error,
+ "Got an exception accessing navigator.mozNetworkStats.getAvailableNetworks");
+ SimpleTest.finish();
+}
</script>
</pre>
</body>
diff --git a/dom/network/tests/test_networkstats_enabled_perm.html b/dom/network/tests/test_networkstats_enabled_perm.html
index 7e24905d9..ceb32abe4 100644
--- a/dom/network/tests/test_networkstats_enabled_perm.html
+++ b/dom/network/tests/test_networkstats_enabled_perm.html
@@ -16,16 +16,25 @@
SimpleTest.waitForExplicitFinish();
-// Test to ensure NetworkStats is not accessible when it is disabled
-SpecialPowers.addPermission("networkstats-manage", true, document);
-SpecialPowers.pushPrefEnv({'set': [["dom.mozNetworkStats.enabled", true]]}, function(){
+// Test to ensure NetworkStats is not accessible when it is disabled.
+SpecialPowers.pushPrefEnv({'set': [["dom.mozNetworkStats.enabled", true]]},
+ function() {
+ SpecialPowers.pushPermissions([{ 'type': 'networkstats-manage', 'allow': 1, 'context': window.document }],
+ function() {
- ok('mozNetworkStats' in navigator, "navigator.mozNetworkStats should exist");
- ok(navigator.mozNetworkStats instanceof SpecialPowers.Ci.nsIDOMMozNetworkStatsManager,
- "navigator.mozNetworkStats should be a nsIDOMMozNetworkStatsManager object");
+ ok(SpecialPowers.hasPermission("networkstats-manage", document),
+ "Has permission 'networkstats-manage'.");
- SpecialPowers.removePermission("networkstats-manage", document);
- SimpleTest.finish();
+ ok(SpecialPowers.getBoolPref("dom.mozNetworkStats.enabled"),
+ "Preference 'dom.mozNetworkStats.enabled' is true.");
+
+ ok('mozNetworkStats' in navigator, "navigator.mozNetworkStats should exist");
+
+ ok(navigator.mozNetworkStats instanceof SpecialPowers.Ci.nsIDOMMozNetworkStatsManager,
+ "navigator.mozNetworkStats should be a nsIDOMMozNetworkStatsManager object");
+
+ SimpleTest.finish();
+ });
});
</script>
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 000000000..bff6071f3
--- /dev/null
+++ b/dom/network/tests/test_tcpsocket_client_and_server_basics.html
@@ -0,0 +1,27 @@
+<!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 type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <script type="application/javascript" src="add_task.js"></script>
+ <script type="application/javascript;version=1.7" 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 000000000..30f83dfdd
--- /dev/null
+++ b/dom/network/tests/test_tcpsocket_client_and_server_basics.js
@@ -0,0 +1,363 @@
+'use strict';
+
+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.open is called or the onconnect method is being
+ * invoked.
+ */
+function listenForEventsOnSocket(socket, socketType) {
+ let wantDataLength = null;
+ 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 = handleGenericEvent;
+ socket.ondata = function(event) {
+ dump('(' + socketType + ' event: ' + event.type + ' length: ' +
+ event.data.byteLength + ')\n');
+ 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: function() {
+ 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: function(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;
+ });
+ }
+ };
+}
+
+/**
+ * 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(socket) {
+ // 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: socket,
+ queue: listenForEventsOnSocket(socket, 'server')
+ });
+ };
+ });
+}
+
+function defer() {
+ var deferred = {};
+ deferred.promise = new Promise(function(resolve, reject) {
+ deferred.resolve = resolve;
+ deferred.reject = reject;
+ });
+ return deferred;
+}
+
+
+function* test_basics() {
+ // Enable our use of TCPSocket
+ let prefDeferred = defer();
+ SpecialPowers.pushPrefEnv(
+ { set: [ ['dom.mozTCPSocket.enabled', true] ] },
+ prefDeferred.resolve);
+ yield prefDeferred.promise;
+
+ let permDeferred = defer();
+ SpecialPowers.pushPermissions(
+ [ { type: 'tcp-socket', allow: true, context: document } ],
+ permDeferred.resolve);
+ yield permDeferred.promise;
+
+ // 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;
+
+ let TCPSocket = navigator.mozTCPSocket;
+ // - Start up a listening socket.
+ let listeningServer = TCPSocket.listen(serverPort,
+ { binaryType: 'arraybuffer' },
+ SERVER_BACKLOG);
+
+ let connectedPromise = waitForConnection(listeningServer);
+
+ // -- Open a connection to the server
+ let clientSocket = TCPSocket.open('127.0.0.1', serverPort,
+ { binaryType: 'arraybuffer' });
+ let clientQueue = listenForEventsOnSocket(clientSocket, 'client');
+
+ // (the client connects)
+ is((yield clientQueue.waitForEvent()).type, 'open', 'got open event');
+ is(clientSocket.readyState, 'open', 'client readyState is open');
+
+ // (the server connected)
+ let { socket: serverSocket, queue: serverQueue } = yield 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 = yield 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 = yield 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 = yield 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 = yield 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;
+ }
+ // Do this twice so we have confidence that the 'drain' event machinery
+ // doesn't break after the first use.
+ for (let iSend = 0; iSend < 2; iSend++) {
+ // - Send "big" data from the client to the server
+ is(clientSocket.send(bigUint8Array.buffer, 0, bigUint8Array.length), false,
+ 'Client sending more than 64k should result in the buffer being full.');
+ is((yield clientQueue.waitForEvent()).type, 'drain',
+ 'The drain event should fire after a large send that indicated full.');
+
+ serverReceived = yield serverQueue.waitForDataWithAtLeastLength(
+ bigUint8Array.length);
+ assertUint8ArraysEqual(serverReceived, bigUint8Array,
+ 'server received/client sent');
+
+ // - Send "big" data from the server to the client
+ is(serverSocket.send(bigUint8Array.buffer, 0, bigUint8Array.length), false,
+ 'Server sending more than 64k should result in the buffer being full.');
+ is((yield serverQueue.waitForEvent()).type, 'drain',
+ 'The drain event should fire after a large send that indicated full.');
+
+ clientReceived = yield 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((yield 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((yield 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 = TCPSocket.open('127.0.0.1', serverPort,
+ { binaryType: 'arraybuffer' });
+ clientQueue = listenForEventsOnSocket(clientSocket, 'client');
+ is((yield clientQueue.waitForEvent()).type, 'open', 'got open event');
+
+ let connectedResult = yield 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((yield 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((yield 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 = TCPSocket.open('127.0.0.1', serverPort,
+ { binaryType: 'arraybuffer' });
+ clientQueue = listenForEventsOnSocket(clientSocket, 'client');
+ is((yield clientQueue.waitForEvent()).type, 'open', 'got open event');
+
+ connectedResult = yield 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((yield clientQueue.waitForEvent()).type, 'drain',
+ 'The drain event should fire after a large send that returned true.');
+ // Then we'll get a close
+ is((yield clientQueue.waitForEvent()).type, 'close',
+ 'The close event should fire after the drain event.');
+
+ // The server will get its data
+ serverReceived = yield serverQueue.waitForDataWithAtLeastLength(
+ bigUint8Array.length);
+ assertUint8ArraysEqual(serverReceived, bigUint8Array,
+ 'server received/client sent');
+ // And a close.
+ is((yield serverQueue.waitForEvent()).type, 'close',
+ 'The drain event should fire after a large send that returned true.');
+
+
+ // -- 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();
+
+ // - try and connect, get an error
+ clientSocket = TCPSocket.open('127.0.0.1', serverPort,
+ { binaryType: 'arraybuffer' });
+ clientQueue = listenForEventsOnSocket(clientSocket, 'client');
+ is((yield 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);
diff --git a/dom/network/tests/test_tcpsocket_enabled_no_perm.html b/dom/network/tests/test_tcpsocket_enabled_no_perm.html
index a028b48d6..80974a801 100644
--- a/dom/network/tests/test_tcpsocket_enabled_no_perm.html
+++ b/dom/network/tests/test_tcpsocket_enabled_no_perm.html
@@ -16,19 +16,19 @@
navigator.mozTCPSocket, but mozTCPSocket.open does not work
in content.
**/
-SpecialPowers.setBoolPref("dom.mozTCPSocket.enabled", true);
+SimpleTest.waitForExplicitFinish();
+SpecialPowers.pushPrefEnv({"set": [['dom.mozTCPSocket.enabled', true]]}, runTest);
+function runTest() {
+ ok('mozTCPSocket' in navigator, "navigator.mozTCPSocket should be accessible if dom.mozTCPSocket.enabled is true");
-ok('mozTCPSocket' in navigator, "navigator.mozTCPSocket should be accessible if dom.mozTCPSocket.enabled is true");
-
-try {
- navigator.mozTCPSocket.open('localhost', 80);
- throw new Error("Error: navigator.mozTCPSocket.open should raise for content that does not have the tcp-socket permission");
-} catch (e) {
- ok(true, "navigator.mozTCPSocket.open should raise for content that does not have the tcp-socket permission");
+ try {
+ navigator.mozTCPSocket.open('localhost', 80);
+ throw new Error("Error: navigator.mozTCPSocket.open should raise for content that does not have the tcp-socket permission");
+ } catch (e) {
+ ok(true, "navigator.mozTCPSocket.open should raise for content that does not have the tcp-socket permission");
+ }
+ SimpleTest.finish();
}
-
-SpecialPowers.setBoolPref("dom.mozTCPSocket.enabled", false);
-
</script>
</pre>
</body>
diff --git a/dom/network/tests/test_tcpsocket_enabled_with_perm.html b/dom/network/tests/test_tcpsocket_enabled_with_perm.html
index 4490743e4..1b2680981 100644
--- a/dom/network/tests/test_tcpsocket_enabled_with_perm.html
+++ b/dom/network/tests/test_tcpsocket_enabled_with_perm.html
@@ -16,15 +16,16 @@
navigator.mozTCPSocket, and mozTCPSocket.open works when
the tcp-socket permission has been granted.
**/
-SpecialPowers.setBoolPref("dom.mozTCPSocket.enabled", true);
-SpecialPowers.addPermission("tcp-socket", true, document);
+SimpleTest.waitForExplicitFinish();
+SpecialPowers.pushPrefEnv({"set": [['dom.mozTCPSocket.enabled', true]]}, runTest);
+function runTest() {
+ SpecialPowers.addPermission("tcp-socket", true, document);
-ok('mozTCPSocket' in navigator, "navigator.mozTCPSocket should be accessible if dom.mozTCPSocket.enabled is true");
-
-ok(navigator.mozTCPSocket.open('localhost', 80), "navigator.mozTCPSocket.open should work for content that has the tcp-socket permission");
-
-SpecialPowers.setBoolPref("dom.mozTCPSocket.enabled", false);
+ ok('mozTCPSocket' in navigator, "navigator.mozTCPSocket should be accessible if dom.mozTCPSocket.enabled is true");
+ ok(navigator.mozTCPSocket.open('localhost', 80), "navigator.mozTCPSocket.open should work for content that has the tcp-socket permission");
+ SimpleTest.finish();
+}
</script>
</pre>
</body>
diff --git a/dom/network/tests/test_udpsocket.html b/dom/network/tests/test_udpsocket.html
new file mode 100644
index 000000000..67478b76e
--- /dev/null
+++ b/dom/network/tests/test_udpsocket.html
@@ -0,0 +1,408 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test UDPSocket API</title>
+ <script type="application/javascript" 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>
+<iframe id="iframe"></iframe>
+<pre id="test">
+<script type="application/javascript;version=1.8">
+'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 recv_callback(msg) {
+ socket.removeEventListener('message', recv_callback);
+ 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);
+ });
+ });
+}
+
+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 recv_callback(msg) {
+ socket.removeEventListener('message', recv_callback);
+ 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);
+ });
+ });
+}
+
+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 recv_callback(msg) {
+ socket.removeEventListener('message', recv_callback);
+ 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);
+ });
+ });
+}
+
+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 recv_callback(msg) {
+ socket.removeEventListener('message', recv_callback);
+ 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);
+ });
+ });
+}
+
+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 recv_callback(msg) {
+ socket.removeEventListener('message', recv_callback);
+ 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);
+ });
+ });
+ });
+}
+
+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 recv_callback(msg) {
+ socket.removeEventListener('message', recv_callback);
+ 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();
+ });
+ });
+ });
+}
+
+function testInvalidUDPOptions() {
+ info('test for invalid UDPOptions');
+ try {
+ let socket = 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 {
+ let socket = 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 {
+ let socket = 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 testBFCache() {
+ info('test for bfcache behavior');
+
+ let socket = new UDPSocket();
+
+ return socket.opened.then(function() {
+ let iframe = document.getElementById('iframe');
+ SpecialPowers.wrap(iframe).mozbrowser = true;
+ iframe.src = 'file_udpsocket_iframe.html?' + socket.localPort;
+
+ return new Promise(function(resolve, reject) {
+ socket.addEventListener('message', function recv_callback(msg) {
+ socket.removeEventListener('message', recv_callback);
+ iframe.src = 'about:blank';
+ iframe.addEventListener('load', function onload() {
+ iframe.removeEventListener('load', onload);
+ socket.send(HELLO_WORLD, '127.0.0.1', msg.remotePort);
+
+ function recv_again_callback(msg) {
+ socket.removeEventListener('message', recv_again_callback);
+ ok(false, 'should not receive packet after page unload');
+ }
+
+ socket.addEventListener('message', recv_again_callback);
+
+ let timeout = setTimeout(function() {
+ socket.removeEventListener('message', recv_again_callback);
+ socket.close();
+ resolve();
+ }, 5000);
+ });
+ });
+ });
+ });
+}
+
+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.pushPermissions([
+ {type: 'udp-socket', allow: true, context: document}], 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/unit/test_multisend.js b/dom/network/tests/unit/test_multisend.js
deleted file mode 100644
index 7f15f4a02..000000000
--- a/dom/network/tests/unit/test_multisend.js
+++ /dev/null
@@ -1,152 +0,0 @@
-const Cc = Components.classes;
-const Ci = Components.interfaces;
-const Cr = Components.results;
-const Cu = Components.utils;
-const CC = Components.Constructor;
-
-Cu.import("resource://gre/modules/Services.jsm");
-
-const ServerSocket = CC("@mozilla.org/network/server-socket;1",
- "nsIServerSocket",
- "init"),
- InputStreamPump = CC("@mozilla.org/network/input-stream-pump;1",
- "nsIInputStreamPump",
- "init"),
- BinaryInputStream = CC("@mozilla.org/binaryinputstream;1",
- "nsIBinaryInputStream",
- "setInputStream"),
- BinaryOutputStream = CC("@mozilla.org/binaryoutputstream;1",
- "nsIBinaryOutputStream",
- "setOutputStream"),
- TCPSocket = new (CC("@mozilla.org/tcp-socket;1",
- "nsIDOMTCPSocket"))();
-
-var server = null, sock = null;
-
-/**
- * Spin up a listening socket and associate at most one live, accepted socket
- * with ourselves.
- */
-function TestServer() {
- this.listener = ServerSocket(-1, true, -1);
- do_print('server: listening on', this.listener.port);
- this.listener.asyncListen(this);
-
- this.binaryInput = null;
- this.input = null;
- this.binaryOutput = null;
- this.output = null;
-
- this.onaccept = null;
- this.ondata = null;
- this.onclose = null;
-}
-
-TestServer.prototype = {
- onSocketAccepted: function(socket, trans) {
- if (this.input)
- do_throw("More than one live connection!?");
-
- do_print('server: got client connection');
- this.input = trans.openInputStream(0, 0, 0);
- this.binaryInput = new BinaryInputStream(this.input);
- this.output = trans.openOutputStream(0, 0, 0);
- this.binaryOutput = new BinaryOutputStream(this.output);
-
- new InputStreamPump(this.input, -1, -1, 0, 0, false).asyncRead(this, null);
-
- if (this.onaccept)
- this.onaccept();
- else
- do_throw("Received unexpected connection!");
- },
-
- onStopListening: function(socket) {
- },
-
- onDataAvailable: function(request, context, inputStream, offset, count) {
- var readData = this.binaryInput.readByteArray(count);
- if (this.ondata) {
- try {
- this.ondata(readData);
- } catch(ex) {
- // re-throw if this is from do_throw
- if (ex === Cr.NS_ERROR_ABORT)
- throw ex;
- // log if there was a test problem
- do_print('Caught exception: ' + ex + '\n' + ex.stack);
- do_throw('test is broken; bad ondata handler; see above');
- }
- } else {
- do_throw('Received ' + count + ' bytes of unexpected data!');
- }
- },
-
- onStartRequest: function(request, context) {
- },
-
- onStopRequest: function(request, context, status) {
- if (this.onclose)
- this.onclose();
- else
- do_throw("Received unexpected close!");
- },
-
- close: function() {
- this.binaryInput.close();
- this.binaryOutput.close();
- },
-
- /**
- * Forget about the socket we knew about before.
- */
- reset: function() {
- this.binaryInput = null;
- this.input = null;
- this.binaryOutput = null;
- this.output = null;
- },
-};
-
-function run_test() {
- Services.prefs.setBoolPref('dom.mozTCPSocket.enabled', true);
-
- do_test_pending();
-
- server = new TestServer();
- server.reset();
- sock = TCPSocket.open(
- '127.0.0.1', server.listener.port,
- { binaryType: 'arraybuffer' });
-
- var encoder = new TextEncoder();
- var ok = encoder.encode("OKBYE");
-
- var expected = ['O', 'K', 'B', 'Y', 'E']
- .map(function(c) { return c.charCodeAt(0); });
- var seenData = [];
-
- server.onaccept = function() {};
- server.ondata = function(data) {
- do_print(data + ":" + data.length);
-
- seenData = seenData.concat(data);
-
- if (seenData.length == expected.length) {
- do_print(expected);
- do_check_eq(seenData.length, expected.length);
- for (var i = 0; i < seenData.length; i++) {
- do_check_eq(seenData[i], expected[i]);
- }
- sock.close();
- server.close();
- do_test_finished();
- }
- };
- server.onclose = function() {};
-
- sock.onopen = function() {
- sock.send(ok.buffer, 0, 2);
- sock.send(ok.buffer, 2, 3);
- };
-} \ No newline at end of file
diff --git a/dom/network/tests/unit/test_tcpsocket.js b/dom/network/tests/unit/test_tcpsocket.js
index c643ee837..a0c823124 100644
--- a/dom/network/tests/unit/test_tcpsocket.js
+++ b/dom/network/tests/unit/test_tcpsocket.js
@@ -74,10 +74,9 @@ Cu.import("resource://gre/modules/Services.jsm");
*
*/
-function get_platform() {
- var xulRuntime = Components.classes["@mozilla.org/xre/app-info;1"]
- .getService(Components.interfaces.nsIXULRuntime);
- return xulRuntime.OS;
+function is_content() {
+ return this._inChild = Cc["@mozilla.org/xre/app-info;1"].getService(Ci.nsIXULRuntime)
+ .processType != Ci.nsIXULRuntime.PROCESS_TYPE_DEFAULT;
}
/**
@@ -86,7 +85,7 @@ function get_platform() {
*/
function TestServer() {
this.listener = ServerSocket(-1, true, -1);
- do_print('server: listening on', this.listener.port);
+ do_print('server: listening on ' + this.listener.port);
this.listener.asyncListen(this);
this.binaryInput = null;
@@ -94,7 +93,7 @@ function TestServer() {
this.binaryOutput = null;
this.output = null;
- this.onaccept = null;
+ this.onconnect = null;
this.ondata = null;
this.onclose = null;
}
@@ -112,8 +111,8 @@ TestServer.prototype = {
new InputStreamPump(this.input, -1, -1, 0, 0, false).asyncRead(this, null);
- if (this.onaccept)
- this.onaccept();
+ if (this.onconnect)
+ this.onconnect();
else
do_throw("Received unexpected connection!");
},
@@ -280,171 +279,11 @@ function connectSock() {
sock.onerror = makeFailureCase('error');
sock.onclose = makeFailureCase('close');
- server.onaccept = yayFuncs.serveropen;
+ server.onconnect = yayFuncs.serveropen;
server.ondata = makeFailureCase('serverdata');
server.onclose = makeFailureCase('serverclose');
}
-/**
- * Test that sending a small amount of data works, and that buffering
- * does not take place for this small amount of data.
- */
-
-function sendData() {
- server.ondata = makeExpectData('serverdata', DATA_ARRAY);
- if (!sock.send(DATA_ARRAY_BUFFER)) {
- do_throw("send should not have buffered such a small amount of data");
- }
-}
-
-/**
- * Test that sending a large amount of data works, that buffering
- * takes place (send returns true), and that ondrain is called once
- * the data has been sent.
- */
-
-function sendBig() {
- var yays = makeJointSuccess(['serverdata', 'clientdrain']),
- amount = 0;
-
- server.ondata = function (data) {
- amount += data.length;
- if (amount === BIG_TYPED_ARRAY.length) {
- yays.serverdata();
- }
- };
- sock.ondrain = function(evt) {
- if (sock.bufferedAmount) {
- do_throw("sock.bufferedAmount was > 0 in ondrain");
- }
- yays.clientdrain(evt);
- }
- if (sock.send(BIG_ARRAY_BUFFER)) {
- do_throw("expected sock.send to return false on large buffer send");
- }
-}
-
-/**
- * Test that data sent from the server correctly fires the ondata
- * callback on the client side.
- */
-
-function receiveData() {
- server.ondata = makeFailureCase('serverdata');
- sock.ondata = makeExpectData('data', DATA_ARRAY, true);
-
- server.binaryOutput.writeByteArray(DATA_ARRAY, DATA_ARRAY.length);
-}
-
-/**
- * Test that when the server closes the connection, the onclose callback
- * is fired on the client side.
- */
-
-function serverCloses() {
- // we don't really care about the server's close event, but we do want to
- // make sure it happened for sequencing purposes.
- var yayFuncs = makeJointSuccess(['clientclose', 'serverclose']);
- sock.ondata = makeFailureCase('data');
- sock.onclose = yayFuncs.clientclose;
- server.onclose = yayFuncs.serverclose;
-
- server.close();
-}
-
-/**
- * Test that when the client closes the connection, the onclose callback
- * is fired on the server side.
- */
-
-function clientCloses() {
- // we want to make sure the server heard the close and also that the client's
- // onclose event fired for consistency.
- var yayFuncs = makeJointSuccess(['clientclose', 'serverclose']);
- server.onclose = yayFuncs.serverclose;
- sock.onclose = yayFuncs.clientclose;
-
- sock.close();
-}
-
-/**
- * Send a large amount of data and immediately call close
- */
-
-function bufferedClose() {
- var yays = makeJointSuccess(['serverdata', 'clientclose', 'serverclose']);
- server.ondata = makeExpectData(
- "ondata", BIG_TYPED_ARRAY, false, yays.serverdata);
- server.onclose = yays.serverclose;
- sock.onclose = yays.clientclose;
- sock.send(BIG_ARRAY_BUFFER);
- sock.close();
-}
-
-/**
- * Connect to a port we know is not listening so an error is assured,
- * and make sure that onerror and onclose are fired on the client side.
- */
-
-function badConnect() {
- // There's probably nothing listening on tcp port 2.
- sock = TCPSocket.open('127.0.0.1', 2);
-
- sock.onopen = makeFailureCase('open');
- sock.ondata = makeFailureCase('data');
-
- let success = makeSuccessCase('error');
- let gotError = false;
- sock.onerror = function(event) {
- do_check_eq(event.data.name, 'ConnectionRefusedError');
- gotError = true;
- };
- sock.onclose = function() {
- if (!gotError)
- do_throw('got close without error!');
- else
- success();
- };
-}
-
-/**
- * Test that calling send with enough data to buffer causes ondrain to
- * be invoked once the data has been sent, and then test that calling send
- * and buffering again causes ondrain to be fired again.
- */
-
-function drainTwice() {
- let yays = makeJointSuccess(
- ['ondrain', 'ondrain2',
- 'ondata', 'ondata2',
- 'serverclose', 'clientclose']);
-
- function serverSideCallback() {
- yays.ondata();
- server.ondata = makeExpectData(
- "ondata2", BIG_TYPED_ARRAY_2, false, yays.ondata2);
-
- sock.ondrain = yays.ondrain2;
-
- if (sock.send(BIG_ARRAY_BUFFER_2)) {
- do_throw("sock.send(BIG_TYPED_ARRAY_2) did not return false to indicate buffering");
- }
-
- sock.close();
- }
-
- server.onclose = yays.serverclose;
- server.ondata = makeExpectData(
- "ondata", BIG_TYPED_ARRAY, false, serverSideCallback);
-
- sock.onclose = yays.clientclose;
- sock.ondrain = yays.ondrain;
-
- if (sock.send(BIG_ARRAY_BUFFER)) {
- throw new Error("sock.send(BIG_TYPED_ARRAY) did not return false to indicate buffering");
- }
-}
-
function cleanup() {
do_print("Cleaning up");
sock.close();
@@ -453,68 +292,74 @@ function cleanup() {
run_next_test();
}
-/**
- * Test that calling send with enough data to buffer twice in a row without
- * waiting for ondrain still results in ondrain being invoked at least once.
- */
-
-function bufferTwice() {
- let yays = makeJointSuccess(
- ['ondata', 'ondrain', 'serverclose', 'clientclose']);
+// Test child behavior when child thinks it's buffering but parent doesn't
+// buffer.
+// 1. set bufferedAmount of content socket to a value that will make next
+// send() call return false.
+// 2. send a small data to make send() return false, but it won't make
+// parent buffer.
+// 3. we should get a ondrain.
+function childbuffered() {
+ let yays = makeJointSuccess(['ondrain', 'serverdata',
+ 'clientclose', 'serverclose']);
+ sock.ondrain = function() {
+ yays.ondrain();
+ sock.close();
+ };
- let double_array = new Uint8Array(BIG_ARRAY.concat(BIG_ARRAY_2));
server.ondata = makeExpectData(
- "ondata", double_array, false, yays.ondata);
+ 'ondata', DATA_ARRAY, false, yays.serverdata);
- server.onclose = yays.serverclose;
- sock.onclose = yays.clientclose;
-
- sock.ondrain = function () {
- sock.close();
- yays.ondrain();
+ let internalSocket = sock.QueryInterface(Ci.nsITCPSocketInternal);
+ internalSocket.updateBufferedAmount(65535, // almost reach buffering threshold
+ 0);
+ if (sock.send(DATA_ARRAY_BUFFER)) {
+ do_throw("expected sock.send to return false.");
}
+ sock.onclose = yays.clientclose;
+ server.onclose = yays.serverclose;
+}
+
+// Test child's behavior when send() of child return true but parent buffers
+// data.
+// 1. send BIG_ARRAY to make parent buffer. This would make child wait for
+// drain as well.
+// 2. set child's bufferedAmount to zero, so child will no longer wait for
+// drain but parent will dispatch a drain event.
+// 3. wait for 1 second, to make sure there's no ondrain event dispatched in
+// child.
+function childnotbuffered() {
+ let yays = makeJointSuccess(['serverdata', 'clientclose', 'serverclose']);
+ server.ondata = makeExpectData('ondata', BIG_ARRAY, false, yays.serverdata);
if (sock.send(BIG_ARRAY_BUFFER)) {
- throw new Error("sock.send(BIG_TYPED_ARRAY) did not return false to indicate buffering");
+ do_throw("sock.send(BIG_TYPED_ARRAY) did not return false to indicate buffering");
}
- if (sock.send(BIG_ARRAY_BUFFER_2)) {
- throw new Error("sock.send(BIG_TYPED_ARRAY_2) did not return false to indicate buffering on second synchronous call to send");
- }
-}
+ let internalSocket = sock.QueryInterface(Ci.nsITCPSocketInternal);
+ internalSocket.updateBufferedAmount(0, // setting zero will clear waitForDrain in sock.
+ 1);
-// - connect, data and events work both ways
-add_test(connectSock);
-add_test(sendData);
-add_test(sendBig);
-add_test(receiveData);
-// - server closes on us
-add_test(serverCloses);
-
-// - connect, we close on the server
-add_test(connectSock);
-add_test(clientCloses);
-
-// - connect, buffer, close
-add_test(connectSock);
-add_test(bufferedClose);
-
-if (get_platform() !== "Darwin") {
- // This test intermittently fails way too often on OS X, for unknown reasons.
- // Please, diagnose and fix it if you can.
- // - get an error on an attempt to connect to a non-listening port
- add_test(badConnect);
-}
+ // shouldn't get ondrain, even after parent have cleared its buffer.
+ sock.ondrain = makeFailureCase('drain');
+ sock.onclose = yays.clientclose;
+ server.onclose = yays.serverclose;
+ do_timeout(1000, function() {
+ sock.close();
+ });
+};
-// send a buffer, get a drain, send a buffer, get a drain
-add_test(connectSock);
-add_test(drainTwice);
+if (is_content()) {
+ add_test(connectSock);
+ add_test(childnotbuffered);
-// send a buffer, get a drain, send a buffer, get a drain
-add_test(connectSock);
-add_test(bufferTwice);
+ add_test(connectSock);
+ add_test(childbuffered);
-// clean up
-add_test(cleanup);
+ // clean up
+ add_test(cleanup);
+} else {
+ do_check_true(true, 'non-content process wants to pretend to one test');
+}
function run_test() {
if (!gInChild)
diff --git a/dom/network/tests/unit/xpcshell.ini b/dom/network/tests/unit/xpcshell.ini
index 84bb4c138..082f5e7b6 100644
--- a/dom/network/tests/unit/xpcshell.ini
+++ b/dom/network/tests/unit/xpcshell.ini
@@ -1,6 +1,6 @@
[DEFAULT]
head =
tail =
+skip-if = toolkit == 'gonk'
[test_tcpsocket.js]
-[test_multisend.js] \ No newline at end of file
diff --git a/dom/network/tests/unit_ipc/xpcshell.ini b/dom/network/tests/unit_ipc/xpcshell.ini
index c5d850058..cf4a3c519 100644
--- a/dom/network/tests/unit_ipc/xpcshell.ini
+++ b/dom/network/tests/unit_ipc/xpcshell.ini
@@ -1,5 +1,6 @@
[DEFAULT]
head =
tail =
+skip-if = toolkit == 'android' || toolkit == 'gonk'
[test_tcpsocket_ipc.js]
diff --git a/dom/network/tests/unit_stats/test_networkstats_db.js b/dom/network/tests/unit_stats/test_networkstats_db.js
index 5ea440b86..16f203050 100644
--- a/dom/network/tests/unit_stats/test_networkstats_db.js
+++ b/dom/network/tests/unit_stats/test_networkstats_db.js
@@ -1,11 +1,38 @@
-/* Any copyright is dedicated to the Public Domain.
+/* Any: copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
const {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components;
Cu.import("resource://gre/modules/NetworkStatsDB.jsm");
-const netStatsDb = new NetworkStatsDB(this);
+const STATS_STORE_NAME = "net_stats_store_v3";
+const netStatsDb = new NetworkStatsDB();
+
+function clearStore(store, callback) {
+ netStatsDb.dbNewTxn(store, "readwrite", function(aTxn, aStore) {
+ aStore.openCursor().onsuccess = function (event) {
+ let cursor = event.target.result;
+ if (cursor){
+ cursor.delete();
+ cursor.continue();
+ }
+ };
+ }, callback);
+}
+
+function getNetworkId(aIccId, aNetworkType) {
+ return aIccId + '' + aNetworkType;
+}
+
+add_test(function prepareDatabase() {
+ // Clear whole database to avoid starting tests with unknown state
+ // due to the previous tests.
+ clearStore(STATS_STORE_NAME, function() {
+ clearStore('net_alarm', function() {
+ run_next_test();
+ });
+ });
+});
function filterTimestamp(date) {
var sampleRate = netStatsDb.sampleRate;
@@ -13,6 +40,15 @@ function filterTimestamp(date) {
return Math.floor((date.getTime() - offset) / sampleRate) * sampleRate;
}
+function getNetworks() {
+ return [{ id: '0', type: Ci.nsIDOMMozNetworkStatsManager.WIFI },
+ { id: '1234', type: Ci.nsIDOMMozNetworkStatsManager.MOBILE }];
+}
+
+function compareNetworks(networkA, networkB) {
+ return (networkA[0] == networkB[0] && networkA[1] == networkB[1]);
+}
+
add_test(function test_sampleRate() {
var sampleRate = netStatsDb.sampleRate;
do_check_true(sampleRate > 0);
@@ -89,21 +125,45 @@ add_test(function test_fillResultSamples_noEmptyData() {
});
add_test(function test_clear() {
- netStatsDb.clear(function (error, result) {
+ var networks = getNetworks();
+ networks.forEach(function(network, index) {
+ networks[index] = {network: network, networkId: getNetworkId(network.id, network.type)};
+ }, this);
+
+ netStatsDb.clearStats(networks, function (error, result) {
+ do_check_eq(error, null);
+ run_next_test();
+ });
+});
+
+add_test(function test_clear_interface() {
+ var networks = getNetworks();
+ networks.forEach(function(network, index) {
+ networks[index] = {network: network, networkId: getNetworkId(network.id, network.type)};
+ }, this);
+
+ netStatsDb.clearInterfaceStats(networks[0], function (error, result) {
do_check_eq(error, null);
run_next_test();
});
});
add_test(function test_internalSaveStats_singleSample() {
- var stats = {connectionType: "wifi",
- timestamp: Date.now(),
- rxBytes: 0,
- txBytes: 0,
- rxTotalBytes: 1234,
- txTotalBytes: 1234};
-
- netStatsDb.dbNewTxn("readwrite", function(txn, store) {
+ var networks = getNetworks();
+
+ var stats = { appId: 0,
+ isInBrowser: 0,
+ serviceType: "",
+ network: [networks[0].id, networks[0].type],
+ timestamp: Date.now(),
+ rxBytes: 0,
+ txBytes: 0,
+ rxSystemBytes: 1234,
+ txSystemBytes: 1234,
+ rxTotalBytes: 1234,
+ txTotalBytes: 1234 };
+
+ netStatsDb.dbNewTxn(STATS_STORE_NAME, "readwrite", function(txn, store) {
netStatsDb._saveStats(txn, store, stats);
}, function(error, result) {
do_check_eq(error, null);
@@ -111,10 +171,15 @@ add_test(function test_internalSaveStats_singleSample() {
netStatsDb.logAllRecords(function(error, result) {
do_check_eq(error, null);
do_check_eq(result.length, 1);
- do_check_eq(result[0].connectionType, stats.connectionType);
+ do_check_eq(result[0].appId, stats.appId);
+ do_check_eq(result[0].isInBrowser, stats.isInBrowser);
+ do_check_eq(result[0].serviceType, stats.serviceType);
+ do_check_true(compareNetworks(result[0].network, stats.network));
do_check_eq(result[0].timestamp, stats.timestamp);
do_check_eq(result[0].rxBytes, stats.rxBytes);
do_check_eq(result[0].txBytes, stats.txBytes);
+ do_check_eq(result[0].rxSystemBytes, stats.rxSystemBytes);
+ do_check_eq(result[0].txSystemBytes, stats.txSystemBytes);
do_check_eq(result[0].rxTotalBytes, stats.rxTotalBytes);
do_check_eq(result[0].txTotalBytes, stats.txTotalBytes);
run_next_test();
@@ -123,35 +188,46 @@ add_test(function test_internalSaveStats_singleSample() {
});
add_test(function test_internalSaveStats_arraySamples() {
- netStatsDb.clear(function (error, result) {
- do_check_eq(error, null);
+ clearStore(STATS_STORE_NAME, function() {
+ var networks = getNetworks();
+ var network = [networks[0].id, networks[0].type];
var samples = 2;
var stats = [];
for (var i = 0; i < samples; i++) {
- stats.push({connectionType: "wifi",
- timestamp: Date.now() + (10 * i),
- rxBytes: 0,
- txBytes: 0,
- rxTotalBytes: 1234,
- txTotalBytes: 1234});
+ stats.push({ appId: 0,
+ isInBrowser: 0,
+ serviceType: "",
+ network: network,
+ timestamp: Date.now() + (10 * i),
+ rxBytes: 0,
+ txBytes: 0,
+ rxSystemBytes: 1234,
+ txSystemBytes: 1234,
+ rxTotalBytes: 1234,
+ txTotalBytes: 1234 });
}
- netStatsDb.dbNewTxn("readwrite", function(txn, store) {
+ netStatsDb.dbNewTxn(STATS_STORE_NAME, "readwrite", function(txn, store) {
netStatsDb._saveStats(txn, store, stats);
}, function(error, result) {
do_check_eq(error, null);
netStatsDb.logAllRecords(function(error, result) {
do_check_eq(error, null);
- do_check_eq(result.length, samples);
+ do_check_eq(result.length, samples);
var success = true;
for (var i = 0; i < samples; i++) {
- if (result[i].connectionType != stats[i].connectionType ||
+ if (result[i].appId != stats[i].appId ||
+ result[i].isInBrowser != stats[i].isInBrowser ||
+ result[i].serviceType != stats[i].serviceType ||
+ !compareNetworks(result[i].network, stats[i].network) ||
result[i].timestamp != stats[i].timestamp ||
result[i].rxBytes != stats[i].rxBytes ||
result[i].txBytes != stats[i].txBytes ||
+ result[i].rxSystemBytes != stats[i].rxSystemBytes ||
+ result[i].txSystemBytes != stats[i].txSystemBytes ||
result[i].rxTotalBytes != stats[i].rxTotalBytes ||
result[i].txTotalBytes != stats[i].txTotalBytes) {
success = false;
@@ -166,26 +242,32 @@ add_test(function test_internalSaveStats_arraySamples() {
});
add_test(function test_internalRemoveOldStats() {
- netStatsDb.clear(function (error, result) {
- do_check_eq(error, null);
-
+ clearStore(STATS_STORE_NAME, function() {
+ var networks = getNetworks();
+ var network = [networks[0].id, networks[0].type];
var samples = 10;
var stats = [];
for (var i = 0; i < samples - 1; i++) {
- stats.push({connectionType: "wifi", timestamp: Date.now() + (10 * i),
- rxBytes: 0, txBytes: 0,
- rxTotalBytes: 1234, txTotalBytes: 1234});
+ stats.push({ appId: 0, isInBrowser: 0,
+ serviceType: "",
+ network: network, timestamp: Date.now() + (10 * i),
+ rxBytes: 0, txBytes: 0,
+ rxSystemBytes: 1234, txSystemBytes: 1234,
+ rxTotalBytes: 1234, txTotalBytes: 1234 });
}
- stats.push({connectionType: "wifi", timestamp: Date.now() + (10 * samples),
- rxBytes: 0, txBytes: 0,
- rxTotalBytes: 1234, txTotalBytes: 1234});
+ stats.push({ appId: 0, isInBrowser: 0,
+ serviceType: "",
+ network: network, timestamp: Date.now() + (10 * samples),
+ rxBytes: 0, txBytes: 0,
+ rxSystemBytes: 1234, txSystemBytes: 1234,
+ rxTotalBytes: 1234, txTotalBytes: 1234 });
- netStatsDb.dbNewTxn("readwrite", function(txn, store) {
+ netStatsDb.dbNewTxn(STATS_STORE_NAME, "readwrite", function(txn, store) {
netStatsDb._saveStats(txn, store, stats);
- var date = stats[stats.length -1].timestamp
+ var date = stats[stats.length - 1].timestamp
+ (netStatsDb.sampleRate * netStatsDb.maxStorageSamples - 1) - 1;
- netStatsDb._removeOldStats(txn, store, "wifi", date);
+ netStatsDb._removeOldStats(txn, store, 0, 0, "", network, date);
}, function(error, result) {
do_check_eq(error, null);
@@ -199,18 +281,17 @@ add_test(function test_internalRemoveOldStats() {
});
});
-function processSamplesDiff(lastStat, newStat, callback) {
- netStatsDb.clear(function (error, result){
- do_check_eq(error, null);
- netStatsDb.dbNewTxn("readwrite", function(txn, store) {
+function processSamplesDiff(networks, lastStat, newStat, callback) {
+ clearStore(STATS_STORE_NAME, function() {
+ netStatsDb.dbNewTxn(STATS_STORE_NAME, "readwrite", function(txn, store) {
netStatsDb._saveStats(txn, store, lastStat);
}, function(error, result) {
- netStatsDb.dbNewTxn("readwrite", function(txn, store) {
- let request = store.index("connectionType").openCursor(newStat.connectionType, "prev");
+ netStatsDb.dbNewTxn(STATS_STORE_NAME, "readwrite", function(txn, store) {
+ let request = store.index("network").openCursor(newStat.network, "prev");
request.onsuccess = function onsuccess(event) {
let cursor = event.target.result;
do_check_neq(cursor, null);
- netStatsDb._processSamplesDiff(txn, store, cursor, newStat);
+ netStatsDb._processSamplesDiff(txn, store, cursor, newStat, true);
};
}, function(error, result) {
do_check_eq(error, null);
@@ -224,93 +305,148 @@ function processSamplesDiff(lastStat, newStat, callback) {
}
add_test(function test_processSamplesDiffSameSample() {
+ var networks = getNetworks();
+ var network = [networks[0].id, networks[0].type];
+
var sampleRate = netStatsDb.sampleRate;
var date = filterTimestamp(new Date());
- var lastStat = {connectionType: "wifi", timestamp: date,
- rxBytes: 0, txBytes: 0,
- rxTotalBytes: 1234, txTotalBytes: 1234};
- var newStat = {connectionType: "wifi", timestamp: date,
- rxBytes: 0, txBytes: 0,
- rxTotalBytes: 2234, txTotalBytes: 2234};
+ var lastStat = { appId: 0, isInBrowser: 0,
+ serviceType: "",
+ network: network, timestamp: date,
+ rxBytes: 0, txBytes: 0,
+ rxSystemBytes: 1234, txSystemBytes: 1234,
+ rxTotalBytes: 2234, txTotalBytes: 2234 };
- processSamplesDiff(lastStat, newStat, function(result) {
+ var newStat = { appId: 0, isInBrowser: 0,
+ serviceType: "",
+ network: network, timestamp: date,
+ rxBytes: 0, txBytes: 0,
+ rxSystemBytes: 2234, txSystemBytes: 2234,
+ rxTotalBytes: 2234, txTotalBytes: 2234 };
+
+ processSamplesDiff(networks, lastStat, newStat, function(result) {
do_check_eq(result.length, 1);
- do_check_eq(result[0].connectionType, newStat.connectionType);
+ do_check_eq(result[0].appId, newStat.appId);
+ do_check_eq(result[0].isInBrowser, newStat.isInBrowser);
+ do_check_eq(result[0].serviceType, newStat.serviceType);
+ do_check_true(compareNetworks(result[0].network, newStat.network));
do_check_eq(result[0].timestamp, newStat.timestamp);
- do_check_eq(result[0].rxBytes, newStat.rxTotalBytes - lastStat.rxTotalBytes);
- do_check_eq(result[0].txBytes, newStat.txTotalBytes - lastStat.txTotalBytes);
- do_check_eq(result[0].rxTotalBytes, newStat.rxTotalBytes);
- do_check_eq(result[0].txTotalBytes, newStat.txTotalBytes);
+ do_check_eq(result[0].rxBytes, newStat.rxSystemBytes - lastStat.rxSystemBytes);
+ do_check_eq(result[0].txBytes, newStat.txSystemBytes - lastStat.txSystemBytes);
+ do_check_eq(result[0].rxTotalBytes, lastStat.rxTotalBytes + newStat.rxSystemBytes - lastStat.rxSystemBytes);
+ do_check_eq(result[0].txTotalBytes, lastStat.txTotalBytes + newStat.txSystemBytes - lastStat.txSystemBytes);
+ do_check_eq(result[0].rxSystemBytes, newStat.rxSystemBytes);
+ do_check_eq(result[0].txSystemBytes, newStat.txSystemBytes);
run_next_test();
});
});
add_test(function test_processSamplesDiffNextSample() {
+ var networks = getNetworks();
+ var network = [networks[0].id, networks[0].type];
+
var sampleRate = netStatsDb.sampleRate;
var date = filterTimestamp(new Date());
- var lastStat = {connectionType: "wifi", timestamp: date,
- rxBytes: 0, txBytes: 0,
- rxTotalBytes: 1234, txTotalBytes: 1234};
- var newStat = {connectionType: "wifi", timestamp: date + sampleRate,
- rxBytes: 0, txBytes: 0,
- rxTotalBytes: 500, txTotalBytes: 500};
+ var lastStat = { appId: 0, isInBrowser: 0,
+ serviceType: "",
+ network: network, timestamp: date,
+ rxBytes: 0, txBytes: 0,
+ rxSystemBytes: 1234, txSystemBytes: 1234,
+ rxTotalBytes: 2234, txTotalBytes: 2234 };
- processSamplesDiff(lastStat, newStat, function(result) {
+ var newStat = { appId: 0, isInBrowser: 0,
+ serviceType: "",
+ network: network, timestamp: date + sampleRate,
+ rxBytes: 0, txBytes: 0,
+ rxSystemBytes: 1734, txSystemBytes: 1734,
+ rxTotalBytes: 0, txTotalBytes: 0 };
+
+ processSamplesDiff(networks, lastStat, newStat, function(result) {
do_check_eq(result.length, 2);
- do_check_eq(result[1].connectionType, newStat.connectionType);
+ do_check_eq(result[1].appId, newStat.appId);
+ do_check_eq(result[1].isInBrowser, newStat.isInBrowser);
+ do_check_eq(result[1].serviceType, newStat.serviceType);
+ do_check_true(compareNetworks(result[1].network, newStat.network));
do_check_eq(result[1].timestamp, newStat.timestamp);
- do_check_eq(result[1].rxBytes, newStat.rxTotalBytes);
- do_check_eq(result[1].txBytes, newStat.txTotalBytes);
- do_check_eq(result[1].rxTotalBytes, newStat.rxTotalBytes);
- do_check_eq(result[1].txTotalBytes, newStat.txTotalBytes);
+ do_check_eq(result[1].rxBytes, newStat.rxSystemBytes - lastStat.rxSystemBytes);
+ do_check_eq(result[1].txBytes, newStat.txSystemBytes - lastStat.txSystemBytes);
+ do_check_eq(result[1].rxSystemBytes, newStat.rxSystemBytes);
+ do_check_eq(result[1].txSystemBytes, newStat.txSystemBytes);
+ do_check_eq(result[1].rxTotalBytes, lastStat.rxTotalBytes + newStat.rxSystemBytes - lastStat.rxSystemBytes);
+ do_check_eq(result[1].txTotalBytes, lastStat.txTotalBytes + newStat.txSystemBytes - lastStat.txSystemBytes);
run_next_test();
});
});
add_test(function test_processSamplesDiffSamplesLost() {
+ var networks = getNetworks();
+ var network = [networks[0].id, networks[0].type];
var samples = 5;
var sampleRate = netStatsDb.sampleRate;
var date = filterTimestamp(new Date());
- var lastStat = {connectionType: "wifi", timestamp: date,
- rxBytes: 0, txBytes: 0,
- rxTotalBytes: 1234, txTotalBytes: 1234};
+ var lastStat = { appId: 0, isInBrowser: 0,
+ serviceType: "",
+ network: network, timestamp: date,
+ rxBytes: 0, txBytes: 0,
+ rxSystemBytes: 1234, txSystemBytes: 1234,
+ rxTotalBytes: 2234, txTotalBytes: 2234};
- var newStat = {connectionType: "wifi", timestamp: date + (sampleRate * samples),
- rxBytes: 0, txBytes: 0,
- rxTotalBytes: 2234, txTotalBytes: 2234};
+ var newStat = { appId: 0, isInBrowser: 0,
+ serviceType: "",
+ network: network, timestamp: date + (sampleRate * samples),
+ rxBytes: 0, txBytes: 0,
+ rxSystemBytes: 2234, txSystemBytes: 2234,
+ rxTotalBytes: 0, txTotalBytes: 0 };
- processSamplesDiff(lastStat, newStat, function(result) {
+ processSamplesDiff(networks, lastStat, newStat, function(result) {
do_check_eq(result.length, samples + 1);
- do_check_eq(result[samples].connectionType, newStat.connectionType);
+ do_check_eq(result[0].appId, newStat.appId);
+ do_check_eq(result[0].isInBrowser, newStat.isInBrowser);
+ do_check_eq(result[0].serviceType, newStat.serviceType);
+ do_check_true(compareNetworks(result[samples].network, newStat.network));
do_check_eq(result[samples].timestamp, newStat.timestamp);
do_check_eq(result[samples].rxBytes, newStat.rxTotalBytes - lastStat.rxTotalBytes);
do_check_eq(result[samples].txBytes, newStat.txTotalBytes - lastStat.txTotalBytes);
- do_check_eq(result[samples].rxTotalBytes, newStat.rxTotalBytes);
- do_check_eq(result[samples].txTotalBytes, newStat.txTotalBytes);
+ do_check_eq(result[samples].rxSystemBytes, newStat.rxSystemBytes);
+ do_check_eq(result[samples].txSystemBytes, newStat.txSystemBytes);
+ do_check_eq(result[samples].rxTotalBytes, lastStat.rxTotalBytes + newStat.rxSystemBytes - lastStat.rxSystemBytes);
+ do_check_eq(result[samples].txTotalBytes, lastStat.txTotalBytes + newStat.txSystemBytes - lastStat.txSystemBytes);
run_next_test();
});
});
add_test(function test_saveStats() {
- var stats = { connectionType: "wifi",
+ var networks = getNetworks();
+ var network = [networks[0].id, networks[0].type];
+
+ var stats = { appId: 0,
+ isInBrowser: false,
+ serviceType: "",
+ networkId: networks[0].id,
+ networkType: networks[0].type,
date: new Date(),
rxBytes: 2234,
- txBytes: 2234};
+ txBytes: 2234,
+ isAccumulative: true };
- netStatsDb.clear(function (error, result) {
- do_check_eq(error, null);
+ clearStore(STATS_STORE_NAME, function() {
netStatsDb.saveStats(stats, function(error, result) {
do_check_eq(error, null);
netStatsDb.logAllRecords(function(error, result) {
do_check_eq(error, null);
do_check_eq(result.length, 1);
- do_check_eq(result[0].connectionType, stats.connectionType);
+ do_check_eq(result[0].appId, stats.appId);
+ do_check_eq(result[0].isInBrowser, stats.isInBrowser);
+ do_check_eq(result[0].serviceType, stats.serviceType);
+ do_check_true(compareNetworks(result[0].network, network));
let timestamp = filterTimestamp(stats.date);
do_check_eq(result[0].timestamp, timestamp);
- do_check_eq(result[0].rxBytes, 0);
- do_check_eq(result[0].txBytes, 0);
+ do_check_eq(result[0].rxBytes, stats.rxBytes);
+ do_check_eq(result[0].txBytes, stats.txBytes);
+ do_check_eq(result[0].rxSystemBytes, stats.rxBytes);
+ do_check_eq(result[0].txSystemBytes, stats.txBytes);
do_check_eq(result[0].rxTotalBytes, stats.rxBytes);
do_check_eq(result[0].txTotalBytes, stats.txBytes);
run_next_test();
@@ -319,10 +455,85 @@ add_test(function test_saveStats() {
});
});
+add_test(function test_saveAppStats() {
+ var networks = getNetworks();
+ var network = [networks[0].id, networks[0].type];
+
+ var stats = { appId: 1,
+ isInBrowser: false,
+ serviceType: "",
+ networkId: networks[0].id,
+ networkType: networks[0].type,
+ date: new Date(),
+ rxBytes: 2234,
+ txBytes: 2234,
+ isAccumulative: false };
+
+ clearStore(STATS_STORE_NAME, function() {
+ netStatsDb.saveStats(stats, function(error, result) {
+ do_check_eq(error, null);
+ netStatsDb.logAllRecords(function(error, result) {
+ do_check_eq(error, null);
+ do_check_eq(result.length, 1);
+ do_check_eq(result[0].appId, stats.appId);
+ do_check_eq(result[0].isInBrowser, 0);
+ do_check_eq(result[0].serviceType, stats.serviceType);
+ do_check_true(compareNetworks(result[0].network, network));
+ let timestamp = filterTimestamp(stats.date);
+ do_check_eq(result[0].timestamp, timestamp);
+ do_check_eq(result[0].rxBytes, stats.rxBytes);
+ do_check_eq(result[0].txBytes, stats.txBytes);
+ do_check_eq(result[0].rxSystemBytes, 0);
+ do_check_eq(result[0].txSystemBytes, 0);
+ do_check_eq(result[0].rxTotalBytes, 0);
+ do_check_eq(result[0].txTotalBytes, 0);
+ run_next_test();
+ });
+ });
+ });
+});
+
+add_test(function test_saveServiceStats() {
+ var networks = getNetworks();
+ var network = [networks[0].id, networks[0].type];
+
+ var stats = { appId: 0,
+ isInBrowser: false,
+ serviceType: "FakeType",
+ networkId: networks[0].id,
+ networkType: networks[0].type,
+ date: new Date(),
+ rxBytes: 2234,
+ txBytes: 2234,
+ isAccumulative: false };
+
+ clearStore(STATS_STORE_NAME, function() {
+ netStatsDb.saveStats(stats, function(error, result) {
+ do_check_eq(error, null);
+ netStatsDb.logAllRecords(function(error, result) {
+ do_check_eq(error, null);
+ do_check_eq(result.length, 1);
+ do_check_eq(result[0].appId, stats.appId);
+ do_check_eq(result[0].isInBrowser, 0);
+ do_check_eq(result[0].serviceType, stats.serviceType);
+ do_check_true(compareNetworks(result[0].network, network));
+ let timestamp = filterTimestamp(stats.date);
+ do_check_eq(result[0].timestamp, timestamp);
+ do_check_eq(result[0].rxBytes, stats.rxBytes);
+ do_check_eq(result[0].txBytes, stats.txBytes);
+ do_check_eq(result[0].rxSystemBytes, 0);
+ do_check_eq(result[0].txSystemBytes, 0);
+ do_check_eq(result[0].rxTotalBytes, 0);
+ do_check_eq(result[0].txTotalBytes, 0);
+ run_next_test();
+ });
+ });
+ });
+});
+
function prepareFind(stats, callback) {
- netStatsDb.clear(function (error, result) {
- do_check_eq(error, null);
- netStatsDb.dbNewTxn("readwrite", function(txn, store) {
+ clearStore(STATS_STORE_NAME, function() {
+ netStatsDb.dbNewTxn(STATS_STORE_NAME, "readwrite", function(txn, store) {
netStatsDb._saveStats(txn, store, stats);
}, function(error, result) {
callback(error, result);
@@ -331,6 +542,13 @@ function prepareFind(stats, callback) {
}
add_test(function test_find () {
+ var networks = getNetworks();
+ var networkWifi = [networks[0].id, networks[0].type];
+ var networkMobile = [networks[1].id, networks[1].type]; // Fake mobile interface
+ var appId = 0;
+ var isInBrowser = 0;
+ var serviceType = "";
+
var samples = 5;
var sampleRate = netStatsDb.sampleRate;
var start = Date.now();
@@ -338,50 +556,530 @@ add_test(function test_find () {
var end = new Date(start + (sampleRate * (samples - 1)));
start = new Date(start - sampleRate);
var stats = [];
- for (var i = 0; i < samples; i++) {i
- stats.push({connectionType: "wifi", timestamp: saveDate + (sampleRate * i),
- rxBytes: 0, txBytes: 10,
- rxTotalBytes: 0, txTotalBytes: 0});
-
- stats.push({connectionType: "mobile", timestamp: saveDate + (sampleRate * i),
- rxBytes: 0, txBytes: 10,
- rxTotalBytes: 0, txTotalBytes: 0});
+ for (var i = 0; i < samples; i++) {
+ stats.push({ appId: appId, isInBrowser: isInBrowser,
+ serviceType: serviceType,
+ network: networkWifi, timestamp: saveDate + (sampleRate * i),
+ rxBytes: 0, txBytes: 10,
+ rxSystemBytes: 0, txSystemBytes: 0,
+ rxTotalBytes: 0, txTotalBytes: 0 });
+
+
+ stats.push({ appId: appId, isInBrowser: isInBrowser,
+ serviceType: serviceType,
+ network: networkMobile, timestamp: saveDate + (sampleRate * i),
+ rxBytes: 0, txBytes: 10,
+ rxSystemBytes: 0, txSystemBytes: 0,
+ rxTotalBytes: 0, txTotalBytes: 0 });
}
prepareFind(stats, function(error, result) {
do_check_eq(error, null);
netStatsDb.find(function (error, result) {
do_check_eq(error, null);
- do_check_eq(result.connectionType, "wifi");
+ do_check_eq(result.browsingTrafficOnly, false);
+ do_check_eq(result.serviceType, serviceType);
+ do_check_eq(result.network.id, networks[0].id);
+ do_check_eq(result.network.type, networks[0].type);
do_check_eq(result.start.getTime(), start.getTime());
do_check_eq(result.end.getTime(), end.getTime());
do_check_eq(result.data.length, samples + 1);
do_check_eq(result.data[0].rxBytes, null);
do_check_eq(result.data[1].rxBytes, 0);
do_check_eq(result.data[samples].rxBytes, 0);
+ run_next_test();
+ }, appId, false, serviceType, networks[0], start, end);
+ });
+});
+
+add_test(function test_findAppStats () {
+ var networks = getNetworks();
+ var networkWifi = [networks[0].id, networks[0].type];
+ var networkMobile = [networks[1].id, networks[1].type]; // Fake mobile interface
+ var appId = 1;
+ var isInBrowser = 0;
+ var serviceType = "";
+
+ var samples = 5;
+ var sampleRate = netStatsDb.sampleRate;
+ var start = Date.now();
+ var saveDate = filterTimestamp(new Date());
+ var end = new Date(start + (sampleRate * (samples - 1)));
+ start = new Date(start - sampleRate);
+ var stats = [];
+ for (var i = 0; i < samples; i++) {
+ stats.push({ appId: appId, isInBrowser: isInBrowser,
+ serviceType: serviceType,
+ network: networkWifi, timestamp: saveDate + (sampleRate * i),
+ rxBytes: 0, txBytes: 10,
+ rxTotalBytes: 0, txTotalBytes: 0 });
+
+ stats.push({ appId: appId, isInBrowser: isInBrowser,
+ serviceType: serviceType,
+ network: networkMobile, timestamp: saveDate + (sampleRate * i),
+ rxBytes: 0, txBytes: 10,
+ rxTotalBytes: 0, txTotalBytes: 0 });
+ }
+
+ prepareFind(stats, function(error, result) {
+ do_check_eq(error, null);
+ netStatsDb.find(function (error, result) {
+ do_check_eq(error, null);
+ do_check_eq(result.browsingTrafficOnly, false);
+ do_check_eq(result.serviceType, serviceType);
+ do_check_eq(result.network.id, networks[0].id);
+ do_check_eq(result.network.type, networks[0].type);
+ do_check_eq(result.start.getTime(), start.getTime());
+ do_check_eq(result.end.getTime(), end.getTime());
+ do_check_eq(result.data.length, samples + 1);
+ do_check_eq(result.data[0].rxBytes, null);
+ do_check_eq(result.data[1].rxBytes, 0);
+ do_check_eq(result.data[samples].rxBytes, 0);
+ run_next_test();
+ }, appId, false, serviceType, networks[0], start, end);
+ });
+});
+
+add_test(function test_findServiceStats () {
+ var networks = getNetworks();
+ var networkWifi = [networks[0].id, networks[0].type];
+ var networkMobile = [networks[1].id, networks[1].type]; // Fake mobile interface
+ var appId = 0;
+ var isInBrowser = 0;
+ var serviceType = "FakeType";
+
+ var samples = 5;
+ var sampleRate = netStatsDb.sampleRate;
+ var start = Date.now();
+ var saveDate = filterTimestamp(new Date());
+ var end = new Date(start + (sampleRate * (samples - 1)));
+ start = new Date(start - sampleRate);
+ var stats = [];
+ for (var i = 0; i < samples; i++) {
+ stats.push({ appId: appId, isInBrowser: isInBrowser,
+ serviceType: serviceType,
+ network: networkWifi, timestamp: saveDate + (sampleRate * i),
+ rxBytes: 0, txBytes: 10,
+ rxTotalBytes: 0, txTotalBytes: 0 });
+
+ stats.push({ appId: appId, isInBrowser: isInBrowser,
+ serviceType: serviceType,
+ network: networkMobile, timestamp: saveDate + (sampleRate * i),
+ rxBytes: 0, txBytes: 10,
+ rxTotalBytes: 0, txTotalBytes: 0 });
+ }
+
+ prepareFind(stats, function(error, result) {
+ do_check_eq(error, null);
+ netStatsDb.find(function (error, result) {
+ do_check_eq(error, null);
+ do_check_eq(result.browsingTrafficOnly, false);
+ do_check_eq(result.serviceType, serviceType);
+ do_check_eq(result.network.id, networks[0].id);
+ do_check_eq(result.network.type, networks[0].type);
+ do_check_eq(result.start.getTime(), start.getTime());
+ do_check_eq(result.end.getTime(), end.getTime());
+ do_check_eq(result.data.length, samples + 1);
+ do_check_eq(result.data[0].rxBytes, null);
+ do_check_eq(result.data[1].rxBytes, 0);
+ do_check_eq(result.data[samples].rxBytes, 0);
+ run_next_test();
+ }, appId, false, serviceType, networks[0], start, end);
+ });
+});
- netStatsDb.findAll(function (error, result) {
+add_test(function test_saveMultipleAppStats () {
+ var networks = getNetworks();
+ var networkWifi = networks[0];
+ var networkMobile = networks[1]; // Fake mobile interface
+
+ var saveDate = filterTimestamp(new Date());
+ var cached = Object.create(null);
+ var serviceType = "FakeType";
+ var wifiNetId = networkWifi.id + '' + networkWifi.type;
+ var mobileNetId = networkMobile.id + '' + networkMobile.type;
+
+ cached[0 + '' + serviceType + wifiNetId] = {
+ appId: 0, date: new Date(),
+ networkId: networkWifi.id, networkType: networkWifi.type,
+ rxBytes: 0, txBytes: 10,
+ serviceType: serviceType, isAccumulative: false,
+ isInBrowser: false
+ };
+
+ cached[0 + '' + serviceType + mobileNetId] = {
+ appId: 0, date: new Date(),
+ networkId: networkMobile.id, networkType: networkMobile.type,
+ rxBytes: 0, txBytes: 10,
+ serviceType: serviceType, isAccumulative: false,
+ isInBrowser: false
+ };
+
+ cached[1 + '' + wifiNetId] = {
+ appId: 1, date: new Date(),
+ networkId: networkWifi.id, networkType: networkWifi.type,
+ rxBytes: 0, txBytes: 10,
+ serviceType: "", isAccumulative: false,
+ isInBrowser: false
+ };
+
+ cached[1 + '' + mobileNetId] = {
+ appId: 1, date: new Date(),
+ networkId: networkMobile.id, networkType: networkMobile.type,
+ rxBytes: 0, txBytes: 10,
+ serviceType: "", isAccumulative: false,
+ isInBrowser: false
+ };
+
+ cached[2 + '' + wifiNetId] = {
+ appId: 2, date: new Date(),
+ networkId: networkWifi.id, networkType: networkWifi.type,
+ rxBytes: 0, txBytes: 10,
+ serviceType: "", isAccumulative: false,
+ isInBrowser: false
+ };
+
+ cached[2 + '' + mobileNetId] = {
+ appId: 2, date: new Date(),
+ networkId: networkMobile.id, networkType: networkMobile.type,
+ rxBytes: 0, txBytes: 10,
+ serviceType: "", isAccumulative: false,
+ isInBrowser: false
+ };
+
+ let keys = Object.keys(cached);
+ let index = 0;
+
+ networks.push(networkMobile);
+
+ clearStore(STATS_STORE_NAME, function() {
+ netStatsDb.saveStats(cached[keys[index]],
+ function callback(error, result) {
do_check_eq(error, null);
- do_check_eq(result.connectionType, null);
- do_check_eq(result.start.getTime(), start.getTime());
- do_check_eq(result.end.getTime(), end.getTime());
- do_check_eq(result.data.length, samples + 1);
- do_check_eq(result.data[0].rxBytes, null);
- do_check_eq(result.data[1].rxBytes, 0);
- do_check_eq(result.data[1].txBytes, 20);
- do_check_eq(result.data[samples].rxBytes, 0);
+
+ if (index == keys.length - 1) {
+ netStatsDb.logAllRecords(function(error, result) {
+ do_check_eq(error, null);
+ do_check_eq(result.length, 6);
+ do_check_eq(result[0].isInBrowser, 0);
+ do_check_eq(result[0].serviceType, serviceType);
+ do_check_eq(result[3].appId, 1);
+ do_check_true(compareNetworks(result[0].network, [networkWifi.id, networkWifi.type]));
+ do_check_eq(result[0].rxBytes, 0);
+ do_check_eq(result[0].txBytes, 10);
+ run_next_test();
+ });
+ return;
+ }
+
+ index += 1;
+ netStatsDb.saveStats(cached[keys[index]], callback);
+ });
+ });
+});
+
+// Test case for find samples with browsingTrafficOnly option.
+add_test(function test_findBrowsingTrafficStats() {
+ var networks = getNetworks();
+ var networkWifi = [networks[0].id, networks[0].type];
+ var networkMobile = [networks[1].id, networks[1].type];
+ var serviceType = "";
+ var samples = 5;
+ var sampleRate = netStatsDb.sampleRate;
+ var start = Date.now();
+ var end = new Date(start + (sampleRate * (samples - 1)));
+ var saveDate = filterTimestamp(new Date());
+ start = new Date(start - sampleRate);
+ var stats = [];
+
+ for (var i = 0; i < samples; i++) {
+ // System app.
+ stats.push({ appId: 1008, isInBrowser: 0,
+ serviceType: serviceType, network: networkMobile,
+ timestamp: saveDate + (sampleRate * i),
+ rxBytes: 200, txBytes: 100});
+ // Browser of system app.
+ stats.push({ appId: 1008, isInBrowser: 1,
+ serviceType: serviceType, network: networkMobile,
+ timestamp: saveDate + (sampleRate * i),
+ rxBytes: 1000, txBytes: 500});
+ // Another app.
+ stats.push({ appId: 1021, isInBrowser: 0,
+ serviceType: serviceType, network: networkMobile,
+ timestamp: saveDate + (sampleRate * i),
+ rxBytes: 300, txBytes: 150});
+ // Browser of another app.
+ stats.push({ appId: 1021, isInBrowser: 1,
+ serviceType: serviceType, network: networkMobile,
+ timestamp: saveDate + (sampleRate * i),
+ rxBytes: 600, txBytes: 300});
+ }
+
+ prepareFind(stats, function(error, result) {
+ do_check_eq(error, null);
+ netStatsDb.find(function(error, result) {
+ do_check_eq(error, null);
+ do_check_eq(result.browsingTrafficOnly, true);
+ do_check_eq(result.serviceType, serviceType);
+ do_check_eq(result.network.id, networks[1].id);
+ do_check_eq(result.network.type, networks[1].type);
+ do_check_eq(result.start.getTime(), start.getTime());
+ do_check_eq(result.end.getTime(), end.getTime());
+ do_check_eq(result.data.length, samples + 1);
+ do_check_eq(result.data[0].rxBytes, null);
+ do_check_eq(result.data[1].txBytes, 500);
+ do_check_eq(result.data[2].rxBytes, 1000);
+ run_next_test();
+ }, 1008, true, serviceType, networks[1], start, end);
+ });
+});
+
+// Test case for find samples with browsingTrafficOnly option.
+add_test(function test_findAppTrafficStats() {
+ var networks = getNetworks();
+ var networkWifi = [networks[0].id, networks[0].type];
+ var networkMobile = [networks[1].id, networks[1].type];
+ var serviceType = "";
+ var samples = 5;
+ var sampleRate = netStatsDb.sampleRate;
+ var start = Date.now();
+ var end = new Date(start + (sampleRate * (samples - 1)));
+ var saveDate = filterTimestamp(new Date());
+ start = new Date(start - sampleRate);
+ var stats = [];
+
+ for (var i = 0; i < samples; i++) {
+ // System app.
+ stats.push({ appId: 1008, isInBrowser: 0,
+ serviceType: serviceType, network: networkMobile,
+ timestamp: saveDate + (sampleRate * i),
+ rxBytes: 200, txBytes: 100});
+ // Browser of system app.
+ stats.push({ appId: 1008, isInBrowser: 1,
+ serviceType: serviceType, network: networkMobile,
+ timestamp: saveDate + (sampleRate * i),
+ rxBytes: 1000, txBytes: 500});
+ // Another app.
+ stats.push({ appId: 1021, isInBrowser: 0,
+ serviceType: serviceType, network: networkMobile,
+ timestamp: saveDate + (sampleRate * i),
+ rxBytes: 300, txBytes: 150});
+ // Browser of another app.
+ stats.push({ appId: 1021, isInBrowser: 1,
+ serviceType: serviceType, network: networkMobile,
+ timestamp: saveDate + (sampleRate * i),
+ rxBytes: 600, txBytes: 300});
+ }
+
+ prepareFind(stats, function(error, result) {
+ do_check_eq(error, null);
+ netStatsDb.find(function(error, result) {
+ do_check_eq(error, null);
+ do_check_eq(result.browsingTrafficOnly, false);
+ do_check_eq(result.serviceType, serviceType);
+ do_check_eq(result.network.id, networks[1].id);
+ do_check_eq(result.network.type, networks[1].type);
+ do_check_eq(result.start.getTime(), start.getTime());
+ do_check_eq(result.end.getTime(), end.getTime());
+ do_check_eq(result.data.length, samples + 1);
+ do_check_eq(result.data[0].rxBytes, null);
+ do_check_eq(result.data[1].txBytes, 600);
+ do_check_eq(result.data[2].rxBytes, 1200);
+ run_next_test();
+ }, 1008, false, serviceType, networks[1], start, end);
+ });
+});
+
+var networkWifi = '00';
+var networkMobile = '11';
+
+var examplePageURL = "http://example.com/index.html";
+var exampleManifestURL = "http://example.com/manifest.webapp";
+
+var testPageURL = "http://test.com/index.html";
+var testManifestURL = "http://test.com/manifest.webapp";
+
+var alarms = [{ id: null,
+ networkId: networkWifi,
+ absoluteThreshold: 10000,
+ relativeThreshold: 10000,
+ data: {foo: "something"},
+ pageURL: examplePageURL,
+ manifestURL: exampleManifestURL },
+ { id: null,
+ networkId: networkWifi,
+ absoluteThreshold: 1000,
+ relativeThreshold: 1000,
+ data: {foo: "else"},
+ pageURL: examplePageURL,
+ manifestURL: exampleManifestURL },
+ { id: null,
+ networkId: networkMobile,
+ absoluteThreshold: 100,
+ relativeThreshold: 100,
+ data: {foo: "to"},
+ pageURL: examplePageURL,
+ manifestURL: exampleManifestURL },
+ { id: null,
+ networkId: networkMobile,
+ absoluteThreshold: 10,
+ relativeThreshold: 10,
+ data: {foo: "test"},
+ pageURL: testPageURL,
+ manifestURL: testManifestURL }];
+
+var alarmsDbId = 1;
+
+add_test(function test_addAlarm() {
+ // Add alarms[0] -> DB: [ alarms[0] (id: 1) ]
+ // Check the insertion is OK.
+ netStatsDb.addAlarm(alarms[0], function(error, result) {
+ do_check_eq(error, null);
+ alarmsDbId = result;
+ netStatsDb.getAlarms(Ci.nsINetworkInterface.NETWORK_TYPE_WIFI, exampleManifestURL, function(error, result) {
+ do_check_eq(error, null);
+ do_check_eq(result.length, 1);
+ do_check_eq(result[0].id, alarmsDbId);
+ do_check_eq(result[0].networkId, alarms[0].networkId);
+ do_check_eq(result[0].absoluteThreshold, alarms[0].absoluteThreshold);
+ do_check_eq(result[0].relativeThreshold, alarms[0].relativeThreshold);
+ do_check_eq(result[0].data.foo, alarms[0].data.foo);
+ run_next_test();
+ });
+ });
+});
+
+add_test(function test_getFirstAlarm() {
+ // Add alarms[1] -> DB: [ alarms[0] (id: 1), alarms[1] (id: 2) ]
+ // Check first alarm is alarms[1] because threshold is lower.
+ alarmsDbId += 1;
+ netStatsDb.addAlarm(alarms[1], function (error, result) {
+ do_check_eq(error, null);
+ do_check_eq(result, alarmsDbId);
+ netStatsDb.getFirstAlarm(networkWifi, function(error, result) {
+ do_check_eq(error, null);
+ do_check_eq(result.id, alarmsDbId);
+ do_check_eq(result.networkId, alarms[1].networkId);
+ do_check_eq(result.absoluteThreshold, alarms[1].absoluteThreshold);
+ do_check_eq(result.relativeThreshold, alarms[1].relativeThreshold);
+ do_check_eq(result.data.foo, alarms[1].data.foo);
+ do_check_eq(result.pageURL, alarms[1].pageURL);
+ do_check_eq(result.manifestURL, alarms[1].manifestURL);
+ run_next_test();
+ });
+ });
+});
+
+add_test(function test_removeAlarm() {
+ // Remove alarms[1] (id: 2) -> DB: [ alarms[0] (id: 1) ]
+ // Check get first return alarms[0].
+ netStatsDb.removeAlarm(alarmsDbId, alarms[0].manifestURL, function (error, result) {
+ do_check_eq(error, null);
+ netStatsDb.getFirstAlarm(networkWifi, function(error, result) {
+ do_check_eq(error, null);
+ do_check_eq(result.id, alarmsDbId - 1);
+ do_check_eq(result.networkId, alarms[0].networkId);
+ do_check_eq(result.absoluteThreshold, alarms[0].absoluteThreshold);
+ do_check_eq(result.relativeThreshold, alarms[0].relativeThreshold);
+ do_check_eq(result.data.foo, alarms[0].data.foo);
+ do_check_eq(result.pageURL, alarms[0].pageURL);
+ do_check_eq(result.manifestURL, alarms[0].manifestURL);
+ run_next_test();
+ });
+ });
+});
+
+add_test(function test_removeAppAlarm() {
+ // Remove alarms[0] (id: 1) -> DB: [ ]
+ netStatsDb.removeAlarm(alarmsDbId - 1, alarms[0].manifestURL, function (error, result) {
+ do_check_eq(error, null);
+ netStatsDb.getAlarms(networkWifi, exampleManifestURL, function(error, result) {
+ do_check_eq(error, null);
+ do_check_eq(result.length, 0);
+ run_next_test();
+ });
+ });
+});
+
+add_test(function test_getAlarms() {
+ // Add all alarms -> DB: [ alarms[0] (id: 3),
+ // alarms[1] (id: 4),
+ // alarms[2] (id: 5),
+ // alarms[3] (id: 6) ]
+ // Check that getAlarms for wifi returns 2 alarms.
+ // Check that getAlarms for all connections returns 3 alarms.
+
+ var callback = function () {
+ netStatsDb.getAlarms(networkWifi, exampleManifestURL, function(error, result) {
+ do_check_eq(error, null);
+ do_check_eq(result.length, 2);
+ netStatsDb.getAlarms(null, exampleManifestURL, function(error, result) {
+ do_check_eq(error, null);
+ do_check_eq(result.length, 3);
run_next_test();
- }, {start: start, end: end});
- }, {start: start, end: end, connectionType: "wifi"});
+ });
+ });
+ };
+
+ var index = 0;
+
+ var addFunction = function () {
+ alarmsDbId += 1;
+ netStatsDb.addAlarm(alarms[index], function (error, result) {
+ do_check_eq(error, null);
+ index += 1;
+ do_check_eq(result, alarmsDbId);
+ if (index >= alarms.length) {
+ callback();
+ return;
+ }
+ addFunction();
+ });
+ };
+
+ addFunction();
+});
+
+add_test(function test_removeAppAllAlarms() {
+ // Remove all alarms for exampleManifestURL -> DB: [ alarms[3] (id: 6) ]
+ netStatsDb.removeAlarms(exampleManifestURL, function (error, result) {
+ do_check_eq(error, null);
+ netStatsDb.getAlarms(null, exampleManifestURL, function(error, result) {
+ do_check_eq(error, null);
+ do_check_eq(result.length, 0);
+ netStatsDb.getAlarms(null, testManifestURL, function(error, result) {
+ do_check_eq(error, null);
+ do_check_eq(result.length, 1);
+ run_next_test();
+ });
+ });
});
});
-function run_test() {
- do_get_profile();
+add_test(function test_updateAlarm() {
+ // Update alarms[3] (id: 6) -> DB: [ alarms[3]* (id: 6) ]
- var idbManager = Cc["@mozilla.org/dom/indexeddb/manager;1"].
- getService(Ci.nsIIndexedDatabaseManager);
- idbManager.initWindowless(this);
+ var updatedAlarm = alarms[1];
+ updatedAlarm.id = alarmsDbId;
+ updatedAlarm.threshold = 10;
+ netStatsDb.updateAlarm(updatedAlarm, function (error, result) {
+ do_check_eq(error, null);
+ netStatsDb.getFirstAlarm(networkWifi, function(error, result) {
+ do_check_eq(error, null);
+ do_check_eq(result.id, updatedAlarm.id);
+ do_check_eq(result.networkId, updatedAlarm.networkId);
+ do_check_eq(result.absoluteThreshold, updatedAlarm.absoluteThreshold);
+ do_check_eq(result.relativeThreshold, updatedAlarm.relativeThreshold);
+ do_check_eq(result.data.foo, updatedAlarm.data.foo);
+ do_check_eq(result.pageURL, updatedAlarm.pageURL);
+ do_check_eq(result.manifestURL, updatedAlarm.manifestURL);
+ run_next_test();
+ });
+ });
+});
+
+function run_test() {
+ do_get_profile();
run_next_test();
}
diff --git a/dom/network/tests/unit_stats/test_networkstats_service.js b/dom/network/tests/unit_stats/test_networkstats_service.js
index 569b2f7ba..17738859e 100644
--- a/dom/network/tests/unit_stats/test_networkstats_service.js
+++ b/dom/network/tests/unit_stats/test_networkstats_service.js
@@ -3,49 +3,90 @@
const {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components;
+const NETWORK_STATUS_READY = 0;
+const NETWORK_STATUS_STANDBY = 1;
+const NETWORK_STATUS_AWAY = 2;
+
+const QUEUE_TYPE_UPDATE_STATS = 0;
+
+var wifiId = '00';
+
+function getNetworks(callback) {
+ NetworkStatsService._db.getAvailableNetworks(function onGetNetworks(aError, aResult) {
+ callback(aError, aResult);
+ });
+}
+
add_test(function test_clearDB() {
- NetworkStatsService._db.clear(function onDBCleared(error, result) {
- do_check_eq(result, null);
- run_next_test();
+ getNetworks(function onGetNetworks(error, result) {
+ do_check_eq(error, null);
+ var networks = result;
+ networks.forEach(function(network, index) {
+ networks[index] = {network: network, networkId: NetworkStatsService.getNetworkId(network.id, network.type)};
+ }, this);
+
+ NetworkStatsService._db.clearStats(networks, function onDBCleared(error, result) {
+ do_check_eq(error, null);
+ run_next_test();
+ });
});
});
+function getNetworkId(callback) {
+ getNetworks(function onGetNetworks(error, result) {
+ do_check_eq(error, null);
+ var netId = NetworkStatsService.getNetworkId(result[0].id, result[0].type);
+ callback(null, netId);
+ });
+}
add_test(function test_networkStatsAvailable_ok() {
- NetworkStatsService.networkStatsAvailable(function (success, msg) {
- do_check_eq(success, true);
- run_next_test();
- }, Ci.nsINetworkInterface.NETWORK_TYPE_WIFI, true, 1234, 4321, new Date());
+ getNetworkId(function onGetId(error, result) {
+ do_check_eq(error, null);
+ var netId = result;
+ NetworkStatsService.networkStatsAvailable(function (success, msg) {
+ do_check_eq(success, true);
+ run_next_test();
+ }, netId, true, 1234, 4321, Date.now());
+ });
});
add_test(function test_networkStatsAvailable_failure() {
- NetworkStatsService.networkStatsAvailable(function (success, msg) {
- do_check_eq(success, false);
- run_next_test();
- }, Ci.nsINetworkInterface.NETWORK_TYPE_WIFI, false, 1234, 4321, new Date());
+ getNetworkId(function onGetId(error, result) {
+ do_check_eq(error, null);
+ var netId = result;
+ NetworkStatsService.networkStatsAvailable(function (success, msg) {
+ do_check_eq(success, false);
+ run_next_test();
+ }, netId, false, 1234, 4321, Date.now());
+ });
});
-add_test(function test_update_invalidConnection() {
+add_test(function test_update_invalidNetwork() {
NetworkStatsService.update(-1, function (success, msg) {
do_check_eq(success, false);
- do_check_eq(msg, "Invalid network type -1");
+ do_check_eq(msg, "Invalid network -1");
run_next_test();
});
});
add_test(function test_update() {
- NetworkStatsService.update(Ci.nsINetworkInterface.NETWORK_TYPE_WIFI, function (success, msg) {
- do_check_eq(success, true);
- run_next_test();
+ getNetworkId(function onGetId(error, result) {
+ do_check_eq(error, null);
+ var netId = result;
+ NetworkStatsService.update(netId, function (success, msg) {
+ do_check_eq(success, true);
+ run_next_test();
+ });
});
});
add_test(function test_updateQueueIndex() {
- NetworkStatsService.updateQueue = [{type: 0, callbacks: null},
- {type: 1, callbacks: null},
- {type: 2, callbacks: null},
- {type: 3, callbacks: null},
- {type: 4, callbacks: null}];
+ NetworkStatsService.updateQueue = [{netId: 0, callbacks: null, queueType: QUEUE_TYPE_UPDATE_STATS},
+ {netId: 1, callbacks: null, queueType: QUEUE_TYPE_UPDATE_STATS},
+ {netId: 2, callbacks: null, queueType: QUEUE_TYPE_UPDATE_STATS},
+ {netId: 3, callbacks: null, queueType: QUEUE_TYPE_UPDATE_STATS},
+ {netId: 4, callbacks: null, queueType: QUEUE_TYPE_UPDATE_STATS}];
var index = NetworkStatsService.updateQueueIndex(3);
do_check_eq(index, 3);
index = NetworkStatsService.updateQueueIndex(10);
@@ -56,16 +97,29 @@ add_test(function test_updateQueueIndex() {
});
add_test(function test_updateAllStats() {
+ NetworkStatsService._networks[wifiId].status = NETWORK_STATUS_READY;
NetworkStatsService.updateAllStats(function(success, msg) {
do_check_eq(success, true);
- run_next_test();
+ NetworkStatsService._networks[wifiId].status = NETWORK_STATUS_STANDBY;
+ NetworkStatsService.updateAllStats(function(success, msg) {
+ do_check_eq(success, true);
+ NetworkStatsService._networks[wifiId].status = NETWORK_STATUS_AWAY;
+ NetworkStatsService.updateAllStats(function(success, msg) {
+ do_check_eq(success, true);
+ run_next_test();
+ });
+ });
});
});
add_test(function test_updateStats_ok() {
- NetworkStatsService.updateStats(Ci.nsINetworkInterface.NETWORK_TYPE_WIFI, function(success, msg){
- do_check_eq(success, true);
- run_next_test();
+ getNetworkId(function onGetId(error, result) {
+ do_check_eq(error, null);
+ var netId = result;
+ NetworkStatsService.updateStats(netId, function(success, msg){
+ do_check_eq(success, true);
+ run_next_test();
+ });
});
});
@@ -76,32 +130,156 @@ add_test(function test_updateStats_failure() {
});
});
+// Define Mockup function to simulate a request to netd
+function MockNetdRequest(aCallback) {
+ var timer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
+ var event = {
+ notify: function (timer) {
+ aCallback();
+ }
+ };
+
+ timer.initWithCallback(event, 100, Ci.nsITimer.TYPE_ONE_SHOT);
+}
+
add_test(function test_queue() {
- // Fill connections with fake network interfaces (wlan0 and rmnet0)
- // to enable netd async requests
- NetworkStatsService._connectionTypes[Ci.nsINetworkInterface.NETWORK_TYPE_WIFI]
- .network.name = 'wlan0';
- NetworkStatsService._connectionTypes[Ci.nsINetworkInterface.NETWORK_TYPE_MOBILE]
- .network.name = 'rmnet0';
-
- NetworkStatsService.updateStats(Ci.nsINetworkInterface.NETWORK_TYPE_WIFI);
- NetworkStatsService.updateStats(Ci.nsINetworkInterface.NETWORK_TYPE_MOBILE);
+
+ // Overwrite update function of NetworkStatsService to avoid netd errors due to use
+ // fake interfaces. First, original function is stored to restore it at the end of the
+ // test.
+ var updateFunctionBackup = NetworkStatsService.update;
+
+ NetworkStatsService.update = function update(aNetId, aCallback) {
+ MockNetdRequest(function () {
+ if (aCallback) {
+ aCallback(true, "ok");
+ }
+ });
+ };
+
+ // Fill networks with fake network interfaces to enable netd async requests.
+ var network = {id: "1234", type: Ci.nsIDOMMozNetworkStatsManager.MOBILE};
+ var netId1 = NetworkStatsService.getNetworkId(network.id, network.type);
+ NetworkStatsService._networks[netId1] = { network: network,
+ interfaceName: "net1" };
+
+ network = {id: "5678", type: Ci.nsIDOMMozNetworkStatsManager.MOBILE};
+ var netId2 = NetworkStatsService.getNetworkId(network.id, network.type);
+ NetworkStatsService._networks[netId2] = { network: network,
+ interfaceName: "net2" };
+
+ NetworkStatsService.updateStats(netId1);
+ NetworkStatsService.updateStats(netId2);
do_check_eq(NetworkStatsService.updateQueue.length, 2);
do_check_eq(NetworkStatsService.updateQueue[0].callbacks.length, 1);
+ var i = 0;
+ var updateCount = 0;
var callback = function(success, msg) {
- return;
+ i++;
+ if (i >= updateCount) {
+ NetworkStatsService.update = updateFunctionBackup;
+ run_next_test();
+ }
};
- NetworkStatsService.updateStats(Ci.nsINetworkInterface.NETWORK_TYPE_WIFI, callback);
- NetworkStatsService.updateStats(Ci.nsINetworkInterface.NETWORK_TYPE_MOBILE, callback);
+ NetworkStatsService.updateStats(netId1, callback);
+ updateCount++;
+ NetworkStatsService.updateStats(netId2, callback);
+ updateCount++;
do_check_eq(NetworkStatsService.updateQueue.length, 2);
do_check_eq(NetworkStatsService.updateQueue[0].callbacks.length, 2);
do_check_eq(NetworkStatsService.updateQueue[0].callbacks[0], null);
do_check_neq(NetworkStatsService.updateQueue[0].callbacks[1], null);
+});
- run_next_test();
+add_test(function test_getAlarmQuota() {
+ let alarm = { networkId: wifiId, absoluteThreshold: 10000 };
+
+ NetworkStatsService._getAlarmQuota(alarm, function onSet(error, quota){
+ do_check_eq(error, null);
+ do_check_neq(quota, undefined);
+ do_check_eq(alarm.absoluteThreshold, alarm.relativeThreshold);
+ run_next_test();
+ });
+});
+
+var testPageURL = "http://test.com";
+var testManifestURL = "http://test.com/manifest.webapp";
+
+add_test(function test_setAlarm() {
+ let alarm = { id: null,
+ networkId: wifiId,
+ threshold: 10000,
+ absoluteThreshold: null,
+ alarmStart: null,
+ alarmEnd: null,
+ data: null,
+ pageURL: testPageURL,
+ manifestURL: testManifestURL };
+
+ NetworkStatsService._setAlarm(alarm, function onSet(error, result) {
+ do_check_eq(result, 1);
+ run_next_test();
+ });
+});
+
+add_test(function test_setAlarm_invalid_threshold() {
+ let alarm = { id: null,
+ networkId: wifiId,
+ threshold: -10000,
+ absoluteThreshold: null,
+ alarmStart: null,
+ alarmEnd: null,
+ data: null,
+ pageURL: testPageURL,
+ manifestURL: testManifestURL };
+
+ NetworkStatsService._networks[wifiId].status = NETWORK_STATUS_READY;
+
+ NetworkStatsService._setAlarm(alarm, function onSet(error, result) {
+ do_check_eq(error, "InvalidStateError");
+ run_next_test();
+ });
+});
+
+add_test(function test_fireAlarm() {
+ // Add a fake alarm into database.
+ let alarm = { id: null,
+ networkId: wifiId,
+ threshold: 10000,
+ absoluteThreshold: null,
+ alarmStart: null,
+ alarmEnd: null,
+ data: null,
+ pageURL: testPageURL,
+ manifestURL: testManifestURL };
+
+ // Set wifi status to standby to avoid connecting to netd when adding an alarm.
+ NetworkStatsService._networks[wifiId].status = NETWORK_STATUS_STANDBY;
+
+ NetworkStatsService._db.addAlarm(alarm, function addSuccessCb(error, newId) {
+ NetworkStatsService._db.getAlarms(Ci.nsINetworkInterface.NETWORK_TYPE_WIFI,
+ testManifestURL, function onGet(error, result) {
+ do_check_eq(error, null);
+ do_check_eq(result.length, 1);
+
+ // Result of getAlarms is based on expected child's data format, so
+ // some changes are needed to be able to use it.
+ result[0].networkId = wifiId;
+ result[0].pageURL = testPageURL;
+ result[0].manifestURL = testManifestURL;
+
+ NetworkStatsService._fireAlarm(result[0], false);
+ NetworkStatsService._db.getAlarms(Ci.nsINetworkInterface.NETWORK_TYPE_WIFI,
+ testManifestURL, function onGet(error, result) {
+ do_check_eq(error, undefined);
+ do_check_eq(result.length, 0);
+ run_next_test();
+ });
+ });
+ });
});
function run_test() {
diff --git a/dom/network/tests/unit_stats/test_networkstats_service_proxy.js b/dom/network/tests/unit_stats/test_networkstats_service_proxy.js
new file mode 100644
index 000000000..04006c68a
--- /dev/null
+++ b/dom/network/tests/unit_stats/test_networkstats_service_proxy.js
@@ -0,0 +1,240 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+const {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components;
+
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+
+XPCOMUtils.defineLazyServiceGetter(this, "nssProxy",
+ "@mozilla.org/networkstatsServiceProxy;1",
+ "nsINetworkStatsServiceProxy");
+
+function mokConvertNetworkInterface() {
+ NetworkStatsService.convertNetworkInterface = function(aNetwork) {
+ if (aNetwork.type != Ci.nsINetworkInterface.NETWORK_TYPE_MOBILE &&
+ aNetwork.type != Ci.nsINetworkInterface.NETWORK_TYPE_WIFI) {
+ return null;
+ }
+
+ let id = '0';
+ if (aNetwork.type == Ci.nsINetworkInterface.NETWORK_TYPE_MOBILE) {
+ id = '1234'
+ }
+
+ let netId = this.getNetworkId(id, aNetwork.type);
+
+ if (!this._networks[netId]) {
+ this._networks[netId] = Object.create(null);
+ this._networks[netId].network = { id: id,
+ type: aNetwork.type };
+ }
+
+ return netId;
+ };
+}
+
+add_test(function test_saveAppStats() {
+ var cachedStats = NetworkStatsService.cachedStats;
+ var timestamp = NetworkStatsService.cachedStatsDate.getTime();
+
+ // Create to fake nsINetworkInterfaces. As nsINetworkInterface can not
+ // be instantiated, these two vars will emulate it by filling the properties
+ // that will be used.
+ var wifi = {type: Ci.nsINetworkInterface.NETWORK_TYPE_WIFI, id: "0"};
+ var mobile = {type: Ci.nsINetworkInterface.NETWORK_TYPE_MOBILE, id: "1234"};
+
+ // Insert fake mobile network interface in NetworkStatsService
+ var mobileNetId = NetworkStatsService.getNetworkId(mobile.id, mobile.type);
+
+ do_check_eq(Object.keys(cachedStats).length, 0);
+
+ nssProxy.saveAppStats(1, false, wifi, timestamp, 10, 20, false,
+ function (success, message) {
+ do_check_eq(success, true);
+ nssProxy.saveAppStats(1, false, mobile, timestamp, 10, 20, false,
+ function (success, message) {
+ var key1 = 1 + "" + false + "" + NetworkStatsService.getNetworkId(wifi.id, wifi.type);
+ var key2 = 1 + "" + false + "" + mobileNetId + "";
+
+ do_check_eq(Object.keys(cachedStats).length, 2);
+ do_check_eq(cachedStats[key1].appId, 1);
+ do_check_eq(cachedStats[key1].isInBrowser, false);
+ do_check_eq(cachedStats[key1].serviceType.length, 0);
+ do_check_eq(cachedStats[key1].networkId, wifi.id);
+ do_check_eq(cachedStats[key1].networkType, wifi.type);
+ do_check_eq(new Date(cachedStats[key1].date).getTime() / 1000,
+ Math.floor(timestamp / 1000));
+ do_check_eq(cachedStats[key1].rxBytes, 10);
+ do_check_eq(cachedStats[key1].txBytes, 20);
+ do_check_eq(cachedStats[key2].appId, 1);
+ do_check_eq(cachedStats[key1].serviceType.length, 0);
+ do_check_eq(cachedStats[key2].networkId, mobile.id);
+ do_check_eq(cachedStats[key2].networkType, mobile.type);
+ do_check_eq(new Date(cachedStats[key2].date).getTime() / 1000,
+ Math.floor(timestamp / 1000));
+ do_check_eq(cachedStats[key2].rxBytes, 10);
+ do_check_eq(cachedStats[key2].txBytes, 20);
+
+ run_next_test();
+ });
+ });
+});
+
+add_test(function test_saveServiceStats() {
+ var timestamp = NetworkStatsService.cachedStatsDate.getTime();
+
+ // Create to fake nsINetworkInterfaces. As nsINetworkInterface can not
+ // be instantiated, these two vars will emulate it by filling the properties
+ // that will be used.
+ var wifi = {type: Ci.nsINetworkInterface.NETWORK_TYPE_WIFI, id: "0"};
+ var mobile = {type: Ci.nsINetworkInterface.NETWORK_TYPE_MOBILE, id: "1234"};
+
+ // Insert fake mobile network interface in NetworkStatsService
+ var mobileNetId = NetworkStatsService.getNetworkId(mobile.id, mobile.type);
+
+ NetworkStatsService.updateCachedStats(function (success, msg) {
+ do_check_eq(success, true);
+
+ var cachedStats = NetworkStatsService.cachedStats;
+ do_check_eq(Object.keys(cachedStats).length, 0);
+
+ var serviceType = 'FakeType';
+ nssProxy.saveServiceStats(serviceType, wifi, timestamp, 10, 20, false,
+ function (success, message) {
+ do_check_eq(success, true);
+ nssProxy.saveServiceStats(serviceType, mobile, timestamp, 10, 20, false,
+ function (success, message) {
+ do_check_eq(success, true);
+ var key1 = 0 + "" + false + "" + serviceType +
+ NetworkStatsService.getNetworkId(wifi.id, wifi.type);
+ var key2 = 0 + "" + false + "" + serviceType + mobileNetId + "";
+
+ do_check_eq(Object.keys(cachedStats).length, 2);
+ do_check_eq(cachedStats[key1].appId, 0);
+ do_check_eq(cachedStats[key1].isInBrowser, false);
+ do_check_eq(cachedStats[key1].serviceType, serviceType);
+ do_check_eq(cachedStats[key1].networkId, wifi.id);
+ do_check_eq(cachedStats[key1].networkType, wifi.type);
+ do_check_eq(new Date(cachedStats[key1].date).getTime() / 1000,
+ Math.floor(timestamp / 1000));
+ do_check_eq(cachedStats[key1].rxBytes, 10);
+ do_check_eq(cachedStats[key1].txBytes, 20);
+ do_check_eq(cachedStats[key2].appId, 0);
+ do_check_eq(cachedStats[key1].serviceType, serviceType);
+ do_check_eq(cachedStats[key2].networkId, mobile.id);
+ do_check_eq(cachedStats[key2].networkType, mobile.type);
+ do_check_eq(new Date(cachedStats[key2].date).getTime() / 1000,
+ Math.floor(timestamp / 1000));
+ do_check_eq(cachedStats[key2].rxBytes, 10);
+ do_check_eq(cachedStats[key2].txBytes, 20);
+
+ run_next_test();
+ });
+ });
+ });
+});
+
+add_test(function test_saveStatsWithDifferentDates() {
+ var today = NetworkStatsService.cachedStatsDate;
+ var tomorrow = new Date(today.getTime() + (24 * 60 * 60 * 1000));
+
+ var mobile = {type: Ci.nsINetworkInterface.NETWORK_TYPE_MOBILE, id: "1234"};
+
+ NetworkStatsService.updateCachedStats(function (success, message) {
+ do_check_eq(success, true);
+
+ do_check_eq(Object.keys(NetworkStatsService.cachedStats).length, 0);
+ nssProxy.saveAppStats(1, false, mobile, today.getTime(), 10, 20, false,
+ function (success, message) {
+ do_check_eq(success, true);
+ nssProxy.saveAppStats(2, false, mobile, tomorrow.getTime(), 30, 40, false,
+ function (success, message) {
+ do_check_eq(success, true);
+
+ var cachedStats = NetworkStatsService.cachedStats;
+ var key = 2 + "" + false + "" +
+ NetworkStatsService.getNetworkId(mobile.id, mobile.type);
+ do_check_eq(Object.keys(cachedStats).length, 1);
+ do_check_eq(cachedStats[key].appId, 2);
+ do_check_eq(cachedStats[key].isInBrowser, false);
+ do_check_eq(cachedStats[key].networkId, mobile.id);
+ do_check_eq(cachedStats[key].networkType, mobile.type);
+ do_check_eq(new Date(cachedStats[key].date).getTime() / 1000,
+ Math.floor(tomorrow.getTime() / 1000));
+ do_check_eq(cachedStats[key].rxBytes, 30);
+ do_check_eq(cachedStats[key].txBytes, 40);
+
+ run_next_test();
+ });
+ });
+ });
+});
+
+add_test(function test_saveStatsWithMaxCachedTraffic() {
+ var timestamp = NetworkStatsService.cachedStatsDate.getTime();
+ var maxtraffic = NetworkStatsService.maxCachedTraffic;
+ var wifi = {type: Ci.nsINetworkInterface.NETWORK_TYPE_WIFI, id: "0"};
+
+ NetworkStatsService.updateCachedStats(function (success, message) {
+ do_check_eq(success, true);
+
+ var cachedStats = NetworkStatsService.cachedStats;
+ do_check_eq(Object.keys(cachedStats).length, 0);
+ nssProxy.saveAppStats(1, false, wifi, timestamp, 10, 20, false,
+ function (success, message) {
+ do_check_eq(success, true);
+ do_check_eq(Object.keys(cachedStats).length, 1);
+ nssProxy.saveAppStats(1, false, wifi, timestamp, maxtraffic, 20, false,
+ function (success, message) {
+ do_check_eq(success, true);
+ do_check_eq(Object.keys(cachedStats).length, 0);
+
+ run_next_test();
+ });
+ });
+ });
+});
+
+add_test(function test_saveAppStats() {
+ var cachedStats = NetworkStatsService.cachedStats;
+ var timestamp = NetworkStatsService.cachedStatsDate.getTime();
+
+ // Create to fake nsINetworkInterfaces. As nsINetworkInterface can not
+ // be instantiated, these two vars will emulate it by filling the properties
+ // that will be used.
+ var wifi = {type: Ci.nsINetworkInterface.NETWORK_TYPE_WIFI, id: "0"};
+ var mobile = {type: Ci.nsINetworkInterface.NETWORK_TYPE_MOBILE, id: "1234"};
+
+ // Insert fake mobile network interface in NetworkStatsService
+ var mobileNetId = NetworkStatsService.getNetworkId(mobile.id, mobile.type);
+
+ do_check_eq(Object.keys(cachedStats).length, 0);
+
+ nssProxy.saveAppStats(1, false, wifi, timestamp, 10, 20, false, { notify:
+ function (success, message) {
+ do_check_eq(success, true);
+ var iterations = 10;
+ var counter = 0;
+ var callback = function (success, message) {
+ if (counter == iterations - 1)
+ run_next_test();
+ counter++;
+ };
+
+ for (var i = 0; i < iterations; i++) {
+ nssProxy.saveAppStats(1, false, mobile, timestamp, 10, 20, false, callback);
+ }
+ }});
+});
+
+function run_test() {
+ do_get_profile();
+
+ Cu.import("resource://gre/modules/NetworkStatsService.jsm");
+
+ // Function convertNetworkInterface of NetworkStatsService causes errors when dealing
+ // with RIL to get the iccid, so overwrite it.
+ mokConvertNetworkInterface();
+
+ run_next_test();
+}
diff --git a/dom/network/tests/unit_stats/xpcshell.ini b/dom/network/tests/unit_stats/xpcshell.ini
index a6fea9b8a..9b69ab755 100644
--- a/dom/network/tests/unit_stats/xpcshell.ini
+++ b/dom/network/tests/unit_stats/xpcshell.ini
@@ -3,4 +3,5 @@ head =
tail =
[test_networkstats_service.js]
+[test_networkstats_service_proxy.js]
[test_networkstats_db.js]