summaryrefslogtreecommitdiff
path: root/dom/network
diff options
context:
space:
mode:
authorwolfbeast <mcwerewolf@gmail.com>2014-05-21 11:38:25 +0200
committerwolfbeast <mcwerewolf@gmail.com>2014-05-21 11:38:25 +0200
commitd25ba7d760b017b038e5aa6c0a605b4a330eb68d (patch)
tree16ec27edc7d5f83986f16236d3a36a2682a0f37e /dom/network
parenta942906574671868daf122284a9c4689e6924f74 (diff)
downloadpalemoon-gre-d25ba7d760b017b038e5aa6c0a605b4a330eb68d.tar.gz
Recommit working copy to repo with proper line endings.
Diffstat (limited to 'dom/network')
-rw-r--r--dom/network/interfaces/Makefile.in15
-rw-r--r--dom/network/interfaces/moz.build33
-rw-r--r--dom/network/interfaces/nsIDOMCFStateChangeEvent.idl74
-rw-r--r--dom/network/interfaces/nsIDOMConnection.idl16
-rw-r--r--dom/network/interfaces/nsIDOMDataErrorEvent.idl21
-rw-r--r--dom/network/interfaces/nsIDOMMobileConnection.idl533
-rw-r--r--dom/network/interfaces/nsIDOMNavigatorNetwork.idl13
-rw-r--r--dom/network/interfaces/nsIDOMNetworkStats.idl35
-rw-r--r--dom/network/interfaces/nsIDOMNetworkStatsManager.idl56
-rw-r--r--dom/network/interfaces/nsIDOMTCPSocket.idl242
-rw-r--r--dom/network/interfaces/nsIDOMUSSDReceivedEvent.idl24
-rw-r--r--dom/network/interfaces/nsIMobileConnectionProvider.idl74
-rw-r--r--dom/network/interfaces/nsINavigatorMobileConnection.idl13
-rw-r--r--dom/network/interfaces/nsITCPSocketChild.idl25
-rw-r--r--dom/network/interfaces/nsITCPSocketParent.idl39
-rw-r--r--dom/network/moz.build8
-rw-r--r--dom/network/src/Connection.cpp117
-rw-r--r--dom/network/src/Connection.h67
-rw-r--r--dom/network/src/Constants.h23
-rw-r--r--dom/network/src/Makefile.in44
-rw-r--r--dom/network/src/MobileConnection.cpp512
-rw-r--r--dom/network/src/MobileConnection.h57
-rw-r--r--dom/network/src/NetworkStatsDB.jsm389
-rw-r--r--dom/network/src/NetworkStatsManager.js246
-rw-r--r--dom/network/src/NetworkStatsManager.manifest9
-rw-r--r--dom/network/src/NetworkStatsService.jsm394
-rw-r--r--dom/network/src/PTCPSocket.ipdl53
-rw-r--r--dom/network/src/TCPSocket.js728
-rw-r--r--dom/network/src/TCPSocket.manifest8
-rw-r--r--dom/network/src/TCPSocketChild.cpp215
-rw-r--r--dom/network/src/TCPSocketChild.h55
-rw-r--r--dom/network/src/TCPSocketParent.cpp217
-rw-r--r--dom/network/src/TCPSocketParent.h48
-rw-r--r--dom/network/src/TCPSocketParentIntermediary.js55
-rw-r--r--dom/network/src/Types.h21
-rw-r--r--dom/network/src/Utils.cpp21
-rw-r--r--dom/network/src/Utils.h24
-rw-r--r--dom/network/src/ipdl.mk7
-rw-r--r--dom/network/src/moz.build41
-rw-r--r--dom/network/tests/Makefile.in30
-rw-r--r--dom/network/tests/marionette/manifest.ini17
-rw-r--r--dom/network/tests/marionette/test_call_barring_get_option.js31
-rw-r--r--dom/network/tests/marionette/test_call_barring_set_error.js65
-rw-r--r--dom/network/tests/marionette/test_mobile_data_location.js119
-rw-r--r--dom/network/tests/marionette/test_mobile_data_state.js121
-rw-r--r--dom/network/tests/marionette/test_mobile_iccinfo.js85
-rw-r--r--dom/network/tests/marionette/test_mobile_mmi.js76
-rw-r--r--dom/network/tests/marionette/test_mobile_networks.js240
-rw-r--r--dom/network/tests/marionette/test_mobile_operator_names.js209
-rw-r--r--dom/network/tests/marionette/test_mobile_preferred_network_type.js65
-rw-r--r--dom/network/tests/marionette/test_mobile_voice_state.js153
-rw-r--r--dom/network/tests/moz.build12
-rw-r--r--dom/network/tests/test_network_basics.html46
-rw-r--r--dom/network/tests/test_networkstats_basics.html303
-rw-r--r--dom/network/tests/test_networkstats_disabled.html31
-rw-r--r--dom/network/tests/test_networkstats_enabled_no_perm.html35
-rw-r--r--dom/network/tests/test_networkstats_enabled_perm.html34
-rw-r--r--dom/network/tests/test_tcpsocket_default_permissions.html27
-rw-r--r--dom/network/tests/test_tcpsocket_enabled_no_perm.html35
-rw-r--r--dom/network/tests/test_tcpsocket_enabled_with_perm.html31
-rw-r--r--dom/network/tests/unit/test_multisend.js152
-rw-r--r--dom/network/tests/unit/test_tcpsocket.js526
-rw-r--r--dom/network/tests/unit/xpcshell.ini6
-rw-r--r--dom/network/tests/unit_ipc/test_tcpsocket_ipc.js9
-rw-r--r--dom/network/tests/unit_ipc/xpcshell.ini5
-rw-r--r--dom/network/tests/unit_stats/test_networkstats_db.js387
-rw-r--r--dom/network/tests/unit_stats/test_networkstats_service.js112
-rw-r--r--dom/network/tests/unit_stats/xpcshell.ini6
68 files changed, 7540 insertions, 0 deletions
diff --git a/dom/network/interfaces/Makefile.in b/dom/network/interfaces/Makefile.in
new file mode 100644
index 000000000..143590c47
--- /dev/null
+++ b/dom/network/interfaces/Makefile.in
@@ -0,0 +1,15 @@
+# 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
new file mode 100644
index 000000000..0b90873af
--- /dev/null
+++ b/dom/network/interfaces/moz.build
@@ -0,0 +1,33 @@
+# -*- 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/.
+
+XPIDL_SOURCES += [
+ 'nsIDOMConnection.idl',
+ 'nsIDOMDataErrorEvent.idl',
+ 'nsIDOMNavigatorNetwork.idl',
+ 'nsIDOMTCPSocket.idl',
+ 'nsIDOMUSSDReceivedEvent.idl',
+ 'nsITCPSocketChild.idl',
+ 'nsITCPSocketParent.idl',
+]
+
+if CONFIG['MOZ_B2G_RIL']:
+ XPIDL_SOURCES += [
+ 'nsIDOMCFStateChangeEvent.idl',
+ 'nsIDOMMobileConnection.idl',
+ 'nsIDOMNetworkStats.idl',
+ 'nsIDOMNetworkStatsManager.idl',
+ 'nsIMobileConnectionProvider.idl',
+ 'nsINavigatorMobileConnection.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
new file mode 100644
index 000000000..0b8d9fd93
--- /dev/null
+++ b/dom/network/interfaces/nsIDOMCFStateChangeEvent.idl
@@ -0,0 +1,74 @@
+/* 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
new file mode 100644
index 000000000..7a4c2b95e
--- /dev/null
+++ b/dom/network/interfaces/nsIDOMConnection.idl
@@ -0,0 +1,16 @@
+/* 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
new file mode 100644
index 000000000..740ada0ae
--- /dev/null
+++ b/dom/network/interfaces/nsIDOMDataErrorEvent.idl
@@ -0,0 +1,21 @@
+/* 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
new file mode 100644
index 000000000..02c4ee6e1
--- /dev/null
+++ b/dom/network/interfaces/nsIDOMMobileConnection.idl
@@ -0,0 +1,533 @@
+/* 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/nsIDOMNavigatorNetwork.idl b/dom/network/interfaces/nsIDOMNavigatorNetwork.idl
new file mode 100644
index 000000000..bb66cbbef
--- /dev/null
+++ b/dom/network/interfaces/nsIDOMNavigatorNetwork.idl
@@ -0,0 +1,13 @@
+/* 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 nsIDOMMozConnection;
+
+[scriptable, uuid(c1685d27-f2e2-4ed9-998f-ff5b1442058f)]
+interface nsIDOMMozNavigatorNetwork : nsISupports
+{
+ readonly attribute nsIDOMMozConnection mozConnection;
+};
diff --git a/dom/network/interfaces/nsIDOMNetworkStats.idl b/dom/network/interfaces/nsIDOMNetworkStats.idl
new file mode 100644
index 000000000..ae9fb9f3b
--- /dev/null
+++ b/dom/network/interfaces/nsIDOMNetworkStats.idl
@@ -0,0 +1,35 @@
+/* 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
new file mode 100644
index 000000000..4da6e151c
--- /dev/null
+++ b/dom/network/interfaces/nsIDOMNetworkStatsManager.idl
@@ -0,0 +1,56 @@
+/* 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 nsIDOMDOMRequest;
+
+dictionary NetworkStatsOptions
+{
+ /**
+ * 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.
+ */
+ DOMString connectionType;
+ jsval start; // date
+ jsval end; // date
+};
+
+[scriptable, uuid(87529a6c-aef6-11e1-a595-4f034275cfa6)]
+interface nsIDOMMozNetworkStatsManager : nsISupports
+{
+ /**
+ * Query network interface statistics.
+ *
+ * If options.connectionType is not provided, return statistics for all known
+ * network interfaces.
+ *
+ * If successful, the request result will be an nsIDOMMozNetworkStats object.
+ *
+ * If network stats are not available for some dates, then rxBytes &
+ * txBytes are undefined for those dates.
+ */
+ nsIDOMDOMRequest getNetworkStats(in jsval options);
+
+ /**
+ * Return available connection types.
+ */
+ readonly attribute jsval connectionTypes; // array of DOMStrings.
+
+ /**
+ * Clear all stats from DB.
+ */
+ nsIDOMDOMRequest clearAllData();
+
+ /**
+ * Time in seconds between samples stored in database.
+ */
+ readonly attribute long sampleRate;
+
+ /**
+ * Maximum number of samples stored in the database per connection type.
+ */
+ readonly attribute long maxStorageSamples;
+};
diff --git a/dom/network/interfaces/nsIDOMTCPSocket.idl b/dom/network/interfaces/nsIDOMTCPSocket.idl
new file mode 100644
index 000000000..70ac57698
--- /dev/null
+++ b/dom/network/interfaces/nsIDOMTCPSocket.idl
@@ -0,0 +1,242 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/**
+ * MozTCPSocket exposes a TCP client socket (no server sockets yet)
+ * to highly privileged apps. It provides a buffered, non-blocking
+ * interface for sending. For receiving, it uses an asynchronous,
+ * event handler based interface.
+ */
+
+#include "domstubs.idl"
+#include "nsIDOMEvent.idl"
+
+// Bug 731746 - Allow chrome JS object to implement nsIDOMEventTarget
+// nsITCPSocket should be an nsIEventTarget but js objects
+// cannot be an nsIEventTarget yet
+// #include "nsIEventTarget.idl"
+
+// Bug 723206 - Constructors implemented in JS from IDL should be
+// allowed to have arguments
+//
+// 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);
+
+[scriptable, uuid(1f99bc6f-73d3-44db-9dbf-5fc441704a7c)]
+interface nsIDOMTCPSocket : nsISupports
+{
+ /**
+ * Create and return a socket object which will attempt to connect to
+ * the given host and port.
+ *
+ * @param host The hostname of the server to connect to.
+ * @param port The port to connect to.
+ * @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.
+ *
+ * binaryType: "arraybuffer" to use ArrayBuffer
+ * instances in the ondata callback and as the argument
+ * to send. Defaults to "string", to use JavaScript strings.
+ *
+ * @return The new TCPSocket instance.
+ */
+ nsIDOMTCPSocket open(in DOMString host, in unsigned short port, [optional] in jsval options);
+
+ /**
+ * The host of this socket object.
+ */
+ readonly attribute DOMString host;
+
+ /**
+ * The port of this socket object.
+ */
+ readonly attribute unsigned short port;
+
+ /**
+ * True if this socket object is an SSL socket.
+ */
+ readonly attribute boolean ssl;
+
+ /**
+ * The number of bytes which have previously been buffered by calls to
+ * send on this socket.
+ */
+ readonly attribute unsigned long bufferedAmount;
+
+ /**
+ * Pause reading incoming data and invocations of the ondata handler until
+ * resume is called.
+ */
+ void suspend();
+
+ /**
+ * Resume reading incoming data and invoking ondata as usual.
+ */
+ void resume();
+
+ /**
+ * Close the socket.
+ */
+ void close();
+
+ /**
+ * Write data to the socket.
+ *
+ * @param data The data to write to the socket. If
+ * binaryType: "arraybuffer" was passed in the options
+ * object, then this object should be an ArrayBuffer instance.
+ * If binaryType: "string" was passed, or if no binaryType
+ * option was specified, then this object should be an
+ * ordinary JavaScript string.
+ * @param byteOffset The offset within the data from which to begin writing.
+ * Has no effect on non-ArrayBuffer data.
+ * @param byteLength The number of bytes to write. Has no effect on
+ * non-ArrayBuffer data.
+ *
+ * @return Send returns true or false as a hint to the caller that
+ * they may either continue sending more data immediately, or
+ * may want to wait until the other side has read some of the
+ * data which has already been written to the socket before
+ * buffering more. If send returns true, then less than 64k
+ * has been buffered and it's safe to immediately write more.
+ * If send returns false, then more than 64k has been buffered,
+ * and the caller may wish to wait until the ondrain event
+ * handler has been called before buffering more data by more
+ * calls to send.
+ */
+ boolean send(in jsval data, [optional] in unsigned long byteOffset, [optional] in unsigned long byteLength);
+
+ /**
+ * The readyState attribute indicates which state the socket is currently
+ * in. The state will be either "connecting", "open", "closing", or "closed".
+ */
+ readonly attribute DOMString readyState;
+
+ /**
+ * The binaryType attribute indicates which mode this socket uses for
+ * sending and receiving data. If the binaryType: "arraybuffer" option
+ * was passed to the open method that created this socket, binaryType
+ * will be "arraybuffer". Otherwise, it will be "string".
+ */
+ readonly attribute DOMString binaryType;
+
+ /**
+ * The onopen event handler is called when the connection to the server
+ * has been established. If the connection is refused, onerror will be
+ * called, instead.
+ */
+ attribute jsval onopen;
+
+ /**
+ * After send has buffered more than 64k of data, it returns false to
+ * indicate that the client should pause before sending more data, to
+ * avoid accumulating large buffers. This is only advisory, and the client
+ * is free to ignore it and buffer as much data as desired, but if reducing
+ * the size of buffers is important (especially for a streaming application)
+ * ondrain will be called once the previously-buffered data has been written
+ * to the network, at which point the client can resume calling send again.
+ */
+ attribute jsval ondrain;
+
+ /**
+ * The ondata handler will be called repeatedly and asynchronously after
+ * onopen has been called, every time some data was available from the server
+ * and was read. If binaryType: "arraybuffer" was passed to open, the data
+ * attribute of the event object will be an ArrayBuffer. If not, it will be a
+ * normal JavaScript string.
+ *
+ * At any time, the client may choose to pause reading and receiving ondata
+ * callbacks, by calling the socket's suspend() method. Further invocations
+ * of ondata will be paused until resume() is called.
+ */
+ attribute jsval ondata;
+
+ /**
+ * The onerror handler will be called when there is an error. The data
+ * attribute of the event passed to the onerror handler will have a
+ * description of the kind of error.
+ *
+ * If onerror is called before onopen, the error was connection refused,
+ * and onclose will not be called. If onerror is called after onopen,
+ * the connection was lost, and onclose will be called after onerror.
+ */
+ attribute jsval onerror;
+
+ /**
+ * The onclose handler is called once the underlying network socket
+ * has been closed, either by the server, or by the client calling
+ * close.
+ *
+ * If onerror was not called before onclose, then either side cleanly
+ * closed the connection.
+ */
+ attribute jsval onclose;
+};
+
+/*
+ * 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)]
+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);
+
+ // Trigger the callback for |type| and provide a string argument
+ void callListenerData(in DOMString type, in DOMString data);
+
+ // Trigger the callback for |type| and provide an ArrayBuffer argument
+ void callListenerArrayBuffer(in DOMString type, in jsval data);
+
+ // 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);
+};
+
+/**
+ * nsITCPSocketEvent is the event object which is passed as the
+ * first argument to all the event handler callbacks. It contains
+ * the socket that was associated with the event, the type of event,
+ * and the data associated with the event (if any).
+ */
+
+[scriptable, uuid(0f2abcca-b483-4539-a3e8-345707f75c44)]
+interface nsITCPSocketEvent : nsISupports {
+ /**
+ * The socket object which produced this event.
+ */
+ readonly attribute nsIDOMTCPSocket target;
+
+ /**
+ * The type of this event. One of:
+ *
+ * open
+ * error
+ * data
+ * drain
+ * close
+ */
+ readonly attribute DOMString type;
+
+ /**
+ * The data related to this event, if any. In the ondata callback,
+ * data will be the bytes read from the network; if the binaryType
+ * of the socket was "arraybuffer", this value will be of type ArrayBuffer;
+ * otherwise, it will be a normal JavaScript string.
+ *
+ * In the onerror callback, data will be a string with a description
+ * of the error.
+ *
+ * In the other callbacks, data will be an empty string.
+ */
+ readonly attribute jsval data;
+};
+
diff --git a/dom/network/interfaces/nsIDOMUSSDReceivedEvent.idl b/dom/network/interfaces/nsIDOMUSSDReceivedEvent.idl
new file mode 100644
index 000000000..b859890ad
--- /dev/null
+++ b/dom/network/interfaces/nsIDOMUSSDReceivedEvent.idl
@@ -0,0 +1,24 @@
+/* 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
new file mode 100644
index 000000000..ddba41fb3
--- /dev/null
+++ b/dom/network/interfaces/nsIMobileConnectionProvider.idl
@@ -0,0 +1,74 @@
+/* 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/nsINavigatorMobileConnection.idl b/dom/network/interfaces/nsINavigatorMobileConnection.idl
new file mode 100644
index 000000000..d6a279210
--- /dev/null
+++ b/dom/network/interfaces/nsINavigatorMobileConnection.idl
@@ -0,0 +1,13 @@
+/* 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/nsITCPSocketChild.idl b/dom/network/interfaces/nsITCPSocketChild.idl
new file mode 100644
index 000000000..83963207d
--- /dev/null
+++ b/dom/network/interfaces/nsITCPSocketChild.idl
@@ -0,0 +1,25 @@
+/* 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"
+
+interface nsITCPSocketInternal;
+interface nsIDOMWindow;
+
+// Interface to allow the content process socket to reach the IPC bridge.
+[scriptable, uuid(bdc91763-e9a1-4122-9c2f-8f17505c8c7a)]
+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);
+
+ // 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();
+};
diff --git a/dom/network/interfaces/nsITCPSocketParent.idl b/dom/network/interfaces/nsITCPSocketParent.idl
new file mode 100644
index 000000000..346e11a24
--- /dev/null
+++ b/dom/network/interfaces/nsITCPSocketParent.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"
+
+interface nsIDOMTCPSocket;
+
+// 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 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);
+};
+
+// 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)]
+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);
+
+ // Send a basic string along the connection
+ void sendString(in DOMString data);
+
+ // Send a typed array
+ void sendArrayBuffer(in jsval data);
+};
diff --git a/dom/network/moz.build b/dom/network/moz.build
new file mode 100644
index 000000000..6834f77cb
--- /dev/null
+++ b/dom/network/moz.build
@@ -0,0 +1,8 @@
+# -*- 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/.
+
+PARALLEL_DIRS += ['interfaces', 'src']
+TEST_DIRS += ['tests']
diff --git a/dom/network/src/Connection.cpp b/dom/network/src/Connection.cpp
new file mode 100644
index 000000000..c77bb3d6d
--- /dev/null
+++ b/dom/network/src/Connection.cpp
@@ -0,0 +1,117 @@
+/* -*- 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/Connection.h b/dom/network/src/Connection.h
new file mode 100644
index 000000000..015994d13
--- /dev/null
+++ b/dom/network/src/Connection.h
@@ -0,0 +1,67 @@
+/* -*- 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_Connection_h
+#define mozilla_dom_network_Connection_h
+
+#include "nsIDOMConnection.h"
+#include "nsDOMEventTargetHelper.h"
+#include "nsCycleCollectionParticipant.h"
+#include "mozilla/Observer.h"
+#include "Types.h"
+
+namespace mozilla {
+
+namespace hal {
+class NetworkInformation;
+} // namespace hal
+
+namespace dom {
+namespace network {
+
+class Connection : public nsDOMEventTargetHelper
+ , public nsIDOMMozConnection
+ , public NetworkObserver
+{
+public:
+ NS_DECL_ISUPPORTS_INHERITED
+ NS_DECL_NSIDOMMOZCONNECTION
+
+ NS_REALLY_FORWARD_NSIDOMEVENTTARGET(nsDOMEventTargetHelper)
+
+ Connection();
+
+ void Init(nsPIDOMWindow *aWindow);
+ void Shutdown();
+
+ // For IObserver
+ void Notify(const hal::NetworkInformation& aNetworkInfo);
+
+private:
+ /**
+ * Update the connection information stored in the object using a
+ * NetworkInformation object.
+ */
+ void UpdateFromNetworkInfo(const hal::NetworkInformation& aNetworkInfo);
+
+ /**
+ * If the connection is of a type that can be metered.
+ */
+ bool mCanBeMetered;
+
+ /**
+ * The connection bandwidth.
+ */
+ double mBandwidth;
+
+ static const char* sMeteredPrefName;
+ static const bool sMeteredDefaultValue;
+};
+
+} // namespace network
+} // namespace dom
+} // namespace mozilla
+
+#endif // mozilla_dom_network_Connection_h
diff --git a/dom/network/src/Constants.h b/dom/network/src/Constants.h
new file mode 100644
index 000000000..1c98f5ebb
--- /dev/null
+++ b/dom/network/src/Constants.h
@@ -0,0 +1,23 @@
+/* -*- 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_Constants_h__
+#define mozilla_dom_network_Constants_h__
+
+/**
+ * A set of constants to be used by network backends.
+ */
+namespace mozilla {
+namespace dom {
+namespace network {
+
+ static const double kDefaultBandwidth = -1.0;
+ static const bool kDefaultCanBeMetered = false;
+
+} // namespace network
+} // namespace dom
+} // namespace mozilla
+
+#endif // mozilla_dom_network_Constants_h__
diff --git a/dom/network/src/Makefile.in b/dom/network/src/Makefile.in
new file mode 100644
index 000000000..057f04638
--- /dev/null
+++ b/dom/network/src/Makefile.in
@@ -0,0 +1,44 @@
+# 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
new file mode 100644
index 000000000..134836638
--- /dev/null
+++ b/dom/network/src/MobileConnection.cpp
@@ -0,0 +1,512 @@
+/* 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
new file mode 100644
index 000000000..3dfc63b99
--- /dev/null
+++ b/dom/network/src/MobileConnection.h
@@ -0,0 +1,57 @@
+/* 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
new file mode 100644
index 000000000..c7dd3e475
--- /dev/null
+++ b/dom/network/src/NetworkStatsDB.jsm
@@ -0,0 +1,389 @@
+/* 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
new file mode 100644
index 000000000..9912d2fbc
--- /dev/null
+++ b/dom/network/src/NetworkStatsManager.js
@@ -0,0 +1,246 @@
+/* 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
new file mode 100644
index 000000000..58d1c2363
--- /dev/null
+++ b/dom/network/src/NetworkStatsManager.manifest
@@ -0,0 +1,9 @@
+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
new file mode 100644
index 000000000..790d9831a
--- /dev/null
+++ b/dom/network/src/NetworkStatsService.jsm
@@ -0,0 +1,394 @@
+/* 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);
+
+ 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/PTCPSocket.ipdl b/dom/network/src/PTCPSocket.ipdl
new file mode 100644
index 000000000..765f9e7c0
--- /dev/null
+++ b/dom/network/src/PTCPSocket.ipdl
@@ -0,0 +1,53 @@
+/* -*- 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 mozilla::void_t;
+
+struct TCPError {
+ nsString name;
+};
+
+union SendableData {
+ uint8_t[];
+ nsString;
+};
+
+union CallbackData {
+ void_t;
+ SendableData;
+ TCPError;
+};
+
+namespace mozilla {
+namespace net {
+
+//-------------------------------------------------------------------
+protocol PTCPSocket
+{
+ manager PNecko;
+
+parent:
+ Data(SendableData data);
+ Suspend();
+ Resume();
+ Close();
+ RequestDelete();
+
+child:
+ Callback(nsString type, CallbackData data,
+ nsString readyState, uint32_t bufferedAmount);
+ __delete__();
+};
+
+
+} // namespace net
+} // namespace mozilla
+
diff --git a/dom/network/src/TCPSocket.js b/dom/network/src/TCPSocket.js
new file mode 100644
index 000000000..6ab48f1b8
--- /dev/null
+++ b/dom/network/src/TCPSocket.js
@@ -0,0 +1,728 @@
+/* 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");
+Cu.import("resource://gre/modules/Services.jsm");
+
+const InputStreamPump = CC(
+ "@mozilla.org/network/input-stream-pump;1", "nsIInputStreamPump", "init"),
+ AsyncStreamCopier = CC(
+ "@mozilla.org/network/async-stream-copier;1", "nsIAsyncStreamCopier", "init"),
+ ScriptableInputStream = CC(
+ "@mozilla.org/scriptableinputstream;1", "nsIScriptableInputStream", "init"),
+ BinaryInputStream = CC(
+ "@mozilla.org/binaryinputstream;1", "nsIBinaryInputStream", "setInputStream"),
+ StringInputStream = CC(
+ '@mozilla.org/io/string-input-stream;1', 'nsIStringInputStream'),
+ ArrayBufferInputStream = CC(
+ '@mozilla.org/io/arraybuffer-input-stream;1', 'nsIArrayBufferInputStream'),
+ MultiplexInputStream = CC(
+ '@mozilla.org/io/multiplex-input-stream;1', 'nsIMultiplexInputStream');
+
+const kCONNECTING = 'connecting';
+const kOPEN = 'open';
+const kCLOSING = 'closing';
+const kCLOSED = 'closed';
+
+const BUFFER_SIZE = 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
+// sub-classes DOMError. Bug 867872 has been filed to implement this and
+// contains a documented TCPError.webidl that maps all the error codes we use in
+// this file to slightly more readable explanations.
+function createTCPError(aWindow, aErrorName, aErrorType) {
+ return new (aWindow ? aWindow.DOMError : DOMError)(aErrorName);
+}
+
+
+/*
+ * Debug logging function
+ */
+
+let debug = false;
+function LOG(msg) {
+ if (debug)
+ dump("TCPSocket: " + msg + "\n");
+}
+
+/*
+ * nsITCPSocketEvent object
+ */
+
+function TCPSocketEvent(type, sock, data) {
+ this._type = type;
+ this._target = sock;
+ this._data = data;
+}
+
+TCPSocketEvent.prototype = {
+ __exposedProps__: {
+ type: 'r',
+ target: 'r',
+ data: 'r'
+ },
+ get type() {
+ return this._type;
+ },
+ get target() {
+ return this._target;
+ },
+ get data() {
+ return this._data;
+ }
+}
+
+/*
+ * nsIDOMTCPSocket object
+ */
+
+function TCPSocket() {
+ this._readyState = kCLOSED;
+
+ this._onopen = null;
+ this._ondrain = null;
+ this._ondata = null;
+ this._onerror = null;
+ this._onclose = null;
+
+ this._binaryType = "string";
+
+ this._host = "";
+ this._port = 0;
+ this._ssl = false;
+
+ this.useWin = null;
+}
+
+TCPSocket.prototype = {
+ __exposedProps__: {
+ open: 'r',
+ host: 'r',
+ port: 'r',
+ ssl: 'r',
+ bufferedAmount: 'r',
+ suspend: 'r',
+ resume: 'r',
+ close: 'r',
+ send: 'r',
+ readyState: 'r',
+ binaryType: 'r',
+ onopen: 'rw',
+ ondrain: 'rw',
+ ondata: 'rw',
+ onerror: 'rw',
+ onclose: 'rw'
+ },
+ // The binary type, "string" or "arraybuffer"
+ _binaryType: null,
+
+ // Internal
+ _hasPrivileges: null,
+
+ // Raw socket streams
+ _transport: null,
+ _socketInputStream: null,
+ _socketOutputStream: null,
+
+ // Input stream machinery
+ _inputStreamPump: null,
+ _inputStreamScriptable: null,
+ _inputStreamBinary: null,
+
+ // Output stream machinery
+ _multiplexStream: null,
+ _multiplexStreamCopier: null,
+
+ _asyncCopierActive: false,
+ _waitingForDrain: false,
+ _suspendCount: 0,
+
+ // Reported parent process buffer
+ _bufferedAmount: 0,
+
+ // IPC socket actor
+ _socketBridge: null,
+
+ // Public accessors.
+ get readyState() {
+ return this._readyState;
+ },
+ get binaryType() {
+ return this._binaryType;
+ },
+ get host() {
+ return this._host;
+ },
+ get port() {
+ return this._port;
+ },
+ get ssl() {
+ return this._ssl;
+ },
+ get bufferedAmount() {
+ if (this._inChild) {
+ return this._bufferedAmount;
+ }
+ return this._multiplexStream.available();
+ },
+ get onopen() {
+ return this._onopen;
+ },
+ set onopen(f) {
+ this._onopen = f;
+ },
+ get ondrain() {
+ return this._ondrain;
+ },
+ set ondrain(f) {
+ this._ondrain = f;
+ },
+ get ondata() {
+ return this._ondata;
+ },
+ set ondata(f) {
+ this._ondata = f;
+ },
+ get onerror() {
+ return this._onerror;
+ },
+ set onerror(f) {
+ this._onerror = f;
+ },
+ get onclose() {
+ return this._onclose;
+ },
+ set onclose(f) {
+ this._onclose = f;
+ },
+
+ // Helper methods.
+ _createTransport: function ts_createTransport(host, port, sslMode) {
+ let options, optlen;
+ if (sslMode) {
+ options = [sslMode];
+ optlen = 1;
+ } else {
+ options = null;
+ optlen = 0;
+ }
+ return Cc["@mozilla.org/network/socket-transport-service;1"]
+ .getService(Ci.nsISocketTransportService)
+ .createTransport(options, optlen, host, port, null);
+ },
+
+ _ensureCopying: function ts_ensureCopying() {
+ let self = this;
+ if (this._asyncCopierActive) {
+ return;
+ }
+ this._asyncCopierActive = true;
+ this._multiplexStreamCopier.asyncCopy({
+ onStartRequest: function ts_output_onStartRequest() {
+ },
+ onStopRequest: function ts_output_onStopRequest(request, context, status) {
+ self._asyncCopierActive = false;
+ self._multiplexStream.removeStream(0);
+
+ if (!Components.isSuccessCode(status)) {
+ // Note that we can/will get an error here as well as in the
+ // onStopRequest for inbound data.
+ self._maybeReportErrorAndCloseIfOpen(status);
+ return;
+ }
+
+ if (self._multiplexStream.count) {
+ self._ensureCopying();
+ } else {
+ if (self._waitingForDrain) {
+ self._waitingForDrain = false;
+ self.callListener("drain");
+ }
+ if (self._readyState === kCLOSING) {
+ self._socketOutputStream.close();
+ self._readyState = kCLOSED;
+ self.callListener("close");
+ }
+ }
+ }
+ }, null);
+ },
+
+ callListener: function ts_callListener(type, data) {
+ if (!this["on" + type])
+ return;
+
+ this["on" + type].call(null, new TCPSocketEvent(type, this, data || ""));
+ },
+
+ /* nsITCPSocketInternal methods */
+ callListenerError: function ts_callListenerError(type, name) {
+ // XXX we're not really using TCPError at this time, so there's only a name
+ // attribute to pass.
+ this.callListener(type, createTCPError(this.useWin, name));
+ },
+
+ callListenerData: function ts_callListenerString(type, data) {
+ this.callListener(type, data);
+ },
+
+ callListenerArrayBuffer: function ts_callListenerArrayBuffer(type, data) {
+ this.callListener(type, data);
+ },
+
+ callListenerVoid: function ts_callListenerVoid(type) {
+ this.callListener(type);
+ },
+
+ updateReadyStateAndBuffered: function ts_setReadyState(readyState, bufferedAmount) {
+ this._readyState = readyState;
+ this._bufferedAmount = bufferedAmount;
+ },
+ /* end nsITCPSocketInternal methods */
+
+ initWindowless: function ts_initWindowless() {
+ return Services.prefs.getBoolPref("dom.mozTCPSocket.enabled");
+ },
+
+ init: function ts_init(aWindow) {
+ if (!this.initWindowless())
+ return null;
+
+ let principal = aWindow.document.nodePrincipal;
+ let secMan = Cc["@mozilla.org/scriptsecuritymanager;1"]
+ .getService(Ci.nsIScriptSecurityManager);
+
+ let perm = principal == secMan.getSystemPrincipal()
+ ? Ci.nsIPermissionManager.ALLOW_ACTION
+ : Services.perms.testExactPermissionFromPrincipal(principal, "tcp-socket");
+
+ this._hasPrivileges = perm == Ci.nsIPermissionManager.ALLOW_ACTION;
+
+ let util = aWindow.QueryInterface(
+ Ci.nsIInterfaceRequestor
+ ).getInterface(Ci.nsIDOMWindowUtils);
+
+ this.useWin = XPCNativeWrapper.unwrap(aWindow);
+ this.innerWindowID = util.currentInnerWindowID;
+ LOG("window init: " + this.innerWindowID);
+ },
+
+ observe: function(aSubject, aTopic, aData) {
+ if (aTopic == "inner-window-destroyed") {
+ let wId = aSubject.QueryInterface(Ci.nsISupportsPRUint64).data;
+ if (wId == this.innerWindowID) {
+ LOG("inner-window-destroyed: " + this.innerWindowID);
+
+ // This window is now dead, so we want to clear the callbacks
+ // so that we don't get a "can't access dead object" when the
+ // underlying stream goes to tell us that we are closed
+ this.onopen = null;
+ this.ondrain = null;
+ this.ondata = null;
+ this.onerror = null;
+ this.onclose = null;
+
+ this.useWin = null;
+
+ // Clean up our socket
+ this.close();
+ }
+ }
+ },
+
+ // 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"));
+
+ // 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 TCPSocket();
+
+ that.useWin = this.useWin;
+ that.innerWindowID = this.innerWindowID;
+ that._inChild = this._inChild;
+
+ LOG("window init: " + that.innerWindowID);
+ Services.obs.addObserver(that, "inner-window-destroyed", true);
+
+ LOG("startup called");
+ LOG("Host info: " + host + ":" + port);
+
+ that._readyState = kCONNECTING;
+ that._host = host;
+ that._port = port;
+ if (options !== undefined) {
+ if (options.useSSL) {
+ that._ssl = 'ssl';
+ } else {
+ that._ssl = false;
+ }
+ that._binaryType = options.binaryType || that._binaryType;
+ }
+
+ LOG("SSL: " + that.ssl);
+
+ 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);
+ return that;
+ }
+
+ let transport = that._transport = this._createTransport(host, port, that._ssl);
+ transport.setEventSink(that, Services.tm.currentThread);
+
+ that._socketInputStream = transport.openInputStream(0, 0, 0);
+ that._socketOutputStream = 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.
+ that._socketInputStream.asyncWait(
+ that, that._socketInputStream.WAIT_CLOSURE_ONLY, 0, Services.tm.currentThread);
+
+ if (that._binaryType === "arraybuffer") {
+ that._inputStreamBinary = new BinaryInputStream(that._socketInputStream);
+ } else {
+ that._inputStreamScriptable = new ScriptableInputStream(that._socketInputStream);
+ }
+
+ that._multiplexStream = new MultiplexInputStream();
+
+ 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);
+
+ return that;
+ },
+
+ close: function ts_close() {
+ if (this._readyState === kCLOSED || this._readyState === kCLOSING)
+ return;
+
+ LOG("close called");
+ this._readyState = kCLOSING;
+
+ if (this._inChild) {
+ this._socketBridge.close();
+ return;
+ }
+
+ if (!this._multiplexStream.count) {
+ this._socketOutputStream.close();
+ }
+ this._socketInputStream.close();
+ },
+
+ send: function ts_send(data, byteOffset, byteLength) {
+ if (this._readyState !== kOPEN) {
+ throw new Error("Socket not open.");
+ }
+
+ if (this._binaryType === "arraybuffer") {
+ byteLength = byteLength || data.byteLength;
+ }
+
+ if (this._inChild) {
+ this._socketBridge.send(data, byteOffset, byteLength);
+ }
+
+ let length = this._binaryType === "arraybuffer" ? byteLength : data.length;
+
+ var newBufferedAmount = this.bufferedAmount + length;
+ var bufferNotFull = newBufferedAmount < BUFFER_SIZE;
+ if (this._inChild) {
+ return bufferNotFull;
+ }
+
+ let new_stream;
+ if (this._binaryType === "arraybuffer") {
+ new_stream = new ArrayBufferInputStream();
+ new_stream.setData(data, byteOffset, byteLength);
+ } else {
+ 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;
+ }
+
+ this._ensureCopying();
+ return bufferNotFull;
+ },
+
+ suspend: function ts_suspend() {
+ if (this._inChild) {
+ this._socketBridge.suspend();
+ return;
+ }
+
+ if (this._inputStreamPump) {
+ this._inputStreamPump.suspend();
+ } else {
+ ++this._suspendCount;
+ }
+ },
+
+ resume: function ts_resume() {
+ if (this._inChild) {
+ this._socketBridge.resume();
+ return;
+ }
+
+ if (this._inputStreamPump) {
+ this._inputStreamPump.resume();
+ } else {
+ --this._suspendCount;
+ }
+ },
+
+ _maybeReportErrorAndCloseIfOpen: function(status) {
+ // If we're closed, we've already reported the error or just don't need to
+ // report the error.
+ if (this._readyState === kCLOSED)
+ return;
+ this._readyState = kCLOSED;
+
+ if (!Components.isSuccessCode(status)) {
+ // Convert the status code to an appropriate error message. Raw constants
+ // are used inline in all cases for consistency. Some error codes are
+ // available in Components.results, some aren't. Network error codes are
+ // effectively stable, NSS error codes are officially not, but we have no
+ // symbolic way to dynamically resolve them anyways (other than an ability
+ // to determine the error class.)
+ let errName, errType;
+ // security module? (and this is an error)
+ if ((status & 0xff0000) === 0x5a0000) {
+ const nsINSSErrorsService = Ci.nsINSSErrorsService;
+ let nssErrorsService = Cc['@mozilla.org/nss_errors_service;1']
+ .getService(nsINSSErrorsService);
+ let errorClass;
+ // getErrorClass will throw a generic NS_ERROR_FAILURE if the error code is
+ // somehow not in the set of covered errors.
+ try {
+ errorClass = nssErrorsService.getErrorClass(status);
+ }
+ catch (ex) {
+ errorClass = 'SecurityProtocol';
+ }
+ switch (errorClass) {
+ case nsINSSErrorsService.ERROR_CLASS_SSL_PROTOCOL:
+ errType = 'SecurityProtocol';
+ break;
+ case nsINSSErrorsService.ERROR_CLASS_BAD_CERT:
+ errType = 'SecurityCertificate';
+ break;
+ // no default is required; the platform impl automatically defaults to
+ // ERROR_CLASS_SSL_PROTOCOL.
+ }
+
+ // NSS_SEC errors (happen below the base value because of negative vals)
+ if ((status & 0xffff) <
+ Math.abs(nsINSSErrorsService.NSS_SEC_ERROR_BASE)) {
+ // The bases are actually negative, so in our positive numeric space, we
+ // need to subtract the base off our value.
+ let nssErr = Math.abs(nsINSSErrorsService.NSS_SEC_ERROR_BASE) -
+ (status & 0xffff);
+ switch (nssErr) {
+ case 11: // SEC_ERROR_EXPIRED_CERTIFICATE, sec(11)
+ errName = 'SecurityExpiredCertificateError';
+ break;
+ case 12: // SEC_ERROR_REVOKED_CERTIFICATE, sec(12)
+ errName = 'SecurityRevokedCertificateError';
+ break;
+ // per bsmith, we will be unable to tell these errors apart very soon,
+ // so it makes sense to just folder them all together already.
+ case 13: // SEC_ERROR_UNKNOWN_ISSUER, sec(13)
+ case 20: // SEC_ERROR_UNTRUSTED_ISSUER, sec(20)
+ case 21: // SEC_ERROR_UNTRUSTED_CERT, sec(21)
+ case 36: // SEC_ERROR_CA_CERT_INVALID, sec(36)
+ errName = 'SecurityUntrustedCertificateIssuerError';
+ break;
+ case 90: // SEC_ERROR_INADEQUATE_KEY_USAGE, sec(90)
+ errName = 'SecurityInadequateKeyUsageError';
+ break;
+ case 176: // SEC_ERROR_CERT_SIGNATURE_ALGORITHM_DISABLED, sec(176)
+ errName = 'SecurityCertificateSignatureAlgorithmDisabledError';
+ break;
+ default:
+ errName = 'SecurityError';
+ break;
+ }
+ }
+ // NSS_SSL errors
+ else {
+ let sslErr = Math.abs(nsINSSErrorsService.NSS_SSL_ERROR_BASE) -
+ (status & 0xffff);
+ switch (sslErr) {
+ case 3: // SSL_ERROR_NO_CERTIFICATE, ssl(3)
+ errName = 'SecurityNoCertificateError';
+ break;
+ case 4: // SSL_ERROR_BAD_CERTIFICATE, ssl(4)
+ errName = 'SecurityBadCertificateError';
+ break;
+ case 8: // SSL_ERROR_UNSUPPORTED_CERTIFICATE_TYPE, ssl(8)
+ errName = 'SecurityUnsupportedCertificateTypeError';
+ break;
+ case 9: // SSL_ERROR_UNSUPPORTED_VERSION, ssl(9)
+ errName = 'SecurityUnsupportedTLSVersionError';
+ break;
+ case 12: // SSL_ERROR_BAD_CERT_DOMAIN, ssl(12)
+ errName = 'SecurityCertificateDomainMismatchError';
+ break;
+ default:
+ errName = 'SecurityError';
+ break;
+ }
+ }
+ }
+ // must be network
+ else {
+ errType = 'Network';
+ switch (status) {
+ // connect to host:port failed
+ case 0x804B000C: // NS_ERROR_CONNECTION_REFUSED, network(13)
+ errName = 'ConnectionRefusedError';
+ break;
+ // network timeout error
+ case 0x804B000E: // NS_ERROR_NET_TIMEOUT, network(14)
+ errName = 'NetworkTimeoutError';
+ break;
+ // hostname lookup failed
+ case 0x804B001E: // NS_ERROR_UNKNOWN_HOST, network(30)
+ errName = 'DomainNotFoundError';
+ break;
+ case 0x804B0047: // NS_ERROR_NET_INTERRUPT, network(71)
+ errName = 'NetworkInterruptError';
+ break;
+ default:
+ errName = 'NetworkError';
+ break;
+ }
+ }
+ let err = createTCPError(this.useWin, errName, errType);
+ this.callListener("error", err);
+ }
+ this.callListener("close");
+ },
+
+ // nsITransportEventSink (Triggered by transport.setEventSink)
+ onTransportStatus: function ts_onTransportStatus(
+ transport, status, progress, max) {
+ if (status === Ci.nsISocketTransport.STATUS_CONNECTED_TO) {
+ this._readyState = kOPEN;
+ this.callListener("open");
+
+ this._inputStreamPump = new InputStreamPump(
+ this._socketInputStream, -1, -1, 0, 0, false
+ );
+
+ while (this._suspendCount--) {
+ this._inputStreamPump.suspend();
+ }
+
+ this._inputStreamPump.asyncRead(this, null);
+ }
+ },
+
+ // nsIAsyncInputStream (Triggered by _socketInputStream.asyncWait)
+ // Only used for detecting connection refused
+ onInputStreamReady: function ts_onInputStreamReady(input) {
+ try {
+ input.available();
+ } catch (e) {
+ // NS_ERROR_CONNECTION_REFUSED
+ this._maybeReportErrorAndCloseIfOpen(0x804B000C);
+ }
+ },
+
+ // nsIRequestObserver (Triggered by _inputStreamPump.asyncRead)
+ onStartRequest: function ts_onStartRequest(request, context) {
+ },
+
+ // nsIRequestObserver (Triggered by _inputStreamPump.asyncRead)
+ onStopRequest: function ts_onStopRequest(request, context, status) {
+ let buffered_output = this._multiplexStream.count !== 0;
+
+ this._inputStreamPump = null;
+
+ let statusIsError = !Components.isSuccessCode(status);
+
+ if (buffered_output && !statusIsError) {
+ // If we have some buffered output still, and status is not an
+ // error, the other side has done a half-close, but we don't
+ // want to be in the close state until we are done sending
+ // everything that was buffered. We also don't want to call onclose
+ // yet.
+ return;
+ }
+
+ // We call this even if there is no error.
+ this._maybeReportErrorAndCloseIfOpen(status);
+ },
+
+ // nsIStreamListener (Triggered by _inputStreamPump.asyncRead)
+ onDataAvailable: function ts_onDataAvailable(request, context, inputStream, offset, count) {
+ if (this._binaryType === "arraybuffer") {
+ let buffer = new (this.useWin ? this.useWin.ArrayBuffer : ArrayBuffer)(count);
+ this._inputStreamBinary.readArrayBuffer(count, buffer);
+ this.callListener("data", buffer);
+ } else {
+ this.callListener("data", this._inputStreamScriptable.read(count));
+ }
+ },
+
+ classID: Components.ID("{cda91b22-6472-11e1-aa11-834fec09cd0a}"),
+
+ classInfo: XPCOMUtils.generateCI({
+ classID: Components.ID("{cda91b22-6472-11e1-aa11-834fec09cd0a}"),
+ contractID: "@mozilla.org/tcp-socket;1",
+ classDescription: "Client TCP Socket",
+ interfaces: [
+ Ci.nsIDOMTCPSocket,
+ ],
+ flags: Ci.nsIClassInfo.DOM_OBJECT,
+ }),
+
+ QueryInterface: XPCOMUtils.generateQI([
+ Ci.nsIDOMTCPSocket,
+ Ci.nsITCPSocketInternal,
+ Ci.nsIDOMGlobalPropertyInitializer,
+ Ci.nsIObserver,
+ Ci.nsISupportsWeakReference
+ ])
+}
+
+this.NSGetFactory = XPCOMUtils.generateNSGetFactory([TCPSocket]);
diff --git a/dom/network/src/TCPSocket.manifest b/dom/network/src/TCPSocket.manifest
new file mode 100644
index 000000000..0d22400ea
--- /dev/null
+++ b/dom/network/src/TCPSocket.manifest
@@ -0,0 +1,8 @@
+# TCPSocket.js
+component {cda91b22-6472-11e1-aa11-834fec09cd0a} TCPSocket.js
+contract @mozilla.org/tcp-socket;1 {cda91b22-6472-11e1-aa11-834fec09cd0a}
+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}
diff --git a/dom/network/src/TCPSocketChild.cpp b/dom/network/src/TCPSocketChild.cpp
new file mode 100644
index 000000000..1e70561d5
--- /dev/null
+++ b/dom/network/src/TCPSocketChild.cpp
@@ -0,0 +1,215 @@
+/* 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 "TCPSocketChild.h"
+#include "mozilla/net/NeckoChild.h"
+#include "mozilla/dom/PBrowserChild.h"
+#include "mozilla/dom/TabChild.h"
+#include "nsIDOMTCPSocket.h"
+#include "nsJSUtils.h"
+#include "nsContentUtils.h"
+#include "jsapi.h"
+#include "jsfriendapi.h"
+#include "jswrapper.h"
+
+using mozilla::net::gNeckoChild;
+
+namespace IPC {
+
+bool
+DeserializeArrayBuffer(JS::Handle<JSObject*> aObj,
+ const InfallibleTArray<uint8_t>& aBuffer,
+ JS::MutableHandle<JS::Value> aVal)
+{
+ 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);
+ if (!data)
+ return false;
+ memcpy(data, aBuffer.Elements(), aBuffer.Length());
+ aVal.set(OBJECT_TO_JSVAL(obj));
+ return true;
+}
+
+} // namespace IPC
+
+namespace mozilla {
+namespace dom {
+
+NS_IMPL_CYCLE_COLLECTION_1(TCPSocketChildBase, mSocket)
+NS_IMPL_CYCLE_COLLECTING_ADDREF(TCPSocketChildBase)
+NS_IMPL_CYCLE_COLLECTING_RELEASE(TCPSocketChildBase)
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(TCPSocketChildBase)
+ NS_INTERFACE_MAP_ENTRY(nsITCPSocketChild)
+ NS_INTERFACE_MAP_ENTRY(nsISupports)
+NS_INTERFACE_MAP_END
+
+TCPSocketChildBase::TCPSocketChildBase()
+: mIPCOpen(false)
+{
+}
+
+TCPSocketChildBase::~TCPSocketChildBase()
+{
+}
+
+NS_IMETHODIMP_(nsrefcnt) TCPSocketChild::Release(void)
+{
+ nsrefcnt refcnt = TCPSocketChildBase::Release();
+ if (refcnt == 1 && mIPCOpen) {
+ PTCPSocketChild::SendRequestDelete();
+ return 1;
+ }
+ return refcnt;
+}
+
+TCPSocketChild::TCPSocketChild()
+: mSocketObj(nullptr)
+{
+}
+
+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)
+{
+ mSocket = aSocket;
+ MOZ_ASSERT(aSocketObj.isObject());
+ mSocketObj = js::CheckedUnwrap(&aSocketObj.toObject());
+ if (!mSocketObj) {
+ return NS_ERROR_FAILURE;
+ }
+ AddIPDLReference();
+ gNeckoChild->SendPTCPSocketConstructor(this, nsString(aHost), aPort,
+ aUseSSL, nsString(aBinaryType),
+ GetTabChildFrom(aWindow));
+ return NS_OK;
+}
+
+void
+TCPSocketChildBase::ReleaseIPDLReference()
+{
+ MOZ_ASSERT(mIPCOpen);
+ mIPCOpen = false;
+ this->Release();
+}
+
+void
+TCPSocketChildBase::AddIPDLReference()
+{
+ MOZ_ASSERT(!mIPCOpen);
+ mIPCOpen = true;
+ this->AddRef();
+}
+
+TCPSocketChild::~TCPSocketChild()
+{
+}
+
+bool
+TCPSocketChild::RecvCallback(const nsString& aType,
+ const CallbackData& aData,
+ const nsString& aReadyState,
+ const uint32_t& aBuffered)
+{
+ if (NS_FAILED(mSocket->UpdateReadyStateAndBuffered(aReadyState, aBuffered)))
+ NS_ERROR("Shouldn't fail!");
+
+ nsresult rv = NS_ERROR_FAILURE;
+ if (aData.type() == CallbackData::Tvoid_t) {
+ rv = mSocket->CallListenerVoid(aType);
+
+ } else if (aData.type() == CallbackData::TTCPError) {
+ const TCPError& err(aData.get_TCPError());
+ rv = mSocket->CallListenerError(aType, err.name());
+
+ } else if (aData.type() == CallbackData::TSendableData) {
+ const SendableData& data = aData.get_SendableData();
+
+ if (data.type() == SendableData::TArrayOfuint8_t) {
+ JSContext* cx = nsContentUtils::GetSafeJSContext();
+ JS::Rooted<JS::Value> val(cx);
+ JS::Rooted<JSObject*> socket(cx, mSocketObj);
+ bool ok = IPC::DeserializeArrayBuffer(socket, data.get_ArrayOfuint8_t(), &val);
+ NS_ENSURE_TRUE(ok, true);
+ rv = mSocket->CallListenerArrayBuffer(aType, val);
+
+ } else if (data.type() == SendableData::TnsString) {
+ rv = mSocket->CallListenerData(aType, data.get_nsString());
+
+ } else {
+ MOZ_NOT_REACHED("Invalid callback data type!");
+ }
+
+ } else {
+ MOZ_NOT_REACHED("Invalid callback type!");
+ }
+ NS_ENSURE_SUCCESS(rv, true);
+ return true;
+}
+
+NS_IMETHODIMP
+TCPSocketChild::Suspend()
+{
+ SendSuspend();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+TCPSocketChild::Resume()
+{
+ SendResume();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+TCPSocketChild::Close()
+{
+ SendClose();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+TCPSocketChild::Send(const JS::Value& aData,
+ uint32_t aByteOffset,
+ uint32_t aByteLength,
+ JSContext* aCx)
+{
+ if (aData.isString()) {
+ JSString* jsstr = aData.toString();
+ nsDependentJSString str;
+ bool ok = str.init(aCx, jsstr);
+ NS_ENSURE_TRUE(ok, NS_ERROR_FAILURE);
+ SendData(str);
+
+ } else {
+ NS_ENSURE_TRUE(aData.isObject(), NS_ERROR_FAILURE);
+ JS::Rooted<JSObject*> obj(aCx, &aData.toObject());
+ NS_ENSURE_TRUE(JS_IsArrayBufferObject(obj), NS_ERROR_FAILURE);
+ 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;
+ }
+ InfallibleTArray<uint8_t> arr;
+ arr.SwapElements(fallibleArr);
+ SendData(arr);
+ }
+ return NS_OK;
+}
+
+} // namespace dom
+} // namespace mozilla
diff --git a/dom/network/src/TCPSocketChild.h b/dom/network/src/TCPSocketChild.h
new file mode 100644
index 000000000..17fbf44d5
--- /dev/null
+++ b/dom/network/src/TCPSocketChild.h
@@ -0,0 +1,55 @@
+/* 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/PTCPSocketChild.h"
+#include "nsITCPSocketChild.h"
+#include "nsCycleCollectionParticipant.h"
+#include "nsCOMPtr.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 {
+
+class TCPSocketChildBase : public nsITCPSocketChild {
+public:
+ NS_DECL_CYCLE_COLLECTION_CLASS(TCPSocketChildBase)
+ NS_DECL_CYCLE_COLLECTING_ISUPPORTS
+
+ void AddIPDLReference();
+ void ReleaseIPDLReference();
+
+protected:
+ TCPSocketChildBase();
+ virtual ~TCPSocketChildBase();
+
+ nsCOMPtr<nsITCPSocketInternal> mSocket;
+ bool mIPCOpen;
+};
+
+class TCPSocketChild : public mozilla::net::PTCPSocketChild
+ , public TCPSocketChildBase
+{
+public:
+ NS_DECL_NSITCPSOCKETCHILD
+ NS_IMETHOD_(nsrefcnt) Release() MOZ_OVERRIDE;
+
+ TCPSocketChild();
+ ~TCPSocketChild();
+
+ virtual bool RecvCallback(const nsString& aType,
+ const CallbackData& aData,
+ const nsString& aReadyState,
+ const uint32_t& aBuffered) MOZ_OVERRIDE;
+private:
+ JSObject* mSocketObj;
+};
+
+} // namespace dom
+} // namespace mozilla
diff --git a/dom/network/src/TCPSocketParent.cpp b/dom/network/src/TCPSocketParent.cpp
new file mode 100644
index 000000000..2fd00f092
--- /dev/null
+++ b/dom/network/src/TCPSocketParent.cpp
@@ -0,0 +1,217 @@
+/* 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
new file mode 100644
index 000000000..82c4f80a2
--- /dev/null
+++ b/dom/network/src/TCPSocketParent.h
@@ -0,0 +1,48 @@
+/* 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
new file mode 100644
index 000000000..0ab1e0cf5
--- /dev/null
+++ b/dom/network/src/TCPSocketParentIntermediary.js
@@ -0,0 +1,55 @@
+/* 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/Types.h b/dom/network/src/Types.h
new file mode 100644
index 000000000..ee2b5e516
--- /dev/null
+++ b/dom/network/src/Types.h
@@ -0,0 +1,21 @@
+/* -*- 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_Types_h
+#define mozilla_dom_network_Types_h
+
+namespace mozilla {
+namespace hal {
+class NetworkInformation;
+} // namespace hal
+
+template <class T>
+class Observer;
+
+typedef Observer<hal::NetworkInformation> NetworkObserver;
+
+} // namespace mozilla
+
+#endif // mozilla_dom_network_Types_h
diff --git a/dom/network/src/Utils.cpp b/dom/network/src/Utils.cpp
new file mode 100644
index 000000000..0de2fc361
--- /dev/null
+++ b/dom/network/src/Utils.cpp
@@ -0,0 +1,21 @@
+/* -*- 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
new file mode 100644
index 000000000..697516ff0
--- /dev/null
+++ b/dom/network/src/Utils.h
@@ -0,0 +1,24 @@
+/* -*- 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
new file mode 100644
index 000000000..6efad3144
--- /dev/null
+++ b/dom/network/src/ipdl.mk
@@ -0,0 +1,7 @@
+# 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
new file mode 100644
index 000000000..bf6554006
--- /dev/null
+++ b/dom/network/src/moz.build
@@ -0,0 +1,41 @@
+# -*- 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
new file mode 100644
index 000000000..cc7c0341b
--- /dev/null
+++ b/dom/network/tests/Makefile.in
@@ -0,0 +1,30 @@
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+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/marionette/manifest.ini b/dom/network/tests/marionette/manifest.ini
new file mode 100644
index 000000000..d61be3dc8
--- /dev/null
+++ b/dom/network/tests/marionette/manifest.ini
@@ -0,0 +1,17 @@
+[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
new file mode 100644
index 000000000..2259da352
--- /dev/null
+++ b/dom/network/tests/marionette/test_call_barring_get_option.js
@@ -0,0 +1,31 @@
+/* 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
new file mode 100644
index 000000000..2afcf297c
--- /dev/null
+++ b/dom/network/tests/marionette/test_call_barring_set_error.js
@@ -0,0 +1,65 @@
+/* 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
new file mode 100644
index 000000000..e39d87f05
--- /dev/null
+++ b/dom/network/tests/marionette/test_mobile_data_location.js
@@ -0,0 +1,119 @@
+/* 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
new file mode 100644
index 000000000..ecae8de48
--- /dev/null
+++ b/dom/network/tests/marionette/test_mobile_data_state.js
@@ -0,0 +1,121 @@
+/* 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
new file mode 100644
index 000000000..b3c63bb53
--- /dev/null
+++ b/dom/network/tests/marionette/test_mobile_iccinfo.js
@@ -0,0 +1,85 @@
+/* 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
new file mode 100644
index 000000000..cc636e5a7
--- /dev/null
+++ b/dom/network/tests/marionette/test_mobile_mmi.js
@@ -0,0 +1,76 @@
+/* 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
new file mode 100644
index 000000000..40e0818f4
--- /dev/null
+++ b/dom/network/tests/marionette/test_mobile_networks.js
@@ -0,0 +1,240 @@
+/* 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
new file mode 100644
index 000000000..db357d7fd
--- /dev/null
+++ b/dom/network/tests/marionette/test_mobile_operator_names.js
@@ -0,0 +1,209 @@
+/* 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
new file mode 100644
index 000000000..4ea62f761
--- /dev/null
+++ b/dom/network/tests/marionette/test_mobile_preferred_network_type.js
@@ -0,0 +1,65 @@
+/* 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
new file mode 100644
index 000000000..2fe45171b
--- /dev/null
+++ b/dom/network/tests/marionette/test_mobile_voice_state.js
@@ -0,0 +1,153 @@
+/* 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/moz.build b/dom/network/tests/moz.build
new file mode 100644
index 000000000..191a5d6ba
--- /dev/null
+++ b/dom/network/tests/moz.build
@@ -0,0 +1,12 @@
+# -*- 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
new file mode 100644
index 000000000..91005e308
--- /dev/null
+++ b/dom/network/tests/test_network_basics.html
@@ -0,0 +1,46 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test for Network 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>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Network API **/
+
+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.mozConnection, "navigator.mozConnection returns an object");
+
+ok(navigator.mozConnection instanceof MozConnection,
+ "navigator.mozConnection is a MozConnection object");
+ok(navigator.mozConnection instanceof EventTarget,
+ "navigator.mozConnection is a EventTarget object");
+
+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");
+
+ok('metered' in navigator.mozConnection,
+ "metered should be a Connection attribute");
+is(navigator.mozConnection.metered, false,
+ "By default the connection is not metered");
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/network/tests/test_networkstats_basics.html b/dom/network/tests/test_networkstats_basics.html
new file mode 100644
index 000000000..1f76ba0bb
--- /dev/null
+++ b/dom/network/tests/test_networkstats_basics.html
@@ -0,0 +1,303 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test for NetworkStats</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>
+<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;
+
+ // 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.");
+
+ // Test IDL methods
+ next();
+ return;
+}
+
+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;
+
+ var counter = 0;
+ var date = start;
+ var success = true;
+
+ do {
+ if(data[counter].date.getTime() != date) {
+ success = false;
+ break;
+ }
+ date += sampleRate;
+ counter++;
+ } while (date <= end);
+
+ ok(success, "data result has correct dates");
+}
+
+var req;
+var index = -1;
+var netStats = null;
+
+var steps = [
+ function () {
+ // Test clearAlldata
+ req = netStats.clearAllData();
+ req.onsuccess = function () {
+ ok(true, "clearAllData deleted the database");
+ next();
+ };
+ req.onerror = function () {
+ ok(false, "clearAllData deleted the database");
+ }
+ },
+ function () {
+ // Check if getNetworkStats launch exception when start is greather than end
+
+ // Prepare get params
+ var type = netStats.connectionTypes[0];
+ // Get dates
+ var endDate = new Date();
+ var startDate = new Date(endDate.getTime() + 1000);
+
+ try {
+ netStats.getNetworkStats({start: startDate, end: endDate});
+ } catch(ex) {
+ ok(true, "getNetworkStats launch exception when start is greater than end");
+ next();
+ return;
+ }
+
+ ok(false, "getNetworkStats launch exceptionwhen start is greater than end");
+ next();
+ return;
+ },
+ function () {
+ // Test if call getNetworkStats with undefined start param launch an exception
+
+ // Prepare get params
+ var type = netStats.connectionTypes[0];
+ setTimeout(function() {
+ try {
+ netStats.getNetworkStats({end: new Date()});
+ } catch(ex) {
+ ok(true, "getNetworkStats launch exception when start param does not exist");
+ next();
+ return;
+ }
+
+ ok(false, "getNetworkStats launch exception when start param does not exist");
+ }, 1000);
+ },
+ function () {
+ // Test if call getNetworkStats with undefined end param launch an exception
+
+ // Prepare get params
+ var type = netStats.connectionTypes[0];
+ setTimeout(function() {
+ try {
+ netStats.getNetworkStats({start: new Date()});
+ } catch(ex) {
+ ok(true, "getNetworkStats launch exception when end param does not exist");
+ next();
+ return;
+ }
+
+ ok(false, "getNetworkStats launch exception when end param does not exist");
+ }, 1000);
+ },
+ function () {
+ ok(true, "Get stats for a connectionType 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.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();
+ };
+ req.onerror = function () {
+ ok(false, "Get stats for a connectionType failure!");
+ }
+ },
+ function () {
+ ok(true, "Get stats for all connectionTypes and dates 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.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();
+ };
+ req.onerror = function () {
+ ok(false, "Get stats for all connectionTypes failure!");
+ }
+ },
+ 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});
+ 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();
+ };
+ req.onerror = function () {
+ ok(false, "Get stats for a connectionType failure!");
+ }
+ },
+ 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});
+ 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();
+ };
+ req.onerror = function () {
+ ok(false, "Get stats for all connectionType failure!");
+ }
+ },
+ function () {
+ ok(true, "all done!\n");
+ SpecialPowers.removePermission("networkstats-manage", document);
+ 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.addPermission("networkstats-manage", true, document);
+SpecialPowers.pushPrefEnv({'set': [["dom.mozNetworkStats.enabled", true]]}, test);
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/network/tests/test_networkstats_disabled.html b/dom/network/tests/test_networkstats_disabled.html
new file mode 100644
index 000000000..6cd05c315
--- /dev/null
+++ b/dom/network/tests/test_networkstats_disabled.html
@@ -0,0 +1,31 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test to ensure NetworkStats is not accessible when it is disabled</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>
+<pre id="test">
+<script type="application/javascript">
+
+"use strict";
+
+SimpleTest.waitForExplicitFinish();
+
+// Test to ensure NetworkStats is not accessible when it is disabled
+SpecialPowers.pushPrefEnv({'set': [["dom.mozNetworkStats.enabled", false]]}, function(){
+
+ ok('mozNetworkStats' in navigator, "navigator.mozNetworkStats should exist");
+ is(navigator.mozNetworkStats, null, "mozNetworkStats should be null when not enabled.");
+
+ SimpleTest.finish();
+});
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/network/tests/test_networkstats_enabled_no_perm.html b/dom/network/tests/test_networkstats_enabled_no_perm.html
new file mode 100644
index 000000000..dd911c506
--- /dev/null
+++ b/dom/network/tests/test_networkstats_enabled_no_perm.html
@@ -0,0 +1,35 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test to ensure NetworkStats enabled and no networkstats-manage perm does not allow open</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>
+<pre id="test">
+<script type="application/javascript">
+
+// Test to ensure NetworkStats is enabled but mozNetworkStats.connectionTypes
+// does not work in content.
+
+SpecialPowers.setBoolPref("dom.mozNetworkStats.enabled", true);
+SpecialPowers.removePermission("networkstats-manage", document);
+
+ok('mozNetworkStats' in navigator, "navigator.mozNetworkStats should be accessible if dom.mozNetworkStats.enabled is true");
+
+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");
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/network/tests/test_networkstats_enabled_perm.html b/dom/network/tests/test_networkstats_enabled_perm.html
new file mode 100644
index 000000000..7e24905d9
--- /dev/null
+++ b/dom/network/tests/test_networkstats_enabled_perm.html
@@ -0,0 +1,34 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test to ensure NetworkStats is not accessible when it is disabled</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>
+<pre id="test">
+<script type="application/javascript">
+
+"use strict";
+
+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(){
+
+ ok('mozNetworkStats' in navigator, "navigator.mozNetworkStats should exist");
+ ok(navigator.mozNetworkStats instanceof SpecialPowers.Ci.nsIDOMMozNetworkStatsManager,
+ "navigator.mozNetworkStats should be a nsIDOMMozNetworkStatsManager object");
+
+ SpecialPowers.removePermission("networkstats-manage", document);
+ SimpleTest.finish();
+});
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/network/tests/test_tcpsocket_default_permissions.html b/dom/network/tests/test_tcpsocket_default_permissions.html
new file mode 100644
index 000000000..8c0f42e32
--- /dev/null
+++ b/dom/network/tests/test_tcpsocket_default_permissions.html
@@ -0,0 +1,27 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test to ensure TCPSocket permission is disabled by default</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>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test to ensure TCPSocket permission is disabled by default **/
+
+try {
+ navigator.mozTCPSocket;
+ throw new Error("Error: navigator.mozTCPSocket should not exist by default");
+} catch (e) {
+ ok(true, "navigator.mozTCPSocket should not exist by default");
+}
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/network/tests/test_tcpsocket_enabled_no_perm.html b/dom/network/tests/test_tcpsocket_enabled_no_perm.html
new file mode 100644
index 000000000..a028b48d6
--- /dev/null
+++ b/dom/network/tests/test_tcpsocket_enabled_no_perm.html
@@ -0,0 +1,35 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test to ensure TCPSocket permission enabled and no tcp-socket perm does not allow open</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>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test to ensure TCPSocket permission being turned on enables
+ navigator.mozTCPSocket, but mozTCPSocket.open does not work
+ in content.
+**/
+SpecialPowers.setBoolPref("dom.mozTCPSocket.enabled", 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");
+}
+
+SpecialPowers.setBoolPref("dom.mozTCPSocket.enabled", false);
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/network/tests/test_tcpsocket_enabled_with_perm.html b/dom/network/tests/test_tcpsocket_enabled_with_perm.html
new file mode 100644
index 000000000..4490743e4
--- /dev/null
+++ b/dom/network/tests/test_tcpsocket_enabled_with_perm.html
@@ -0,0 +1,31 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test to ensure TCPSocket permission enabled and open works with tcp-socket perm</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>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test to ensure TCPSocket permission being turned on enables
+ 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);
+
+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);
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/network/tests/unit/test_multisend.js b/dom/network/tests/unit/test_multisend.js
new file mode 100644
index 000000000..7f15f4a02
--- /dev/null
+++ b/dom/network/tests/unit/test_multisend.js
@@ -0,0 +1,152 @@
+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
new file mode 100644
index 000000000..c643ee837
--- /dev/null
+++ b/dom/network/tests/unit/test_tcpsocket.js
@@ -0,0 +1,526 @@
+/**
+ * Test TCPSocket.js by creating an XPCOM-style server socket, then sending
+ * data in both directions and making sure each side receives their data
+ * correctly and with the proper events.
+ *
+ * This test is derived from netwerk/test/unit/test_socks.js, except we don't
+ * involve a subprocess.
+ *
+ * Future work:
+ * - SSL. see https://bugzilla.mozilla.org/show_bug.cgi?id=466524
+ * https://bugzilla.mozilla.org/show_bug.cgi?id=662180
+ * Alternatively, mochitests could be used.
+ * - Testing overflow logic.
+ *
+ **/
+
+const Cc = Components.classes;
+const Ci = Components.interfaces;
+const Cr = Components.results;
+const Cu = Components.utils;
+const CC = Components.Constructor;
+
+/**
+ *
+ * Constants
+ *
+ */
+
+// Some binary data to send.
+const DATA_ARRAY = [0, 255, 254, 0, 1, 2, 3, 0, 255, 255, 254, 0],
+ DATA_ARRAY_BUFFER = new ArrayBuffer(DATA_ARRAY.length),
+ TYPED_DATA_ARRAY = new Uint8Array(DATA_ARRAY_BUFFER),
+ HELLO_WORLD = "hlo wrld. ",
+ BIG_ARRAY = new Array(65539),
+ BIG_ARRAY_2 = new Array(65539);
+
+TYPED_DATA_ARRAY.set(DATA_ARRAY, 0);
+
+for (var i_big = 0; i_big < BIG_ARRAY.length; i_big++) {
+ BIG_ARRAY[i_big] = Math.floor(Math.random() * 256);
+ BIG_ARRAY_2[i_big] = Math.floor(Math.random() * 256);
+}
+
+const BIG_ARRAY_BUFFER = new ArrayBuffer(BIG_ARRAY.length),
+ BIG_ARRAY_BUFFER_2 = new ArrayBuffer(BIG_ARRAY_2.length);
+const BIG_TYPED_ARRAY = new Uint8Array(BIG_ARRAY_BUFFER),
+ BIG_TYPED_ARRAY_2 = new Uint8Array(BIG_ARRAY_BUFFER_2);
+BIG_TYPED_ARRAY.set(BIG_ARRAY);
+BIG_TYPED_ARRAY_2.set(BIG_ARRAY_2);
+
+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"))();
+
+const gInChild = Cc["@mozilla.org/xre/app-info;1"].getService(Ci.nsIXULRuntime)
+ .processType != Ci.nsIXULRuntime.PROCESS_TYPE_DEFAULT;
+
+Cu.import("resource://gre/modules/Services.jsm");
+
+/**
+ *
+ * Helper functions
+ *
+ */
+
+function get_platform() {
+ var xulRuntime = Components.classes["@mozilla.org/xre/app-info;1"]
+ .getService(Components.interfaces.nsIXULRuntime);
+ return xulRuntime.OS;
+}
+
+/**
+ * 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 makeSuccessCase(name) {
+ return function() {
+ do_print('got expected: ' + name);
+ run_next_test();
+ };
+}
+
+function makeJointSuccess(names) {
+ let funcs = {}, successCount = 0;
+ names.forEach(function(name) {
+ funcs[name] = function() {
+ do_print('got expected: ' + name);
+ if (++successCount === names.length)
+ run_next_test();
+ };
+ });
+ return funcs;
+}
+
+function makeFailureCase(name) {
+ return function() {
+ let argstr;
+ if (arguments.length) {
+ argstr = '(args: ' +
+ Array.map(arguments, function(x) { return x.data + ""; }).join(" ") + ')';
+ }
+ else {
+ argstr = '(no arguments)';
+ }
+ do_throw('got unexpected: ' + name + ' ' + argstr);
+ };
+}
+
+function makeExpectData(name, expectedData, fromEvent, callback) {
+ let dataBuffer = fromEvent ? null : [], done = false;
+ let dataBufferView = null;
+ return function(receivedData) {
+ if (receivedData.data) {
+ receivedData = receivedData.data;
+ }
+ let recvLength = receivedData.byteLength !== undefined ?
+ receivedData.byteLength : receivedData.length;
+
+ if (fromEvent) {
+ if (dataBuffer) {
+ let newBuffer = new ArrayBuffer(dataBuffer.byteLength + recvLength);
+ let newBufferView = new Uint8Array(newBuffer);
+ newBufferView.set(dataBufferView, 0);
+ newBufferView.set(receivedData, dataBuffer.byteLength);
+ dataBuffer = newBuffer;
+ dataBufferView = newBufferView;
+ }
+ else {
+ dataBuffer = receivedData;
+ dataBufferView = new Uint8Array(dataBuffer);
+ }
+ }
+ else {
+ dataBuffer = dataBuffer.concat(receivedData);
+ }
+ do_print(name + ' received ' + recvLength + ' bytes');
+
+ if (done)
+ do_throw(name + ' Received data event when already done!');
+
+ let dataView = dataBuffer.byteLength !== undefined ? new Uint8Array(dataBuffer) : dataBuffer;
+ if (dataView.length >= expectedData.length) {
+ // check the bytes are equivalent
+ for (let i = 0; i < expectedData.length; i++) {
+ if (dataView[i] !== expectedData[i]) {
+ do_throw(name + ' Received mismatched character at position ' + i);
+ }
+ }
+ if (dataView.length > expectedData.length)
+ do_throw(name + ' Received ' + dataView.length + ' bytes but only expected ' +
+ expectedData.length + ' bytes.');
+
+ done = true;
+ if (callback) {
+ callback();
+ } else {
+ run_next_test();
+ }
+ }
+ };
+}
+
+var server = null, sock = null, failure_drain = null;
+
+/**
+ *
+ * Test functions
+ *
+ */
+
+/**
+ * Connect the socket to the server. This test is added as the first
+ * test, and is also added after every test which results in the socket
+ * being closed.
+ */
+
+function connectSock() {
+ server.reset();
+ var yayFuncs = makeJointSuccess(['serveropen', 'clientopen']);
+
+ sock = TCPSocket.open(
+ '127.0.0.1', server.listener.port,
+ { binaryType: 'arraybuffer' });
+
+ sock.onopen = yayFuncs.clientopen;
+ sock.ondrain = null;
+ sock.ondata = makeFailureCase('data');
+ sock.onerror = makeFailureCase('error');
+ sock.onclose = makeFailureCase('close');
+
+ server.onaccept = 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();
+ if (!gInChild)
+ Services.prefs.clearUserPref('dom.mozTCPSocket.enabled');
+ 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']);
+
+ let double_array = new Uint8Array(BIG_ARRAY.concat(BIG_ARRAY_2));
+ server.ondata = makeExpectData(
+ "ondata", double_array, false, yays.ondata);
+
+ server.onclose = yays.serverclose;
+ sock.onclose = yays.clientclose;
+
+ sock.ondrain = function () {
+ sock.close();
+ yays.ondrain();
+ }
+
+ if (sock.send(BIG_ARRAY_BUFFER)) {
+ throw new Error("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");
+ }
+}
+
+// - 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);
+}
+
+// send a buffer, get a drain, send a buffer, get a drain
+add_test(connectSock);
+add_test(drainTwice);
+
+// send a buffer, get a drain, send a buffer, get a drain
+add_test(connectSock);
+add_test(bufferTwice);
+
+// clean up
+add_test(cleanup);
+
+function run_test() {
+ if (!gInChild)
+ Services.prefs.setBoolPref('dom.mozTCPSocket.enabled', true);
+
+ server = new TestServer();
+
+ run_next_test();
+}
diff --git a/dom/network/tests/unit/xpcshell.ini b/dom/network/tests/unit/xpcshell.ini
new file mode 100644
index 000000000..84bb4c138
--- /dev/null
+++ b/dom/network/tests/unit/xpcshell.ini
@@ -0,0 +1,6 @@
+[DEFAULT]
+head =
+tail =
+
+[test_tcpsocket.js]
+[test_multisend.js] \ No newline at end of file
diff --git a/dom/network/tests/unit_ipc/test_tcpsocket_ipc.js b/dom/network/tests/unit_ipc/test_tcpsocket_ipc.js
new file mode 100644
index 000000000..9f876e0ac
--- /dev/null
+++ b/dom/network/tests/unit_ipc/test_tcpsocket_ipc.js
@@ -0,0 +1,9 @@
+Components.utils.import("resource://gre/modules/Services.jsm");
+
+function run_test() {
+ Services.prefs.setBoolPref('dom.mozTCPSocket.enabled', true);
+ run_test_in_child("../unit/test_tcpsocket.js", function() {
+ Services.prefs.clearUserPref('dom.mozTCPSocket.enabled');
+ do_test_finished();
+ });
+} \ No newline at end of file
diff --git a/dom/network/tests/unit_ipc/xpcshell.ini b/dom/network/tests/unit_ipc/xpcshell.ini
new file mode 100644
index 000000000..c5d850058
--- /dev/null
+++ b/dom/network/tests/unit_ipc/xpcshell.ini
@@ -0,0 +1,5 @@
+[DEFAULT]
+head =
+tail =
+
+[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
new file mode 100644
index 000000000..5ea440b86
--- /dev/null
+++ b/dom/network/tests/unit_stats/test_networkstats_db.js
@@ -0,0 +1,387 @@
+/* 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);
+
+function filterTimestamp(date) {
+ var sampleRate = netStatsDb.sampleRate;
+ var offset = date.getTimezoneOffset() * 60 * 1000;
+ return Math.floor((date.getTime() - offset) / sampleRate) * sampleRate;
+}
+
+add_test(function test_sampleRate() {
+ var sampleRate = netStatsDb.sampleRate;
+ do_check_true(sampleRate > 0);
+ netStatsDb.sampleRate = 0;
+ sampleRate = netStatsDb.sampleRate;
+ do_check_true(sampleRate > 0);
+
+ run_next_test();
+});
+
+add_test(function test_maxStorageSamples() {
+ var maxStorageSamples = netStatsDb.maxStorageSamples;
+ do_check_true(maxStorageSamples > 0);
+ netStatsDb.maxStorageSamples = 0;
+ maxStorageSamples = netStatsDb.maxStorageSamples;
+ do_check_true(maxStorageSamples > 0);
+
+ run_next_test();
+});
+
+add_test(function test_fillResultSamples_emptyData() {
+ var samples = 3;
+ var data = [];
+ var start = filterTimestamp(new Date());
+ var sampleRate = netStatsDb.sampleRate;
+ var end = start + (sampleRate * samples);
+ netStatsDb.fillResultSamples(start, end, data);
+ do_check_eq(data.length, samples + 1);
+
+ var aux = start;
+ var success = true;
+ for (var i = 0; i <= samples; i++) {
+ if (data[i].date.getTime() != aux || data[i].rxBytes != undefined || data[i].txBytes != undefined) {
+ success = false;
+ break;
+ }
+ aux += sampleRate;
+ }
+ do_check_true(success);
+
+ run_next_test();
+});
+
+add_test(function test_fillResultSamples_noEmptyData() {
+ var samples = 3;
+ var sampleRate = netStatsDb.sampleRate;
+ var start = filterTimestamp(new Date());
+ var end = start + (sampleRate * samples);
+ var data = [{date: new Date(start + sampleRate),
+ rxBytes: 0,
+ txBytes: 0}];
+ netStatsDb.fillResultSamples(start, end, data);
+ do_check_eq(data.length, samples + 1);
+
+ var aux = start;
+ var success = true;
+ for (var i = 0; i <= samples; i++) {
+ if (i == 1) {
+ if (data[i].date.getTime() != aux || data[i].rxBytes != 0 || data[i].txBytes != 0) {
+ success = false;
+ break;
+ }
+ } else {
+ if (data[i].date.getTime() != aux || data[i].rxBytes != undefined || data[i].txBytes != undefined) {
+ success = false;
+ break;
+ }
+ }
+ aux += sampleRate;
+ }
+ do_check_true(success);
+
+ run_next_test();
+});
+
+add_test(function test_clear() {
+ netStatsDb.clear(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) {
+ 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, 1);
+ do_check_eq(result[0].connectionType, stats.connectionType);
+ 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].rxTotalBytes, stats.rxTotalBytes);
+ do_check_eq(result[0].txTotalBytes, stats.txTotalBytes);
+ run_next_test();
+ });
+ });
+});
+
+add_test(function test_internalSaveStats_arraySamples() {
+ netStatsDb.clear(function (error, result) {
+ do_check_eq(error, null);
+
+ 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});
+ }
+
+ netStatsDb.dbNewTxn("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);
+
+ var success = true;
+ for (var i = 0; i < samples; i++) {
+ if (result[i].connectionType != stats[i].connectionType ||
+ result[i].timestamp != stats[i].timestamp ||
+ result[i].rxBytes != stats[i].rxBytes ||
+ result[i].txBytes != stats[i].txBytes ||
+ result[i].rxTotalBytes != stats[i].rxTotalBytes ||
+ result[i].txTotalBytes != stats[i].txTotalBytes) {
+ success = false;
+ break;
+ }
+ }
+ do_check_true(success);
+ run_next_test();
+ });
+ });
+ });
+});
+
+add_test(function test_internalRemoveOldStats() {
+ netStatsDb.clear(function (error, result) {
+ do_check_eq(error, null);
+
+ 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({connectionType: "wifi", timestamp: Date.now() + (10 * samples),
+ rxBytes: 0, txBytes: 0,
+ rxTotalBytes: 1234, txTotalBytes: 1234});
+
+ netStatsDb.dbNewTxn("readwrite", function(txn, store) {
+ netStatsDb._saveStats(txn, store, stats);
+ var date = stats[stats.length -1].timestamp
+ + (netStatsDb.sampleRate * netStatsDb.maxStorageSamples - 1) - 1;
+ netStatsDb._removeOldStats(txn, store, "wifi", date);
+ }, function(error, result) {
+ do_check_eq(error, null);
+
+ netStatsDb.logAllRecords(function(error, result) {
+ do_check_eq(error, null);
+ do_check_eq(result.length, 1);
+
+ run_next_test();
+ });
+ });
+ });
+});
+
+function processSamplesDiff(lastStat, newStat, callback) {
+ netStatsDb.clear(function (error, result){
+ do_check_eq(error, null);
+ netStatsDb.dbNewTxn("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");
+ request.onsuccess = function onsuccess(event) {
+ let cursor = event.target.result;
+ do_check_neq(cursor, null);
+ netStatsDb._processSamplesDiff(txn, store, cursor, newStat);
+ };
+ }, function(error, result) {
+ do_check_eq(error, null);
+ netStatsDb.logAllRecords(function(error, result) {
+ do_check_eq(error, null);
+ callback(result);
+ });
+ });
+ });
+ });
+}
+
+add_test(function test_processSamplesDiffSameSample() {
+ 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};
+
+ processSamplesDiff(lastStat, newStat, function(result) {
+ do_check_eq(result.length, 1);
+ do_check_eq(result[0].connectionType, newStat.connectionType);
+ 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);
+ run_next_test();
+ });
+});
+
+add_test(function test_processSamplesDiffNextSample() {
+ 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};
+
+ processSamplesDiff(lastStat, newStat, function(result) {
+ do_check_eq(result.length, 2);
+ do_check_eq(result[1].connectionType, newStat.connectionType);
+ 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);
+ run_next_test();
+ });
+});
+
+add_test(function test_processSamplesDiffSamplesLost() {
+ 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 newStat = {connectionType: "wifi", timestamp: date + (sampleRate * samples),
+ rxBytes: 0, txBytes: 0,
+ rxTotalBytes: 2234, txTotalBytes: 2234};
+
+ processSamplesDiff(lastStat, newStat, function(result) {
+ do_check_eq(result.length, samples + 1);
+ do_check_eq(result[samples].connectionType, newStat.connectionType);
+ 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);
+ run_next_test();
+ });
+});
+
+add_test(function test_saveStats() {
+ var stats = { connectionType: "wifi",
+ date: new Date(),
+ rxBytes: 2234,
+ txBytes: 2234};
+
+ netStatsDb.clear(function (error, result) {
+ do_check_eq(error, null);
+ 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);
+ 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].rxTotalBytes, stats.rxBytes);
+ do_check_eq(result[0].txTotalBytes, stats.txBytes);
+ run_next_test();
+ });
+ });
+ });
+});
+
+function prepareFind(stats, callback) {
+ netStatsDb.clear(function (error, result) {
+ do_check_eq(error, null);
+ netStatsDb.dbNewTxn("readwrite", function(txn, store) {
+ netStatsDb._saveStats(txn, store, stats);
+ }, function(error, result) {
+ callback(error, result);
+ });
+ });
+}
+
+add_test(function test_find () {
+ 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++) {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});
+ }
+
+ 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.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);
+
+ netStatsDb.findAll(function (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);
+ run_next_test();
+ }, {start: start, end: end});
+ }, {start: start, end: end, connectionType: "wifi"});
+ });
+});
+
+function run_test() {
+ do_get_profile();
+
+ var idbManager = Cc["@mozilla.org/dom/indexeddb/manager;1"].
+ getService(Ci.nsIIndexedDatabaseManager);
+ idbManager.initWindowless(this);
+
+ 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
new file mode 100644
index 000000000..569b2f7ba
--- /dev/null
+++ b/dom/network/tests/unit_stats/test_networkstats_service.js
@@ -0,0 +1,112 @@
+/* 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;
+
+add_test(function test_clearDB() {
+ NetworkStatsService._db.clear(function onDBCleared(error, result) {
+ do_check_eq(result, null);
+ run_next_test();
+ });
+});
+
+
+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());
+});
+
+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());
+});
+
+add_test(function test_update_invalidConnection() {
+ NetworkStatsService.update(-1, function (success, msg) {
+ do_check_eq(success, false);
+ do_check_eq(msg, "Invalid network type -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();
+ });
+});
+
+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}];
+ var index = NetworkStatsService.updateQueueIndex(3);
+ do_check_eq(index, 3);
+ index = NetworkStatsService.updateQueueIndex(10);
+ do_check_eq(index, -1);
+
+ NetworkStatsService.updateQueue = [];
+ run_next_test();
+});
+
+add_test(function test_updateAllStats() {
+ 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();
+ });
+});
+
+add_test(function test_updateStats_failure() {
+ NetworkStatsService.updateStats(-1, function(success, msg){
+ do_check_eq(success, false);
+ run_next_test();
+ });
+});
+
+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);
+ do_check_eq(NetworkStatsService.updateQueue.length, 2);
+ do_check_eq(NetworkStatsService.updateQueue[0].callbacks.length, 1);
+
+ var callback = function(success, msg) {
+ return;
+ };
+
+ NetworkStatsService.updateStats(Ci.nsINetworkInterface.NETWORK_TYPE_WIFI, callback);
+ NetworkStatsService.updateStats(Ci.nsINetworkInterface.NETWORK_TYPE_MOBILE, callback);
+
+ 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();
+});
+
+function run_test() {
+ do_get_profile();
+
+ Cu.import("resource://gre/modules/NetworkStatsService.jsm");
+ run_next_test();
+}
diff --git a/dom/network/tests/unit_stats/xpcshell.ini b/dom/network/tests/unit_stats/xpcshell.ini
new file mode 100644
index 000000000..a6fea9b8a
--- /dev/null
+++ b/dom/network/tests/unit_stats/xpcshell.ini
@@ -0,0 +1,6 @@
+[DEFAULT]
+head =
+tail =
+
+[test_networkstats_service.js]
+[test_networkstats_db.js]