--- a/docshell/base/nsDocShell.cpp
+++ b/docshell/base/nsDocShell.cpp
@@ -7561,16 +7561,20 @@ nsDocShell::OnSecurityChange(nsIWebProgr
NS_NOTREACHED("notification excluded in AddProgressListener(...)");
return NS_OK;
}
nsresult
nsDocShell::EndPageLoad(nsIWebProgress* aProgress,
nsIChannel* aChannel, nsresult aStatus)
{
+ // We can release any pressure we may have had on the throttling service and
+ // let background channels continue.
+ mThrottler.reset();
+
if (!aChannel) {
return NS_ERROR_NULL_POINTER;
}
nsCOMPtr<nsIConsoleReportCollector> reporter = do_QueryInterface(aChannel);
if (reporter) {
nsCOMPtr<nsILoadGroup> loadGroup;
aChannel->GetLoadGroup(getter_AddRefs(loadGroup));
@@ -10744,16 +10748,26 @@ nsDocShell::InternalLoad(nsIURI* aURI,
attrs.Inherit(GetOriginAttributes());
attrs.SetFirstPartyDomain(isTopLevelDoc, aURI);
net::PredictorLearn(aURI, nullptr,
nsINetworkPredictor::LEARN_LOAD_TOPLEVEL, attrs);
net::PredictorPredict(aURI, nullptr,
nsINetworkPredictor::PREDICT_LOAD, attrs, nullptr);
+ // Increase pressure on the throttling service so background channels will be
+ // appropriately de-prioritized. We need to explicitly check for http[s] here
+ // so that we don't throttle while loading, say, about:blank.
+ bool isHTTP, isHTTPS;
+ aURI->SchemeIs("http", &isHTTP);
+ aURI->SchemeIs("https", &isHTTPS);
+ if (isHTTP || isHTTPS) {
+ mThrottler.reset(new mozilla::net::Throttler());
+ }
+
nsCOMPtr<nsIRequest> req;
rv = DoURILoad(aURI, aOriginalURI, aLoadReplace, aReferrer,
!(aFlags & INTERNAL_LOAD_FLAGS_DONT_SEND_REFERRER),
aReferrerPolicy,
aTriggeringPrincipal, principalToInherit, aTypeHint,
aFileName, aPostData, aHeadersData,
aFirstParty, aDocShell, getter_AddRefs(req),
(aFlags & INTERNAL_LOAD_FLAGS_FIRST_LOAD) != 0,
--- a/docshell/base/nsDocShell.h
+++ b/docshell/base/nsDocShell.h
@@ -55,16 +55,17 @@
#include "nsILinkHandler.h"
#include "nsIClipboardCommands.h"
#include "nsITabParent.h"
#include "nsCRT.h"
#include "prtime.h"
#include "nsRect.h"
#include "Units.h"
#include "nsIDeprecationWarner.h"
+#include "nsIThrottlingService.h"
namespace mozilla {
namespace dom {
class EventTarget;
class PendingGlobalHistoryEntry;
typedef uint32_t ScreenOrientationInternal;
} // namespace dom
} // namespace mozilla
@@ -1089,11 +1090,14 @@ public:
NS_DECL_THREADSAFE_ISUPPORTS
NS_DECL_NSIINTERFACEREQUESTOR
protected:
virtual ~InterfaceRequestorProxy();
InterfaceRequestorProxy() {}
nsWeakPtr mWeakPtr;
};
+
+private:
+ mozilla::UniquePtr<mozilla::net::Throttler> mThrottler;
};
#endif /* nsDocShell_h__ */
--- a/modules/libpref/init/all.js
+++ b/modules/libpref/init/all.js
@@ -1979,16 +1979,22 @@ pref("network.auth.subresource-http-auth
// for credentials from the user explicitly.
// If set to true, and a server URL conforms other conditions for sending default
// credentials, those will be sent automatically in Private Browsing windows.
//
// This preference has no effect when the browser is set to "Never Remember History",
// in that case default credentials will always be used.
pref("network.auth.private-browsing-sso", false);
+// Control how the throttling service works - number of ms that each
+// suspend and resume period lasts (prefs named appropriately)
+pref("network.throttle.suspend-for", 2000);
+pref("network.throttle.resume-for", 2000);
+pref("network.throttle.enable", true);
+
pref("permissions.default.image", 1); // 1-Accept, 2-Deny, 3-dontAcceptForeign
pref("network.proxy.type", 5);
pref("network.proxy.ftp", "");
pref("network.proxy.ftp_port", 0);
pref("network.proxy.http", "");
pref("network.proxy.http_port", 0);
pref("network.proxy.ssl", "");
new file mode 100644
--- /dev/null
+++ b/netwerk/base/ThrottlingService.cpp
@@ -0,0 +1,445 @@
+/* vim: set ts=2 sts=2 et sw=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/. */
+
+
+ // Things to think about
+ // * do we need to be multithreaded, or is mt-only ok?
+
+#include "ThrottlingService.h"
+
+#include "nsIHttpChannel.h"
+#include "nsIObserverService.h"
+
+#include "mozilla/Preferences.h"
+#include "mozilla/Services.h"
+
+#include "mozilla/net/NeckoChild.h"
+
+namespace mozilla {
+namespace net{
+
+static const char kEnabledPref[] = "network.throttle.enable";
+static const bool kDefaultEnabled = true;
+static const char kSuspendPeriodPref[] = "network.throttle.suspend-for";
+static const uint32_t kDefaultSuspendPeriod = 2000;
+static const char kResumePeriodPref[] = "network.throttle.resume-for";
+static const uint32_t kDefaultResumePeriod = 2000;
+
+NS_IMPL_ISUPPORTS(ThrottlingService, nsIThrottlingService, nsIObserver, nsITimerCallback)
+
+ThrottlingService::ThrottlingService()
+ :mEnabled(kDefaultEnabled)
+ ,mInitCalled(false)
+ ,mSuspended(false)
+ ,mPressureCount(0)
+ ,mSuspendPeriod(kDefaultSuspendPeriod)
+ ,mResumePeriod(kDefaultResumePeriod)
+ ,mIteratingHash(false)
+{
+}
+
+ThrottlingService::~ThrottlingService()
+{
+ Shutdown();
+}
+
+
+nsresult
+ThrottlingService::Init()
+{
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_ASSERT(!mInitCalled);
+
+ mInitCalled = true;
+
+ nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
+ if (!obs) {
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ nsresult rv = obs->AddObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID, false);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ mEnabled = Preferences::GetBool(kEnabledPref, kDefaultEnabled);
+ rv = Preferences::AddStrongObserver(this, kEnabledPref);
+ NS_ENSURE_SUCCESS(rv, rv);
+ Preferences::AddUintVarCache(&mSuspendPeriod, kSuspendPeriodPref, kDefaultSuspendPeriod);
+ Preferences::AddUintVarCache(&mResumePeriod, kResumePeriodPref, kDefaultResumePeriod);
+
+ mTimer = do_CreateInstance(NS_TIMER_CONTRACTID);
+
+ return NS_OK;
+}
+
+void
+ThrottlingService::Shutdown()
+{
+ if (!mInitCalled) {
+ return;
+ }
+
+ if (mTimer) {
+ mTimer->Cancel();
+ }
+
+ nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
+ if (obs) {
+ obs->RemoveObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID);
+ }
+
+ Preferences::RemoveObserver(this, kEnabledPref);
+
+ MaybeResumeAll();
+ mChannelHash.Clear();
+}
+
+nsresult
+ThrottlingService::Create(nsISupports *outer, const nsIID& iid, void **result)
+{
+ MOZ_ASSERT(NS_IsMainThread());
+
+ if (outer != nullptr) {
+ return NS_ERROR_NO_AGGREGATION;
+ }
+
+ RefPtr<ThrottlingService> svc = new ThrottlingService();
+ if (!IsNeckoChild()) {
+ // We only need to do any work on the parent, so only bother initializing
+ // there. Child-side, we'll just error out since we only deal with parent
+ // channels.)
+ nsresult rv = svc->Init();
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ return svc->QueryInterface(iid, result);
+}
+
+// nsIThrottlingService
+
+nsresult
+ThrottlingService::AddChannel(nsIHttpChannel *channel)
+{
+ MOZ_ASSERT(NS_IsMainThread());
+
+ // We don't check mEnabled, because we always want to put channels in the hash
+ // to avoid potential inconsistencies in the case where the user changes the
+ // enabled pref at run-time.
+
+ if (IsNeckoChild()) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ nsAutoCString strKey;
+ nsresult rv = channel->GetChannelId(strKey);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsID key;
+ if (!key.Parse(strKey.get())) {
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ if (mChannelHash.Get(key, nullptr)) {
+ // We already have this channel under our control, not adding it again.
+ MOZ_ASSERT(false, "Trying to throttle an already-throttled channel");
+ return NS_ERROR_ILLEGAL_VALUE;
+ }
+
+ if (!mIteratingHash) {
+ // This should be the common case, and as such is easy to handle
+ mChannelHash.Put(key, channel);
+
+ if (mSuspended) {
+ channel->Suspend();
+ }
+ } else {
+ // This gets tricky - we've somehow re-entrantly gotten here through the
+ // hash iteration in one of MaybeSuspendAll or MaybeResumeAll. Keep track
+ // of the fact that this add came in now, and once we're done iterating, we
+ // can add this into the hash. This avoids unexpectedly modifying the hash
+ // while it's being iterated over, which could lead to inconsistencies.
+ mChannelsToAddRemove.AppendElement(channel);
+ mChannelIsAdd.AppendElement(true);
+ }
+
+ return NS_OK;
+}
+
+nsresult
+ThrottlingService::RemoveChannel(nsIHttpChannel *channel)
+{
+ MOZ_ASSERT(NS_IsMainThread());
+
+ // Just like above, don't worry about mEnabled to avoid inconsistencies when
+ // the pref changes at run-time
+
+ if (IsNeckoChild()) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ nsAutoCString strKey;
+ nsresult rv = channel->GetChannelId(strKey);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsID key;
+ if (!key.Parse(strKey.get())) {
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ if (!mChannelHash.Get(key, nullptr)) {
+ // TODO - warn?
+ return NS_ERROR_ILLEGAL_VALUE;
+ }
+
+ if (!mIteratingHash) {
+ // This should be the common case, and easy to handle.
+ mChannelHash.Remove(key);
+
+ if (mSuspended) {
+ // This channel is no longer under our control for suspend/resume, but
+ // we've suspended it. Time to let it go.
+ channel->Resume();
+ }
+ } else {
+ // This gets tricky - we've somehow re-entrantly gotten here through the
+ // hash iteration in one of MaybeSuspendAll or MaybeResumeAll. Keep track
+ // of the fact that this add came in now, and once we're done iterating, we
+ // can add this into the hash. This avoids unexpectedly modifying the hash
+ // while it's being iterated over, which could lead to inconsistencies.
+ mChannelsToAddRemove.AppendElement(channel);
+ mChannelIsAdd.AppendElement(false);
+ }
+
+ return NS_OK;
+}
+
+nsresult
+ThrottlingService::IncreasePressure()
+{
+ MOZ_ASSERT(NS_IsMainThread());
+
+ // Just like add/removing channels, we don't check mEnabled here in order to
+ // avoid inconsistencies that could occur if the pref is flipped at runtime
+
+ if (IsNeckoChild()) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ if (mPressureCount++ == 0) {
+ MOZ_ASSERT(!mSuspended, "Suspended with 0 pressure?");
+ MaybeSuspendAll();
+ if (mSuspended) {
+ // MaybeSuspendAll() may not actually suspend things, and we only want to
+ // bother setting a timer to resume if we actually suspended.
+ mTimer->InitWithCallback(this, mSuspendPeriod, nsITimer::TYPE_ONE_SHOT);
+ }
+ }
+
+ return NS_OK;
+}
+
+nsresult
+ThrottlingService::DecreasePressure()
+{
+ MOZ_ASSERT(NS_IsMainThread());
+
+ // Just like add/removing channels, we don't check mEnabled here in order to
+ // avoid inconsistencies that could occur if the pref is flipped at runtime
+
+ if (IsNeckoChild()) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ MOZ_ASSERT(mPressureCount > 0, "Unbalanced throttle pressure");
+
+ if (--mPressureCount == 0) {
+ MaybeResumeAll();
+ mTimer->Cancel();
+ }
+
+ return NS_OK;
+}
+
+// nsIObserver
+
+nsresult
+ThrottlingService::Observe(nsISupports *subject, const char *topic,
+ const char16_t *data_unicode)
+{
+ MOZ_ASSERT(!IsNeckoChild());
+ MOZ_ASSERT(NS_IsMainThread());
+
+ if (!strcmp(NS_XPCOM_SHUTDOWN_OBSERVER_ID, topic)) {
+ Shutdown();
+ } else if (!strcmp("nsPref:changed", topic)) {
+ mEnabled = Preferences::GetBool(kEnabledPref, mEnabled);
+ if (mEnabled && mPressureCount) {
+ // We weren't enabled, but we are now, AND we're under pressure. Go ahead
+ // and suspend things.
+ MaybeSuspendAll();
+ if (mSuspended) {
+ mTimer->InitWithCallback(this, mSuspendPeriod, nsITimer::TYPE_ONE_SHOT);
+ }
+ } else if (!mEnabled) {
+ // We were enabled, but we aren't any longer. Make sure we aren't
+ // suspending channels and that we don't have any timer that wants to
+ // change things unexpectedly.
+ mTimer->Cancel();
+ MaybeResumeAll();
+ }
+ }
+
+ return NS_OK;
+}
+
+// nsITimerCallback
+
+nsresult
+ThrottlingService::Notify(nsITimer *timer)
+{
+ MOZ_ASSERT(!IsNeckoChild());
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_ASSERT(timer == mTimer);
+
+ if (mSuspended) {
+ MaybeResumeAll();
+ // Always try to resume if we were suspended, but only time-limit the
+ // resumption if we're under pressure and we're enabled. If either of those
+ // conditions is false, it doesn't make any sense to set a timer to suspend
+ // things when we don't want to be suspended anyway.
+ if (mPressureCount && mEnabled) {
+ mTimer->InitWithCallback(this, mResumePeriod, nsITimer::TYPE_ONE_SHOT);
+ }
+ } else if (mPressureCount) {
+ MaybeSuspendAll();
+ if (mSuspended) {
+ // MaybeSuspendAll() may not actually suspend, and it only makes sense to
+ // set a timer to resume if we actually suspended the channels.
+ mTimer->InitWithCallback(this, mSuspendPeriod, nsITimer::TYPE_ONE_SHOT);
+ }
+ }
+
+ return NS_OK;
+}
+
+// Internal methods
+
+void
+ThrottlingService::MaybeSuspendAll()
+{
+ MOZ_ASSERT(!IsNeckoChild());
+ MOZ_ASSERT(NS_IsMainThread());
+
+ if (!mEnabled) {
+ // We don't actually suspend when disabled, even though it's possible we get
+ // called in that state in order to avoid inconsistencies in the hash and
+ // the count if the pref changes at runtime.
+ return;
+ }
+
+ if (mSuspended) {
+ // Already suspended, nothing to do!
+ return;
+ }
+ mSuspended = true;
+
+ IterateHash([](ChannelHash::Iterator &iter) -> void {
+ const nsCOMPtr<nsIHttpChannel> channel = iter.UserData();
+ channel->Suspend();
+ });
+}
+
+void
+ThrottlingService::MaybeResumeAll()
+{
+ MOZ_ASSERT(!IsNeckoChild());
+ MOZ_ASSERT(NS_IsMainThread());
+
+ if (!mSuspended) {
+ // Already resumed, nothing to do!
+ return;
+ }
+ mSuspended = false;
+
+ IterateHash([](ChannelHash::Iterator &iter) -> void {
+ const nsCOMPtr<nsIHttpChannel> channel = iter.UserData();
+ channel->Resume();
+ });
+}
+
+void
+ThrottlingService::IterateHash(void (* callback)(ChannelHash::Iterator &iter))
+{
+ MOZ_ASSERT(!mIteratingHash);
+ mIteratingHash = true;
+ for (ChannelHash::Iterator iter = mChannelHash.ConstIter(); !iter.Done(); iter.Next()) {
+ callback(iter);
+ }
+ mIteratingHash = false;
+ HandleExtraAddRemove();
+}
+
+void
+ThrottlingService::HandleExtraAddRemove()
+{
+ MOZ_ASSERT(!mIteratingHash);
+ MOZ_ASSERT(mChannelsToAddRemove.Length() == mChannelIsAdd.Length());
+
+ nsCOMArray<nsIHttpChannel> channelsToAddRemove;
+ channelsToAddRemove.SwapElements(mChannelsToAddRemove);
+
+ nsTArray<bool> channelIsAdd;
+ channelIsAdd.SwapElements(mChannelIsAdd);
+
+ for (size_t i = 0; i < channelsToAddRemove.Length(); ++i) {
+ if (channelIsAdd[i]) {
+ AddChannel(channelsToAddRemove[i]);
+ } else {
+ RemoveChannel(channelsToAddRemove[i]);
+ }
+ }
+
+ channelsToAddRemove.Clear();
+ channelIsAdd.Clear();
+}
+
+// The publicly available way to throttle things
+
+Throttler::Throttler()
+{
+ MOZ_ASSERT(NS_IsMainThread());
+ if (IsNeckoChild()) {
+ if (gNeckoChild) {
+ // The child object may have already gone away, so we need to guard
+ // guard against deref'ing a nullptr here. If that's what happened, then
+ // our pageload won't be continuing anyway, so what we do is pretty much
+ // irrelevant.
+ gNeckoChild->SendIncreaseThrottlePressure();
+ }
+ } else {
+ mThrottlingService = do_GetService("@mozilla.org/network/throttling-service;1");
+ mThrottlingService->IncreasePressure();
+ }
+}
+
+Throttler::~Throttler()
+{
+ MOZ_ASSERT(NS_IsMainThread());
+ if (IsNeckoChild()) {
+ if (gNeckoChild) {
+ // The child object may have already gone away, so we need to guard
+ // guard against deref'ing a nullptr here. If that's what happened, then
+ // NeckoParent::ActorDestroy will take care of releasing the pressure we
+ // created.
+ gNeckoChild->SendDecreaseThrottlePressure();
+ }
+ } else {
+ MOZ_RELEASE_ASSERT(mThrottlingService);
+ mThrottlingService->DecreasePressure();
+ mThrottlingService = nullptr;
+ }
+}
+
+}
+}
new file mode 100644
--- /dev/null
+++ b/netwerk/base/ThrottlingService.h
@@ -0,0 +1,67 @@
+/* vim: set ts=2 sts=2 et sw=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__ThrottlingService_h
+#define mozilla__net__ThrottlingService_h
+
+#include "nsIThrottlingService.h"
+
+#include "nsCOMArray.h"
+#include "nsCOMPtr.h"
+#include "nsInterfaceHashtable.h"
+#include "nsIObserver.h"
+#include "nsITimer.h"
+
+class nsIHttpChannel;
+
+namespace mozilla {
+namespace net {
+
+class ThrottlingService : public nsIThrottlingService
+ , public nsIObserver
+ , public nsITimerCallback
+{
+public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSITHROTTLINGSERVICE
+ NS_DECL_NSIOBSERVER
+ NS_DECL_NSITIMERCALLBACK
+
+ ThrottlingService();
+
+ nsresult Init();
+ void Shutdown();
+ static nsresult Create(nsISupports *outer, const nsIID& iid, void **result);
+
+private:
+ virtual ~ThrottlingService();
+
+ void MaybeSuspendAll();
+ void MaybeResumeAll();
+
+ void HandleExtraAddRemove();
+
+ bool mEnabled;
+ bool mInitCalled;
+ bool mSuspended;
+ uint32_t mPressureCount;
+ uint32_t mSuspendPeriod; // How long we should Suspend() channels for
+ uint32_t mResumePeriod; // How long we should Resume() channels for
+ nsCOMPtr<nsITimer> mTimer;
+ typedef nsInterfaceHashtable<nsIDHashKey, nsIHttpChannel> ChannelHash;
+ ChannelHash mChannelHash;
+
+ // Used to avoid inconsistencies in the hash and the suspend/resume count of
+ // channels. See comments in AddChannel and RemoveChannel for details.
+ void IterateHash(void (* callback)(ChannelHash::Iterator &iter));
+ bool mIteratingHash;
+ nsCOMArray<nsIHttpChannel> mChannelsToAddRemove;
+ nsTArray<bool> mChannelIsAdd;
+};
+
+} // ::mozilla::net
+} // ::mozilla
+
+#endif // mozilla__net__ThrottlingService_h
--- a/netwerk/base/moz.build
+++ b/netwerk/base/moz.build
@@ -115,16 +115,17 @@ XPIDL_SOURCES += [
'nsIStreamListenerTee.idl',
'nsIStreamLoader.idl',
'nsIStreamTransportService.idl',
'nsISyncStreamListener.idl',
'nsISystemProxySettings.idl',
'nsIThreadRetargetableRequest.idl',
'nsIThreadRetargetableStreamListener.idl',
'nsIThrottledInputChannel.idl',
+ 'nsIThrottlingService.idl',
'nsITimedChannel.idl',
'nsITLSServerSocket.idl',
'nsITraceableChannel.idl',
'nsITransport.idl',
'nsIUDPSocket.idl',
'nsIUnicharStreamLoader.idl',
'nsIUploadChannel.idl',
'nsIUploadChannel2.idl',
@@ -249,16 +250,17 @@ UNIFIED_SOURCES += [
'PollableEvent.cpp',
'Predictor.cpp',
'ProxyAutoConfig.cpp',
'RedirectChannelRegistrar.cpp',
'RequestContextService.cpp',
'SimpleBuffer.cpp',
'StreamingProtocolService.cpp',
'ThrottleQueue.cpp',
+ 'ThrottlingService.cpp',
'Tickler.cpp',
'TLSServerSocket.cpp',
]
if CONFIG['MOZ_RUST'] and CONFIG['MOZ_RUST_URLPARSE']:
EXPORTS.mozilla.net += [ 'RustURL.h' ]
UNIFIED_SOURCES += [ 'RustURL.cpp' ]
new file mode 100644
--- /dev/null
+++ b/netwerk/base/nsIThrottlingService.idl
@@ -0,0 +1,37 @@
+/* vim: set ts=2 sts=2 et sw=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 "nsISupports.idl"
+
+interface nsIHttpChannel;
+
+[builtinclass, uuid(c755ef98-b749-4f30-a658-1e6110013a66)]
+interface nsIThrottlingService : nsISupports
+{
+ void addChannel(in nsIHttpChannel channel);
+ void removeChannel(in nsIHttpChannel channel);
+
+ /* Don't call these directly, use mozilla::net::Throttler instead! */
+ void increasePressure();
+ void decreasePressure();
+};
+
+%{C++
+namespace mozilla {
+namespace net {
+
+class Throttler
+{
+public:
+ Throttler();
+ ~Throttler();
+
+private:
+ nsCOMPtr<nsIThrottlingService> mThrottlingService;
+};
+
+} // ::mozilla::net
+} // ::mozilla
+%}
--- a/netwerk/build/nsNetCID.h
+++ b/netwerk/build/nsNetCID.h
@@ -499,16 +499,27 @@
#define NS_REQUESTCONTEXTSERVICE_CID \
{ /* d5499fa7-7ba8-49ff-9e30-1858b99ace69 */ \
0xd5499fa7, \
0x7ba8, \
0x49ff, \
{0x93, 0x30, 0x18, 0x58, 0xb9, 0x9a, 0xce, 0x69} \
}
+// service implementing nsIThrottlingService
+#define NS_THROTTLINGSERVICE_CONTRACTID \
+ "@mozilla.org/network/throttling-service;1"
+#define NS_THROTTLINGSERVICE_CID \
+{ /* c1c48f2b-cb9c-415e-b4f9-5e4c3476ca86 */ \
+ 0xc1c48f2b, \
+ 0xcb9c, \
+ 0x415e, \
+ {0xb4, 0xf9, 0x5e, 0x4c, 0x34, 0x76, 0xca, 0x86} \
+}
+
/******************************************************************************
* netwerk/cache/ classes
*/
// service implementing nsICacheService.
#define NS_CACHESERVICE_CONTRACTID \
"@mozilla.org/network/cache-service;1"
#define NS_CACHESERVICE_CID \
--- a/netwerk/build/nsNetModule.cpp
+++ b/netwerk/build/nsNetModule.cpp
@@ -33,16 +33,17 @@
#include "nsApplicationCacheService.h"
#include "nsMimeTypes.h"
#include "nsDNSPrefetch.h"
#include "nsAboutProtocolHandler.h"
#include "nsXULAppAPI.h"
#include "nsCategoryCache.h"
#include "nsIContentSniffer.h"
#include "Predictor.h"
+#include "ThrottlingService.h"
#include "nsIThreadPool.h"
#include "mozilla/net/NeckoChild.h"
#include "nsNetCID.h"
#ifndef XP_MACOSX
#define BUILD_BINHEX_DECODER 1
#endif
@@ -871,16 +872,17 @@ NS_DEFINE_NAMED_CID(NS_REDIRECTCHANNELRE
NS_DEFINE_NAMED_CID(NS_CACHE_STORAGE_SERVICE_CID);
NS_DEFINE_NAMED_CID(NS_NSILOADCONTEXTINFOFACTORY_CID);
NS_DEFINE_NAMED_CID(NS_NETWORKPREDICTOR_CID);
NS_DEFINE_NAMED_CID(NS_CAPTIVEPORTAL_CID);
NS_DEFINE_NAMED_CID(NS_REQUESTCONTEXTSERVICE_CID);
#ifdef BUILD_NETWORK_INFO_SERVICE
NS_DEFINE_NAMED_CID(NETWORKINFOSERVICE_CID);
#endif // BUILD_NETWORK_INFO_SERVICE
+NS_DEFINE_NAMED_CID(NS_THROTTLINGSERVICE_CID);
static const mozilla::Module::CIDEntry kNeckoCIDs[] = {
{ &kNS_IOSERVICE_CID, false, nullptr, nsIOServiceConstructor },
{ &kNS_STREAMTRANSPORTSERVICE_CID, false, nullptr, nsStreamTransportServiceConstructor },
{ &kNS_SOCKETTRANSPORTSERVICE_CID, false, nullptr, nsSocketTransportServiceConstructor },
{ &kNS_SERVERSOCKET_CID, false, nullptr, nsServerSocketConstructor },
{ &kNS_TLSSERVERSOCKET_CID, false, nullptr, TLSServerSocketConstructor },
{ &kNS_UDPSOCKET_CID, false, nullptr, nsUDPSocketConstructor },
@@ -1025,16 +1027,17 @@ static const mozilla::Module::CIDEntry k
{ &kNS_CACHE_STORAGE_SERVICE_CID, false, nullptr, CacheStorageServiceConstructor },
{ &kNS_NSILOADCONTEXTINFOFACTORY_CID, false, nullptr, LoadContextInfoFactoryConstructor },
{ &kNS_NETWORKPREDICTOR_CID, false, nullptr, mozilla::net::Predictor::Create },
{ &kNS_CAPTIVEPORTAL_CID, false, nullptr, mozilla::net::CaptivePortalServiceConstructor },
{ &kNS_REQUESTCONTEXTSERVICE_CID, false, nullptr, RequestContextServiceConstructor },
#ifdef BUILD_NETWORK_INFO_SERVICE
{ &kNETWORKINFOSERVICE_CID, false, nullptr, nsNetworkInfoServiceConstructor },
#endif
+ { &kNS_THROTTLINGSERVICE_CID, false, nullptr, mozilla::net::ThrottlingService::Create },
{ nullptr }
};
static const mozilla::Module::ContractIDEntry kNeckoContracts[] = {
{ NS_IOSERVICE_CONTRACTID, &kNS_IOSERVICE_CID },
{ NS_NETUTIL_CONTRACTID, &kNS_IOSERVICE_CID },
{ NS_STREAMTRANSPORTSERVICE_CONTRACTID, &kNS_STREAMTRANSPORTSERVICE_CID },
{ NS_SOCKETTRANSPORTSERVICE_CONTRACTID, &kNS_SOCKETTRANSPORTSERVICE_CID },
@@ -1184,16 +1187,17 @@ static const mozilla::Module::ContractID
{ NS_CACHE_STORAGE_SERVICE_CONTRACTID2, &kNS_CACHE_STORAGE_SERVICE_CID },
{ NS_NSILOADCONTEXTINFOFACTORY_CONTRACTID, &kNS_NSILOADCONTEXTINFOFACTORY_CID },
{ NS_NETWORKPREDICTOR_CONTRACTID, &kNS_NETWORKPREDICTOR_CID },
{ NS_CAPTIVEPORTAL_CONTRACTID, &kNS_CAPTIVEPORTAL_CID },
{ NS_REQUESTCONTEXTSERVICE_CONTRACTID, &kNS_REQUESTCONTEXTSERVICE_CID },
#ifdef BUILD_NETWORK_INFO_SERVICE
{ NETWORKINFOSERVICE_CONTRACT_ID, &kNETWORKINFOSERVICE_CID },
#endif
+ { NS_THROTTLINGSERVICE_CONTRACTID, &kNS_THROTTLINGSERVICE_CID },
{ nullptr }
};
static const mozilla::Module kNeckoModule = {
mozilla::Module::kVersion,
kNeckoCIDs,
kNeckoContracts,
kNeckoCategories,
--- a/netwerk/ipc/NeckoParent.cpp
+++ b/netwerk/ipc/NeckoParent.cpp
@@ -39,16 +39,17 @@
#include "nsEscape.h"
#include "SerializedLoadContext.h"
#include "nsAuthInformationHolder.h"
#include "nsIAuthPromptCallback.h"
#include "nsPrincipal.h"
#include "nsINetworkPredictor.h"
#include "nsINetworkPredictorVerifier.h"
#include "nsISpeculativeConnect.h"
+#include "nsIThrottlingService.h"
using mozilla::OriginAttributes;
using mozilla::dom::ChromeUtils;
using mozilla::dom::ContentParent;
using mozilla::dom::TabContext;
using mozilla::dom::TabParent;
using mozilla::net::PTCPSocketParent;
using mozilla::dom::TCPSocketParent;
@@ -886,10 +887,27 @@ NeckoParent::RecvRemoveRequestContext(co
nsID id;
id.Parse(rcid.BeginReading());
rcsvc->RemoveRequestContext(id);
return IPC_OK();
}
+mozilla::ipc::IPCResult
+NeckoParent::RecvIncreaseThrottlePressure()
+{
+ mThrottlers.AppendElement(mozilla::UniquePtr<mozilla::net::Throttler>(new mozilla::net::Throttler));
+ return IPC_OK();
+}
+
+mozilla::ipc::IPCResult
+NeckoParent::RecvDecreaseThrottlePressure()
+{
+ MOZ_ASSERT(!mThrottlers.IsEmpty());
+ // We do this because we don't actually care which throttler gets removed,
+ // just that one of them does.
+ mThrottlers.RemoveElementAt(0);
+ return IPC_OK();
+}
+
} // namespace net
} // namespace mozilla
--- a/netwerk/ipc/NeckoParent.h
+++ b/netwerk/ipc/NeckoParent.h
@@ -5,16 +5,17 @@
* 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/BasePrincipal.h"
#include "mozilla/net/PNeckoParent.h"
#include "mozilla/net/NeckoCommon.h"
#include "nsIAuthPrompt2.h"
#include "nsINetworkPredictor.h"
+#include "nsIThrottlingService.h"
#include "nsNetUtil.h"
#ifndef mozilla_net_NeckoParent_h
#define mozilla_net_NeckoParent_h
namespace mozilla {
namespace net {
@@ -214,14 +215,21 @@ protected:
virtual mozilla::ipc::IPCResult RecvPredLearn(const ipc::URIParams& aTargetURI,
const ipc::OptionalURIParams& aSourceURI,
const PredictorPredictReason& aReason,
const OriginAttributes& aOriginAttributes) override;
virtual mozilla::ipc::IPCResult RecvPredReset() override;
virtual mozilla::ipc::IPCResult RecvRemoveRequestContext(const nsCString& rcid) override;
+
+ /* Throttler messages */
+ virtual mozilla::ipc::IPCResult RecvIncreaseThrottlePressure() override;
+ virtual mozilla::ipc::IPCResult RecvDecreaseThrottlePressure() override;
+
+private:
+ nsTArray<mozilla::UniquePtr<mozilla::net::Throttler>> mThrottlers;
};
} // namespace net
} // namespace mozilla
#endif // mozilla_net_NeckoParent_h
--- a/netwerk/ipc/PNecko.ipdl
+++ b/netwerk/ipc/PNecko.ipdl
@@ -110,16 +110,22 @@ parent:
async OnAuthAvailable(uint64_t callbackId, nsString user,
nsString password, nsString domain);
async OnAuthCancelled(uint64_t callbackId, bool userCancel);
async RemoveRequestContext(nsCString rcid);
async PAltDataOutputStream(nsCString type, PHttpChannel channel);
+ /**
+ * Throttling of channels
+ */
+ async IncreaseThrottlePressure();
+ async DecreaseThrottlePressure();
+
child:
/*
* Bring up the http auth prompt for a nested remote mozbrowser.
* NestedFrameId is the id corresponding to the PBrowser. It is the same id
* that was passed to the PBrowserOrId param in to the PHttpChannel constructor
*/
async AsyncAuthPromptForNestedFrame(TabId nestedFrameId, nsCString uri,
nsString realm, uint64_t callbackId);
--- a/netwerk/protocol/http/HttpBaseChannel.cpp
+++ b/netwerk/protocol/http/HttpBaseChannel.cpp
@@ -54,16 +54,17 @@
#include "nsIConsoleService.h"
#include "mozilla/BinarySearch.h"
#include "mozilla/DebugOnly.h"
#include "nsIHttpHeaderVisitor.h"
#include "nsIMIMEInputStream.h"
#include "nsIXULRuntime.h"
#include "nsICacheInfoChannel.h"
#include "nsIDOMWindowUtils.h"
+#include "nsIThrottlingService.h"
#include <algorithm>
#include "HttpBaseChannel.h"
namespace mozilla {
namespace net {
static
@@ -2906,16 +2907,23 @@ HttpBaseChannel::SetNewListener(nsIStrea
// HttpBaseChannel helpers
//-----------------------------------------------------------------------------
void
HttpBaseChannel::ReleaseListeners()
{
MOZ_ASSERT(NS_IsMainThread(), "Should only be called on the main thread.");
+ if (mClassOfService & nsIClassOfService::Throttleable) {
+ nsIThrottlingService *throttler = gHttpHandler->GetThrottlingService();
+ if (throttler) {
+ throttler->RemoveChannel(this);
+ }
+ }
+
mListener = nullptr;
mListenerContext = nullptr;
mCallbacks = nullptr;
mProgressSink = nullptr;
mCompressListener = nullptr;
}
void
--- a/netwerk/protocol/http/nsHttpChannel.cpp
+++ b/netwerk/protocol/http/nsHttpChannel.cpp
@@ -98,16 +98,17 @@
#include "nsCORSListenerProxy.h"
#include "nsISocketProvider.h"
#include "mozilla/net/Predictor.h"
#include "CacheControlParser.h"
#include "nsMixedContentBlocker.h"
#include "HSTSPrimerListener.h"
#include "CacheStorageService.h"
#include "HttpChannelParent.h"
+#include "nsIThrottlingService.h"
#ifdef MOZ_TASK_TRACER
#include "GeckoTaskTracer.h"
#endif
namespace mozilla { namespace net {
namespace {
@@ -1322,16 +1323,26 @@ nsHttpChannel::CallOnStartRequest()
mOnStartRequestCalled = true;
if (NS_FAILED(rv))
return rv;
} else {
NS_WARNING("OnStartRequest skipped because of null listener");
mOnStartRequestCalled = true;
}
+ if (mClassOfService & nsIClassOfService::Throttleable) {
+ nsIThrottlingService *throttler = gHttpHandler->GetThrottlingService();
+ if (throttler) {
+ // This may immediately Suspend() this channel. We also may have
+ // done this already, during AsyncOpen. However, calling AddChannel
+ // twice doesn't hurt anything.
+ throttler->AddChannel(this);
+ }
+ }
+
// Install stream converter if required.
// If we use unknownDecoder, stream converters will be installed later (in
// nsUnknownDecoder) after OnStartRequest is called for the real listener.
if (!unknownDecoderStarted) {
nsCOMPtr<nsIStreamListener> listener;
nsISupports *ctxt = mListenerContext;
rv = DoApplyContentConversions(mListener, getter_AddRefs(listener), ctxt);
if (NS_FAILED(rv)) {
@@ -6076,16 +6087,24 @@ nsHttpChannel::BeginConnect()
// We may have been cancelled already, either by on-modify-request
// listeners or load group observers; in that case, we should not send the
// request to the server
if (mCanceled) {
return mStatus;
}
+ if (mClassOfService & nsIClassOfService::Throttleable) {
+ nsIThrottlingService *throttler = gHttpHandler->GetThrottlingService();
+ if (throttler) {
+ // This may immediately Suspend() this channel.
+ throttler->AddChannel(this);
+ }
+ }
+
if (!(mLoadFlags & LOAD_CLASSIFY_URI)) {
return ContinueBeginConnectWithResult();
}
// mLocalBlocklist is true only if tracking protection is enabled and the
// URI is a tracking domain, it makes no guarantees about phishing or
// malware, so if LOAD_CLASSIFY_URI is true we must call
// nsChannelClassifier to catch phishing and malware URIs.
--- a/netwerk/protocol/http/nsHttpHandler.cpp
+++ b/netwerk/protocol/http/nsHttpHandler.cpp
@@ -48,16 +48,17 @@
#include "nsPIDOMWindow.h"
#include "nsINetworkLinkService.h"
#include "nsHttpChannelAuthProvider.h"
#include "nsServiceManagerUtils.h"
#include "nsComponentManagerUtils.h"
#include "nsSocketTransportService2.h"
#include "nsIOService.h"
#include "nsIUUIDGenerator.h"
+#include "nsIThrottlingService.h"
#include "mozilla/net/NeckoChild.h"
#include "mozilla/net/NeckoParent.h"
#include "mozilla/ipc/URIUtils.h"
#include "mozilla/Telemetry.h"
#include "mozilla/Unused.h"
#include "mozilla/BasePrincipal.h"
@@ -584,16 +585,27 @@ nsresult
nsHttpHandler::GetIOService(nsIIOService** result)
{
NS_ENSURE_ARG_POINTER(result);
NS_ADDREF(*result = mIOService);
return NS_OK;
}
+nsIThrottlingService *
+nsHttpHandler::GetThrottlingService()
+{
+ if (!mThrottlingService) {
+ nsCOMPtr<nsIThrottlingService> service = do_GetService(NS_THROTTLINGSERVICE_CONTRACTID);
+ mThrottlingService = new nsMainThreadPtrHolder<nsIThrottlingService>(service);
+ }
+
+ return mThrottlingService;
+}
+
uint32_t
nsHttpHandler::Get32BitsOfPseudoRandom()
{
// only confirm rand seeding on socket thread
MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
// rand() provides different amounts of PRNG on different platforms.
// 15 or 31 bits are common amounts.
--- a/netwerk/protocol/http/nsHttpHandler.h
+++ b/netwerk/protocol/http/nsHttpHandler.h
@@ -22,16 +22,17 @@
class nsIHttpChannel;
class nsIPrefBranch;
class nsICancelable;
class nsICookieService;
class nsIIOService;
class nsIRequestContextService;
class nsISiteSecurityService;
class nsIStreamConverterService;
+class nsIThrottlingService;
class nsITimer;
class nsIUUIDGenerator;
namespace mozilla {
namespace net {
extern Atomic<PRThread*, Relaxed> gSocketThread;
@@ -260,16 +261,17 @@ public:
//
// The HTTP handler caches pointers to specific XPCOM services, and
// provides the following helper routines for accessing those services:
//
nsresult GetStreamConverterService(nsIStreamConverterService **);
nsresult GetIOService(nsIIOService** service);
nsICookieService * GetCookieService(); // not addrefed
nsISiteSecurityService * GetSSService();
+ nsIThrottlingService * GetThrottlingService();
// callable from socket thread only
uint32_t Get32BitsOfPseudoRandom();
// Called by the channel synchronously during asyncOpen
void OnOpeningRequest(nsIHttpChannel *chan)
{
NotifyObservers(chan, NS_HTTP_ON_OPENING_REQUEST_TOPIC);
@@ -401,16 +403,17 @@ private:
static void TimerCallback(nsITimer * aTimer, void * aClosure);
private:
// cached services
nsMainThreadPtrHandle<nsIIOService> mIOService;
nsMainThreadPtrHandle<nsIStreamConverterService> mStreamConvSvc;
nsMainThreadPtrHandle<nsICookieService> mCookieService;
nsMainThreadPtrHandle<nsISiteSecurityService> mSSService;
+ nsMainThreadPtrHandle<nsIThrottlingService> mThrottlingService;
// the authentication credentials cache
nsHttpAuthCache mAuthCache;
nsHttpAuthCache mPrivateAuthCache;
// the connection manager
RefPtr<nsHttpConnectionMgr> mConnMgr;