Bug 777445 - Network activity indicator for B2G, r=jduell
authorMichal Novotny <michal.novotny@gmail.com>
Sat, 29 Sep 2012 00:39:20 +0200
changeset 108661 de06aeb3c7f393dd03f920f7e38d6e16af1d0897
parent 108660 c19a02de09c4e42951e8061ce13d32fe3e6de697
child 108662 bd990a83194e76ef61ee2a5bf53d9e3f8ee32dc2
push id82
push usershu@rfrn.org
push dateFri, 05 Oct 2012 13:20:22 +0000
reviewersjduell
bugs777445
milestone18.0a1
Bug 777445 - Network activity indicator for B2G, r=jduell
b2g/app/b2g.js
modules/libpref/src/init/all.js
netwerk/base/public/nsPISocketTransportService.idl
netwerk/base/src/Makefile.in
netwerk/base/src/NetworkActivityMonitor.cpp
netwerk/base/src/NetworkActivityMonitor.h
netwerk/base/src/nsSocketTransport2.cpp
netwerk/base/src/nsSocketTransportService2.cpp
--- a/b2g/app/b2g.js
+++ b/b2g/app/b2g.js
@@ -554,8 +554,14 @@ pref("dom.disable_window_open_dialog_fea
 pref("accessibility.accessfu.activate", 2);
 
 // Enable hit-target fluffing
 pref("ui.touch.radius.enabled", true);
 pref("ui.mouse.radius.enabled", true);
 
 // Disable native prompt
 pref("browser.prompt.allowNative", false);
+
+// Minimum delay in milliseconds between network activity notifications (0 means
+// no notifications). The delay is the same for both download and upload, though
+// they are handled separately. This pref is only read once at startup:
+// a restart is required to enable a new value.
+pref("network.activity.blipIntervalMilliseconds", 250);
--- a/modules/libpref/src/init/all.js
+++ b/modules/libpref/src/init/all.js
@@ -3744,8 +3744,14 @@ pref("toolkit.identity.debug", false);
 pref("dom.mozApps.dev_mode", false);
 
 // Lowest localId for apps.
 pref("dom.mozApps.maxLocalId", 1000);
 
 // Let us know wether we should run the permissions update algorithm.
 // See Bug 787439
 pref("dom.mozApps.runUpdate", true);
+
+// Minimum delay in milliseconds between network activity notifications (0 means
+// no notifications). The delay is the same for both download and upload, though
+// they are handled separately. This pref is only read once at startup:
+// a restart is required to enable a new value.
+pref("network.activity.blipIntervalMilliseconds", 0);
--- a/netwerk/base/public/nsPISocketTransportService.idl
+++ b/netwerk/base/public/nsPISocketTransportService.idl
@@ -32,8 +32,21 @@ interface nsPISocketTransportService : n
   readonly attribute long sendBufferSize;
 
   /**
    * Controls whether the socket transport service is offline.
    * Setting it offline will cause non-local socket detachment.
    */
   attribute boolean offline;
 };
+
+%{C++
+/*
+ * Network activity indicator: we send out these topics no more than every
+ * blipIntervalMilliseconds (as set by the
+ * "network.activity.blipIntervalMilliseconds" preference: if 0 no notifications
+ * are sent) if the network is currently active (i.e. we're sending/receiving
+ * data to/from the socket).
+ */
+#define NS_NETWORK_ACTIVITY_BLIP_UPLOAD_TOPIC   "network-activity-blip-upload"
+#define NS_NETWORK_ACTIVITY_BLIP_DOWNLOAD_TOPIC "network-activity-blip-download"
+
+%}
--- a/netwerk/base/src/Makefile.in
+++ b/netwerk/base/src/Makefile.in
@@ -62,16 +62,17 @@ CPPSRCS		= \
 		nsNetStrings.cpp \
 		nsBase64Encoder.cpp \
 		nsSerializationHelper.cpp \
 		nsDNSPrefetch.cpp \
 		RedirectChannelRegistrar.cpp \
 		nsPreloadedStream.cpp \
 		nsStreamListenerWrapper.cpp \
 		ProxyAutoConfig.cpp \
