michael@0: /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ michael@0: /* This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: #include "Tickler.h" michael@0: michael@0: #ifdef MOZ_USE_WIFI_TICKLER michael@0: #include "nsComponentManagerUtils.h" michael@0: #include "nsIPrefBranch.h" michael@0: #include "nsIPrefService.h" michael@0: #include "nsServiceManagerUtils.h" michael@0: #include "nsThreadUtils.h" michael@0: #include "prnetdb.h" michael@0: michael@0: #include "AndroidBridge.h" michael@0: michael@0: namespace mozilla { michael@0: namespace net { michael@0: michael@0: NS_IMPL_ISUPPORTS(Tickler, nsISupportsWeakReference, Tickler) michael@0: michael@0: Tickler::Tickler() michael@0: : mLock("Tickler::mLock") michael@0: , mActive(false) michael@0: , mCanceled(false) michael@0: , mEnabled(false) michael@0: , mDelay(16) michael@0: , mDuration(TimeDuration::FromMilliseconds(400)) michael@0: , mFD(nullptr) michael@0: { michael@0: MOZ_ASSERT(NS_IsMainThread()); michael@0: } michael@0: michael@0: class TicklerThreadDestructor : public nsRunnable michael@0: { michael@0: public: michael@0: explicit TicklerThreadDestructor(nsIThread *aThread) michael@0: : mThread(aThread) { } michael@0: michael@0: NS_IMETHOD Run() MOZ_OVERRIDE michael@0: { michael@0: MOZ_ASSERT(NS_IsMainThread()); michael@0: if (mThread) michael@0: mThread->Shutdown(); michael@0: return NS_OK; michael@0: } michael@0: michael@0: private: michael@0: ~TicklerThreadDestructor() { } michael@0: nsCOMPtr mThread; michael@0: }; michael@0: michael@0: Tickler::~Tickler() michael@0: { michael@0: // non main thread uses of the tickler should hold weak michael@0: // references to it if they must hold a reference at all michael@0: MOZ_ASSERT(NS_IsMainThread()); michael@0: michael@0: // Shutting down a thread can spin the event loop - which is a surprising michael@0: // thing to do from a dtor. Running it on its own event is safer. michael@0: nsRefPtr event = new TicklerThreadDestructor(mThread); michael@0: if (NS_FAILED(NS_DispatchToCurrentThread(event))) { michael@0: mThread->Shutdown(); michael@0: } michael@0: mThread = nullptr; michael@0: michael@0: if (mTimer) michael@0: mTimer->Cancel(); michael@0: if (mFD) michael@0: PR_Close(mFD); michael@0: } michael@0: michael@0: nsresult michael@0: Tickler::Init() michael@0: { michael@0: MOZ_ASSERT(NS_IsMainThread()); michael@0: MOZ_ASSERT(!mTimer); michael@0: MOZ_ASSERT(!mActive); michael@0: MOZ_ASSERT(!mThread); michael@0: MOZ_ASSERT(!mFD); michael@0: michael@0: if (AndroidBridge::HasEnv()) { michael@0: mozilla::widget::android::GeckoAppShell::EnableNetworkNotifications(); michael@0: } michael@0: michael@0: mFD = PR_OpenUDPSocket(PR_AF_INET); michael@0: if (!mFD) michael@0: return NS_ERROR_FAILURE; michael@0: michael@0: // make sure new socket has a ttl of 1 michael@0: // failure is not fatal. michael@0: PRSocketOptionData opt; michael@0: opt.option = PR_SockOpt_IpTimeToLive; michael@0: opt.value.ip_ttl = 1; michael@0: PR_SetSocketOption(mFD, &opt); michael@0: michael@0: nsresult rv = NS_NewNamedThread("wifi tickler", michael@0: getter_AddRefs(mThread)); michael@0: if (NS_FAILED(rv)) michael@0: return rv; michael@0: michael@0: nsCOMPtr tmpTimer(do_CreateInstance(NS_TIMER_CONTRACTID, &rv)); michael@0: if (NS_FAILED(rv)) michael@0: return rv; michael@0: michael@0: rv = tmpTimer->SetTarget(mThread); michael@0: if (NS_FAILED(rv)) michael@0: return rv; michael@0: michael@0: mTimer.swap(tmpTimer); michael@0: michael@0: mAddr.inet.family = PR_AF_INET; michael@0: mAddr.inet.port = PR_htons (4886); michael@0: mAddr.inet.ip = 0; michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: void Tickler::Tickle() michael@0: { michael@0: MutexAutoLock lock(mLock); michael@0: MOZ_ASSERT(mThread); michael@0: mLastTickle = TimeStamp::Now(); michael@0: if (!mActive) michael@0: MaybeStartTickler(); michael@0: } michael@0: michael@0: void Tickler::PostCheckTickler() michael@0: { michael@0: mLock.AssertCurrentThreadOwns(); michael@0: mThread->Dispatch(NS_NewRunnableMethod(this, &Tickler::CheckTickler), michael@0: NS_DISPATCH_NORMAL); michael@0: return; michael@0: } michael@0: michael@0: void Tickler::MaybeStartTicklerUnlocked() michael@0: { michael@0: MutexAutoLock lock(mLock); michael@0: MaybeStartTickler(); michael@0: } michael@0: michael@0: void Tickler::MaybeStartTickler() michael@0: { michael@0: mLock.AssertCurrentThreadOwns(); michael@0: if (!NS_IsMainThread()) { michael@0: NS_DispatchToMainThread( michael@0: NS_NewRunnableMethod(this, &Tickler::MaybeStartTicklerUnlocked), michael@0: NS_DISPATCH_NORMAL); michael@0: return; michael@0: } michael@0: michael@0: if (!mPrefs) michael@0: mPrefs = do_GetService(NS_PREFSERVICE_CONTRACTID); michael@0: if (mPrefs) { michael@0: int32_t val; michael@0: bool boolVal; michael@0: michael@0: if (NS_SUCCEEDED(mPrefs->GetBoolPref("network.tickle-wifi.enabled", &boolVal))) michael@0: mEnabled = boolVal; michael@0: michael@0: if (NS_SUCCEEDED(mPrefs->GetIntPref("network.tickle-wifi.duration", &val))) { michael@0: if (val < 1) michael@0: val = 1; michael@0: if (val > 100000) michael@0: val = 100000; michael@0: mDuration = TimeDuration::FromMilliseconds(val); michael@0: } michael@0: michael@0: if (NS_SUCCEEDED(mPrefs->GetIntPref("network.tickle-wifi.delay", &val))) { michael@0: if (val < 1) michael@0: val = 1; michael@0: if (val > 1000) michael@0: val = 1000; michael@0: mDelay = static_cast(val); michael@0: } michael@0: } michael@0: michael@0: PostCheckTickler(); michael@0: } michael@0: michael@0: void Tickler::CheckTickler() michael@0: { michael@0: MutexAutoLock lock(mLock); michael@0: MOZ_ASSERT(mThread == NS_GetCurrentThread()); michael@0: michael@0: bool shouldRun = (!mCanceled) && michael@0: ((TimeStamp::Now() - mLastTickle) <= mDuration); michael@0: michael@0: if ((shouldRun && mActive) || (!shouldRun && !mActive)) michael@0: return; // no change in state michael@0: michael@0: if (mActive) michael@0: StopTickler(); michael@0: else michael@0: StartTickler(); michael@0: } michael@0: michael@0: void Tickler::Cancel() michael@0: { michael@0: MutexAutoLock lock(mLock); michael@0: MOZ_ASSERT(NS_IsMainThread()); michael@0: mCanceled = true; michael@0: if (mThread) michael@0: PostCheckTickler(); michael@0: } michael@0: michael@0: void Tickler::StopTickler() michael@0: { michael@0: mLock.AssertCurrentThreadOwns(); michael@0: MOZ_ASSERT(mThread == NS_GetCurrentThread()); michael@0: MOZ_ASSERT(mTimer); michael@0: MOZ_ASSERT(mActive); michael@0: michael@0: mTimer->Cancel(); michael@0: mActive = false; michael@0: } michael@0: michael@0: class TicklerTimer MOZ_FINAL : public nsITimerCallback michael@0: { michael@0: NS_DECL_THREADSAFE_ISUPPORTS michael@0: NS_DECL_NSITIMERCALLBACK michael@0: michael@0: TicklerTimer(Tickler *aTickler) michael@0: { michael@0: mTickler = do_GetWeakReference(aTickler); michael@0: } michael@0: michael@0: ~TicklerTimer() {}; michael@0: michael@0: private: michael@0: nsWeakPtr mTickler; michael@0: }; michael@0: michael@0: void Tickler::StartTickler() michael@0: { michael@0: mLock.AssertCurrentThreadOwns(); michael@0: MOZ_ASSERT(mThread == NS_GetCurrentThread()); michael@0: MOZ_ASSERT(!mActive); michael@0: MOZ_ASSERT(mTimer); michael@0: michael@0: if (NS_SUCCEEDED(mTimer->InitWithCallback(new TicklerTimer(this), michael@0: mEnabled ? mDelay : 1000, michael@0: nsITimer::TYPE_REPEATING_SLACK))) michael@0: mActive = true; michael@0: } michael@0: michael@0: // argument should be in network byte order michael@0: void Tickler::SetIPV4Address(uint32_t address) michael@0: { michael@0: mAddr.inet.ip = address; michael@0: } michael@0: michael@0: // argument should be in network byte order michael@0: void Tickler::SetIPV4Port(uint16_t port) michael@0: { michael@0: mAddr.inet.port = port; michael@0: } michael@0: michael@0: NS_IMPL_ISUPPORTS(TicklerTimer, nsITimerCallback) michael@0: michael@0: NS_IMETHODIMP TicklerTimer::Notify(nsITimer *timer) michael@0: { michael@0: nsRefPtr tickler = do_QueryReferent(mTickler); michael@0: if (!tickler) michael@0: return NS_ERROR_FAILURE; michael@0: MutexAutoLock lock(tickler->mLock); michael@0: michael@0: if (!tickler->mFD) { michael@0: tickler->StopTickler(); michael@0: return NS_ERROR_FAILURE; michael@0: } michael@0: michael@0: if (tickler->mCanceled || michael@0: ((TimeStamp::Now() - tickler->mLastTickle) > tickler->mDuration)) { michael@0: tickler->StopTickler(); michael@0: return NS_OK; michael@0: } michael@0: michael@0: if (!tickler->mEnabled) michael@0: return NS_OK; michael@0: michael@0: PR_SendTo(tickler->mFD, "", 0, 0, &tickler->mAddr, 0); michael@0: return NS_OK; michael@0: } michael@0: michael@0: } // namespace mozilla::net michael@0: } // namespace mozilla michael@0: michael@0: #else // not defined MOZ_USE_WIFI_TICKLER michael@0: michael@0: namespace mozilla { michael@0: namespace net { michael@0: NS_IMPL_ISUPPORTS0(Tickler) michael@0: } // namespace mozilla::net michael@0: } // namespace mozilla michael@0: michael@0: #endif // defined MOZ_USE_WIFI_TICKLER michael@0: