diff options
author | wolfbeast <mcwerewolf@gmail.com> | 2014-05-21 11:38:25 +0200 |
---|---|---|
committer | wolfbeast <mcwerewolf@gmail.com> | 2014-05-21 11:38:25 +0200 |
commit | d25ba7d760b017b038e5aa6c0a605b4a330eb68d (patch) | |
tree | 16ec27edc7d5f83986f16236d3a36a2682a0f37e /dom/network | |
parent | a942906574671868daf122284a9c4689e6924f74 (diff) | |
download | palemoon-gre-d25ba7d760b017b038e5aa6c0a605b4a330eb68d.tar.gz |
Recommit working copy to repo with proper line endings.
Diffstat (limited to 'dom/network')
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] |