diff options
Diffstat (limited to 'dom/network')
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] |