зеркало из https://github.com/mozilla/gecko-dev.git
Bug 1828013 - Add a scriptable API to list all current nsITimer instances, r=smaug.
Differential Revision: https://phabricator.services.mozilla.com/D175430
This commit is contained in:
Родитель
58bcaf9a55
Коммит
acf498ccc7
|
@ -0,0 +1,90 @@
|
|||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
const { AppConstants } = ChromeUtils.importESModule(
|
||||
"resource://gre/modules/AppConstants.sys.mjs"
|
||||
);
|
||||
|
||||
const timerManager = Cc["@mozilla.org/timer-manager;1"].getService(
|
||||
Ci.nsITimerManager
|
||||
);
|
||||
|
||||
function newTimer(name, delay, type) {
|
||||
let timer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
|
||||
timer.initWithCallback(
|
||||
{
|
||||
QueryInterface: ChromeUtils.generateQI(["nsITimerCallback", "nsINamed"]),
|
||||
name,
|
||||
notify: () => {},
|
||||
},
|
||||
delay,
|
||||
type
|
||||
);
|
||||
return timer;
|
||||
}
|
||||
|
||||
function getTimers() {
|
||||
return timerManager.getTimers().filter(t => {
|
||||
if (t.name == "BackgroundHangThread_timer") {
|
||||
// BHR is Nightly-only, so just ignore it.
|
||||
return false;
|
||||
}
|
||||
|
||||
if (AppConstants.platform == "win" && t.name == "nsAnonTempFileRemover") {
|
||||
// On Windows there's a 3min timer added at startup to then add an
|
||||
// idle observer that finally triggers removing leftover temp files.
|
||||
// Ignore that too.
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
function run_test() {
|
||||
{
|
||||
let timers = getTimers();
|
||||
for (let timer of timers) {
|
||||
// Print info about unexpected startup timers to help debugging.
|
||||
info(`${timer.name}: ${timer.delay}ms, ${timer.type}`);
|
||||
}
|
||||
Assert.equal(
|
||||
timers.length,
|
||||
0,
|
||||
"there should be no timer at xpcshell startup"
|
||||
);
|
||||
}
|
||||
|
||||
let timerData = [
|
||||
["t1", 500, Ci.nsITimer.TYPE_ONE_SHOT],
|
||||
["t2", 1500, Ci.nsITimer.TYPE_REPEATING_SLACK],
|
||||
["t3", 2500, Ci.nsITimer.TYPE_REPEATING_PRECISE],
|
||||
["t4", 3500, Ci.nsITimer.TYPE_REPEATING_PRECISE_CAN_SKIP],
|
||||
["t5", 5500, Ci.nsITimer.TYPE_REPEATING_SLACK_LOW_PRIORITY],
|
||||
["t6", 7500, Ci.nsITimer.TYPE_ONE_SHOT_LOW_PRIORITY],
|
||||
];
|
||||
|
||||
info("Add timers one at a time.");
|
||||
for (let [name, delay, type] of timerData) {
|
||||
let timer = newTimer(name, delay, type);
|
||||
let timers = getTimers();
|
||||
Assert.equal(timers.length, 1, "there should be only one timer");
|
||||
Assert.equal(name, timers[0].name, "the name is correct");
|
||||
Assert.equal(delay, timers[0].delay, "the delay is correct");
|
||||
Assert.equal(type, timers[0].type, "the type is correct");
|
||||
|
||||
timer.cancel();
|
||||
Assert.equal(getTimers().length, 0, "no timer left after cancelling");
|
||||
}
|
||||
|
||||
info("Add all timers at once.");
|
||||
let timers = [];
|
||||
for (let [name, delay, type] of timerData) {
|
||||
timers.push(newTimer(name, delay, type));
|
||||
}
|
||||
while (timers.length) {
|
||||
Assert.equal(getTimers().length, timers.length, "correct timer count");
|
||||
timers.pop().cancel();
|
||||
}
|
||||
Assert.equal(getTimers().length, 0, "no timer left after cancelling");
|
||||
}
|
|
@ -22,6 +22,7 @@ fail-if = os == "android"
|
|||
[test_debugger_malloc_size_of.js]
|
||||
[test_file_createUnique.js]
|
||||
[test_file_equality.js]
|
||||
[test_getTimers.js]
|
||||
[test_hidden_files.js]
|
||||
[test_home.js]
|
||||
# Bug 676998: test fails consistently on Android
|
||||
|
|
|
@ -1363,3 +1363,121 @@ void TimerThread::PrintStatistics() const {
|
|||
mTotalEarlyWakeupTime / mEarlyWakeups);
|
||||
}
|
||||
#endif
|
||||
|
||||
/* This nsReadOnlyTimer class is used for the values returned by the
|
||||
* TimerThread::GetTimers method.
|
||||
* It is not possible to return a strong reference to the nsTimerImpl
|
||||
* instance (that could extend the lifetime of the timer and cause it to fire
|
||||
* a callback pointing to already freed memory) or a weak reference
|
||||
* (nsSupportsWeakReference doesn't support freeing the referee on a thread
|
||||
* that isn't the thread that owns the weak reference), so instead the timer
|
||||
* name, delay and type are copied to a new object. */
|
||||
class nsReadOnlyTimer final : public nsITimer {
|
||||
public:
|
||||
explicit nsReadOnlyTimer(const nsACString& aName, uint32_t aDelay,
|
||||
uint32_t aType)
|
||||
: mName(aName), mDelay(aDelay), mType(aType) {}
|
||||
NS_DECL_ISUPPORTS
|
||||
|
||||
NS_IMETHOD Init(nsIObserver* aObserver, uint32_t aDelayInMs,
|
||||
uint32_t aType) override {
|
||||
return NS_ERROR_NOT_IMPLEMENTED;
|
||||
}
|
||||
NS_IMETHOD InitWithCallback(nsITimerCallback* aCallback, uint32_t aDelayInMs,
|
||||
uint32_t aType) override {
|
||||
return NS_ERROR_NOT_IMPLEMENTED;
|
||||
}
|
||||
NS_IMETHOD InitHighResolutionWithCallback(nsITimerCallback* aCallback,
|
||||
const mozilla::TimeDuration& aDelay,
|
||||
uint32_t aType) override {
|
||||
return NS_ERROR_NOT_IMPLEMENTED;
|
||||
}
|
||||
NS_IMETHOD Cancel(void) override { return NS_ERROR_NOT_IMPLEMENTED; }
|
||||
NS_IMETHOD InitWithNamedFuncCallback(nsTimerCallbackFunc aCallback,
|
||||
void* aClosure, uint32_t aDelay,
|
||||
uint32_t aType,
|
||||
const char* aName) override {
|
||||
return NS_ERROR_NOT_IMPLEMENTED;
|
||||
}
|
||||
NS_IMETHOD InitHighResolutionWithNamedFuncCallback(
|
||||
nsTimerCallbackFunc aCallback, void* aClosure,
|
||||
const mozilla::TimeDuration& aDelay, uint32_t aType,
|
||||
const char* aName) override {
|
||||
return NS_ERROR_NOT_IMPLEMENTED;
|
||||
}
|
||||
|
||||
NS_IMETHOD GetName(nsACString& aName) override {
|
||||
aName = mName;
|
||||
return NS_OK;
|
||||
}
|
||||
NS_IMETHOD GetDelay(uint32_t* aDelay) override {
|
||||
*aDelay = mDelay;
|
||||
return NS_OK;
|
||||
}
|
||||
NS_IMETHOD SetDelay(uint32_t aDelay) override {
|
||||
return NS_ERROR_NOT_IMPLEMENTED;
|
||||
}
|
||||
NS_IMETHOD GetType(uint32_t* aType) override {
|
||||
*aType = mType;
|
||||
return NS_OK;
|
||||
}
|
||||
NS_IMETHOD SetType(uint32_t aType) override {
|
||||
return NS_ERROR_NOT_IMPLEMENTED;
|
||||
}
|
||||
NS_IMETHOD GetClosure(void** aClosure) override {
|
||||
return NS_ERROR_NOT_IMPLEMENTED;
|
||||
}
|
||||
NS_IMETHOD GetCallback(nsITimerCallback** aCallback) override {
|
||||
return NS_ERROR_NOT_IMPLEMENTED;
|
||||
}
|
||||
NS_IMETHOD GetTarget(nsIEventTarget** aTarget) override {
|
||||
return NS_ERROR_NOT_IMPLEMENTED;
|
||||
}
|
||||
NS_IMETHOD SetTarget(nsIEventTarget* aTarget) override {
|
||||
return NS_ERROR_NOT_IMPLEMENTED;
|
||||
}
|
||||
NS_IMETHOD GetAllowedEarlyFiringMicroseconds(
|
||||
uint32_t* aAllowedEarlyFiringMicroseconds) override {
|
||||
return NS_ERROR_NOT_IMPLEMENTED;
|
||||
}
|
||||
size_t SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) override {
|
||||
return sizeof(*this);
|
||||
}
|
||||
|
||||
private:
|
||||
nsCString mName;
|
||||
uint32_t mDelay;
|
||||
uint32_t mType;
|
||||
~nsReadOnlyTimer() = default;
|
||||
};
|
||||
|
||||
NS_IMPL_ISUPPORTS(nsReadOnlyTimer, nsITimer)
|
||||
|
||||
nsresult TimerThread::GetTimers(nsTArray<RefPtr<nsITimer>>& aRetVal) {
|
||||
nsTArray<RefPtr<nsTimerImpl>> timers;
|
||||
{
|
||||
MonitorAutoLock lock(mMonitor);
|
||||
for (const auto& entry : mTimers) {
|
||||
nsTimerImpl* timer = entry.Value();
|
||||
if (!timer) {
|
||||
continue;
|
||||
}
|
||||
timers.AppendElement(timer);
|
||||
}
|
||||
}
|
||||
|
||||
for (nsTimerImpl* timer : timers) {
|
||||
nsAutoCString name;
|
||||
timer->GetName(name);
|
||||
|
||||
uint32_t delay;
|
||||
timer->GetDelay(&delay);
|
||||
|
||||
uint32_t type;
|
||||
timer->GetType(&type);
|
||||
|
||||
aRetVal.AppendElement(new nsReadOnlyTimer(name, delay, type));
|
||||
}
|
||||
|
||||
return NS_OK;
|
||||
}
|
||||
|
|
|
@ -64,6 +64,7 @@ class TimerThread final : public mozilla::Runnable, public nsIObserver {
|
|||
bool IsOnTimerThread() const { return mThread->IsOnCurrentThread(); }
|
||||
|
||||
uint32_t AllowedEarlyFiringMicroseconds();
|
||||
nsresult GetTimers(nsTArray<RefPtr<nsITimer>>& aRetVal);
|
||||
|
||||
private:
|
||||
~TimerThread();
|
||||
|
|
|
@ -20,4 +20,10 @@ Classes = [
|
|||
'headers': ['/xpcom/threads/nsTimerImpl.h'],
|
||||
'processes': ProcessSelector.ALLOW_IN_GPU_RDD_SOCKET_AND_UTILITY_PROCESS,
|
||||
},
|
||||
{
|
||||
'cid': '{d39a8904-2e09-4a3a-a273-c3bec7db2bfe}',
|
||||
'contract_ids': ['@mozilla.org/timer-manager;1'],
|
||||
'headers': ['/xpcom/threads/nsTimerImpl.h'],
|
||||
'type': 'nsTimerManager',
|
||||
},
|
||||
]
|
||||
|
|
|
@ -237,6 +237,8 @@ interface nsITimer : nsISupports
|
|||
*/
|
||||
attribute nsIEventTarget target;
|
||||
|
||||
readonly attribute ACString name;
|
||||
|
||||
/**
|
||||
* The number of microseconds this nsITimer implementation can possibly
|
||||
* fire early.
|
||||
|
@ -361,3 +363,14 @@ NS_NewTimerWithFuncCallback(nsTimerCallbackFunc aCallback,
|
|||
}
|
||||
#endif
|
||||
%}
|
||||
|
||||
[scriptable, builtinclass, uuid(5482506d-1d21-4d08-b01c-95c87e1295ad)]
|
||||
interface nsITimerManager : nsISupports
|
||||
{
|
||||
/**
|
||||
* Returns a read-only list of nsITimer objects, implementing only the name,
|
||||
* delay and type attribute getters.
|
||||
* This is meant to be used for tests, to verify that no timer is leftover
|
||||
* at the end of a test. */
|
||||
Array<nsITimer> getTimers();
|
||||
};
|
||||
|
|
|
@ -56,6 +56,7 @@ class TimerThreadWrapper {
|
|||
TimeStamp FindNextFireTimeForCurrentThread(TimeStamp aDefault,
|
||||
uint32_t aSearchBound);
|
||||
uint32_t AllowedEarlyFiringMicroseconds();
|
||||
nsresult GetTimers(nsTArray<RefPtr<nsITimer>>& aRetVal);
|
||||
|
||||
private:
|
||||
static mozilla::StaticMutex sMutex;
|
||||
|
@ -124,6 +125,18 @@ uint32_t TimerThreadWrapper::AllowedEarlyFiringMicroseconds() {
|
|||
return mThread ? mThread->AllowedEarlyFiringMicroseconds() : 0;
|
||||
}
|
||||
|
||||
nsresult TimerThreadWrapper::GetTimers(nsTArray<RefPtr<nsITimer>>& aRetVal) {
|
||||
RefPtr<TimerThread> thread;
|
||||
{
|
||||
mozilla::StaticMutexAutoLock lock(sMutex);
|
||||
if (!mThread) {
|
||||
return NS_ERROR_NOT_AVAILABLE;
|
||||
}
|
||||
thread = mThread;
|
||||
}
|
||||
return thread->GetTimers(aRetVal);
|
||||
}
|
||||
|
||||
static TimerThreadWrapper gThreadWrapper;
|
||||
|
||||
// This module prints info about the precision of timers.
|
||||
|
@ -337,6 +350,12 @@ static void myNS_MeanAndStdDev(double n, double sumOfValues,
|
|||
NS_IMPL_QUERY_INTERFACE(nsTimer, nsITimer)
|
||||
NS_IMPL_ADDREF(nsTimer)
|
||||
|
||||
NS_IMPL_ISUPPORTS(nsTimerManager, nsITimerManager)
|
||||
|
||||
NS_IMETHODIMP nsTimerManager::GetTimers(nsTArray<RefPtr<nsITimer>>& aRetVal) {
|
||||
return gThreadWrapper.GetTimers(aRetVal);
|
||||
}
|
||||
|
||||
NS_IMETHODIMP_(MozExternalRefCountType)
|
||||
nsTimer::Release(void) {
|
||||
nsrefcnt count = --mRefCnt;
|
||||
|
@ -772,9 +791,10 @@ void nsTimerImpl::GetName(nsACString& aName,
|
|||
[&](const ClosureCallback& c) { aName.Assign(c.mName); });
|
||||
}
|
||||
|
||||
void nsTimerImpl::GetName(nsACString& aName) {
|
||||
nsresult nsTimerImpl::GetName(nsACString& aName) {
|
||||
MutexAutoLock lock(mMutex);
|
||||
GetName(aName, lock);
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
nsTimer::~nsTimer() = default;
|
||||
|
|
|
@ -134,8 +134,6 @@ class nsTimerImpl {
|
|||
void GetName(nsACString& aName, const mozilla::MutexAutoLock& aProofOfLock)
|
||||
MOZ_REQUIRES(mMutex);
|
||||
|
||||
void GetName(nsACString& aName);
|
||||
|
||||
bool IsInTimerThread() const { return mIsInTimerThread; }
|
||||
void SetIsInTimerThread(bool aIsInTimerThread) {
|
||||
mIsInTimerThread = aIsInTimerThread;
|
||||
|
@ -222,4 +220,12 @@ class nsTimer final : public nsITimer {
|
|||
RefPtr<nsTimerImpl> mImpl;
|
||||
};
|
||||
|
||||
class nsTimerManager final : public nsITimerManager {
|
||||
public:
|
||||
NS_DECL_ISUPPORTS
|
||||
NS_DECL_NSITIMERMANAGER
|
||||
private:
|
||||
~nsTimerManager() = default;
|
||||
};
|
||||
|
||||
#endif /* nsTimerImpl_h___ */
|
||||
|
|
Загрузка…
Ссылка в новой задаче