+		NetworkActivityMonitor.cpp \
 		$(NULL)
 
 LOCAL_INCLUDES	+= -I$(topsrcdir)/dom/base
 
 ifeq ($(MOZ_WIDGET_TOOLKIT),os2)
 	CPPSRCS += nsURLHelperOS2.cpp
 else
 ifeq ($(MOZ_WIDGET_TOOLKIT),windows)
new file mode 100644
--- /dev/null
+++ b/netwerk/base/src/NetworkActivityMonitor.cpp
@@ -0,0 +1,293 @@
+/* -*- 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 "NetworkActivityMonitor.h"
+#include "prmem.h"
+#include "nsIObserverService.h"
+#include "nsPISocketTransportService.h"
+#include "nsSocketTransportService2.h"
+#include "nsThreadUtils.h"
+#include "mozilla/Services.h"
+#include "prerror.h"
+
+using namespace mozilla::net;
+
+static PRStatus
+nsNetMon_Connect(PRFileDesc *fd, const PRNetAddr *addr, PRIntervalTime timeout)
+{
+  PRStatus ret;
+  PRErrorCode code;
+  ret = fd->lower->methods->connect(fd->lower, addr, timeout);
+  if (ret == PR_SUCCESS || (code = PR_GetError()) == PR_WOULD_BLOCK_ERROR ||
+      code == PR_IN_PROGRESS_ERROR)
+    NetworkActivityMonitor::DataInOut(NetworkActivityMonitor::kUpload);
+  return ret;
+}
+
+static int32_t
+nsNetMon_Read(PRFileDesc *fd, void *buf, int32_t len)
+{
+  int32_t ret;
+  ret = fd->lower->methods->read(fd->lower, buf, len);
+  if (ret >= 0)
+    NetworkActivityMonitor::DataInOut(NetworkActivityMonitor::kDownload);
+  return ret;
+}
+
+static int32_t
+nsNetMon_Write(PRFileDesc *fd, const void *buf, int32_t len)
+{
+  int32_t ret;
+  ret = fd->lower->methods->write(fd->lower, buf, len);
+  if (ret > 0)
+    NetworkActivityMonitor::DataInOut(NetworkActivityMonitor::kUpload);
+  return ret;
+}
+
+static int32_t
+nsNetMon_Writev(PRFileDesc *fd,
+                const PRIOVec *iov,
+                int32_t size,
+                PRIntervalTime timeout)
+{
+  int32_t ret;
+  ret = fd->lower->methods->writev(fd->lower, iov, size, timeout);
+  if (ret > 0)
+    NetworkActivityMonitor::DataInOut(NetworkActivityMonitor::kUpload);
+  return ret;
+}
+
+static int32_t
+nsNetMon_Recv(PRFileDesc *fd,
+              void *buf,
+              int32_t amount,
+              int flags,
+              PRIntervalTime timeout)
+{
+  int32_t ret;
+  ret = fd->lower->methods->recv(fd->lower, buf, amount, flags, timeout);
+  if (ret >= 0)
+    NetworkActivityMonitor::DataInOut(NetworkActivityMonitor::kDownload);
+  return ret;
+}
+
+static int32_t
+nsNetMon_Send(PRFileDesc *fd,
+              const void *buf,
+              int32_t amount,
+              int flags,
+              PRIntervalTime timeout)
+{
+  int32_t ret;
+  ret = fd->lower->methods->send(fd->lower, buf, amount, flags, timeout);
+  if (ret > 0)
+    NetworkActivityMonitor::DataInOut(NetworkActivityMonitor::kUpload);
+  return ret;
+}
+
+static int32_t
+nsNetMon_RecvFrom(PRFileDesc *fd,
+                  void *buf,
+                  int32_t amount,
+                  int flags,
+                  PRNetAddr *addr,
+                  PRIntervalTime timeout)
+{
+  int32_t ret;
+  ret = fd->lower->methods->recvfrom(fd->lower,
+                                     buf,
+                                     amount,
+                                     flags,
+                                     addr,
+                                     timeout);
+  if (ret >= 0)
+    NetworkActivityMonitor::DataInOut(NetworkActivityMonitor::kDownload);
+  return ret;
+}
+
+static int32_t
+nsNetMon_SendTo(PRFileDesc *fd,
+                const void *buf,
+                int32_t amount,
+                int flags,
+                const PRNetAddr *addr,
+                PRIntervalTime timeout)
+{
+  int32_t ret;
+  ret = fd->lower->methods->sendto(fd->lower,
+                                   buf,
+                                   amount,
+                                   flags,
+                                   addr,
+                                   timeout);
+  if (ret > 0)
+    NetworkActivityMonitor::DataInOut(NetworkActivityMonitor::kUpload);
+  return ret;
+}
+
+static int32_t
+nsNetMon_AcceptRead(PRFileDesc *listenSock,
+                    PRFileDesc **acceptedSock,
+                    PRNetAddr **peerAddr,
+                    void *buf,
+                    int32_t amount,
+                    PRIntervalTime timeout)
+{
+  int32_t ret;
+  ret = listenSock->lower->methods->acceptread(listenSock->lower,
+                                               acceptedSock,
+                                               peerAddr,
+                                               buf,
+                                               amount,
+                                               timeout);
+  if (ret > 0)
+    NetworkActivityMonitor::DataInOut(NetworkActivityMonitor::kDownload);
+  return ret;
+}
+
+
+class NotifyNetworkActivity : public nsRunnable {
+public:
+  NotifyNetworkActivity(nsIObserverService* aObs,
+                        NetworkActivityMonitor::Direction aDirection)
+    : mObs(aObs)
+    , mDirection(aDirection)
+  {}
+  NS_IMETHOD Run()
+  {
+    mObs->NotifyObservers(nullptr,
+                          mDirection == NetworkActivityMonitor::kUpload
+                            ? NS_NETWORK_ACTIVITY_BLIP_UPLOAD_TOPIC
+                            : NS_NETWORK_ACTIVITY_BLIP_DOWNLOAD_TOPIC,
+                          nullptr);
+    return NS_OK;
+  }
+private:
+  nsCOMPtr<nsIObserverService>      mObs;
+  NetworkActivityMonitor::Direction mDirection;
+};
+
+NetworkActivityMonitor * NetworkActivityMonitor::gInstance = nullptr;
+
+NetworkActivityMonitor::NetworkActivityMonitor()
+  : mLayerIdentity(PR_INVALID_IO_LAYER)
+  , mBlipInterval(PR_INTERVAL_NO_TIMEOUT)
+{
+  NS_ASSERTION(gInstance==nullptr,
+               "multiple NetworkActivityMonitor instances!");
+}
+
+NetworkActivityMonitor::~NetworkActivityMonitor()
+{
+  gInstance = nullptr;
+}
+
+nsresult
+NetworkActivityMonitor::Init(int32_t blipInterval)
+{
+  nsresult rv;
+
+  if (gInstance)
+    return NS_ERROR_ALREADY_INITIALIZED;
+
+  NetworkActivityMonitor * mon = new NetworkActivityMonitor();
+  rv = mon->Init_Internal(blipInterval);
+  if (NS_FAILED(rv)) {
+    delete mon;
+    return rv;
+  }
+
+  gInstance = mon;
+  return NS_OK;
+}
+
+nsresult
+NetworkActivityMonitor::Shutdown()
+{
+  if (!gInstance)
+    return NS_ERROR_NOT_INITIALIZED;
+
+  delete gInstance;
+  return NS_OK;
+}
+
+nsresult
+NetworkActivityMonitor::Init_Internal(int32_t blipInterval)
+{
+  mLayerIdentity = PR_GetUniqueIdentity("network activity monitor layer");
+  mLayerMethods  = *PR_GetDefaultIOMethods();
+  mLayerMethods.connect    = nsNetMon_Connect;
+  mLayerMethods.read       = nsNetMon_Read;
+  mLayerMethods.write      = nsNetMon_Write;
+  mLayerMethods.writev     = nsNetMon_Writev;
+  mLayerMethods.recv       = nsNetMon_Recv;
+  mLayerMethods.send       = nsNetMon_Send;
+  mLayerMethods.recvfrom   = nsNetMon_RecvFrom;
+  mLayerMethods.sendto     = nsNetMon_SendTo;
+  mLayerMethods.acceptread = nsNetMon_AcceptRead;
+
+  mObserverService = mozilla::services::GetObserverService();
+  if (!mObserverService)
+    return NS_ERROR_FAILURE;
+
+  mBlipInterval = PR_MillisecondsToInterval(blipInterval);
+  // Set the last notification times to time that has just expired, so any
+  // activity even right now will trigger notification.
+  mLastNotificationTime[kUpload] = PR_IntervalNow() - mBlipInterval;
+  mLastNotificationTime[kDownload] = mLastNotificationTime[kUpload];
+
+  return NS_OK;
+}
+
+nsresult
+NetworkActivityMonitor::AttachIOLayer(PRFileDesc *fd)
+{
+  if (!gInstance)
+    return NS_OK;
+
+  PRFileDesc * layer;
+  PRStatus     status;
+
+  layer = PR_CreateIOLayerStub(gInstance->mLayerIdentity,
+                               &gInstance->mLayerMethods);
+  if (!layer) {
+    return NS_ERROR_FAILURE;
+  }
+
+  status = PR_PushIOLayer(fd, PR_NSPR_IO_LAYER, layer);
+
+  if (status == PR_FAILURE) {
+    PR_DELETE(layer);
+    return NS_ERROR_FAILURE;
+  }
+
+  return NS_OK;
+}
+
+nsresult
+NetworkActivityMonitor::DataInOut(Direction direction)
+{
+  NS_ASSERTION(PR_GetCurrentThread() == gSocketThread, "wrong thread");
+
+  if (gInstance) {
+    PRIntervalTime now = PR_IntervalNow();
+    if ((now - gInstance->mLastNotificationTime[direction]) >
+        gInstance->mBlipInterval) {
+      gInstance->mLastNotificationTime[direction] = now;
+      gInstance->PostNotification(direction);
+    }
+  }
+
+  return NS_OK;
+}
+
+void
+NetworkActivityMonitor::PostNotification(Direction direction)
+{
+  nsRefPtr<nsIRunnable> ev = new NotifyNetworkActivity(mObserverService,
+                                                       direction);
+  NS_DispatchToMainThread(ev);
+}
new file mode 100644
--- /dev/null
+++ b/netwerk/base/src/NetworkActivityMonitor.h
@@ -0,0 +1,49 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ *
+ * 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 NetworkActivityMonitor_h___
+#define NetworkActivityMonitor_h___
+
+#include "nsCOMPtr.h"
+#include "prio.h"
+#include "prinrval.h"
+
+class nsIObserverService;
+
+namespace mozilla { namespace net {
+
+class NetworkActivityMonitor
+{
+public:
+  enum Direction {
+    kUpload   = 0,
+    kDownload = 1
+  };
+
+  NetworkActivityMonitor();
+  ~NetworkActivityMonitor();
+
+  static nsresult Init(int32_t blipInterval);
+  static nsresult Shutdown();
+
+  static nsresult AttachIOLayer(PRFileDesc *fd);
+  static nsresult DataInOut(Direction direction);
+
+private:
+  nsresult Init_Internal(int32_t blipInterval);
+  void PostNotification(Direction direction);
+
+  static NetworkActivityMonitor * gInstance;
+  PRDescIdentity                  mLayerIdentity;
+  PRIOMethods                     mLayerMethods;
+  PRIntervalTime                  mBlipInterval;
+  PRIntervalTime                  mLastNotificationTime[2];
+  nsCOMPtr<nsIObserverService>    mObserverService;
+};
+
+}} // namespace mozilla::net
+
+#endif /* NetworkActivityMonitor_h___ */
--- a/netwerk/base/src/nsSocketTransport2.cpp
+++ b/netwerk/base/src/nsSocketTransport2.cpp
@@ -19,16 +19,17 @@
 #include "nsAutoPtr.h"
 #include "nsCOMPtr.h"
 #include "netCore.h"
 #include "prmem.h"
 #include "plstr.h"
 #include "prnetdb.h"
 #include "prerror.h"
 #include "prerr.h"
