bug 888268 - wifi tickler for mitigating 802.11 psp mode on android r=dougt
authorPatrick McManus <mcmanus@ducksong.com>
Thu, 11 Jul 2013 11:39:36 -0400
changeset 145760 e6257c74be3b6be73005651791f0eb2fd38771fe
parent 145759 5e2920156db28ec5d31b3aec382b6a8b8dc788a2
child 145761 86451fc62d8229a6831a3620ef9f2c00637e9d38
push id4085
push userbbajaj@mozilla.com
push dateMon, 05 Aug 2013 20:29:25 +0000
treeherdermozilla-aurora@ede8780a15bc [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersdougt
bugs888268
milestone25.0a1
bug 888268 - wifi tickler for mitigating 802.11 psp mode on android r=dougt
dom/network/src/Connection.cpp
dom/network/src/Connection.h
dom/network/src/Constants.h
hal/fallback/FallbackNetwork.cpp
hal/sandbox/PHal.ipdl
mobile/android/app/mobile.js
mobile/android/base/GeckoEvent.java
mobile/android/base/GeckoNetworkManager.java
modules/libpref/src/init/all.js
netwerk/base/public/moz.build
netwerk/base/public/nsINetworkProperties.idl
netwerk/base/src/Tickler.cpp
netwerk/base/src/Tickler.h
netwerk/base/src/moz.build
netwerk/protocol/http/nsHttpHandler.cpp
netwerk/protocol/http/nsHttpHandler.h
netwerk/protocol/http/nsHttpTransaction.h
widget/android/AndroidBridge.cpp
widget/android/AndroidJavaWrappers.cpp
widget/android/AndroidJavaWrappers.h
widget/android/nsAppShell.cpp
--- a/dom/network/src/Connection.cpp
+++ b/dom/network/src/Connection.cpp
@@ -23,29 +23,32 @@ 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_INTERFACE_MAP_ENTRY(nsINetworkProperties)
   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)
+  , mIsWifi(kDefaultIsWifi)
+  , mDHCPGateway(kDefaultDHCPGateway)
 {
 }
 
 void
 Connection::Init(nsPIDOMWindow* aWindow)
 {
   BindToOwner(aWindow);
 
@@ -83,21 +86,37 @@ Connection::GetMetered(bool* aMetered)
     return NS_OK;
   }
 
   *aMetered = Preferences::GetBool(sMeteredPrefName,
                                    sMeteredDefaultValue);
   return NS_OK;
 }
 
+NS_IMETHODIMP
+Connection::GetIsWifi(bool *aIsWifi)
+{
+  *aIsWifi = mIsWifi;
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+Connection::GetDhcpGateway(uint32_t *aGW)
+{
+  *aGW = mDHCPGateway;
+  return NS_OK;
+}
+
 void
 Connection::UpdateFromNetworkInfo(const hal::NetworkInformation& aNetworkInfo)
 {
   mBandwidth = aNetworkInfo.bandwidth();
   mCanBeMetered = aNetworkInfo.canBeMetered();
+  mIsWifi = aNetworkInfo.isWifi();
+  mDHCPGateway = aNetworkInfo.dhcpGateway();
 }
 
 void
 Connection::Notify(const hal::NetworkInformation& aNetworkInfo)
 {
   double previousBandwidth = mBandwidth;
   bool previousCanBeMetered = mCanBeMetered;
 
--- a/dom/network/src/Connection.h
+++ b/dom/network/src/Connection.h
@@ -2,16 +2,17 @@
 /* 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 "nsINetworkProperties.h"
 #include "nsDOMEventTargetHelper.h"
 #include "nsCycleCollectionParticipant.h"
 #include "mozilla/Observer.h"
 #include "Types.h"
 
 namespace mozilla {
 
 namespace hal {
@@ -19,20 +20,22 @@ class NetworkInformation;
 } // namespace hal
 
 namespace dom {
 namespace network {
 
 class Connection : public nsDOMEventTargetHelper
                  , public nsIDOMMozConnection
                  , public NetworkObserver
+                 , public nsINetworkProperties
 {
 public:
   NS_DECL_ISUPPORTS_INHERITED
   NS_DECL_NSIDOMMOZCONNECTION
+  NS_DECL_NSINETWORKPROPERTIES
 
   NS_REALLY_FORWARD_NSIDOMEVENTTARGET(nsDOMEventTargetHelper)
 
   Connection();
 
   void Init(nsPIDOMWindow *aWindow);
   void Shutdown();
 
@@ -51,16 +54,26 @@ private:
    */
   bool mCanBeMetered;
 
   /**
    * The connection bandwidth.
    */
   double mBandwidth;
 
+  /**
+   * If the connection is WIFI
+   */
+  bool mIsWifi;
+
+  /**
+   * DHCP Gateway information for IPV4, in network byte order. 0 if unassigned.
+   */
+  uint32_t mDHCPGateway;
+
   static const char* sMeteredPrefName;
   static const bool  sMeteredDefaultValue;
 };
 
 } // namespace network
 } // namespace dom
 } // namespace mozilla
 
--- a/dom/network/src/Constants.h
+++ b/dom/network/src/Constants.h
@@ -10,14 +10,16 @@
  * 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;
+  static const bool   kDefaultIsWifi = false;
+  static const uint32_t kDefaultDHCPGateway = 0;
 
 } // namespace network
 } // namespace dom
 } // namespace mozilla
 
 #endif // mozilla_dom_network_Constants_h__