+#include "NetworkActivityMonitor.h"
 
 #include "nsIServiceManager.h"
 #include "nsISocketProviderService.h"
 #include "nsISocketProvider.h"
 #include "nsISSLSocketControl.h"
 #include "nsINSSErrorsService.h"
 #include "nsIPipe.h"
 #include "nsIProgrammingLanguage.h"
@@ -1090,16 +1091,19 @@ nsSocketTransport::InitiateSocket()
     bool usingSSL;
 
     rv = BuildSocket(fd, proxyTransparent, usingSSL);
     if (NS_FAILED(rv)) {
         SOCKET_LOG(("  BuildSocket failed [rv=%x]\n", rv));
         return rv;
     }
 
+    // Attach network activity monitor
+    mozilla::net::NetworkActivityMonitor::AttachIOLayer(fd);
+
     PRStatus status;
 
     // Make the socket non-blocking...
     PRSocketOptionData opt;
     opt.option = PR_SockOpt_Nonblocking;
     opt.value.non_blocking = true;
     status = PR_SetSocketOption(fd, &opt);
     NS_ASSERTION(status == PR_SUCCESS, "unable to make socket non-blocking");
--- a/netwerk/base/src/nsSocketTransportService2.cpp
+++ b/netwerk/base/src/nsSocketTransportService2.cpp
@@ -13,16 +13,17 @@
 #include "nsError.h"
 #include "prnetdb.h"
 #include "prerror.h"
 #include "plstr.h"
 #include "nsIPrefService.h"
 #include "nsIPrefBranch.h"
 #include "nsServiceManagerUtils.h"
 #include "nsIOService.h"
+#include "NetworkActivityMonitor.h"
 
 
 // XXX: There is no good header file to put these in. :(
 namespace mozilla { namespace psm {
 
 void InitializeSSLServerCertVerificationThreads();
 void StopSSLServerCertVerificationThreads();
 
@@ -35,16 +36,17 @@ PRLogModuleInfo *gSocketTransportLog = n
 #endif
 
 nsSocketTransportService *gSocketTransportService = nullptr;
 PRThread                 *gSocketThread           = nullptr;
 
 #define SEND_BUFFER_PREF "network.tcp.sendbuffer"
 #define SOCKET_LIMIT_TARGET 550U
 #define SOCKET_LIMIT_MIN     50U
+#define BLIB_INTERVAL_PREF "network.activity.blipIntervalMilliseconds"
 
 uint32_t nsSocketTransportService::gMaxCount;
 PRCallOnceType nsSocketTransportService::gMaxCountInitOnce;
 
 //-----------------------------------------------------------------------------
 // ctor/dtor (called on the main/UI thread by the service manager)
 
 nsSocketTransportService::nsSocketTransportService()
@@ -450,18 +452,28 @@ nsSocketTransportService::Init()
     
     {
         MutexAutoLock lock(mLock);
         // Install our mThread, protecting against concurrent readers
         thread.swap(mThread);
     }
 
     nsCOMPtr<nsIPrefBranch> tmpPrefService = do_GetService(NS_PREFSERVICE_CONTRACTID);
-    if (tmpPrefService) 
+    if (tmpPrefService) {
         tmpPrefService->AddObserver(SEND_BUFFER_PREF, this, false);
+
+        int32_t blipInterval = 0;
+        rv = tmpPrefService->GetIntPref(BLIB_INTERVAL_PREF, &blipInterval);
+        if (NS_SUCCEEDED(rv) && blipInterval > 0) {
+            rv = mozilla::net::NetworkActivityMonitor::Init(blipInterval);
+            if (NS_FAILED(rv)) {
+                NS_WARNING("Can't initialize NetworkActivityMonitor");
+            }
+        }
+    }
     UpdatePrefs();
 
     mInitialized = true;
     return NS_OK;
 }
 
 // called from main thread only
 NS_IMETHODIMP
@@ -496,16 +508,18 @@ nsSocketTransportService::Shutdown()
         // readers are excluded
         mThread = nullptr;
     }
 
     nsCOMPtr<nsIPrefBranch> tmpPrefService = do_GetService(NS_PREFSERVICE_CONTRACTID);
     if (tmpPrefService) 
         tmpPrefService->RemoveObserver(SEND_BUFFER_PREF, this);
 
+    mozilla::net::NetworkActivityMonitor::Shutdown();
+
     mInitialized = false;
     mShuttingDown = false;
 
     return NS_OK;
 }
 
 NS_IMETHODIMP
 nsSocketTransportService::GetOffline(bool *offline)