--- a/hal/fallback/FallbackNetwork.cpp
+++ b/hal/fallback/FallbackNetwork.cpp
@@ -19,12 +19,14 @@ void
 DisableNetworkNotifications()
 {}
 
 void
 GetCurrentNetworkInformation(hal::NetworkInformation* aNetworkInfo)
 {
   aNetworkInfo->bandwidth() = dom::network::kDefaultBandwidth;
   aNetworkInfo->canBeMetered() = dom::network::kDefaultCanBeMetered;
+  aNetworkInfo->isWifi() = dom::network::kDefaultIsWifi;
+  aNetworkInfo->dhcpGateway() = dom::network::kDefaultDHCPGateway;
 }
 
 } // namespace hal_impl
 } // namespace mozilla
--- a/hal/sandbox/PHal.ipdl
+++ b/hal/sandbox/PHal.ipdl
@@ -51,16 +51,18 @@ struct SensorData {
   PRTime timestamp;
   float[] values;
   SensorAccuracyType accuracy;
 };
 
 struct NetworkInformation {
   double bandwidth;
   bool   canBeMetered;
+  bool   isWifi;
+  uint32_t dhcpGateway;
 };
 
 struct SwitchEvent {
   SwitchDevice device;
   SwitchState status;
 };
 
 struct WakeLockInformation {
--- a/mobile/android/app/mobile.js
+++ b/mobile/android/app/mobile.js
@@ -656,16 +656,20 @@ pref("ui.scrolling.axis_lock_mode", "sta
 // Enable accessibility mode if platform accessibility is enabled.
 pref("accessibility.accessfu.activate", 2);
 pref("accessibility.accessfu.quicknav_modes", "Link,Heading,FormElement,ListItem");
 // Setting for an utterance order (0 - description first, 1 - description last).
 pref("accessibility.accessfu.utterance", 1);
 // Whether to skip images with empty alt text
 pref("accessibility.accessfu.skip_empty_images", true);
 
+// Transmit UDP busy-work to the LAN when anticipating low latency
+// network reads and on wifi to mitigate 802.11 Power Save Polling delays
+pref("network.tickle-wifi.enabled", true);
+
 // Mobile manages state by autodetection
 pref("network.manage-offline-status", true);
 
 // increase the timeout clamp for background tabs to 15 minutes
 pref("dom.min_background_timeout_value", 900000);
 
 // The default state of reader mode works on loaded a page.
 pref("reader.parse-on-load.enabled", true);
--- a/mobile/android/base/GeckoEvent.java
+++ b/mobile/android/base/GeckoEvent.java
@@ -168,16 +168,18 @@ public class GeckoEvent {
     private int mRangeBackColor;
     private int mRangeLineColor;
     private Location mLocation;
     private Address mAddress;
     private DomKeyLocation mDomKeyLocation;
 
     private double mBandwidth;
     private boolean mCanBeMetered;
+    private boolean mIsWifi;
+    private int     mDHCPGateway;
 
     private int mNativeWindow;
 
     private short mScreenOrientation;
 
     private ByteBuffer mBuffer;
 
     private int mWidth;
@@ -642,20 +644,23 @@ public class GeckoEvent {
     }
 
     public static GeckoEvent createVisitedEvent(String data) {
         GeckoEvent event = new GeckoEvent(NativeGeckoEvent.VISITED);
         event.mCharacters = data;
         return event;
     }
 
-    public static GeckoEvent createNetworkEvent(double bandwidth, boolean canBeMetered) {
+    public static GeckoEvent createNetworkEvent(double bandwidth, boolean canBeMetered,
+                                                boolean isWifi, int DHCPGateway) {
         GeckoEvent event = new GeckoEvent(NativeGeckoEvent.NETWORK_CHANGED);
         event.mBandwidth = bandwidth;
         event.mCanBeMetered = canBeMetered;
+        event.mIsWifi = isWifi;
+        event.mDHCPGateway = DHCPGateway;
         return event;
     }
 
     public static GeckoEvent createThumbnailEvent(int tabId, int bufw, int bufh, ByteBuffer buffer) {
         GeckoEvent event = new GeckoEvent(NativeGeckoEvent.THUMBNAIL);
         event.mPoints = new Point[1];
         event.mPoints[0] = new Point(bufw, bufh);
         event.mMetaState = tabId;
--- a/mobile/android/base/GeckoNetworkManager.java
+++ b/mobile/android/base/GeckoNetworkManager.java
@@ -5,17 +5,19 @@
 
 package org.mozilla.gecko;
 
 import android.content.BroadcastReceiver;
 import android.content.Context;
 import android.content.Intent;
 import android.content.IntentFilter;
 import android.net.ConnectivityManager;
+import android.net.DhcpInfo;
 import android.net.NetworkInfo;
+import android.net.wifi.WifiManager;
 import android.telephony.TelephonyManager;
 import android.util.Log;
 
 /*
  * A part of the work of GeckoNetworkManager is to give an estimation of the
  * download speed of the current connection. For known to be fast connection, we
  * simply use a predefined value (we don't care about being precise). For mobile
  * connections, we sort them in groups (generations) and estimate the average
@@ -137,37 +139,56 @@ public class GeckoNetworkManager extends
         stopListening();
         }
     }
 
     private void stopListening() {
         mApplicationContext.unregisterReceiver(sInstance);
     }
 
+    private int wifiDhcpGatewayAddress() {
+        if (mNetworkType != NetworkType.NETWORK_WIFI) {
+            return 0;
+        }
+ 
+        WifiManager mgr = (WifiManager)sInstance.mApplicationContext.getSystemService(Context.WIFI_SERVICE);
+        DhcpInfo d = mgr.getDhcpInfo();
+        if (d == null) {
+            return 0;
+        }
+
+        return d.gateway;
+    }
+
     private void updateNetworkType() {
         NetworkType previousNetworkType = mNetworkType;
         mNetworkType = getNetworkType();
 
         if (mNetworkType == previousNetworkType || !mShouldNotify) {
             return;
         }
 
         GeckoAppShell.sendEventToGecko(GeckoEvent.createNetworkEvent(
                                        getNetworkSpeed(mNetworkType),
-                                       isNetworkUsuallyMetered(mNetworkType)));
+                                       isNetworkUsuallyMetered(mNetworkType),
+                                       mNetworkType == NetworkType.NETWORK_WIFI,
+                                       wifiDhcpGatewayAddress()));
     }
 
     public double[] getCurrentInformation() {
         return new double[] { getNetworkSpeed(mNetworkType),
-                              isNetworkUsuallyMetered(mNetworkType) ? 1.0 : 0.0 };
+                              isNetworkUsuallyMetered(mNetworkType) ? 1.0 : 0.0,
+                              (mNetworkType == NetworkType.NETWORK_WIFI) ? 1.0 : 0.0,
+                              wifiDhcpGatewayAddress()};
     }
 
     public void enableNotifications() {
         // We set mShouldNotify *after* calling updateNetworkType() to make sure we
         // don't notify an eventual change in mNetworkType.
+        mNetworkType = NetworkType.NETWORK_NONE; // force a notification
         updateNetworkType();
         mShouldNotify = true;
 
         if (mShouldBeListening) {
             startListening();
         }
     }
 
--- a/modules/libpref/src/init/all.js
+++ b/modules/libpref/src/init/all.js
@@ -860,16 +860,22 @@ pref("security.fileuri.strict_origin_pol
 // prevents necko connecting to ports 1-5 unless the protocol
 // overrides.
 
 // Allow necko to do A/B testing. Will generally only happen if
 // telemetry is also enabled as otherwise there is no way to report
 // the results
 pref("network.allow-experiments", true);
 
+// Transmit UDP busy-work to the LAN when anticipating low latency
+// network reads and on wifi to mitigate 802.11 Power Save Polling delays
+pref("network.tickle-wifi.enabled", false);
+pref("network.tickle-wifi.duration", 400);
+pref("network.tickle-wifi.delay", 16);
+
 // Turn off interprocess security checks. Needed to run xpcshell tests.
 pref("network.disable.ipc.security", false);
 
 // Default action for unlisted external protocol handlers
 pref("network.protocol-handler.external-default", true);      // OK to load
 pref("network.protocol-handler.warn-external-default", true); // warn before load
 
 // Prevent using external protocol handlers for these schemes
--- a/netwerk/base/public/moz.build
+++ b/netwerk/base/public/moz.build
@@ -50,16 +50,17 @@ XPIDL_SOURCES += [
     'nsILoadGroupChild.idl',
     'nsIMIMEInputStream.idl',
     'nsIMultiPartChannel.idl',
     'nsINSSErrorsService.idl',
     'nsINestedURI.idl',
     'nsINetAddr.idl',
     'nsINetUtil.idl',
     'nsINetworkLinkService.idl',
+    'nsINetworkProperties.idl',
     'nsIParentChannel.idl',
     'nsIParentRedirectingChannel.idl',
     'nsIPermission.idl',
     'nsIPermissionManager.idl',
     'nsIPrivateBrowsingChannel.idl',
     'nsIProgressEventSink.idl',
     'nsIPrompt.idl',
     'nsIProtocolHandler.idl',
new file mode 100644
--- /dev/null
+++ b/netwerk/base/public/nsINetworkProperties.idl
@@ -0,0 +1,18 @@
+/* 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"
+
+/* This interface provides supplemental information
+   to that which is provided by the network info definition. It is
+   reasonable to expect it to grow.
+*/
+
+
+[scriptable, builtinclass, uuid(0af94dec-7ffc-4301-8937-766c214ac688)]
+interface nsINetworkProperties : nsISupports
+{
+    readonly attribute boolean isWifi;
+    readonly attribute unsigned long dhcpGateway;
+};
new file mode 100644
--- /dev/null
+++ b/netwerk/base/src/Tickler.cpp
@@ -0,0 +1,271 @@
+/* -*- 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 "nsComponentManagerUtils.h"
+#include "nsIPrefBranch.h"
+#include "nsIPrefService.h"
+#include "nsServiceManagerUtils.h"
+#include "prnetdb.h"
+#include "Tickler.h"
+
+#ifdef MOZ_USE_WIFI_TICKLER
+
+#include "AndroidBridge.h"
+
+namespace mozilla {
+namespace net {
+
+NS_IMPL_THREADSAFE_ISUPPORTS1(Tickler, nsISupportsWeakReference)
+
+Tickler::Tickler()
+    : mLock("Tickler::mLock")
+    , mActive(false)
+    , mCanceled(false)
+    , mEnabled(false)
+    , mDelay(16)
+    , mDuration(TimeDuration::FromMilliseconds(400))
+    , mFD(nullptr)
+{
+  MOZ_ASSERT(NS_IsMainThread());
+}
+
+Tickler::~Tickler()
+{
+  // non main thread uses of the tickler should hold weak
+  // references to it if they must hold a reference at all
+  MOZ_ASSERT(NS_IsMainThread());
+
+  if (mThread)
+    mThread->Shutdown();
+  if (mTimer)
+    mTimer->Cancel();
+  if (mFD)
+    PR_Close(mFD);
+}
+
+nsresult
+Tickler::Init()
+{
+  MOZ_ASSERT(NS_IsMainThread());
+  MOZ_ASSERT(!mTimer);
+  MOZ_ASSERT(!mActive);
+  MOZ_ASSERT(!mThread);
+  MOZ_ASSERT(!mFD);
+
+  AndroidBridge::Bridge()->EnableNetworkNotifications();
+
+  mFD = PR_OpenUDPSocket(PR_AF_INET);
+  if (!mFD)
+    return NS_ERROR_FAILURE;
+
+  // make sure new socket has a ttl of 1
+  // failure is not fatal.
+  PRSocketOptionData opt;
+  opt.option = PR_SockOpt_IpTimeToLive;
+  opt.value.ip_ttl = 1;
+  PR_SetSocketOption(mFD, &opt);
+
+  nsresult rv = NS_NewNamedThread("wifi tickler",
+                                  getter_AddRefs(mThread));
+  if (NS_FAILED(rv))
+    return rv;
+
+  nsCOMPtr<nsITimer> tmpTimer(do_CreateInstance(NS_TIMER_CONTRACTID, &rv));
+  if (NS_FAILED(rv))
+    return rv;
+
+  rv = tmpTimer->SetTarget(mThread);
+  if (NS_FAILED(rv))
+    return rv;
+
+  mTimer.swap(tmpTimer);
+
+  mAddr.inet.family = PR_AF_INET;
+  mAddr.inet.port = PR_htons (4886);
+  mAddr.inet.ip = 0;
+
+  return NS_OK;
+}
+
+void Tickler::Tickle()
+{
+  MutexAutoLock lock(mLock);
+  MOZ_ASSERT(mThread);
+  mLastTickle = TimeStamp::Now();
+  if (!mActive)
+    MaybeStartTickler();
+}
+
+void Tickler::PostCheckTickler()
+{
+  mLock.AssertCurrentThreadOwns();
+  mThread->Dispatch(NS_NewRunnableMethod(this, &Tickler::CheckTickler),
+                    NS_DISPATCH_NORMAL);
+  return;
+}
+
+void Tickler::MaybeStartTicklerUnlocked()
+{
+  MutexAutoLock lock(mLock);
+  MaybeStartTickler();
+}
+
+void Tickler::MaybeStartTickler()
+{
+  mLock.AssertCurrentThreadOwns();
+  if (!NS_IsMainThread()) {
+    NS_DispatchToMainThread(
+      NS_NewRunnableMethod(this, &Tickler::MaybeStartTicklerUnlocked),
+      NS_DISPATCH_NORMAL);
+    return;
+  }
+
+  if (!mPrefs)
+    mPrefs = do_GetService(NS_PREFSERVICE_CONTRACTID);
+  if (mPrefs) {
+    int32_t val;
+    bool boolVal;
+
+    if (NS_SUCCEEDED(mPrefs->GetBoolPref("network.tickle-wifi.enabled", &boolVal)))
+      mEnabled = boolVal;
+
+    if (NS_SUCCEEDED(mPrefs->GetIntPref("network.tickle-wifi.duration", &val))) {
+      if (val < 1)
+        val = 1;
+      if (val > 100000)
+        val = 100000;
+      mDuration = TimeDuration::FromMilliseconds(val);
+    }
+
+    if (NS_SUCCEEDED(mPrefs->GetIntPref("network.tickle-wifi.delay", &val))) {
+      if (val < 1)
+        val = 1;
+      if (val > 1000)
+        val = 1000;
+      mDelay = static_cast<uint32_t>(val);
+    }
+  }
+
+  PostCheckTickler();
+}
+
+void Tickler::CheckTickler()
+{
+  MutexAutoLock lock(mLock);
+  MOZ_ASSERT(mThread == NS_GetCurrentThread());
+
+  bool shouldRun = (!mCanceled) &&
+    ((TimeStamp::Now() - mLastTickle) <= mDuration);
+
+  if ((shouldRun && mActive) || (!shouldRun && !mActive))
+    return; // no change in state
+
+  if (mActive)
+    StopTickler();
+  else
+    StartTickler();
+}
+
+void Tickler::Cancel()
+{
+  MutexAutoLock lock(mLock);
+  MOZ_ASSERT(NS_IsMainThread());
+  mCanceled = true;
+  if (mThread)
+    PostCheckTickler();
+}
+
+void Tickler::StopTickler()
+{
+  mLock.AssertCurrentThreadOwns();
+  MOZ_ASSERT(mThread == NS_GetCurrentThread());
+  MOZ_ASSERT(mTimer);
+  MOZ_ASSERT(mActive);
+
+  mTimer->Cancel();
+  mActive = false;
+}
+
+class TicklerTimer MOZ_FINAL : public nsITimerCallback
+{
+  NS_DECL_ISUPPORTS
+  NS_DECL_NSITIMERCALLBACK
+
+  TicklerTimer(Tickler *aTickler)
+  {
+    mTickler = do_GetWeakReference(aTickler);
+  }
+
+  ~TicklerTimer() {};
+
+private:
+  nsWeakPtr mTickler;
+};
+
+void Tickler::StartTickler()
+{
+  mLock.AssertCurrentThreadOwns();
+  MOZ_ASSERT(mThread == NS_GetCurrentThread());
+  MOZ_ASSERT(!mActive);
+  MOZ_ASSERT(mTimer);
+
+  if (NS_SUCCEEDED(mTimer->InitWithCallback(new TicklerTimer(this),
+                                            mEnabled ? mDelay : 1000,
+                                            nsITimer::TYPE_REPEATING_SLACK)))
+    mActive = true;
+}
+
+// argument should be in network byte order
+void Tickler::SetIPV4Address(uint32_t address)
+{
+  mAddr.inet.ip = address;
+}
+
+// argument should be in network byte order
+void Tickler::SetIPV4Port(uint16_t port)
+{
+  mAddr.inet.port = port;
+}
+
+NS_IMPL_THREADSAFE_ISUPPORTS1(TicklerTimer, nsITimerCallback)
+
+NS_IMETHODIMP TicklerTimer::Notify(nsITimer *timer)
+{
+  nsRefPtr<Tickler> tickler = do_QueryReferent(mTickler);
+  if (!tickler)
+    return NS_ERROR_FAILURE;
+  MutexAutoLock lock(tickler->mLock);
+
+  if (!tickler->mFD) {
+    tickler->StopTickler();
+    return NS_ERROR_FAILURE;
+  }
+
+  if (tickler->mCanceled ||
+      ((TimeStamp::Now() - tickler->mLastTickle) > tickler->mDuration)) {
+    tickler->StopTickler();
+    return NS_OK;
+  }
+
+  if (!tickler->mEnabled)
+    return NS_OK;
+
+  PR_SendTo(tickler->mFD, "", 0, 0, &tickler->mAddr, 0);
+  return NS_OK;
+}
+
+} // namespace mozilla::net
+} // namespace mozilla
+
+#else // not defined MOZ_USE_WIFI_TICKLER
+
+namespace mozilla {
+namespace net {
+NS_IMPL_THREADSAFE_ISUPPORTS0(Tickler)
+} // namespace mozilla::net
+} // namespace mozilla
+
+#endif // defined MOZ_USE_WIFI_TICKLER
+
new file mode 100644
--- /dev/null
+++ b/netwerk/base/src/Tickler.h
@@ -0,0 +1,116 @@
+/* -*- 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_net_Tickler_h
+#define mozilla_net_Tickler_h
+
+// The tickler sends a regular 0 byte UDP heartbeat out to a
+// particular address for a short time after it has been touched. This
+// is used on some mobile wifi chipsets to mitigate Power Save Polling
+// (PSP) Mode when we are anticipating a response packet
+// soon. Typically PSP adds 100ms of latency to a read event because
+// the packet delivery is not triggered until the 802.11 beacon is
+// delivered to the host (100ms is the standard Access Point
+// configuration for the beacon interval.) Requesting a frequent
+// transmission and getting a CTS frame from the AP at least that
+// frequently allows for low latency receives when we have reason to
+// expect them (e.g a SYN-ACK).
+//
+// The tickler is used to allow RTT based phases of web transport to
+// complete quickly when on wifi - ARP, DNS, TCP handshake, SSL
+// handshake, HTTP headers, and the TCP slow start phase. The
+// transaction is given up to 400 miliseconds by default to get
+// through those phases before the tickler is disabled.
+//
+// The tickler only applies to wifi on mobile right now. Hopefully it
+// can also be restricted to particular handset models in the future.
+
+#include "mozilla/Mutex.h"
+#include "mozilla/TimeStamp.h"
+#include "nsAutoPtr.h"
+#include "nsISupports.h"
+#include "nsIThread.h"
+#include "nsThreadUtils.h"
+#include "nsITimer.h"
+#include "nsWeakReference.h"
+
+class nsIPrefBranch;
+
+namespace mozilla {
+namespace net {
+
+#if defined(ANDROID) && !defined(MOZ_B2G)
+#define MOZ_USE_WIFI_TICKLER
+#endif
+
+#ifdef MOZ_USE_WIFI_TICKLER
+
+class Tickler MOZ_FINAL : public nsSupportsWeakReference
+{
+public:
+  NS_DECL_ISUPPORTS
+
+  // These methods are main thread only
+  Tickler();
+  ~Tickler();
+  void Cancel();
+  nsresult Init();
+  void SetIPV4Address(uint32_t address);
+  void SetIPV4Port(uint16_t port);
+
+  // Tickle the tickler to (re-)start the activity.
+  // May call from any thread
+  void Tickle();
+
+private:
+  friend class TicklerTimer;
+  Mutex mLock;
+  nsCOMPtr<nsIThread> mThread;
+  nsCOMPtr<nsITimer> mTimer;
+  nsCOMPtr<nsIPrefBranch> mPrefs;
+
+  bool mActive;
+  bool mCanceled;
+  bool mEnabled;
+  uint32_t mDelay;
+  TimeDuration mDuration;
+  PRFileDesc* mFD;
+
+  TimeStamp mLastTickle;
+  PRNetAddr mAddr;
+
+  // These functions may be called from any thread
+  void PostCheckTickler();
+  void MaybeStartTickler();
+  void MaybeStartTicklerUnlocked();
+
+  // Tickler thread only
+  void CheckTickler();
+  void StartTickler();
+  void StopTickler();
+};
+
+#else // not defined MOZ_USE_WIFI_TICKLER
+
+class Tickler MOZ_FINAL : public nsISupports
+{
+public:
+  NS_DECL_ISUPPORTS
+
+  Tickler() { }
+  ~Tickler() { }
+  nsresult Init() { return NS_ERROR_NOT_IMPLEMENTED; }
+  void Cancel() { }
+  void SetIPV4Address(uint32_t) { };
+  void SetIPV4Port(uint16_t) { }
+  void Tickle() { }
+};
+
+#endif // defined MOZ_USE_WIFI_TICKLER
+
+} // namespace mozilla::net
+} // namespace mozilla
+
+#endif // mozilla_net_Tickler_h
--- a/netwerk/base/src/moz.build
+++ b/netwerk/base/src/moz.build
@@ -21,16 +21,17 @@ EXPORTS.mozilla.net += [
 CPP_SOURCES += [
     'ArrayBufferInputStream.cpp',
     'BackgroundFileSaver.cpp',
     'Dashboard.cpp',
     'EventTokenBucket.cpp',
     'NetworkActivityMonitor.cpp',
     'ProxyAutoConfig.cpp',
     'RedirectChannelRegistrar.cpp',
+    'Tickler.cpp',
     'nsAsyncRedirectVerifyHelper.cpp',
     'nsAsyncStreamCopier.cpp',
     'nsAuthInformationHolder.cpp',
     'nsBase64Encoder.cpp',
     'nsBaseChannel.cpp',
     'nsBaseContentStream.cpp',
     'nsBufferedStreams.cpp',
     'nsChannelClassifier.cpp',
--- a/netwerk/protocol/http/nsHttpHandler.cpp
+++ b/netwerk/protocol/http/nsHttpHandler.cpp
@@ -10,16 +10,21 @@
 #include "nsHttp.h"
 #include "nsHttpHandler.h"
 #include "nsHttpChannel.h"
 #include "nsHttpConnection.h"
 #include "nsHttpResponseHead.h"
 #include "nsHttpTransaction.h"
 #include "nsHttpAuthCache.h"
 #include "nsStandardURL.h"
+#include "nsIDOMConnection.h"
+#include "nsIDOMWindow.h"
+#include "nsIDOMNavigator.h"
+#include "nsIDOMNavigatorNetwork.h"
+#include "nsINetworkProperties.h"
 #include "nsIHttpChannel.h"
 #include "nsIURL.h"
 #include "nsIStandardURL.h"
 #include "nsICacheService.h"
 #include "nsICategoryManager.h"
 #include "nsCategoryManagerUtils.h"
 #include "nsICacheService.h"
 #include "nsIPrefService.h"
@@ -37,16 +42,17 @@
 #include "nsIOService.h"
 #include "nsAsyncRedirectVerifyHelper.h"
 #include "nsSocketTransportService2.h"
 #include "nsAlgorithm.h"
 #include "ASpdySession.h"
 #include "mozIApplicationClearPrivateDataParams.h"
 #include "nsICancelable.h"
 #include "EventTokenBucket.h"
+#include "Tickler.h"
 
 #include "nsIXULAppInfo.h"
 
 #include "mozilla/net/NeckoChild.h"
 #include "mozilla/Telemetry.h"
 
 #if defined(XP_UNIX)
 #include <sys/utsname.h>
@@ -339,16 +345,20 @@ nsHttpHandler::Init()
         mObserverService->AddObserver(this, "net:clear-active-logins", true);
         mObserverService->AddObserver(this, "net:prune-dead-connections", true);
         mObserverService->AddObserver(this, "net:failed-to-process-uri-content", true);
         mObserverService->AddObserver(this, "last-pb-context-exited", true);
         mObserverService->AddObserver(this, "webapps-clear-data", true);
     }
 
     MakeNewRequestTokenBucket();
+    mWifiTickler = new Tickler();
+    if (NS_FAILED(mWifiTickler->Init()))
+        mWifiTickler = nullptr;
+
     return NS_OK;
 }
 
 void
 nsHttpHandler::MakeNewRequestTokenBucket()
 {
     if (!mConnMgr)
         return;
@@ -1738,16 +1748,18 @@ nsHttpHandler::Observe(nsISupports *subj
     else if (strcmp(topic, "profile-change-net-teardown")    == 0 ||
              strcmp(topic, NS_XPCOM_SHUTDOWN_OBSERVER_ID)    == 0) {
 
         mHandlerActive = false;
 
         // clear cache of all authentication credentials.
         mAuthCache.ClearAll();
         mPrivateAuthCache.ClearAll();
+        if (mWifiTickler)
+            mWifiTickler->Cancel();
 
         // ensure connection manager is shutdown
         if (mConnMgr)
             mConnMgr->Shutdown();
 
         // need to reset the session start time since cache validation may
         // depend on this value.
         mSessionStartTime = NowInSeconds();
@@ -1873,16 +1885,61 @@ nsHttpHandler::SpeculativeConnect(nsIURI
         return rv;
 
     nsHttpConnectionInfo *ci =
         new nsHttpConnectionInfo(host, port, nullptr, usingSSL);
 
     return SpeculativeConnect(ci, aCallbacks);
 }
 
+void
+nsHttpHandler::TickleWifi(nsIInterfaceRequestor *cb)
+{
+    if (!cb || !mWifiTickler)
+        return;
+
+    // If B2G requires a similar mechanism nsINetworkManager, currently only avail
+    // on B2G, contains the necessary information on wifi and gateway
+
+    nsCOMPtr<nsIDOMWindow> domWindow;
+    cb->GetInterface(NS_GET_IID(nsIDOMWindow), getter_AddRefs(domWindow));
+    if (!domWindow)
+        return;
+
+    nsCOMPtr<nsIDOMNavigator> domNavigator;
+    domWindow->GetNavigator(getter_AddRefs(domNavigator));
+    nsCOMPtr<nsIDOMMozNavigatorNetwork> networkNavigator =
+        do_QueryInterface(domNavigator);
+    if (!networkNavigator)
+        return;
+
+    nsCOMPtr<nsIDOMMozConnection> mozConnection;
+    networkNavigator->GetMozConnection(getter_AddRefs(mozConnection));
+    nsCOMPtr<nsINetworkProperties> networkProperties =
+        do_QueryInterface(mozConnection);
+    if (!networkProperties)
+        return;
+
+    uint32_t gwAddress;
+    bool isWifi;
+    nsresult rv;
+
+    rv = networkProperties->GetDhcpGateway(&gwAddress);
+    if (NS_SUCCEEDED(rv))
+        rv = networkProperties->GetIsWifi(&isWifi);
+    if (NS_FAILED(rv))
+        return;
+
+    if (!gwAddress || !isWifi)
+        return;
+
+    mWifiTickler->SetIPV4Address(gwAddress);
+    mWifiTickler->Tickle();
+}
+
 //-----------------------------------------------------------------------------
 // nsHttpsHandler implementation
 //-----------------------------------------------------------------------------
 
 NS_IMPL_THREADSAFE_ISUPPORTS5(nsHttpsHandler,
                               nsIHttpProtocolHandler,
                               nsIProxiedProtocolHandler,
                               nsIProtocolHandler,
--- a/netwerk/protocol/http/nsHttpHandler.h
+++ b/netwerk/protocol/http/nsHttpHandler.h
@@ -36,16 +36,17 @@ class nsAHttpTransaction;
 class nsIHttpChannel;
 class nsIPrefBranch;
 class nsICancelable;
 
 namespace mozilla {
 namespace net {
 class ATokenBucketEvent;
 class EventTokenBucket;
+class Tickler;
 }
 }
 
 //-----------------------------------------------------------------------------
 // nsHttpHandler - protocol handler for HTTP and HTTPS
 //-----------------------------------------------------------------------------
 
 class nsHttpHandler : public nsIHttpProtocolHandler
@@ -179,16 +180,17 @@ public:
     {
         return mConnMgr->GetSocketThreadTarget(target);
     }
 
     nsresult SpeculativeConnect(nsHttpConnectionInfo *ci,
                                 nsIInterfaceRequestor *callbacks,
                                 uint32_t caps = 0)
     {
+        TickleWifi(callbacks);
         return mConnMgr->SpeculativeConnect(ci, callbacks, caps);
     }
 
     //
     // The HTTP handler caches pointers to specific XPCOM services, and
     // provides the following helper routines for accessing those services:
     //
     nsresult GetStreamConverterService(nsIStreamConverterService **);
@@ -467,16 +469,20 @@ public:
         return mRequestTokenBucket->SubmitEvent(event, cancel);
     }
 
     // Socket thread only
     void SetRequestTokenBucket(mozilla::net::EventTokenBucket *aTokenBucket)
     {
         mRequestTokenBucket = aTokenBucket;
     }
+
+private:
+    nsRefPtr<mozilla::net::Tickler> mWifiTickler;
+    void TickleWifi(nsIInterfaceRequestor *cb);
 };
 
 extern nsHttpHandler *gHttpHandler;
 
 //-----------------------------------------------------------------------------
 // nsHttpsHandler - thin wrapper to distinguish the HTTP handler from the
 //                  HTTPS handler (even though they share the same impl).
 //-----------------------------------------------------------------------------
--- a/netwerk/protocol/http/nsHttpTransaction.h
+++ b/netwerk/protocol/http/nsHttpTransaction.h
@@ -20,17 +20,16 @@
 #include "nsIInterfaceRequestor.h"
 #include "nsISocketTransportService.h"
 #include "nsITransport.h"
 #include "nsIEventTarget.h"
 #include "TimingStruct.h"
 
 //-----------------------------------------------------------------------------
 
-class nsHttpTransaction;
 class nsHttpRequestHead;
 class nsHttpResponseHead;
 class nsHttpChunkedDecoder;
 class nsIHttpActivityObserver;
 class UpdateSecurityCallbacks;
 
 //-----------------------------------------------------------------------------
 // nsHttpTransaction represents a single HTTP transaction.  It is thread-safe,
--- a/widget/android/AndroidBridge.cpp
+++ b/widget/android/AndroidBridge.cpp
@@ -1853,30 +1853,32 @@ AndroidBridge::GetCurrentNetworkInformat
 
     JNIEnv *env = GetJNIEnv();
     if (!env)
         return;
 
     AutoLocalJNIFrame jniFrame(env);
 
     // To prevent calling too many methods through JNI, the Java method returns
-    // an array of double even if we actually want a double and a boolean.
+    // an array of double even if we actually want a double, two booleans, and an integer.
     jobject obj = env->CallStaticObjectMethod(mGeckoAppShellClass, jGetCurrentNetworkInformation);
     if (jniFrame.CheckForException())
         return;
 
     jdoubleArray arr = static_cast<jdoubleArray>(obj);
-    if (!arr || env->GetArrayLength(arr) != 2) {
+    if (!arr || env->GetArrayLength(arr) != 4) {
         return;
     }
 
     jdouble* info = env->GetDoubleArrayElements(arr, 0);
 
     aNetworkInfo->bandwidth() = info[0];
     aNetworkInfo->canBeMetered() = info[1] == 1.0f;
+    aNetworkInfo->isWifi() = info[2] == 1.0f;
+    aNetworkInfo->dhcpGateway() = info[3];
 
     env->ReleaseDoubleArrayElements(arr, info, 0);
 }
 
 void
 AndroidBridge::EnableNetworkNotifications()
 {
     ALOG_BRIDGE("AndroidBridge::EnableNetworkNotifications");
--- a/widget/android/AndroidJavaWrappers.cpp
+++ b/widget/android/AndroidJavaWrappers.cpp
@@ -49,16 +49,18 @@ jfieldID AndroidGeckoEvent::jRangeStyles
 jfieldID AndroidGeckoEvent::jRangeLineStyleField = 0;
 jfieldID AndroidGeckoEvent::jRangeBoldLineField = 0;
 jfieldID AndroidGeckoEvent::jRangeForeColorField = 0;
 jfieldID AndroidGeckoEvent::jRangeBackColorField = 0;
 jfieldID AndroidGeckoEvent::jRangeLineColorField = 0;
 jfieldID AndroidGeckoEvent::jLocationField = 0;
 jfieldID AndroidGeckoEvent::jBandwidthField = 0;
 jfieldID AndroidGeckoEvent::jCanBeMeteredField = 0;
+jfieldID AndroidGeckoEvent::jIsWifiField = 0;
+jfieldID AndroidGeckoEvent::jDHCPGatewayField = 0;
 jfieldID AndroidGeckoEvent::jScreenOrientationField = 0;
 jfieldID AndroidGeckoEvent::jByteBufferField = 0;
 jfieldID AndroidGeckoEvent::jWidthField = 0;
 jfieldID AndroidGeckoEvent::jHeightField = 0;
 
 jclass AndroidGeckoEvent::jDomKeyLocationClass = 0;
 jfieldID AndroidGeckoEvent::jDomKeyLocationValueField = 0;
 
@@ -253,16 +255,18 @@ AndroidGeckoEvent::InitGeckoEventClass(J
     jRangeLineStyleField = getField("mRangeLineStyle", "I");
     jRangeBoldLineField = getField("mRangeBoldLine", "Z");
     jRangeForeColorField = getField("mRangeForeColor", "I");
     jRangeBackColorField = getField("mRangeBackColor", "I");
     jRangeLineColorField = getField("mRangeLineColor", "I");
     jLocationField = getField("mLocation", "Landroid/location/Location;");
     jBandwidthField = getField("mBandwidth", "D");
     jCanBeMeteredField = getField("mCanBeMetered", "Z");
+    jIsWifiField = getField("mIsWifi", "Z");
+    jDHCPGatewayField = getField("mDHCPGateway", "I");
     jScreenOrientationField = getField("mScreenOrientation", "S");
     jByteBufferField = getField("mBuffer", "Ljava/nio/ByteBuffer;");
     jWidthField = getField("mWidth", "I");
     jHeightField = getField("mHeight", "I");
 
     // Init GeckoEvent.DomKeyLocation enum
     jDomKeyLocationClass = getClassGlobalRef("org/mozilla/gecko/GeckoEvent$DomKeyLocation");
     jDomKeyLocationValueField = getField("value", "I");
@@ -652,16 +656,18 @@ AndroidGeckoEvent::Init(JNIEnv *jenv, jo
             ReadCharactersField(jenv);
             ReadCharactersExtraField(jenv);
             break;
         }
 
         case NETWORK_CHANGED: {
             mBandwidth = jenv->GetDoubleField(jobj, jBandwidthField);
             mCanBeMetered = jenv->GetBooleanField(jobj, jCanBeMeteredField);
+            mIsWifi = jenv->GetBooleanField(jobj, jIsWifiField);
+            mDHCPGateway = jenv->GetIntField(jobj, jDHCPGatewayField);
             break;
         }
 
         case VISITED: {
             ReadCharactersField(jenv);
             break;
         }
 
--- a/widget/android/AndroidJavaWrappers.h
+++ b/widget/android/AndroidJavaWrappers.h
@@ -566,16 +566,18 @@ public:
     int RangeLineStyle() { return mRangeLineStyle; }
     bool RangeBoldLine() { return mRangeBoldLine; }
     int RangeForeColor() { return mRangeForeColor; }
     int RangeBackColor() { return mRangeBackColor; }
     int RangeLineColor() { return mRangeLineColor; }
     nsGeoPosition* GeoPosition() { return mGeoPosition; }
     double Bandwidth() { return mBandwidth; }
     bool CanBeMetered() { return mCanBeMetered; }
+    bool IsWifi() { return mIsWifi; }
+    int DHCPGateway() { return mDHCPGateway; }
     short ScreenOrientation() { return mScreenOrientation; }
     RefCountedJavaObject* ByteBuffer() { return mByteBuffer; }
     int Width() { return mWidth; }
     int Height() { return mHeight; }
     nsTouchEvent MakeTouchEvent(nsIWidget* widget);
     MultiTouchInput MakeMultiTouchInput(nsIWidget* widget);
     void UnionRect(nsIntRect const& aRect);
 
@@ -600,16 +602,18 @@ protected:
     bool mRangeBoldLine;
     int mRangeForeColor, mRangeBackColor, mRangeLineColor;
     double mX, mY, mZ;
     int mPointerIndex;
     nsString mCharacters, mCharactersExtra, mData;
     nsRefPtr<nsGeoPosition> mGeoPosition;
     double mBandwidth;
     bool mCanBeMetered;
+    bool mIsWifi;
+    int mDHCPGateway;
     short mScreenOrientation;
     nsRefPtr<RefCountedJavaObject> mByteBuffer;
     int mWidth, mHeight;
 
     void ReadIntArray(nsTArray<int> &aVals,
                       JNIEnv *jenv,
                       jfieldID field,
                       int32_t count);
@@ -665,16 +669,18 @@ protected:
     static jfieldID jRangeBoldLineField;
     static jfieldID jRangeForeColorField;
     static jfieldID jRangeBackColorField;
     static jfieldID jRangeLineColorField;
     static jfieldID jLocationField;
 
     static jfieldID jBandwidthField;
     static jfieldID jCanBeMeteredField;
+    static jfieldID jIsWifiField;
+    static jfieldID jDHCPGatewayField;
 
     static jfieldID jScreenOrientationField;
     static jfieldID jByteBufferField;
 
     static jfieldID jWidthField;
     static jfieldID jHeightField;
 
     static jclass jDomKeyLocationClass;
--- a/widget/android/nsAppShell.cpp
+++ b/widget/android/nsAppShell.cpp
@@ -484,17 +484,19 @@ nsAppShell::ProcessNextNativeEvent(bool 
             history->NotifyVisited(visitedURI);
         }
 #endif
         break;
     }
 
     case AndroidGeckoEvent::NETWORK_CHANGED: {
         hal::NotifyNetworkChange(hal::NetworkInformation(curEvent->Bandwidth(),
-                                                         curEvent->CanBeMetered()));
+                                                         curEvent->CanBeMetered(),
+                                                         curEvent->IsWifi(),
+                                                         curEvent->DHCPGateway()));
         break;
     }
 
     case AndroidGeckoEvent::SCREENORIENTATION_CHANGED: {
         nsresult rv;
         nsCOMPtr<nsIScreenManager> screenMgr =
             do_GetService("@mozilla.org/gfx/screenmanager;1", &rv);
         if (NS_FAILED(rv)) {