diff --git a/accessible/generic/ARIAGridAccessible.cpp b/accessible/generic/ARIAGridAccessible.cpp index 8473436362..56b97da9e3 100644 --- a/accessible/generic/ARIAGridAccessible.cpp +++ b/accessible/generic/ARIAGridAccessible.cpp @@ -646,8 +646,9 @@ GroupPos ARIAGridCellAccessible::GroupPosition() { int32_t count = 0, index = 0; - if (nsCoreUtils::GetUIntAttr(Table()->AsAccessible()->GetContent(), - nsGkAtoms::aria_colcount, &count) && + TableAccessible* table = Table(); + if (table && nsCoreUtils::GetUIntAttr(table->AsAccessible()->GetContent(), + nsGkAtoms::aria_colcount, &count) && nsCoreUtils::GetUIntAttr(mContent, nsGkAtoms::aria_colindex, &index)) { return GroupPos(0, index, count); } diff --git a/accessible/html/HTMLTableAccessible.cpp b/accessible/html/HTMLTableAccessible.cpp index d14b48c9a4..ca391899a9 100644 --- a/accessible/html/HTMLTableAccessible.cpp +++ b/accessible/html/HTMLTableAccessible.cpp @@ -147,8 +147,9 @@ GroupPos HTMLTableCellAccessible::GroupPosition() { int32_t count = 0, index = 0; - if (nsCoreUtils::GetUIntAttr(Table()->AsAccessible()->GetContent(), - nsGkAtoms::aria_colcount, &count) && + TableAccessible* table = Table(); + if (table && nsCoreUtils::GetUIntAttr(table->AsAccessible()->GetContent(), + nsGkAtoms::aria_colcount, &count) && nsCoreUtils::GetUIntAttr(mContent, nsGkAtoms::aria_colindex, &index)) { return GroupPos(0, index, count); } @@ -312,46 +313,43 @@ HTMLTableHeaderCellAccessible::NativeRole() { // Check value of @scope attribute. static nsIContent::AttrValuesArray scopeValues[] = - {&nsGkAtoms::col, &nsGkAtoms::row, nullptr}; + { &nsGkAtoms::col, &nsGkAtoms::colgroup, + &nsGkAtoms::row, &nsGkAtoms::rowgroup, nullptr }; int32_t valueIdx = mContent->FindAttrValueIn(kNameSpaceID_None, nsGkAtoms::scope, scopeValues, eCaseMatters); switch (valueIdx) { case 0: - return roles::COLUMNHEADER; case 1: + return roles::COLUMNHEADER; + case 2: + case 3: return roles::ROWHEADER; } - // Assume it's columnheader if there are headers in siblings, otherwise - // rowheader. - // This should iterate the flattened tree - nsIContent* parentContent = mContent->GetParent(); - if (!parentContent) { - NS_ERROR("Deattached content on alive accessible?"); + TableAccessible* table = Table(); + if (!table) return roles::NOTHING; - } - for (nsIContent* siblingContent = mContent->GetPreviousSibling(); siblingContent; - siblingContent = siblingContent->GetPreviousSibling()) { - if (siblingContent->IsElement()) { - return nsCoreUtils::IsHTMLTableHeader(siblingContent) ? - roles::COLUMNHEADER : roles::ROWHEADER; - } - } + // If the cell next to this one is not a header cell then assume this cell is + // a row header for it. + uint32_t rowIdx = RowIdx(), colIdx = ColIdx(); + Accessible* cell = table->CellAt(rowIdx, colIdx + ColExtent()); + if (cell && !nsCoreUtils::IsHTMLTableHeader(cell->GetContent())) + return roles::ROWHEADER; - for (nsIContent* siblingContent = mContent->GetNextSibling(); siblingContent; - siblingContent = siblingContent->GetNextSibling()) { - if (siblingContent->IsElement()) { - return nsCoreUtils::IsHTMLTableHeader(siblingContent) ? - roles::COLUMNHEADER : roles::ROWHEADER; - } - } + // If the cell below this one is not a header cell then assume this cell is + // a column header for it. + uint32_t rowExtent = RowExtent(); + cell = table->CellAt(rowIdx + rowExtent, colIdx); + if (cell && !nsCoreUtils::IsHTMLTableHeader(cell->GetContent())) + return roles::COLUMNHEADER; - // No elements in siblings what means the table has one column only. Therefore - // it should be column header. - return roles::COLUMNHEADER; + // Otherwise if this cell is surrounded by header cells only then make a guess + // based on its cell spanning. In other words if it is row spanned then assume + // it's a row header, otherwise it's a column header. + return rowExtent > 1 ? roles::ROWHEADER : roles::COLUMNHEADER; } @@ -376,8 +374,9 @@ GroupPos HTMLTableRowAccessible::GroupPosition() { int32_t count = 0, index = 0; - if (nsCoreUtils::GetUIntAttr(nsAccUtils::TableFor(this)->GetContent(), - nsGkAtoms::aria_rowcount, &count) && + Accessible* table = nsAccUtils::TableFor(this); + if (table && nsCoreUtils::GetUIntAttr(table->GetContent(), + nsGkAtoms::aria_rowcount, &count) && nsCoreUtils::GetUIntAttr(mContent, nsGkAtoms::aria_rowindex, &index)) { return GroupPos(0, index, count); } diff --git a/accessible/tests/mochitest/table/test_headers_table.html b/accessible/tests/mochitest/table/test_headers_table.html index 99c36f837b..335849484a 100644 --- a/accessible/tests/mochitest/table/test_headers_table.html +++ b/accessible/tests/mochitest/table/test_headers_table.html @@ -156,6 +156,278 @@ testHeaderCells(headerInfoMap); + ////////////////////////////////////////////////////////////////////////// + // @scope="rowgroup" and @scope="row" + + headerInfoMap = [ + { + cell: "t7_r1c1", + rowHeaderCells: [ "t7_Females", "t7_Mary" ], + columnHeaderCells: [ "t7_1km" ] + }, + { + cell: "t7_r1c2", + rowHeaderCells: [ "t7_Females", "t7_Mary" ], + columnHeaderCells: [ "t7_5km" ] + }, + { + cell: "t7_r1c3", + rowHeaderCells: [ "t7_Females", "t7_Mary" ], + columnHeaderCells: [ "t7_10km" ] + }, + { + cell: "t7_r2c1", + rowHeaderCells: [ "t7_Females", "t7_Betsy" ], + columnHeaderCells: [ "t7_1km" ] + }, + { + cell: "t7_r2c2", + rowHeaderCells: [ "t7_Females", "t7_Betsy" ], + columnHeaderCells: [ "t7_5km" ] + }, + { + cell: "t7_r2c3", + rowHeaderCells: [ "t7_Females", "t7_Betsy" ], + columnHeaderCells: [ "t7_10km" ] + }, + { + cell: "t7_r3c1", + rowHeaderCells: [ "t7_Males", "t7_Matt" ], + columnHeaderCells: [ "t7_1km" ] + }, + { + cell: "t7_r3c2", + rowHeaderCells: [ "t7_Males", "t7_Matt" ], + columnHeaderCells: [ "t7_5km" ] + }, + { + cell: "t7_r3c3", + rowHeaderCells: [ "t7_Males", "t7_Matt" ], + columnHeaderCells: [ "t7_10km" ] + }, + { + cell: "t7_r4c1", + rowHeaderCells: [ "t7_Males", "t7_Todd" ], + columnHeaderCells: [ "t7_1km" ] + }, + { + cell: "t7_r4c2", + rowHeaderCells: [ "t7_Males", "t7_Todd" ], + columnHeaderCells: [ "t7_5km" ] + }, + { + cell: "t7_r4c3", + rowHeaderCells: [ "t7_Males", "t7_Todd" ], + columnHeaderCells: [ "t7_10km" ] + } + ]; + + testHeaderCells(headerInfoMap); + + ////////////////////////////////////////////////////////////////////////// + // @scope="colgroup" and @scope="col" + + headerInfoMap = [ + { + cell: "t8_r1c1", + rowHeaderCells: [ "t8_1km" ], + columnHeaderCells: [ "t7_Females", "t7_Mary" ] + }, + { + cell: "t8_r1c2", + rowHeaderCells: [ "t8_5km" ], + columnHeaderCells: [ "t8_Females", "t8_Mary" ] + }, + { + cell: "t8_r1c3", + rowHeaderCells: [ "t8_10km" ], + columnHeaderCells: [ "t8_Females", "t8_Mary" ] + }, + { + cell: "t8_r1c4", + rowHeaderCells: [ "t8_1km" ], + columnHeaderCells: [ "t8_Females", "t8_Betsy" ] + }, + { + cell: "t8_r2c1", + rowHeaderCells: [ "t8_5km" ], + columnHeaderCells: [ "t8_Females", "t8_Betsy" ] + }, + { + cell: "t8_r2c2", + rowHeaderCells: [ "t8_10km" ], + columnHeaderCells: [ "t8_Females", "t8_Betsy" ] + }, + { + cell: "t8_r2c3", + rowHeaderCells: [ "t8_1km" ], + columnHeaderCells: [ "t8_Males", "t8_Matt" ] + }, + { + cell: "t8_r2c4", + rowHeaderCells: [ "t8_5km" ], + columnHeaderCells: [ "t8_Males", "t8_Matt" ] + }, + { + cell: "t8_r3c1", + rowHeaderCells: [ "t8_10km" ], + columnHeaderCells: [ "t8_Males", "t8_Matt" ] + }, + { + cell: "t8_r3c2", + rowHeaderCells: [ "t8_1km" ], + columnHeaderCells: [ "t8_Males", "t8_Todd" ] + }, + { + cell: "t8_r3c3", + rowHeaderCells: [ "t8_5km" ], + columnHeaderCells: [ "t8_Males", "t8_Todd" ] + }, + { + cell: "t8_r3c4", + rowHeaderCells: [ "t8_10km" ], + columnHeaderCells: [ "t8_Males", "t8_Todd" ] + } + ]; + + testHeaderCells(headerInfoMap); + + ////////////////////////////////////////////////////////////////////////// + // spanned table header cells (v1), @headers define header order + + headerInfoMap = [ + { + cell: "t9_r1c1", + rowHeaderCells: [ "t9_females", "t9_mary" ], + columnHeaderCells: [ "t9_1km" ] + }, + { + cell: "t9_r1c2", + rowHeaderCells: [ "t9_females", "t9_mary" ], + columnHeaderCells: [ "t9_5km" ] + }, + { + cell: "t9_r1c3", + rowHeaderCells: [ "t9_females", "t9_mary" ], + columnHeaderCells: [ "t9_10km" ] + }, + { + cell: "t9_r2c1", + rowHeaderCells: [ "t9_females", "t9_betsy" ], + columnHeaderCells: [ "t9_1km" ] + }, + { + cell: "t9_r2c2", + rowHeaderCells: [ "t9_females", "t9_betsy" ], + columnHeaderCells: [ "t9_5km" ] + }, + { + cell: "t9_r2c3", + rowHeaderCells: [ "t9_females", "t9_betsy" ], + columnHeaderCells: [ "t9_10km" ] + }, + { + cell: "t9_r3c1", + rowHeaderCells: [ "t9_males", "t9_matt" ], + columnHeaderCells: [ "t9_1km" ] + }, + { + cell: "t9_r3c2", + rowHeaderCells: [ "t9_males", "t9_matt" ], + columnHeaderCells: [ "t9_5km" ] + }, + { + cell: "t9_r3c3", + rowHeaderCells: [ "t9_males", "t9_matt" ], + columnHeaderCells: [ "t9_10km" ] + }, + { + cell: "t9_r4c1", + rowHeaderCells: [ "t9_males", "t9_todd" ], + columnHeaderCells: [ "t9_1km" ] + }, + { + cell: "t9_r4c2", + rowHeaderCells: [ "t9_males", "t9_todd" ], + columnHeaderCells: [ "t9_5km" ] + }, + { + cell: "t9_r4c3", + rowHeaderCells: [ "t9_males", "t9_todd" ], + columnHeaderCells: [ "t9_10km" ] + } + ]; + + testHeaderCells(headerInfoMap); + + ////////////////////////////////////////////////////////////////////////// + // spanned table header cells (v2), @headers define header order + + headerInfoMap = [ + { + cell: "t10_r1c1", + rowHeaderCells: [ "t10_1km" ], + columnHeaderCells: [ "t10_females", "t10_mary" ] + }, + { + cell: "t10_r1c2", + rowHeaderCells: [ "t10_1km" ], + columnHeaderCells: [ "t10_females", "t10_betsy" ] + }, + { + cell: "t10_r1c3", + rowHeaderCells: [ "t10_1km" ], + columnHeaderCells: [ "t10_males", "t10_matt" ] + }, + { + cell: "t10_r1c4", + rowHeaderCells: [ "t10_1km" ], + columnHeaderCells: [ "t10_males", "t10_todd" ] + }, + { + cell: "t10_r2c1", + rowHeaderCells: [ "t10_5km" ], + columnHeaderCells: [ "t10_females", "t10_mary" ] + }, + { + cell: "t10_r2c2", + rowHeaderCells: [ "t10_5km" ], + columnHeaderCells: [ "t10_females", "t10_betsy" ] + }, + { + cell: "t10_r2c3", + rowHeaderCells: [ "t10_5km" ], + columnHeaderCells: [ "t10_males", "t10_matt" ] + }, + { + cell: "t10_r2c4", + rowHeaderCells: [ "t10_5km" ], + columnHeaderCells: [ "t10_males", "t10_todd" ] + }, + { + cell: "t10_r3c1", + rowHeaderCells: [ "t10_10km" ], + columnHeaderCells: [ "t10_females", "t10_mary" ] + }, + { + cell: "t10_r3c2", + rowHeaderCells: [ "t10_10km" ], + columnHeaderCells: [ "t10_females", "t10_betsy" ] + }, + { + cell: "t10_r3c3", + rowHeaderCells: [ "t10_10km" ], + columnHeaderCells: [ "t10_males", "t10_matt" ] + }, + { + cell: "t10_r3c4", + rowHeaderCells: [ "t10_10km" ], + columnHeaderCells: [ "t10_males", "t10_todd" ] + } + ]; + + testHeaderCells(headerInfoMap); + SimpleTest.finish(); } @@ -175,6 +447,11 @@ href="https://bugzilla.mozilla.org/show_bug.cgi?id=704465"> Bug 704465 + + Bug 1141978 +

@@ -275,5 +552,162 @@ cell + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Version 1 with rowgroup
 1 km5 km10 km
FemalesMary8:3228:041:01:16
Betsy7:4326:4755:38
MalesMatt7:5527:2957:04
Todd7:0124:2150:35
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Version 2 with colgroup
 FemalesMales
MaryBetsyMattTodd
1 km8:327:437:557:01
5 km28:0426:4727:2724:21
10 km1:01:1655:3857:0450:35
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ Example 1 (row group headers): +
empty1 km5 km10 km
FemalesMary8:3228:041:01:16
Betsy7:4326:4755:38
MalesMatt7:5527:2957:04
Todd7:0124:2150:35
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ Example 2 (column group headers): +
emptyFemalesMales
MaryBetsyMattTodd
1 km8:327:437:557:01
5 km28:0426:4727:2924:21
10 km1:01:1655:3857:0450:35
diff --git a/dom/base/nsGlobalWindow.cpp b/dom/base/nsGlobalWindow.cpp index 69b42cff35..3bd9571713 100644 --- a/dom/base/nsGlobalWindow.cpp +++ b/dom/base/nsGlobalWindow.cpp @@ -54,8 +54,8 @@ #include "ScriptSettings.h" #include "mozilla/Preferences.h" #include "mozilla/Likely.h" +#include "mozilla/Snprintf.h" #include "mozilla/unused.h" -#include "../../js/public/HashTable.h" // for MultiCompartmentMatcher // Other Classes #include "mozilla/dom/BarProps.h" @@ -547,8 +547,9 @@ NS_IMPL_CYCLE_COLLECTION_UNROOT_NATIVE(nsTimeout, Release) nsresult nsTimeout::InitTimer(uint32_t aDelay) { - return mTimer->InitWithFuncCallback(nsGlobalWindow::TimerCallback, this, - aDelay, nsITimer::TYPE_ONE_SHOT); + return mTimer->InitWithNameableFuncCallback( + nsGlobalWindow::TimerCallback, this, aDelay, + nsITimer::TYPE_ONE_SHOT, nsGlobalWindow::TimerNameCallback); } // Return true if this timeout has a refcount of 1. This is used to check @@ -8897,22 +8898,6 @@ struct BrowserCompartmentMatcher : public js::CompartmentFilter { } }; -class MultiCompartmentMatcher : public js::CompartmentFilter { - friend class WindowRequestCleanupEvent; - - js::HashSet,js::SystemAllocPolicy> compartments; - - MultiCompartmentMatcher() - { - compartments.init(); - } - -public: - virtual bool match(JSCompartment* c) const override - { - return compartments.has(c); - } -}; class WindowDestroyedEvent : public nsRunnable { @@ -12893,6 +12878,19 @@ nsGlobalWindow::TimerCallback(nsITimer *aTimer, void *aClosure) timeout->mWindow->RunTimeout(timeout); } +// static +void +nsGlobalWindow::TimerNameCallback(nsITimer* aTimer, void* aClosure, char* aBuf, + size_t aLen) +{ + nsRefPtr timeout = (nsTimeout*)aClosure; + + const char* filename; + uint32_t lineNum, column; + timeout->mScriptHandler->GetLocation(&filename, &lineNum, &column); + snprintf(aBuf, aLen, "[content] %s:%u:%u", filename, lineNum, column); +} + //***************************************************************************** // nsGlobalWindow: Helper Functions //***************************************************************************** diff --git a/dom/base/nsGlobalWindow.h b/dom/base/nsGlobalWindow.h index ef68bfec41..2156d6dd8b 100644 --- a/dom/base/nsGlobalWindow.h +++ b/dom/base/nsGlobalWindow.h @@ -1421,6 +1421,8 @@ public: // fire after it, but no earlier than mTimeoutInsertionPoint, if any. void InsertTimeoutIntoList(nsTimeout *aTimeout); static void TimerCallback(nsITimer *aTimer, void *aClosure); + static void TimerNameCallback(nsITimer* aTimer, void* aClosure, char* aBuf, + size_t aLen); // Helper Functions already_AddRefed GetTreeOwner(); diff --git a/dom/base/nsJSEnvironment.cpp b/dom/base/nsJSEnvironment.cpp index 0e52c65faa..ea42f74197 100644 --- a/dom/base/nsJSEnvironment.cpp +++ b/dom/base/nsJSEnvironment.cpp @@ -1636,10 +1636,10 @@ nsJSContext::BeginCycleCollectionCallback() // an incremental collection, and we want to be sure to finish it. CallCreateInstance("@mozilla.org/timer;1", &sICCTimer); if (sICCTimer) { - sICCTimer->InitWithFuncCallback(ICCTimerFired, - nullptr, - kICCIntersliceDelay, - nsITimer::TYPE_REPEATING_SLACK); + sICCTimer->InitWithNamedFuncCallback(ICCTimerFired, nullptr, + kICCIntersliceDelay, + nsITimer::TYPE_REPEATING_SLACK, + "ICCTimerFired"); } } @@ -2036,14 +2036,15 @@ nsJSContext::PokeGC(JS::gcreason::Reason aReason, int aDelay) static bool first = true; - sGCTimer->InitWithFuncCallback(GCTimerFired, reinterpret_cast(aReason), - aDelay - ? aDelay - : (first - ? NS_FIRST_GC_DELAY - : NS_GC_DELAY), - nsITimer::TYPE_ONE_SHOT); - + sGCTimer->InitWithNamedFuncCallback(GCTimerFired, + reinterpret_cast(aReason), + aDelay + ? aDelay + : (first + ? NS_FIRST_GC_DELAY + : NS_GC_DELAY), + nsITimer::TYPE_ONE_SHOT, + "GCTimerFired"); first = false; } @@ -2062,9 +2063,11 @@ nsJSContext::PokeShrinkGCBuffers() return; } - sShrinkGCBuffersTimer->InitWithFuncCallback(ShrinkGCBuffersTimerFired, nullptr, - NS_SHRINK_GC_BUFFERS_DELAY, - nsITimer::TYPE_ONE_SHOT); + sShrinkGCBuffersTimer->InitWithNamedFuncCallback(ShrinkGCBuffersTimerFired, + nullptr, + NS_SHRINK_GC_BUFFERS_DELAY, + nsITimer::TYPE_ONE_SHOT, + "ShrinkGCBuffersTimerFired"); } // static @@ -2082,9 +2085,10 @@ nsJSContext::PokeShrinkingGC() return; } - sShrinkingGCTimer->InitWithFuncCallback(ShrinkingGCTimerFired, nullptr, - sCompactOnUserInactiveDelay, - nsITimer::TYPE_ONE_SHOT); + sShrinkingGCTimer->InitWithNamedFuncCallback(ShrinkingGCTimerFired, nullptr, + sCompactOnUserInactiveDelay, + nsITimer::TYPE_ONE_SHOT, + "ShrinkingGCTimerFired"); } // static @@ -2104,9 +2108,10 @@ nsJSContext::MaybePokeCC() // We can kill some objects before running forgetSkippable. nsCycleCollector_dispatchDeferredDeletion(); - sCCTimer->InitWithFuncCallback(CCTimerFired, nullptr, - NS_CC_SKIPPABLE_DELAY, - nsITimer::TYPE_REPEATING_SLACK); + sCCTimer->InitWithNamedFuncCallback(CCTimerFired, nullptr, + NS_CC_SKIPPABLE_DELAY, + nsITimer::TYPE_REPEATING_SLACK, + "CCTimerFired"); } } @@ -2263,10 +2268,11 @@ DOMGCSliceCallback(JSRuntime *aRt, JS::GCProgress aProgress, const JS::GCDescrip if (aDesc.isCompartment_) { if (!sFullGCTimer && !sShuttingDown) { CallCreateInstance("@mozilla.org/timer;1", &sFullGCTimer); - sFullGCTimer->InitWithFuncCallback(FullGCTimerFired, - nullptr, - NS_FULL_GC_DELAY, - nsITimer::TYPE_ONE_SHOT); + sFullGCTimer->InitWithNamedFuncCallback(FullGCTimerFired, + nullptr, + NS_FULL_GC_DELAY, + nsITimer::TYPE_ONE_SHOT, + "FullGCTimerFired"); } } else { nsJSContext::KillFullGCTimer(); @@ -2295,10 +2301,11 @@ DOMGCSliceCallback(JSRuntime *aRt, JS::GCProgress aProgress, const JS::GCDescrip nsJSContext::KillInterSliceGCTimer(); if (!sShuttingDown) { CallCreateInstance("@mozilla.org/timer;1", &sInterSliceGCTimer); - sInterSliceGCTimer->InitWithFuncCallback(InterSliceGCTimerFired, - nullptr, - NS_INTERSLICE_GC_DELAY, - nsITimer::TYPE_ONE_SHOT); + sInterSliceGCTimer->InitWithNamedFuncCallback(InterSliceGCTimerFired, + nullptr, + NS_INTERSLICE_GC_DELAY, + nsITimer::TYPE_ONE_SHOT, + "InterSliceGCTimerFired"); } if (ShouldTriggerCC(nsCycleCollector_suspectedCount())) { diff --git a/dom/html/HTMLLinkElement.cpp b/dom/html/HTMLLinkElement.cpp index bead7f5c43..90e7a7a1ca 100644 --- a/dom/html/HTMLLinkElement.cpp +++ b/dom/html/HTMLLinkElement.cpp @@ -333,7 +333,7 @@ HTMLLinkElement::AfterSetAttr(int32_t aNameSpaceID, nsIAtom* aName, // already been set or unset. ResetLinkState needs the updated attribute // value because notifying the document that content states have changed will // call IntrinsicState, which will try to get updated information about the - // visited state from Link. + // visitedness from Link. if (aName == nsGkAtoms::href && kNameSpaceID_None == aNameSpaceID) { bool hasHref = aValue; Link::ResetLinkState(!!aNotify, hasHref); @@ -370,7 +370,7 @@ HTMLLinkElement::AfterSetAttr(int32_t aNameSpaceID, nsIAtom* aName, UpdatePreconnect(); } } - + UpdateStyleSheetInternal(nullptr, nullptr, dropSheet || (aName == nsGkAtoms::title || @@ -394,7 +394,7 @@ HTMLLinkElement::AfterSetAttr(int32_t aNameSpaceID, nsIAtom* aName, } } } - + return nsGenericHTMLElement::AfterSetAttr(aNameSpaceID, aName, aValue, aNotify); } diff --git a/dom/html/HTMLMediaElement.cpp b/dom/html/HTMLMediaElement.cpp index 2bbb7ece3e..3553eac900 100644 --- a/dom/html/HTMLMediaElement.cpp +++ b/dom/html/HTMLMediaElement.cpp @@ -3521,10 +3521,9 @@ void HTMLMediaElement::StartProgressTimer() NS_ASSERTION(!mProgressTimer, "Already started progress timer."); mProgressTimer = do_CreateInstance("@mozilla.org/timer;1"); - mProgressTimer->InitWithFuncCallback(ProgressTimerCallback, - this, - PROGRESS_MS, - nsITimer::TYPE_REPEATING_SLACK); + mProgressTimer->InitWithNamedFuncCallback( + ProgressTimerCallback, this, PROGRESS_MS, nsITimer::TYPE_REPEATING_SLACK, + "HTMLMediaElement::ProgressTimerCallback"); } void HTMLMediaElement::StartProgress() diff --git a/dom/media/MediaTimer.cpp b/dom/media/MediaTimer.cpp index eacb3b8f65..e563861f2a 100644 --- a/dom/media/MediaTimer.cpp +++ b/dom/media/MediaTimer.cpp @@ -174,7 +174,9 @@ MediaTimer::ArmTimer(const TimeStamp& aTarget, const TimeStamp& aNow) unsigned long delay = std::ceil((aTarget - aNow).ToMilliseconds()); TIMER_LOG("MediaTimer::ArmTimer delay=%lu", delay); mCurrentTimerTarget = aTarget; - nsresult rv = mTimer->InitWithFuncCallback(&TimerCallback, this, delay, nsITimer::TYPE_ONE_SHOT); + nsresult rv = mTimer->InitWithNamedFuncCallback(&TimerCallback, this, delay, + nsITimer::TYPE_ONE_SHOT, + "MediaTimer::TimerCallback"); MOZ_DIAGNOSTIC_ASSERT(NS_SUCCEEDED(rv)); (void) rv; } diff --git a/dom/network/NetworkStatsService.jsm b/dom/network/NetworkStatsService.jsm index fa03848715..f86b81d662 100644 --- a/dom/network/NetworkStatsService.jsm +++ b/dom/network/NetworkStatsService.jsm @@ -127,7 +127,7 @@ this.NetworkStatsService = { // Stats for all interfaces are updated periodically this.timer.initWithCallback(this, this._db.sampleRate, - Ci.nsITimer.TYPE_REPEATING_PRECISE); + Ci.nsITimer.TYPE_REPEATING_PRECISE_CAN_SKIP); // Stats not from netd are firstly stored in the cached. this.cachedStats = Object.create(null); diff --git a/dom/workers/WorkerPrivate.cpp b/dom/workers/WorkerPrivate.cpp index dff78317ba..bb24624af2 100644 --- a/dom/workers/WorkerPrivate.cpp +++ b/dom/workers/WorkerPrivate.cpp @@ -1188,8 +1188,9 @@ public: return false; } - if (NS_FAILED(timer->InitWithFuncCallback(DummyCallback, nullptr, aDelayMS, - nsITimer::TYPE_ONE_SHOT))) { + if (NS_FAILED(timer->InitWithNamedFuncCallback( + DummyCallback, nullptr, aDelayMS, nsITimer::TYPE_ONE_SHOT, + "dom::workers::DummyCallback(1)"))) { JS_ReportError(aCx, "Failed to start timer!"); return false; } @@ -4507,9 +4508,9 @@ WorkerPrivate::SetGCTimerMode(GCTimerMode aMode) } MOZ_ALWAYS_TRUE(NS_SUCCEEDED(mGCTimer->SetTarget(target))); - MOZ_ALWAYS_TRUE(NS_SUCCEEDED(mGCTimer->InitWithFuncCallback(DummyCallback, - nullptr, delay, - type))); + MOZ_ALWAYS_TRUE(NS_SUCCEEDED( + mGCTimer->InitWithNamedFuncCallback(DummyCallback, nullptr, delay, type, + "dom::workers::DummyCallback(2)"))); if (aMode == PeriodicTimer) { LOG(("Worker %p scheduled periodic GC timer\n", this)); @@ -5921,8 +5922,9 @@ WorkerPrivate::RescheduleTimeoutTimer(JSContext* aCx) (mTimeouts[0]->mTargetTime - TimeStamp::Now()).ToMilliseconds(); uint32_t delay = delta > 0 ? std::min(delta, double(UINT32_MAX)) : 0; - nsresult rv = mTimer->InitWithFuncCallback(DummyCallback, nullptr, delay, - nsITimer::TYPE_ONE_SHOT); + nsresult rv = mTimer->InitWithNamedFuncCallback( + DummyCallback, nullptr, delay, nsITimer::TYPE_ONE_SHOT, + "dom::workers::DummyCallback(3)"); if (NS_FAILED(rv)) { JS_ReportError(aCx, "Failed to start timer!"); return false; diff --git a/layout/base/nsPresShell.cpp b/layout/base/nsPresShell.cpp index 2fce4694f0..78a363ce93 100644 --- a/layout/base/nsPresShell.cpp +++ b/layout/base/nsPresShell.cpp @@ -1760,9 +1760,9 @@ PresShell::Initialize(nscoord aWidth, nscoord aHeight) Preferences::GetInt("nglayout.initialpaint.delay", PAINTLOCK_EVENT_DELAY); - mPaintSuppressionTimer->InitWithFuncCallback(sPaintSuppressionCallback, - this, delay, - nsITimer::TYPE_ONE_SHOT); + mPaintSuppressionTimer->InitWithNamedFuncCallback( + sPaintSuppressionCallback, this, delay, nsITimer::TYPE_ONE_SHOT, + "PresShell::sPaintSuppressionCallback"); } } diff --git a/layout/generic/ScrollbarActivity.cpp b/layout/generic/ScrollbarActivity.cpp index 01296cec9a..dfe6ed7557 100644 --- a/layout/generic/ScrollbarActivity.cpp +++ b/layout/generic/ScrollbarActivity.cpp @@ -426,9 +426,9 @@ ScrollbarActivity::StartFadeBeginTimer() if (!mFadeBeginTimer) { mFadeBeginTimer = do_CreateInstance("@mozilla.org/timer;1"); } - mFadeBeginTimer->InitWithFuncCallback(FadeBeginTimerFired, this, - mScrollbarFadeBeginDelay, - nsITimer::TYPE_ONE_SHOT); + mFadeBeginTimer->InitWithNamedFuncCallback( + FadeBeginTimerFired, this, mScrollbarFadeBeginDelay, + nsITimer::TYPE_ONE_SHOT, "ScrollbarActivity::FadeBeginTimerFired"); } void diff --git a/parser/html/nsHtml5TreeOpExecutor.cpp b/parser/html/nsHtml5TreeOpExecutor.cpp index f9ef429936..501c4e42ca 100644 --- a/parser/html/nsHtml5TreeOpExecutor.cpp +++ b/parser/html/nsHtml5TreeOpExecutor.cpp @@ -275,8 +275,9 @@ nsHtml5TreeOpExecutor::ContinueInterruptedParsingAsync() // The timer value 50 should not hopefully slow down background pages too // much, yet lets event loop to process enough between ticks. // See bug 734015. - gFlushTimer->InitWithFuncCallback(FlushTimerCallback, nullptr, - 50, nsITimer::TYPE_REPEATING_SLACK); + gFlushTimer->InitWithNamedFuncCallback(FlushTimerCallback, nullptr, + 50, nsITimer::TYPE_REPEATING_SLACK, + "FlushTimerCallback"); } } } diff --git a/toolkit/components/promiseworker/tests/xpcshell/data/worker.js b/toolkit/components/promiseworker/tests/xpcshell/data/worker.js index 36ea527ca1..3c612a5286 100644 --- a/toolkit/components/promiseworker/tests/xpcshell/data/worker.js +++ b/toolkit/components/promiseworker/tests/xpcshell/data/worker.js @@ -6,9 +6,9 @@ // Trivial worker definition importScripts("resource://gre/modules/workers/require.js"); -let PromiseWorker = require("resource://gre/modules/workers/PromiseWorker.js"); +var PromiseWorker = require("resource://gre/modules/workers/PromiseWorker.js"); -let worker = new PromiseWorker.AbstractWorker(); +var worker = new PromiseWorker.AbstractWorker(); worker.dispatch = function(method, args = []) { return Agent[method](...args); }, @@ -23,7 +23,7 @@ worker.log = function(...args) { }; self.addEventListener("message", msg => worker.handleMessage(msg)); -let Agent = { +var Agent = { bounce: function(...args) { return args; } diff --git a/toolkit/components/promiseworker/tests/xpcshell/test_Promise.js b/toolkit/components/promiseworker/tests/xpcshell/test_Promise.js index de20837c31..24f91bcc48 100644 --- a/toolkit/components/promiseworker/tests/xpcshell/test_Promise.js +++ b/toolkit/components/promiseworker/tests/xpcshell/test_Promise.js @@ -3,7 +3,7 @@ "use strict"; -let Cu = Components.utils; +var Cu = Components.utils; Cu.import("resource://gre/modules/PromiseWorker.jsm", this); Cu.import("resource://gre/modules/Timer.jsm", this); @@ -11,9 +11,9 @@ Cu.import("resource://gre/modules/Timer.jsm", this); // Worker must be loaded from a chrome:// uri, not a file:// // uri, so we first need to load it. -let WORKER_SOURCE_URI = "chrome://promiseworker/content/worker.js"; +var WORKER_SOURCE_URI = "chrome://promiseworker/content/worker.js"; do_load_manifest("data/chrome.manifest"); -let worker = new BasePromiseWorker(WORKER_SOURCE_URI); +var worker = new BasePromiseWorker(WORKER_SOURCE_URI); worker.log = function(...args) { do_print("Controller: " + args.join(" ")); }; diff --git a/toolkit/components/prompts/content/commonDialog.js b/toolkit/components/prompts/content/commonDialog.js index 1f8fad85f5..7c47b92106 100644 --- a/toolkit/components/prompts/content/commonDialog.js +++ b/toolkit/components/prompts/content/commonDialog.js @@ -2,15 +2,15 @@ * 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/. */ -const Ci = Components.interfaces; -const Cr = Components.results; -const Cc = Components.classes; -const Cu = Components.utils; +var Ci = Components.interfaces; +var Cr = Components.results; +var Cc = Components.classes; +var Cu = Components.utils; Cu.import("resource://gre/modules/Services.jsm"); Cu.import("resource://gre/modules/CommonDialog.jsm"); -let propBag, args, Dialog; +var propBag, args, Dialog; function commonDialogOnLoad() { propBag = window.arguments[0].QueryInterface(Ci.nsIWritablePropertyBag2) diff --git a/toolkit/components/prompts/content/selectDialog.js b/toolkit/components/prompts/content/selectDialog.js index c6067d57f9..689f42d53d 100644 --- a/toolkit/components/prompts/content/selectDialog.js +++ b/toolkit/components/prompts/content/selectDialog.js @@ -2,12 +2,12 @@ * 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/. */ -const Ci = Components.interfaces; -const Cr = Components.results; -const Cc = Components.classes; -const Cu = Components.utils; +var Ci = Components.interfaces; +var Cr = Components.results; +var Cc = Components.classes; +var Cu = Components.utils; -let gArgs, listBox; +var gArgs, listBox; function dialogOnLoad() { gArgs = window.arguments[0].QueryInterface(Ci.nsIWritablePropertyBag2) diff --git a/toolkit/components/prompts/src/nsPrompter.js b/toolkit/components/prompts/src/nsPrompter.js index 95cdabdd54..41a4efb144 100644 --- a/toolkit/components/prompts/src/nsPrompter.js +++ b/toolkit/components/prompts/src/nsPrompter.js @@ -117,7 +117,7 @@ Prompter.prototype = { // Common utils not specific to a particular prompter style. -let PromptUtilsTemp = { +var PromptUtilsTemp = { __proto__ : PromptUtils, getLocalizedString : function (key, formatArgs) { diff --git a/toolkit/components/reader/AboutReader.jsm b/toolkit/components/reader/AboutReader.jsm index 17e1e765e1..d66b437f89 100644 --- a/toolkit/components/reader/AboutReader.jsm +++ b/toolkit/components/reader/AboutReader.jsm @@ -4,7 +4,7 @@ "use strict"; -let Ci = Components.interfaces, Cc = Components.classes, Cu = Components.utils; +var Ci = Components.interfaces, Cc = Components.classes, Cu = Components.utils; this.EXPORTED_SYMBOLS = [ "AboutReader" ]; @@ -18,9 +18,9 @@ XPCOMUtils.defineLazyModuleGetter(this, "UITelemetry", "resource://gre/modules/U const READINGLIST_COMMAND_ID = "readingListSidebar"; -let gStrings = Services.strings.createBundle("chrome://global/locale/aboutReader.properties"); +var gStrings = Services.strings.createBundle("chrome://global/locale/aboutReader.properties"); -let AboutReader = function(mm, win, articlePromise) { +var AboutReader = function(mm, win, articlePromise) { let url = this._getOriginalUrl(win); if (!(url.startsWith("http://") || url.startsWith("https://"))) { Cu.reportError("Only http:// and https:// URLs can be loaded in about:reader"); diff --git a/toolkit/components/reader/ReaderWorker.js b/toolkit/components/reader/ReaderWorker.js index 69f48f17e7..20023d4e09 100644 --- a/toolkit/components/reader/ReaderWorker.js +++ b/toolkit/components/reader/ReaderWorker.js @@ -12,11 +12,11 @@ importScripts("resource://gre/modules/workers/require.js", "resource://gre/modules/reader/JSDOMParser.js", "resource://gre/modules/reader/Readability.js"); -let PromiseWorker = require("resource://gre/modules/workers/PromiseWorker.js"); +var PromiseWorker = require("resource://gre/modules/workers/PromiseWorker.js"); const DEBUG = false; -let worker = new PromiseWorker.AbstractWorker(); +var worker = new PromiseWorker.AbstractWorker(); worker.dispatch = function(method, args = []) { return Agent[method](...args); }; @@ -34,7 +34,7 @@ worker.log = function(...args) { self.addEventListener("message", msg => worker.handleMessage(msg)); -let Agent = { +var Agent = { /** * Parses structured article data from a document. * diff --git a/toolkit/components/satchel/FormHistory.jsm b/toolkit/components/satchel/FormHistory.jsm index 7d59da448e..54b40672e4 100644 --- a/toolkit/components/satchel/FormHistory.jsm +++ b/toolkit/components/satchel/FormHistory.jsm @@ -101,14 +101,14 @@ const DAY_IN_MS = 86400000; // 1 day in milliseconds const MAX_SEARCH_TOKENS = 10; const NOOP = function noop() {}; -let supportsDeletedTable = +var supportsDeletedTable = #ifdef ANDROID true; #else false; #endif -let Prefs = { +var Prefs = { initialized: false, get debug() { this.ensureInitialized(); return this._debug; }, @@ -371,7 +371,7 @@ function generateGUID() { * Database creation and access */ -let _dbConnection = null; +var _dbConnection = null; XPCOMUtils.defineLazyGetter(this, "dbConnection", function() { let dbFile; @@ -392,7 +392,7 @@ XPCOMUtils.defineLazyGetter(this, "dbConnection", function() { }); -let dbStmts = new Map(); +var dbStmts = new Map(); /* * dbCreateAsyncStatement diff --git a/toolkit/components/satchel/nsFormHistory.js b/toolkit/components/satchel/nsFormHistory.js index e0d71d056e..bbbaf9598f 100644 --- a/toolkit/components/satchel/nsFormHistory.js +++ b/toolkit/components/satchel/nsFormHistory.js @@ -888,5 +888,5 @@ FormHistory.prototype = { } }; -let component = [FormHistory]; +var component = [FormHistory]; this.NSGetFactory = XPCOMUtils.generateNSGetFactory(component); diff --git a/toolkit/components/satchel/nsInputListAutoComplete.js b/toolkit/components/satchel/nsInputListAutoComplete.js index 1fe2907235..f529b3ae01 100644 --- a/toolkit/components/satchel/nsInputListAutoComplete.js +++ b/toolkit/components/satchel/nsInputListAutoComplete.js @@ -60,5 +60,5 @@ InputListAutoComplete.prototype = { } }; -let component = [InputListAutoComplete]; +var component = [InputListAutoComplete]; this.NSGetFactory = XPCOMUtils.generateNSGetFactory(component); diff --git a/toolkit/components/search/SearchSuggestionController.jsm b/toolkit/components/search/SearchSuggestionController.jsm index 8109fbb358..39df4243c5 100644 --- a/toolkit/components/search/SearchSuggestionController.jsm +++ b/toolkit/components/search/SearchSuggestionController.jsm @@ -23,7 +23,7 @@ const BROWSER_SUGGEST_PREF = "browser.search.suggest.enabled"; * Remote search suggestions will be shown if gRemoteSuggestionsEnabled * is true. Global because only one pref observer is needed for all instances. */ -let gRemoteSuggestionsEnabled = Services.prefs.getBoolPref(BROWSER_SUGGEST_PREF); +var gRemoteSuggestionsEnabled = Services.prefs.getBoolPref(BROWSER_SUGGEST_PREF); Services.prefs.addObserver(BROWSER_SUGGEST_PREF, function(aSubject, aTopic, aData) { gRemoteSuggestionsEnabled = Services.prefs.getBoolPref(BROWSER_SUGGEST_PREF); }, false); diff --git a/toolkit/components/social/FrameWorkerContent.js b/toolkit/components/social/FrameWorkerContent.js index c88ef8ebd0..302438b622 100644 --- a/toolkit/components/social/FrameWorkerContent.js +++ b/toolkit/components/social/FrameWorkerContent.js @@ -8,7 +8,7 @@ "use strict"; // the singleton frameworker, available for (ab)use by tests. -let frameworker; +var frameworker; (function () { // bug 673569 workaround :( @@ -21,7 +21,7 @@ let frameworker; * by cloning methods from the worker's JS origin. */ -const {classes: Cc, interfaces: Ci, utils: Cu} = Components; +var {classes: Cc, interfaces: Ci, utils: Cu} = Components; Cu.import("resource://gre/modules/Services.jsm"); Cu.import("resource://gre/modules/MessagePortBase.jsm"); diff --git a/toolkit/components/sqlite/sqlite_internal.js b/toolkit/components/sqlite/sqlite_internal.js index e7225a7d7a..56615bfdfe 100644 --- a/toolkit/components/sqlite/sqlite_internal.js +++ b/toolkit/components/sqlite/sqlite_internal.js @@ -16,11 +16,11 @@ importScripts("resource://gre/modules/workers/require.js"); -let SharedAll = require( +var SharedAll = require( "resource://gre/modules/osfile/osfile_shared_allthreads.jsm"); // Open the sqlite3 library. -let path; +var path; if (SharedAll.Constants.Sys.Name === "Android") { path = ctypes.libraryName("sqlite3"); } else if (SharedAll.Constants.Win) { @@ -29,16 +29,16 @@ if (SharedAll.Constants.Sys.Name === "Android") { path = SharedAll.Constants.Path.libxul; } -let lib; +var lib; try { lib = ctypes.open(path); } catch (ex) { throw new Error("Could not open system library: " + ex.message); } -let declareLazyFFI = SharedAll.declareLazyFFI; +var declareLazyFFI = SharedAll.declareLazyFFI; -let Type = Object.create(SharedAll.Type); +var Type = Object.create(SharedAll.Type); /** * Opaque Structure |sqlite3_ptr|. @@ -73,7 +73,7 @@ Type.sqlite3_int64 = Type.int64_t.withName("sqlite3_int64"); /** * Sqlite3 constants. */ -let Constants = {}; +var Constants = {}; /** * |SQLITE_STATIC| a special value for the destructor that is passed as an @@ -109,7 +109,7 @@ Constants.SQLITE_ROW = 100; */ Constants.SQLITE_DONE = 101; -let Sqlite3 = { +var Sqlite3 = { Constants: Constants, Type: Type }; diff --git a/toolkit/components/startup/tests/unit/head_startup.js b/toolkit/components/startup/tests/unit/head_startup.js index 3103874b49..b231226b1b 100644 --- a/toolkit/components/startup/tests/unit/head_startup.js +++ b/toolkit/components/startup/tests/unit/head_startup.js @@ -6,7 +6,7 @@ const XULRUNTIME_CID = Components.ID("7685dac8-3637-4660-a544-928c5ec0e714}"); Components.utils.import("resource://gre/modules/XPCOMUtils.jsm"); -let gAppInfo = null; +var gAppInfo = null; function createAppInfo(id, name, version, platformVersion) { gAppInfo = { diff --git a/toolkit/components/startup/tests/unit/test_startup_crash.js b/toolkit/components/startup/tests/unit/test_startup_crash.js index 5be81c0d6f..2836330866 100644 --- a/toolkit/components/startup/tests/unit/test_startup_crash.js +++ b/toolkit/components/startup/tests/unit/test_startup_crash.js @@ -3,13 +3,13 @@ Components.utils.import("resource://gre/modules/Services.jsm"); -const Cc = Components.classes; -const Ci = Components.interfaces; +var Cc = Components.classes; +var Ci = Components.interfaces; createAppInfo("xpcshell@tests.mozilla.org", "XPCShell", "1", "10.0"); -let prefService = Services.prefs; -let appStartup = Cc["@mozilla.org/toolkit/app-startup;1"]. +var prefService = Services.prefs; +var appStartup = Cc["@mozilla.org/toolkit/app-startup;1"]. getService(Ci.nsIAppStartup); const pref_last_success = "toolkit.startup.last_success"; @@ -297,4 +297,4 @@ function test_maxResumed() { // should remain the same since the last startup was not a crash do_check_eq(max_resumed + 2, prefService.getIntPref(pref_recent_crashes)); do_check_false(appStartup.automaticSafeModeNecessary); -} \ No newline at end of file +} diff --git a/toolkit/components/statusfilter/nsBrowserStatusFilter.cpp b/toolkit/components/statusfilter/nsBrowserStatusFilter.cpp index 464fc2ffe5..a6136c3940 100644 --- a/toolkit/components/statusfilter/nsBrowserStatusFilter.cpp +++ b/toolkit/components/statusfilter/nsBrowserStatusFilter.cpp @@ -355,8 +355,9 @@ nsBrowserStatusFilter::StartDelayTimer() if (!mTimer) return NS_ERROR_FAILURE; - return mTimer->InitWithFuncCallback(TimeoutHandler, this, 160, - nsITimer::TYPE_ONE_SHOT); + return mTimer->InitWithNamedFuncCallback( + TimeoutHandler, this, 160, nsITimer::TYPE_ONE_SHOT, + "nsBrowserStatusFilter::TimeoutHandler"); } void diff --git a/toolkit/components/telemetry/TelemetrySession.jsm b/toolkit/components/telemetry/TelemetrySession.jsm index a852a488dc..0ed930306d 100644 --- a/toolkit/components/telemetry/TelemetrySession.jsm +++ b/toolkit/components/telemetry/TelemetrySession.jsm @@ -84,7 +84,7 @@ const IDLE_TIMEOUT_SECONDS = 5 * 60; var gLastMemoryPoll = null; -let gWasDebuggerAttached = false; +var gWasDebuggerAttached = false; function getLocale() { return Cc["@mozilla.org/chrome/chrome-registry;1"]. @@ -141,7 +141,7 @@ function generateUUID() { /** * This is a policy object used to override behavior for testing. */ -let Policy = { +var Policy = { now: () => new Date(), setDailyTimeout: (callback, delayMs) => setTimeout(callback, delayMs), clearDailyTimeout: (id) => clearTimeout(id), @@ -188,7 +188,7 @@ function toLocalTimeISOString(date) { /** * Read current process I/O counters. */ -let processInfo = { +var processInfo = { _initialized: false, _IO_COUNTERS: null, _kernel32: null, @@ -434,7 +434,7 @@ this.TelemetrySession = Object.freeze({ }, }); -let Impl = { +var Impl = { _histograms: {}, _initialized: false, _log: null, diff --git a/toolkit/components/telemetry/TelemetryStopwatch.jsm b/toolkit/components/telemetry/TelemetryStopwatch.jsm index 989485da9a..c531bb2ff2 100644 --- a/toolkit/components/telemetry/TelemetryStopwatch.jsm +++ b/toolkit/components/telemetry/TelemetryStopwatch.jsm @@ -8,14 +8,14 @@ const Cu = Components.utils; this.EXPORTED_SYMBOLS = ["TelemetryStopwatch"]; -let Telemetry = Cc["@mozilla.org/base/telemetry;1"] +var Telemetry = Cc["@mozilla.org/base/telemetry;1"] .getService(Ci.nsITelemetry); // simpleTimers are directly associated with a histogram // name. objectTimers are associated with an object _and_ // a histogram name. -let simpleTimers = {}; -let objectTimers = new WeakMap(); +var simpleTimers = {}; +var objectTimers = new WeakMap(); this.TelemetryStopwatch = { /** diff --git a/toolkit/components/telemetry/ThirdPartyCookieProbe.jsm b/toolkit/components/telemetry/ThirdPartyCookieProbe.jsm index 4e14c58f39..769487d419 100644 --- a/toolkit/components/telemetry/ThirdPartyCookieProbe.jsm +++ b/toolkit/components/telemetry/ThirdPartyCookieProbe.jsm @@ -4,9 +4,9 @@ "use strict"; -let Ci = Components.interfaces; -let Cu = Components.utils; -let Cr = Components.results; +var Ci = Components.interfaces; +var Cu = Components.utils; +var Cr = Components.results; Cu.import("resource://gre/modules/XPCOMUtils.jsm", this); Cu.import("resource://gre/modules/Services.jsm", this); @@ -138,7 +138,7 @@ this.ThirdPartyCookieProbe.prototype = { * * @constructor */ -let RejectStats = function() { +var RejectStats = function() { /** * The set of all sites for which we have accepted third-party cookies. */ diff --git a/toolkit/components/terminator/nsTerminatorTelemetry.js b/toolkit/components/terminator/nsTerminatorTelemetry.js index 30e2b56706..9978c81633 100644 --- a/toolkit/components/terminator/nsTerminatorTelemetry.js +++ b/toolkit/components/terminator/nsTerminatorTelemetry.js @@ -31,7 +31,7 @@ XPCOMUtils.defineLazyModuleGetter(this, "Services", function nsTerminatorTelemetry() {} -let HISTOGRAMS = { +var HISTOGRAMS = { "quit-application": "SHUTDOWN_PHASE_DURATION_TICKS_QUIT_APPLICATION", "profile-change-teardown": "SHUTDOWN_PHASE_DURATION_TICKS_PROFILE_CHANGE_TEARDOWN", "profile-before-change": "SHUTDOWN_PHASE_DURATION_TICKS_PROFILE_BEFORE_CHANGE", diff --git a/toolkit/components/thumbnails/PageThumbsWorker.js b/toolkit/components/thumbnails/PageThumbsWorker.js index f54c0b7fd8..d0fcf554c4 100644 --- a/toolkit/components/thumbnails/PageThumbsWorker.js +++ b/toolkit/components/thumbnails/PageThumbsWorker.js @@ -13,12 +13,12 @@ importScripts("resource://gre/modules/osfile.jsm"); -let PromiseWorker = require("resource://gre/modules/workers/PromiseWorker.js"); +var PromiseWorker = require("resource://gre/modules/workers/PromiseWorker.js"); -let File = OS.File; -let Type = OS.Shared.Type; +var File = OS.File; +var Type = OS.Shared.Type; -let worker = new PromiseWorker.AbstractWorker(); +var worker = new PromiseWorker.AbstractWorker(); worker.dispatch = function(method, args = []) { return Agent[method](...args); }; @@ -32,7 +32,7 @@ worker.close = function() { self.addEventListener("message", msg => worker.handleMessage(msg)); -let Agent = { +var Agent = { // Checks if the specified file exists and has an age less than as // specifed (in seconds). isFileRecent: function Agent_isFileRecent(path, maxAge) { diff --git a/toolkit/components/thumbnails/content/backgroundPageThumbsContent.js b/toolkit/components/thumbnails/content/backgroundPageThumbsContent.js index 78a4f86b69..a3db5ccfc4 100644 --- a/toolkit/components/thumbnails/content/backgroundPageThumbsContent.js +++ b/toolkit/components/thumbnails/content/backgroundPageThumbsContent.js @@ -2,7 +2,7 @@ * 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/. */ -const { classes: Cc, interfaces: Ci, utils: Cu } = Components; +var { classes: Cc, interfaces: Ci, utils: Cu } = Components; Cu.importGlobalProperties(['Blob']); diff --git a/toolkit/components/viewsource/ViewSourceBrowser.jsm b/toolkit/components/viewsource/ViewSourceBrowser.jsm index cb844fcd54..2fbccd7954 100644 --- a/toolkit/components/viewsource/ViewSourceBrowser.jsm +++ b/toolkit/components/viewsource/ViewSourceBrowser.jsm @@ -31,7 +31,7 @@ this.EXPORTED_SYMBOLS = ["ViewSourceBrowser"]; // Keep a set of browsers we've seen before, so we can load our frame script as // needed into any new ones. -let gKnownBrowsers = new WeakSet(); +var gKnownBrowsers = new WeakSet(); /** * ViewSourceBrowser manages the view source from the chrome side. diff --git a/toolkit/components/viewsource/content/viewSource-content.js b/toolkit/components/viewsource/content/viewSource-content.js index cb84977be2..e220f75bda 100644 --- a/toolkit/components/viewsource/content/viewSource-content.js +++ b/toolkit/components/viewsource/content/viewSource-content.js @@ -2,7 +2,7 @@ * 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/. */ -const { utils: Cu, interfaces: Ci, classes: Cc } = Components; +var { utils: Cu, interfaces: Ci, classes: Cc } = Components; Cu.import("resource://gre/modules/Services.jsm"); Cu.import("resource://gre/modules/XPCOMUtils.jsm"); @@ -23,13 +23,13 @@ const BUNDLE_URL = "chrome://global/locale/viewSource.properties"; const MARK_SELECTION_START = "\uFDD0"; const MARK_SELECTION_END = "\uFDEF"; -let global = this; +var global = this; /** * ViewSourceContent should be loaded in the of the * view source window, and initialized as soon as it has loaded. */ -let ViewSourceContent = { +var ViewSourceContent = { /** * We'll act as an nsISelectionListener as well so that we can send * updates to the view source window's status bar. diff --git a/toolkit/components/viewsource/content/viewSource.js b/toolkit/components/viewsource/content/viewSource.js index df8ae17587..96475d6c37 100644 --- a/toolkit/components/viewsource/content/viewSource.js +++ b/toolkit/components/viewsource/content/viewSource.js @@ -4,7 +4,7 @@ * 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/. */ -const { utils: Cu, interfaces: Ci, classes: Cc } = Components; +var { utils: Cu, interfaces: Ci, classes: Cc } = Components; Cu.import("resource://gre/modules/XPCOMUtils.jsm"); Cu.import("resource://gre/modules/ViewSourceBrowser.jsm"); @@ -693,12 +693,12 @@ ViewSourceChrome.prototype = { }, }; -let viewSourceChrome = new ViewSourceChrome(); +var viewSourceChrome = new ViewSourceChrome(); /** * PrintUtils uses this to make Print Preview work. */ -let PrintPreviewListener = { +var PrintPreviewListener = { getPrintPreviewBrowser() { let browser = document.getElementById("ppBrowser"); if (!browser) { diff --git a/toolkit/components/viewsource/test/browser/browser_gotoline.js b/toolkit/components/viewsource/test/browser/browser_gotoline.js index 2db18adb11..b84f725a6c 100644 --- a/toolkit/components/viewsource/test/browser/browser_gotoline.js +++ b/toolkit/components/viewsource/test/browser/browser_gotoline.js @@ -4,7 +4,7 @@ Cu.import("resource://testing-common/ContentTaskUtils.jsm", this); -let content = "line 1\nline 2\nline 3"; +var content = "line 1\nline 2\nline 3"; add_task(function*() { // First test with text with the text/html mimetype. @@ -17,7 +17,7 @@ add_task(function*() { yield BrowserTestUtils.closeWindow(win); }); -let checkViewSource = Task.async(function* (aWindow) { +var checkViewSource = Task.async(function* (aWindow) { is(aWindow.gBrowser.contentDocument.body.textContent, content, "Correct content loaded"); let selection = aWindow.gBrowser.contentWindow.getSelection(); let statusPanel = aWindow.document.getElementById("statusbar-line-col"); diff --git a/toolkit/components/viewsource/test/browser/browser_viewsourceprefs.js b/toolkit/components/viewsource/test/browser/browser_viewsourceprefs.js index 0227ee1241..7361a70a5a 100644 --- a/toolkit/components/viewsource/test/browser/browser_viewsourceprefs.js +++ b/toolkit/components/viewsource/test/browser/browser_viewsourceprefs.js @@ -2,8 +2,8 @@ * http://creativecommons.org/publicdomain/zero/1.0/ */ -let plaintextURL = "data:text/plain,hello+world"; -let htmlURL = "about:mozilla"; +var plaintextURL = "data:text/plain,hello+world"; +var htmlURL = "about:mozilla"; add_task(function* setup() { registerCleanupFunction(function() { @@ -18,7 +18,7 @@ add_task(function*() { yield exercisePrefs(htmlURL, true); }); -let exercisePrefs = Task.async(function* (source, highlightable) { +var exercisePrefs = Task.async(function* (source, highlightable) { let win = yield loadViewSourceWindow(source); let wrapMenuItem = win.document.getElementById("menu_wrapLongLines"); let syntaxMenuItem = win.document.getElementById("menu_highlightSyntax"); @@ -114,7 +114,7 @@ function simulateClick(aMenuItem) { aMenuItem.click(); } -let checkStyle = Task.async(function* (win, styleProperty, expected) { +var checkStyle = Task.async(function* (win, styleProperty, expected) { let browser = win.gBrowser; let value = yield ContentTask.spawn(browser, styleProperty, function* (styleProperty) { let style = content.getComputedStyle(content.document.body, null); @@ -123,7 +123,7 @@ let checkStyle = Task.async(function* (win, styleProperty, expected) { is(value, expected, "Correct value of " + styleProperty); }); -let checkHighlight = Task.async(function* (win, expected) { +var checkHighlight = Task.async(function* (win, expected) { let browser = win.gBrowser; let highlighted = yield ContentTask.spawn(browser, {}, function* () { let spans = content.document.getElementsByTagName("span"); diff --git a/xpcom/ds/nsExpirationTracker.h b/xpcom/ds/nsExpirationTracker.h index e70e77cf74..8a1da8014d 100644 --- a/xpcom/ds/nsExpirationTracker.h +++ b/xpcom/ds/nsExpirationTracker.h @@ -360,8 +360,8 @@ private: if (!mTimer) { return NS_ERROR_OUT_OF_MEMORY; } - mTimer->InitWithFuncCallback(TimerCallback, this, mTimerPeriod, - nsITimer::TYPE_REPEATING_SLACK); + mTimer->InitWithNamedFuncCallback(TimerCallback, this, mTimerPeriod, + nsITimer::TYPE_REPEATING_SLACK, mName); return NS_OK; } }; diff --git a/xpcom/tests/TestTimers.cpp b/xpcom/tests/TestTimers.cpp index 29534d2d03..928b015383 100644 --- a/xpcom/tests/TestTimers.cpp +++ b/xpcom/tests/TestTimers.cpp @@ -18,6 +18,11 @@ #include "mozilla/Attributes.h" #include "mozilla/ReentrantMonitor.h" + +#include +#include +#include + using namespace mozilla; typedef nsresult(*TestFuncPtr)(); @@ -55,7 +60,7 @@ class AutoCreateAndDestroyReentrantMonitor public: AutoCreateAndDestroyReentrantMonitor() { mReentrantMonitor = new ReentrantMonitor("TestTimers::AutoMon"); - NS_ASSERTION(mReentrantMonitor, "Out of memory!"); + MOZ_ASSERT(mReentrantMonitor, "Out of memory!"); } ~AutoCreateAndDestroyReentrantMonitor() { @@ -79,12 +84,12 @@ public: : mThreadPtr(aThreadPtr), mReentrantMonitor(aReentrantMonitor) { } NS_IMETHOD Notify(nsITimer* aTimer) override { - NS_ASSERTION(mThreadPtr, "Callback was not supposed to be called!"); + MOZ_ASSERT(mThreadPtr, "Callback was not supposed to be called!"); nsCOMPtr current(do_GetCurrentThread()); ReentrantMonitorAutoEnter mon(*mReentrantMonitor); - NS_ASSERTION(!*mThreadPtr, "Timer called back more than once!"); + MOZ_ASSERT(!*mThreadPtr, "Timer called back more than once!"); *mThreadPtr = current; mon.Notify(); @@ -124,8 +129,7 @@ TestTargetedTimers() new TimerCallback(¬ifiedThread, newMon); NS_ENSURE_TRUE(callback, NS_ERROR_OUT_OF_MEMORY); - rv = timer->InitWithCallback(callback, PR_MillisecondsToInterval(2000), - nsITimer::TYPE_ONE_SHOT); + rv = timer->InitWithCallback(callback, 2000, nsITimer::TYPE_ONE_SHOT); NS_ENSURE_SUCCESS(rv, rv); ReentrantMonitorAutoEnter mon(*newMon); @@ -157,8 +161,7 @@ TestTimerWithStoppedTarget() new TimerCallback(nullptr, nullptr); NS_ENSURE_TRUE(callback, NS_ERROR_OUT_OF_MEMORY); - rv = timer->InitWithCallback(callback, PR_MillisecondsToInterval(100), - nsITimer::TYPE_ONE_SHOT); + rv = timer->InitWithCallback(callback, 100, nsITimer::TYPE_ONE_SHOT); NS_ENSURE_SUCCESS(rv, rv); testThread->Shutdown(); @@ -168,6 +171,305 @@ TestTimerWithStoppedTarget() return NS_OK; } +#define FUZZ_MAX_TIMEOUT 9 +class FuzzTestThreadState final : public nsITimerCallback { + public: + NS_DECL_THREADSAFE_ISUPPORTS + + explicit FuzzTestThreadState(nsIThread* thread) : + mThread(thread), + mStopped(false) + {} + + class StartRunnable final : public nsRunnable { + public: + explicit StartRunnable(FuzzTestThreadState* threadState) : + mThreadState(threadState) + {} + + NS_IMETHOD Run() override + { + mThreadState->ScheduleOrCancelTimers(); + return NS_OK; + } + + private: + nsRefPtr mThreadState; + }; + + void Start() + { + nsCOMPtr runnable = new StartRunnable(this); + nsresult rv = mThread->Dispatch(runnable, NS_DISPATCH_NORMAL); + if (NS_FAILED(rv)) { + MOZ_ASSERT(false, "Failed to dispatch StartRunnable."); + } + } + + void Stop() + { + mStopped = true; + } + + NS_IMETHOD Notify(nsITimer* aTimer) override + { + bool onCorrectThread; + nsresult rv = mThread->IsOnCurrentThread(&onCorrectThread); + MOZ_ASSERT(NS_SUCCEEDED(rv), "Failed to perform thread check."); + MOZ_ASSERT(onCorrectThread, "Notify invoked on wrong thread."); + + uint32_t delay; + rv = aTimer->GetDelay(&delay); + if (NS_FAILED(rv)) { + MOZ_ASSERT(false, "GetDelay failed."); + return rv; + } + + if (delay > FUZZ_MAX_TIMEOUT) { + MOZ_ASSERT(false, "Delay was an invalid value for this test."); + return NS_ERROR_FAILURE; + } + + uint32_t type; + rv = aTimer->GetType(&type); + MOZ_ASSERT(NS_SUCCEEDED(rv), "Failed to get timer type."); + MOZ_ASSERT(type <= nsITimer::TYPE_REPEATING_PRECISE_CAN_SKIP); + + if (type == nsITimer::TYPE_ONE_SHOT) { + if (mOneShotTimersByDelay[delay].empty()) { + MOZ_ASSERT(false, "Unexpected one-shot timer."); + return NS_ERROR_FAILURE; + } + + if (mOneShotTimersByDelay[delay].front().get() != aTimer) { + MOZ_ASSERT(false, + "One-shot timers for a given duration have been reordered."); + return NS_ERROR_FAILURE; + } + + mOneShotTimersByDelay[delay].pop_front(); + --mTimersOutstanding; + } else if (mStopped) { + CancelRepeatingTimer(aTimer); + } + + ScheduleOrCancelTimers(); + RescheduleSomeTimers(); + return NS_OK; + } + + bool HasTimersOutstanding() const + { + return !!mTimersOutstanding; + } + + private: + ~FuzzTestThreadState() + { + for (size_t i = 0; i <= FUZZ_MAX_TIMEOUT; ++i) { + MOZ_ASSERT(mOneShotTimersByDelay[i].empty(), + "Timers remain at end of test."); + } + } + + uint32_t GetRandomType() const + { + return rand() % (nsITimer::TYPE_REPEATING_PRECISE_CAN_SKIP + 1); + } + + size_t CountOneShotTimers() const + { + size_t count = 0; + for (size_t i = 0; i <= FUZZ_MAX_TIMEOUT; ++i) { + count += mOneShotTimersByDelay[i].size(); + } + return count; + } + + void ScheduleOrCancelTimers() + { + if (mStopped) { + return; + } + + const size_t numTimersDesired = (rand() % 100) + 100; + MOZ_ASSERT(numTimersDesired >= 100); + MOZ_ASSERT(numTimersDesired < 200); + int adjustment = numTimersDesired - mTimersOutstanding; + + while (adjustment > 0) { + CreateRandomTimer(); + --adjustment; + } + + while (adjustment < 0) { + CancelRandomTimer(); + ++adjustment; + } + + MOZ_ASSERT(numTimersDesired == mTimersOutstanding); + } + + void RescheduleSomeTimers() + { + if (mStopped) { + return; + } + + static const size_t kNumRescheduled = 40; + + // Reschedule some timers with a Cancel first. + for (size_t i = 0; i < kNumRescheduled; ++i) { + InitRandomTimer(CancelRandomTimer().get()); + } + // Reschedule some timers without a Cancel first. + for (size_t i = 0; i < kNumRescheduled; ++i) { + InitRandomTimer(RemoveRandomTimer().get()); + } + } + + void CreateRandomTimer() + { + nsresult rv; + nsCOMPtr timer = do_CreateInstance(NS_TIMER_CONTRACTID, &rv); + if (NS_FAILED(rv)) { + MOZ_ASSERT(false, "Failed to create timer."); + return; + } + + rv = timer->SetTarget(static_cast(mThread.get())); + if (NS_FAILED(rv)) { + MOZ_ASSERT(false, "Failed to set target."); + return; + } + + InitRandomTimer(timer.get()); + } + + nsCOMPtr CancelRandomTimer() + { + nsCOMPtr timer(RemoveRandomTimer()); + timer->Cancel(); + return timer; + } + + nsCOMPtr RemoveRandomTimer() + { + MOZ_ASSERT(mTimersOutstanding); + + if ((GetRandomType() == nsITimer::TYPE_ONE_SHOT && CountOneShotTimers()) + || mRepeatingTimers.empty()) { + uint32_t delayToRemove = rand() % (FUZZ_MAX_TIMEOUT + 1); + while (mOneShotTimersByDelay[delayToRemove].empty()) { + // ++delayToRemove mod FUZZ_MAX_TIMEOUT + 1 + delayToRemove = (delayToRemove + 1) % (FUZZ_MAX_TIMEOUT + 1); + } + + uint32_t indexToRemove = + rand() % mOneShotTimersByDelay[delayToRemove].size(); + + for (auto it = mOneShotTimersByDelay[delayToRemove].begin(); + it != mOneShotTimersByDelay[delayToRemove].end(); + ++it) { + if (indexToRemove) { + --indexToRemove; + continue; + } + + nsCOMPtr removed = *it; + mOneShotTimersByDelay[delayToRemove].erase(it); + --mTimersOutstanding; + return removed; + } + } else { + size_t indexToRemove = rand() % mRepeatingTimers.size(); + nsCOMPtr removed(mRepeatingTimers[indexToRemove]); + mRepeatingTimers.erase(mRepeatingTimers.begin() + indexToRemove); + --mTimersOutstanding; + return removed; + } + + MOZ_ASSERT_UNREACHABLE("Unable to remove a timer"); + return nullptr; + } + + void InitRandomTimer(nsITimer* aTimer) + { + // Between 0 and FUZZ_MAX_TIMEOUT + uint32_t delay = rand() % (FUZZ_MAX_TIMEOUT + 1); + uint32_t type = GetRandomType(); + nsresult rv = aTimer->InitWithCallback(this, delay, type); + if (NS_FAILED(rv)) { + MOZ_ASSERT(false, "Failed to set timer."); + return; + } + + if (type == nsITimer::TYPE_ONE_SHOT) { + mOneShotTimersByDelay[delay].push_back(aTimer); + } else { + mRepeatingTimers.push_back(aTimer); + } + ++mTimersOutstanding; + } + + void CancelRepeatingTimer(nsITimer* aTimer) + { + for (auto it = mRepeatingTimers.begin(); + it != mRepeatingTimers.end(); + ++it) { + if (it->get() == aTimer) { + mRepeatingTimers.erase(it); + aTimer->Cancel(); + --mTimersOutstanding; + return; + } + } + } + + nsCOMPtr mThread; + // Scheduled timers, indexed by delay between 0-9 ms, in lists + // with most recently scheduled last. + std::list> mOneShotTimersByDelay[FUZZ_MAX_TIMEOUT + 1]; + std::vector> mRepeatingTimers; + Atomic mStopped; + Atomic mTimersOutstanding; +}; + +NS_IMPL_ISUPPORTS(FuzzTestThreadState, nsITimerCallback) + +nsresult +FuzzTestTimers() +{ + static const size_t kNumThreads(10); + AutoTestThread threads[kNumThreads]; + nsRefPtr threadStates[kNumThreads]; + + for (size_t i = 0; i < kNumThreads; ++i) { + threadStates[i] = new FuzzTestThreadState(&*threads[i]); + threadStates[i]->Start(); + } + + PR_Sleep(PR_MillisecondsToInterval(20000)); + + for (size_t i = 0; i < kNumThreads; ++i) { + threadStates[i]->Stop(); + } + + // Wait at most 10 seconds for all outstanding timers to pop + PRIntervalTime start = PR_IntervalNow(); + for (auto& threadState : threadStates) { + while (threadState->HasTimersOutstanding()) { + if (PR_IntervalToMilliseconds(PR_IntervalNow() - start) > 10000) { + MOZ_ASSERT(false, "Timed out waiting for all timers to pop"); + return NS_ERROR_FAILURE; + } + PR_Sleep(PR_MillisecondsToInterval(10)); + } + } + + return NS_OK; +} + int main(int argc, char** argv) { ScopedXPCOM xpcom("TestTimers"); @@ -175,7 +477,8 @@ int main(int argc, char** argv) static TestFuncPtr testsToRun[] = { TestTargetedTimers, - TestTimerWithStoppedTarget + TestTimerWithStoppedTarget, + FuzzTestTimers }; static uint32_t testCount = sizeof(testsToRun) / sizeof(testsToRun[0]); diff --git a/xpcom/tests/unit/test_bug325418.js b/xpcom/tests/unit/test_bug325418.js index a26abec3e4..632864180a 100644 --- a/xpcom/tests/unit/test_bug325418.js +++ b/xpcom/tests/unit/test_bug325418.js @@ -52,10 +52,12 @@ function run_test() { // Initialize the timer (with some delay), then cancel it. gStartTime1 = Date.now(); - timer.init(observer1, kExpectedDelay1 * 1000, timer.TYPE_REPEATING_PRECISE); + timer.init(observer1, kExpectedDelay1 * 1000, + timer.TYPE_REPEATING_PRECISE_CAN_SKIP); timer.cancel(); // Re-initialize the timer (with a different delay). gStartTime2 = Date.now(); - timer.init(observer2, kExpectedDelay2 * 1000, timer.TYPE_REPEATING_PRECISE); + timer.init(observer2, kExpectedDelay2 * 1000, + timer.TYPE_REPEATING_PRECISE_CAN_SKIP); } diff --git a/xpcom/threads/TimerThread.cpp b/xpcom/threads/TimerThread.cpp index 6afbfd3191..cb4b5ee6ba 100644 --- a/xpcom/threads/TimerThread.cpp +++ b/xpcom/threads/TimerThread.cpp @@ -8,6 +8,7 @@ #include "TimerThread.h" #include "nsThreadUtils.h" +#include "plarena.h" #include "pratom.h" #include "nsIObserverService.h" @@ -79,6 +80,200 @@ TimerObserverRunnable::Run() } // namespace +namespace { + +// TimerEventAllocator is a thread-safe allocator used only for nsTimerEvents. +// It's needed to avoid contention over the default allocator lock when +// firing timer events (see bug 733277). The thread-safety is required because +// nsTimerEvent objects are allocated on the timer thread, and freed on another +// thread. Because TimerEventAllocator has its own lock, contention over that +// lock is limited to the allocation and deallocation of nsTimerEvent objects. +// +// Because this allocator is layered over PLArenaPool, it never shrinks -- even +// "freed" nsTimerEvents aren't truly freed, they're just put onto a free-list +// for later recycling. So the amount of memory consumed will always be equal +// to the high-water mark consumption. But nsTimerEvents are small and it's +// unusual to have more than a few hundred of them, so this shouldn't be a +// problem in practice. + +class TimerEventAllocator +{ +private: + struct FreeEntry + { + FreeEntry* mNext; + }; + + PLArenaPool mPool; + FreeEntry* mFirstFree; + mozilla::Monitor mMonitor; + +public: + TimerEventAllocator() + : mFirstFree(nullptr) + , mMonitor("TimerEventAllocator") + { + PL_InitArenaPool(&mPool, "TimerEventPool", 4096, /* align = */ 0); + } + + ~TimerEventAllocator() + { + PL_FinishArenaPool(&mPool); + } + + void* Alloc(size_t aSize); + void Free(void* aPtr); +}; + +} // namespace + +class nsTimerEvent : public nsRunnable +{ +public: + NS_IMETHOD Run(); + + nsTimerEvent() + : mTimer() + , mGeneration(0) + { + MOZ_COUNT_CTOR(nsTimerEvent); + + // Note: We override operator new for this class, and the override is + // fallible! + sAllocatorUsers++; + } + + TimeStamp mInitTime; + + static void Init(); + static void Shutdown(); + static void DeleteAllocatorIfNeeded(); + + static void* operator new(size_t aSize) CPP_THROW_NEW + { + return sAllocator->Alloc(aSize); + } + void operator delete(void* aPtr) + { + sAllocator->Free(aPtr); + DeleteAllocatorIfNeeded(); + } + + already_AddRefed ForgetTimer() + { + return mTimer.forget(); + } + + void SetTimer(already_AddRefed aTimer) + { + mTimer = aTimer; + mGeneration = mTimer->GetGeneration(); + } + +private: + ~nsTimerEvent() + { + MOZ_COUNT_DTOR(nsTimerEvent); + + MOZ_ASSERT(!sCanDeleteAllocator || sAllocatorUsers > 0, + "This will result in us attempting to deallocate the nsTimerEvent allocator twice"); + sAllocatorUsers--; + } + + nsRefPtr mTimer; + int32_t mGeneration; + + static TimerEventAllocator* sAllocator; + static Atomic sAllocatorUsers; + static bool sCanDeleteAllocator; +}; + +TimerEventAllocator* nsTimerEvent::sAllocator = nullptr; +Atomic nsTimerEvent::sAllocatorUsers; +bool nsTimerEvent::sCanDeleteAllocator = false; + +namespace { + +void* +TimerEventAllocator::Alloc(size_t aSize) +{ + MOZ_ASSERT(aSize == sizeof(nsTimerEvent)); + + mozilla::MonitorAutoLock lock(mMonitor); + + void* p; + if (mFirstFree) { + p = mFirstFree; + mFirstFree = mFirstFree->mNext; + } else { + PL_ARENA_ALLOCATE(p, &mPool, aSize); + if (!p) { + return nullptr; + } + } + + return p; +} + +void +TimerEventAllocator::Free(void* aPtr) +{ + mozilla::MonitorAutoLock lock(mMonitor); + + FreeEntry* entry = reinterpret_cast(aPtr); + + entry->mNext = mFirstFree; + mFirstFree = entry; +} + +} // namespace + +void +nsTimerEvent::Init() +{ + sAllocator = new TimerEventAllocator(); +} + +void +nsTimerEvent::Shutdown() +{ + sCanDeleteAllocator = true; + DeleteAllocatorIfNeeded(); +} + +void +nsTimerEvent::DeleteAllocatorIfNeeded() +{ + if (sCanDeleteAllocator && sAllocatorUsers == 0) { + delete sAllocator; + sAllocator = nullptr; + } +} + +NS_IMETHODIMP +nsTimerEvent::Run() +{ + if (mGeneration != mTimer->GetGeneration()) { + return NS_OK; + } + + if (MOZ_LOG_TEST(GetTimerLog(), LogLevel::Debug)) { + TimeStamp now = TimeStamp::Now(); + MOZ_LOG(GetTimerLog(), LogLevel::Debug, + ("[this=%p] time between PostTimerEvent() and Fire(): %fms\n", + this, (now - mInitTime).ToMilliseconds())); + } + + mTimer->Fire(); + // Since nsTimerImpl is not thread-safe, we should release |mTimer| + // here in the target thread to avoid race condition. Otherwise, + // ~nsTimerEvent() which calls nsTimerImpl::Release() could run in the + // timer thread and result in race condition. + mTimer = nullptr; + + return NS_OK; +} + nsresult TimerThread::Init() { @@ -93,6 +288,8 @@ TimerThread::Init() return NS_OK; } + nsTimerEvent::Init(); + if (mInitInProgress.exchange(true) == false) { // We hold on to mThread to keep the thread alive. nsresult rv = NS_NewThread(getter_AddRefs(mThread), this); @@ -167,6 +364,8 @@ TimerThread::Shutdown() mThread->Shutdown(); // wait for the thread to die + nsTimerEvent::Shutdown(); + MOZ_LOG(GetTimerLog(), LogLevel::Debug, ("TimerThread::Shutdown end\n")); return NS_OK; } @@ -262,15 +461,10 @@ TimerThread::Run() ("Timer thread woke up %fms from when it was supposed to\n", fabs((now - timerRef->mTimeout).ToMilliseconds()))); - { - // We release mMonitor around the Fire call to avoid deadlock. - MonitorAutoUnlock unlock(mMonitor); - - // We are going to let the call to PostTimerEvent here handle the - // release of the timer so that we don't end up releasing the timer - // on the TimerThread instead of on the thread it targets. - timerRef = nsTimerImpl::PostTimerEvent(timerRef.forget()); - } + // We are going to let the call to PostTimerEvent here handle the + // release of the timer so that we don't end up releasing the timer + // on the TimerThread instead of on the thread it targets. + timerRef = PostTimerEvent(timerRef.forget()); if (timerRef) { // We got our reference back due to an error. @@ -484,6 +678,68 @@ TimerThread::ReleaseTimerInternal(nsTimerImpl* aTimer) NS_RELEASE(aTimer); } +already_AddRefed +TimerThread::PostTimerEvent(already_AddRefed aTimerRef) +{ + mMonitor.AssertCurrentThreadOwns(); + + nsRefPtr timer(aTimerRef); + if (!timer->mEventTarget) { + NS_ERROR("Attempt to post timer event to NULL event target"); + return timer.forget(); + } + + // XXX we may want to reuse this nsTimerEvent in the case of repeating timers. + + // Since we already addref'd 'timer', we don't need to addref here. + // We will release either in ~nsTimerEvent(), or pass the reference back to + // the caller. We need to copy the generation number from this timer into the + // event, so we can avoid firing a timer that was re-initialized after being + // canceled. + + nsRefPtr event = new nsTimerEvent; + if (!event) { + return timer.forget(); + } + + if (MOZ_LOG_TEST(GetTimerLog(), LogLevel::Debug)) { + event->mInitTime = TimeStamp::Now(); + } + + // If this is a repeating precise timer, we need to calculate the time for + // the next timer to fire before we make the callback. But don't re-arm. + if (timer->IsRepeatingPrecisely()) { + timer->SetDelayInternal(timer->mDelay); + } + +#ifdef MOZ_TASK_TRACER + // During the dispatch of TimerEvent, we overwrite the current TraceInfo + // partially with the info saved in timer earlier, and restore it back by + // AutoSaveCurTraceInfo. + AutoSaveCurTraceInfo saveCurTraceInfo; + (timer->GetTracedTask()).SetTLSTraceInfo(); +#endif + + nsIEventTarget* target = timer->mEventTarget; + event->SetTimer(timer.forget()); + + nsresult rv; + { + // We release mMonitor around the Dispatch because if this timer is targeted + // at the TimerThread we'll deadlock. + MonitorAutoUnlock unlock(mMonitor); + rv = target->Dispatch(event, NS_DISPATCH_NORMAL); + } + + if (NS_FAILED(rv)) { + timer = event->ForgetTimer(); + RemoveTimerInternal(timer); + return timer.forget(); + } + + return nullptr; +} + void TimerThread::DoBeforeSleep() { diff --git a/xpcom/threads/TimerThread.h b/xpcom/threads/TimerThread.h index 6443542406..7d18a4c6ee 100644 --- a/xpcom/threads/TimerThread.h +++ b/xpcom/threads/TimerThread.h @@ -68,6 +68,8 @@ private: bool RemoveTimerInternal(nsTimerImpl* aTimer); void ReleaseTimerInternal(nsTimerImpl* aTimer); + already_AddRefed PostTimerEvent(already_AddRefed aTimerRef); + nsCOMPtr mThread; Monitor mMonitor; diff --git a/xpcom/threads/nsITimer.idl b/xpcom/threads/nsITimer.idl index ae5de7fe9b..ade2168f2d 100644 --- a/xpcom/threads/nsITimer.idl +++ b/xpcom/threads/nsITimer.idl @@ -21,9 +21,24 @@ interface nsIEventTarget; */ class nsITimer; typedef void (*nsTimerCallbackFunc) (nsITimer *aTimer, void *aClosure); + +/** + * The signature of the timer name callback function passed to + * initWithNameableFuncCallback. + * This is the function that will get called when timer profiling is enabled + * via the "TimerFirings" log module. + * + * @param aTimer the timer which has expired + * @param aClosure opaque parameter passed to initWithFuncCallback + * @param aBuf a buffer in which to put the name + * @param aLen the length of the buffer + */ +typedef void (*nsTimerNameCallbackFunc) (nsITimer *aTimer, void *aClosure, + char *aBuf, size_t aLen); %} native nsTimerCallbackFunc(nsTimerCallbackFunc); +native nsTimerNameCallbackFunc(nsTimerNameCallbackFunc); /** * The callback interface for timers. @@ -57,7 +72,7 @@ interface nsITimerCallback : nsISupports * target thread, or races may occur with bad results like timers firing after * they've been canceled, and/or not firing after re-initiatization. */ -[scriptable, uuid(193fc37a-8aa4-4d29-aa57-1acd87c26b66)] +[scriptable, uuid(3de4b105-363c-482c-a409-baac83a01bfc)] interface nsITimer : nsISupports { /* Timer types */ @@ -65,7 +80,7 @@ interface nsITimer : nsISupports /** * Type of a timer that fires once only. */ - const short TYPE_ONE_SHOT = 0; + const short TYPE_ONE_SHOT = 0; /** * After firing, a TYPE_REPEATING_SLACK timer is stopped and not restarted @@ -75,28 +90,15 @@ interface nsITimer : nsISupports * * This is the preferable repeating type for most situations. */ - const short TYPE_REPEATING_SLACK = 1; - + const short TYPE_REPEATING_SLACK = 1; + /** - * An TYPE_REPEATING_PRECISE repeating timer aims to have constant period - * between firings. The processing time for each timer callback should not - * influence the timer period. However, if the processing for the last - * timer firing could not be completed until just before the next firing - * occurs, then you could have two timer notification routines being - * executed in quick succession. Furthermore, if your callback processing - * time is longer than the timer period, then the timer will post more - * notifications while your callback is running. For example, if a - * REPEATING_PRECISE timer has a 10ms period and a callback takes 50ms, - * then by the time the callback is done there will be 5 events to run the - * timer callback in the event queue. Furthermore, the next scheduled time - * will always advance by exactly the delay every time the timer fires. - * This means that if the clock increments without the timer thread running - * (e.g. the computer is asleep) when the timer thread gets to run again it - * will post all the events that it "missed" while it wasn't running. Use - * this timer type with extreme caution. Chances are, this is not what you - * want. + * TYPE_REPEATING_PRECISE is just a synonym for + * TYPE_REPEATING_PRECISE_CAN_SKIP. They used to be distinct, but the old + * TYPE_REPEATING_PRECISE kind was similar to TYPE_REPEATING_PRECISE_CAN_SKIP + * while also being less useful. So the distinction was removed. */ - const short TYPE_REPEATING_PRECISE = 2; + const short TYPE_REPEATING_PRECISE = 2; /** * A TYPE_REPEATING_PRECISE_CAN_SKIP repeating timer aims to have constant @@ -105,10 +107,9 @@ interface nsITimer : nsISupports * guarantees that it will not queue up new events to fire the callback * until the previous callback event finishes firing. If the callback * takes a long time, then the next callback will be scheduled immediately - * afterward, but only once, unlike TYPE_REPEATING_PRECISE. If you want a - * non-slack timer, you probably want this one. + * afterward, but only once. This is the only non-slack timer available. */ - const short TYPE_REPEATING_PRECISE_CAN_SKIP = 3; + const short TYPE_REPEATING_PRECISE_CAN_SKIP = 3; /** * Initialize a timer that will fire after the said delay. @@ -165,6 +166,40 @@ interface nsITimer : nsISupports */ void cancel(); + /** + * Like initWithFuncCallback, but also takes a name for the timer; the name + * will be used when timer profiling is enabled via the "TimerFirings" log + * module. + * + * @param aFunc The function to invoke + * @param aClosure An opaque pointer to pass to that function + * @param aDelay The millisecond interval + * @param aType Timer type per TYPE* consts defined above + * @param aName The timer's name + */ + [noscript] void initWithNamedFuncCallback(in nsTimerCallbackFunc aCallback, + in voidPtr aClosure, + in unsigned long aDelay, + in unsigned long aType, + in string aName); + + /** + * Like initWithNamedFuncCallback, but instead of a timer name it takes a + * callback that will provide a name when the timer fires. + * + * @param aFunc The function to invoke + * @param aClosure An opaque pointer to pass to that function + * @param aDelay The millisecond interval + * @param aType Timer type per TYPE* consts defined above + * @param aNameCallback The callback function + */ + [noscript] void initWithNameableFuncCallback( + in nsTimerCallbackFunc aCallback, + in voidPtr aClosure, + in unsigned long aDelay, + in unsigned long aType, + in nsTimerNameCallbackFunc aNameCallback); + /** * The millisecond delay of the timeout. * diff --git a/xpcom/threads/nsTimerImpl.cpp b/xpcom/threads/nsTimerImpl.cpp index 4b196e60f4..bbafcb8eea 100644 --- a/xpcom/threads/nsTimerImpl.cpp +++ b/xpcom/threads/nsTimerImpl.cpp @@ -9,7 +9,6 @@ #include "nsAutoPtr.h" #include "nsThreadManager.h" #include "nsThreadUtils.h" -#include "plarena.h" #include "pratom.h" #include "GeckoProfiler.h" #include "mozilla/Atomics.h" @@ -22,6 +21,15 @@ using namespace mozilla::tasktracer; #endif +#ifdef XP_WIN +#include +#ifndef getpid +#define getpid _getpid +#endif +#else +#include +#endif + using mozilla::Atomic; using mozilla::LogLevel; using mozilla::TimeDuration; @@ -30,6 +38,7 @@ using mozilla::TimeStamp; static Atomic gGenerator; static TimerThread* gThread = nullptr; +// This module prints info about the precision of timers. PRLogModuleInfo* GetTimerLog() { @@ -40,6 +49,44 @@ GetTimerLog() return sLog; } +// This module prints info about which timers are firing, which is useful for +// wakeups for the purposes of power profiling. Set the following environment +// variable before starting the browser. +// +// NSPR_LOG_MODULES=TimerFirings:4 +// +// Then a line will be printed for every timer that fires. The name used for a +// |CallbackType::Function| timer depends on the circumstances. +// +// - If it was explicitly named (e.g. it was initialized with +// InitWithNamedFuncCallback()) then that explicit name will be shown. +// +// - Otherwise, if we are on a platform that supports function name lookup +// (currently only Mac) then the looked-up name will be shown with a +// "[from dladdr]" annotation. +// +// - Otherwise, no name will be printed. If many timers hit this case then +// you'll need to re-run the workload on a Mac to find out which timers they +// are, and then give them explicit names. +// +// If you redirect this output to a file called "out", you can then +// post-process it with a command something like the following. +// +// cat out | grep timer | sort | uniq -c | sort -r -n +// +// This will show how often each unique line appears, with the most common ones +// first. +// +PRLogModuleInfo* +GetTimerFiringsLog() +{ + static PRLogModuleInfo* sLog; + if (!sLog) { + sLog = PR_NewLogModule("TimerFirings"); + } + return sLog; +} + #include double nsTimerImpl::sDeltaSumSquared = 0; @@ -66,155 +113,6 @@ myNS_MeanAndStdDev(double n, double sumOfValues, double sumOfSquaredValues, *stdDevResult = stdDev; } -namespace { - -// TimerEventAllocator is a thread-safe allocator used only for nsTimerEvents. -// It's needed to avoid contention over the default allocator lock when -// firing timer events (see bug 733277). The thread-safety is required because -// nsTimerEvent objects are allocated on the timer thread, and freed on another -// thread. Because TimerEventAllocator has its own lock, contention over that -// lock is limited to the allocation and deallocation of nsTimerEvent objects. -// -// Because this allocator is layered over PLArenaPool, it never shrinks -- even -// "freed" nsTimerEvents aren't truly freed, they're just put onto a free-list -// for later recycling. So the amount of memory consumed will always be equal -// to the high-water mark consumption. But nsTimerEvents are small and it's -// unusual to have more than a few hundred of them, so this shouldn't be a -// problem in practice. - -class TimerEventAllocator -{ -private: - struct FreeEntry - { - FreeEntry* mNext; - }; - - PLArenaPool mPool; - FreeEntry* mFirstFree; - mozilla::Monitor mMonitor; - -public: - TimerEventAllocator() - : mFirstFree(nullptr) - , mMonitor("TimerEventAllocator") - { - PL_InitArenaPool(&mPool, "TimerEventPool", 4096, /* align = */ 0); - } - - ~TimerEventAllocator() - { - PL_FinishArenaPool(&mPool); - } - - void* Alloc(size_t aSize); - void Free(void* aPtr); -}; - -} // namespace - -class nsTimerEvent : public nsRunnable -{ -public: - NS_IMETHOD Run(); - - nsTimerEvent() - : mTimer() - , mGeneration(0) - { - MOZ_COUNT_CTOR(nsTimerEvent); - - MOZ_ASSERT(gThread->IsOnTimerThread(), - "nsTimer must always be allocated on the timer thread"); - - sAllocatorUsers++; - } - - TimeStamp mInitTime; - - static void Init(); - static void Shutdown(); - static void DeleteAllocatorIfNeeded(); - - static void* operator new(size_t aSize) CPP_THROW_NEW - { - return sAllocator->Alloc(aSize); - } - void operator delete(void* aPtr) - { - sAllocator->Free(aPtr); - DeleteAllocatorIfNeeded(); - } - - already_AddRefed ForgetTimer() - { - return mTimer.forget(); - } - - void SetTimer(already_AddRefed aTimer) - { - mTimer = aTimer; - mGeneration = mTimer->GetGeneration(); - } - -private: - ~nsTimerEvent() - { - MOZ_COUNT_DTOR(nsTimerEvent); - - MOZ_ASSERT(!sCanDeleteAllocator || sAllocatorUsers > 0, - "This will result in us attempting to deallocate the nsTimerEvent allocator twice"); - sAllocatorUsers--; - } - - nsRefPtr mTimer; - int32_t mGeneration; - - static TimerEventAllocator* sAllocator; - static Atomic sAllocatorUsers; - static bool sCanDeleteAllocator; -}; - -TimerEventAllocator* nsTimerEvent::sAllocator = nullptr; -Atomic nsTimerEvent::sAllocatorUsers; -bool nsTimerEvent::sCanDeleteAllocator = false; - -namespace { - -void* -TimerEventAllocator::Alloc(size_t aSize) -{ - MOZ_ASSERT(aSize == sizeof(nsTimerEvent)); - - mozilla::MonitorAutoLock lock(mMonitor); - - void* p; - if (mFirstFree) { - p = mFirstFree; - mFirstFree = mFirstFree->mNext; - } else { - PL_ARENA_ALLOCATE(p, &mPool, aSize); - if (!p) { - return nullptr; - } - } - - return p; -} - -void -TimerEventAllocator::Free(void* aPtr) -{ - mozilla::MonitorAutoLock lock(mMonitor); - - FreeEntry* entry = reinterpret_cast(aPtr); - - entry->mNext = mFirstFree; - mFirstFree = entry; -} - -} // namespace - NS_IMPL_QUERY_INTERFACE(nsTimerImpl, nsITimer) NS_IMPL_ADDREF(nsTimerImpl) @@ -278,6 +176,7 @@ nsTimerImpl::Release(void) nsTimerImpl::nsTimerImpl() : mClosure(nullptr), + mName(nsTimerImpl::Nothing), mCallbackType(CallbackType::Unknown), mFiring(false), mArmed(false), @@ -302,8 +201,6 @@ nsTimerImpl::Startup() { nsresult rv; - nsTimerEvent::Init(); - gThread = new TimerThread(); NS_ADDREF(gThread); @@ -336,8 +233,6 @@ nsTimerImpl::Shutdown() gThread->Shutdown(); NS_RELEASE(gThread); - - nsTimerEvent::Shutdown(); } @@ -359,23 +254,7 @@ nsTimerImpl::InitCommon(uint32_t aDelay, uint32_t aType) return rv; } - /** - * In case of re-Init, both with and without a preceding Cancel, clear the - * mCanceled flag and assign a new mGeneration. But first, remove any armed - * timer from the timer thread's list. - * - * If we are racing with the timer thread to remove this timer and we lose, - * the RemoveTimer call made here will fail to find this timer in the timer - * thread's list, and will return false harmlessly. We test mArmed here to - * avoid the small overhead in RemoveTimer of locking the timer thread and - * checking its list for this timer. It's safe to test mArmed even though - * it might be cleared on another thread in the next cycle (or even already - * be cleared by another CPU whose store hasn't reached our CPU's cache), - * because RemoveTimer is idempotent. - */ - if (mArmed) { - gThread->RemoveTimer(this); - } + gThread->RemoveTimer(this); mCanceled = false; mTimeout = TimeStamp(); mGeneration = gGenerator++; @@ -386,11 +265,12 @@ nsTimerImpl::InitCommon(uint32_t aDelay, uint32_t aType) return gThread->AddTimer(this); } -NS_IMETHODIMP -nsTimerImpl::InitWithFuncCallback(nsTimerCallbackFunc aFunc, - void* aClosure, - uint32_t aDelay, - uint32_t aType) +nsresult +nsTimerImpl::InitWithFuncCallbackCommon(nsTimerCallbackFunc aFunc, + void* aClosure, + uint32_t aDelay, + uint32_t aType, + Name aName) { if (NS_WARN_IF(!aFunc)) { return NS_ERROR_INVALID_ARG; @@ -400,10 +280,43 @@ nsTimerImpl::InitWithFuncCallback(nsTimerCallbackFunc aFunc, mCallbackType = CallbackType::Function; mCallback.c = aFunc; mClosure = aClosure; + mName = aName; return InitCommon(aDelay, aType); } +NS_IMETHODIMP +nsTimerImpl::InitWithFuncCallback(nsTimerCallbackFunc aFunc, + void* aClosure, + uint32_t aDelay, + uint32_t aType) +{ + Name name(nsTimerImpl::Nothing); + return InitWithFuncCallbackCommon(aFunc, aClosure, aDelay, aType, name); +} + +NS_IMETHODIMP +nsTimerImpl::InitWithNamedFuncCallback(nsTimerCallbackFunc aFunc, + void* aClosure, + uint32_t aDelay, + uint32_t aType, + const char* aNameString) +{ + Name name(aNameString); + return InitWithFuncCallbackCommon(aFunc, aClosure, aDelay, aType, name); +} + +NS_IMETHODIMP +nsTimerImpl::InitWithNameableFuncCallback(nsTimerCallbackFunc aFunc, + void* aClosure, + uint32_t aDelay, + uint32_t aType, + nsTimerNameCallbackFunc aNameFunc) +{ + Name name(aNameFunc); + return InitWithFuncCallbackCommon(aFunc, aClosure, aDelay, aType, name); +} + NS_IMETHODIMP nsTimerImpl::InitWithCallback(nsITimerCallback* aCallback, uint32_t aDelay, @@ -461,12 +374,6 @@ nsTimerImpl::SetDelay(uint32_t aDelay) return NS_ERROR_NOT_INITIALIZED; } - // If we're already repeating precisely, update mTimeout now so that the - // new delay takes effect in the future. - if (!mTimeout.IsNull() && mType == TYPE_REPEATING_PRECISE) { - mTimeout = TimeStamp::Now(); - } - SetDelayInternal(aDelay); if (!mFiring && gThread) { @@ -608,6 +515,10 @@ nsTimerImpl::Fire() } ReleaseCallback(); + if (MOZ_LOG_TEST(GetTimerFiringsLog(), LogLevel::Debug)) { + LogFiring(callbackType, callback); + } + switch (callbackType) { case CallbackType::Function: callback.c(this, mClosure); @@ -646,10 +557,9 @@ nsTimerImpl::Fire() ("[this=%p] Took %fms to fire timer callback\n", this, (TimeStamp::Now() - now).ToMilliseconds())); - // Reschedule repeating timers, except REPEATING_PRECISE which already did - // that in PostTimerEvent, but make sure that we aren't armed already (which - // can happen if the callback reinitialized the timer). - if (IsRepeating() && mType != TYPE_REPEATING_PRECISE && !mArmed) { + // Reschedule repeating timers, but make sure that we aren't armed already + // (which can happen if the callback reinitialized the timer). + if (IsRepeating() && !mArmed) { if (mType == TYPE_REPEATING_SLACK) { SetDelayInternal(mDelay); // force mTimeout to be recomputed. For } @@ -661,115 +571,106 @@ nsTimerImpl::Fire() } } -void -nsTimerEvent::Init() -{ - sAllocator = new TimerEventAllocator(); -} - -void -nsTimerEvent::Shutdown() -{ - sCanDeleteAllocator = true; - DeleteAllocatorIfNeeded(); -} - -void -nsTimerEvent::DeleteAllocatorIfNeeded() -{ - if (sCanDeleteAllocator && sAllocatorUsers == 0) { - delete sAllocator; - sAllocator = nullptr; - } -} - -NS_IMETHODIMP -nsTimerEvent::Run() -{ - if (mGeneration != mTimer->GetGeneration()) { - return NS_OK; - } - - if (MOZ_LOG_TEST(GetTimerLog(), LogLevel::Debug)) { - TimeStamp now = TimeStamp::Now(); - MOZ_LOG(GetTimerLog(), LogLevel::Debug, - ("[this=%p] time between PostTimerEvent() and Fire(): %fms\n", - this, (now - mInitTime).ToMilliseconds())); - } - - mTimer->Fire(); - // Since nsTimerImpl is not thread-safe, we should release |mTimer| - // here in the target thread to avoid race condition. Otherwise, - // ~nsTimerEvent() which calls nsTimerImpl::Release() could run in the - // timer thread and result in race condition. - mTimer = nullptr; - - return NS_OK; -} - -already_AddRefed -nsTimerImpl::PostTimerEvent(already_AddRefed aTimerRef) -{ - nsRefPtr timer(aTimerRef); - if (!timer->mEventTarget) { - NS_ERROR("Attempt to post timer event to NULL event target"); - return timer.forget(); - } - - // XXX we may want to reuse this nsTimerEvent in the case of repeating timers. - - // Since TimerThread addref'd 'timer' for us, we don't need to addref here. - // We will release either in ~nsTimerEvent(), or pass the reference back to - // the caller. We need to copy the generation number from this timer into the - // event, so we can avoid firing a timer that was re-initialized after being - // canceled. - - // Note: We override operator new for this class, and the override is - // fallible! - nsRefPtr event = new nsTimerEvent; - if (!event) { - return timer.forget(); - } - - if (MOZ_LOG_TEST(GetTimerLog(), LogLevel::Debug)) { - event->mInitTime = TimeStamp::Now(); - } - - // If this is a repeating precise timer, we need to calculate the time for - // the next timer to fire before we make the callback. - if (timer->IsRepeatingPrecisely()) { - timer->SetDelayInternal(timer->mDelay); - - // But only re-arm REPEATING_PRECISE timers. - if (gThread && timer->mType == TYPE_REPEATING_PRECISE) { - nsresult rv = gThread->AddTimer(timer); - if (NS_FAILED(rv)) { - return timer.forget(); - } - } - } - -#ifdef MOZ_TASK_TRACER - // During the dispatch of TimerEvent, we overwrite the current TraceInfo - // partially with the info saved in timer earlier, and restore it back by - // AutoSaveCurTraceInfo. - AutoSaveCurTraceInfo saveCurTraceInfo; - (timer->GetTracedTask()).SetTLSTraceInfo(); +#if defined(XP_MACOSX) +#include +#include #endif - nsIEventTarget* target = timer->mEventTarget; - event->SetTimer(timer.forget()); - - nsresult rv = target->Dispatch(event, NS_DISPATCH_NORMAL); - if (NS_FAILED(rv)) { - timer = event->ForgetTimer(); - if (gThread) { - gThread->RemoveTimer(timer); - } - return timer.forget(); +// See the big comment above GetTimerFiringsLog() to understand this code. +void +nsTimerImpl::LogFiring(CallbackType aCallbackType, CallbackUnion aCallback) +{ + const char* typeStr; + switch (mType) { + case TYPE_ONE_SHOT: typeStr = "ONE_SHOT"; break; + case TYPE_REPEATING_SLACK: typeStr = "SLACK "; break; + case TYPE_REPEATING_PRECISE: /* fall through */ + case TYPE_REPEATING_PRECISE_CAN_SKIP: typeStr = "PRECISE "; break; + default: MOZ_CRASH("bad type"); } - return nullptr; + switch (aCallbackType) { + case CallbackType::Function: { + bool needToFreeName = false; + const char* annotation = ""; + const char* name; + static const size_t buflen = 1024; + char buf[buflen]; + + if (mName.is()) { + name = mName.as(); + + } else if (mName.is()) { + mName.as()(this, mClosure, buf, buflen); + name = buf; + + } else { + MOZ_ASSERT(mName.is()); +#if defined(XP_MACOSX) + annotation = "[from dladdr] "; + + Dl_info info; + if (dladdr(reinterpret_cast(aCallback.c), &info) == 0) { + name = "???[dladdr: failed]"; + } else if (!info.dli_sname) { + name = "???[dladdr: no matching symbol]"; + + } else { + int status; + name = abi::__cxa_demangle(info.dli_sname, nullptr, nullptr, &status); + if (status == 0) { + // Success. Because we didn't pass in a buffer to __cxa_demangle it + // allocates its own one with malloc() which we must free() later. + MOZ_ASSERT(name); + needToFreeName = true; + } else if (status == -1) { + name = "???[__cxa_demangle: OOM]"; + } else if (status == -2) { + name = "???[__cxa_demangle: invalid mangled name]"; + } else if (status == -3) { + name = "???[__cxa_demangle: invalid argument]"; + } else { + name = "???[__cxa_demangle: unexpected status value]"; + } + } +#else + name = "???[dladdr: unavailable/doesn't work on this platform]"; +#endif + } + + MOZ_LOG(GetTimerFiringsLog(), LogLevel::Debug, + ("[%d] fn timer (%s %5d ms): %s%s\n", + getpid(), typeStr, mDelay, annotation, name)); + + if (needToFreeName) { + free(const_cast(name)); + } + + break; + } + + case CallbackType::Interface: { + MOZ_LOG(GetTimerFiringsLog(), LogLevel::Debug, + ("[%d] iface timer (%s %5d ms): %p\n", + getpid(), typeStr, mDelay, aCallback.i)); + break; + } + + case CallbackType::Observer: { + MOZ_LOG(GetTimerFiringsLog(), LogLevel::Debug, + ("[%d] obs timer (%s %5d ms): %p\n", + getpid(), typeStr, mDelay, aCallback.o)); + break; + } + + case CallbackType::Unknown: + default: { + MOZ_LOG(GetTimerFiringsLog(), LogLevel::Debug, + ("[%d] ??? timer (%s, %5d ms)\n", + getpid(), typeStr, mDelay)); + break; + } + } } void @@ -780,9 +681,7 @@ nsTimerImpl::SetDelayInternal(uint32_t aDelay) mDelay = aDelay; TimeStamp now = TimeStamp::Now(); - if (mTimeout.IsNull() || mType != TYPE_REPEATING_PRECISE) { - mTimeout = now; - } + mTimeout = now; mTimeout += delayInterval; @@ -801,6 +700,8 @@ nsTimerImpl::SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) const return aMallocSizeOf(this); } +/* static */ const nsTimerImpl::NameNothing nsTimerImpl::Nothing = 0; + #ifdef MOZ_TASK_TRACER void nsTimerImpl::GetTLSTraceInfo() diff --git a/xpcom/threads/nsTimerImpl.h b/xpcom/threads/nsTimerImpl.h index ec7769dccc..6f1c900dc6 100644 --- a/xpcom/threads/nsTimerImpl.h +++ b/xpcom/threads/nsTimerImpl.h @@ -13,9 +13,10 @@ #include "nsCOMPtr.h" +#include "mozilla/Attributes.h" #include "mozilla/Logging.h" #include "mozilla/TimeStamp.h" -#include "mozilla/Attributes.h" +#include "mozilla/Variant.h" #ifdef MOZ_TASK_TRACER #include "TracedTaskCommon.h" @@ -42,30 +43,33 @@ public: static void Shutdown(); friend class TimerThread; + friend class nsTimerEvent; friend struct TimerAdditionComparator; - void Fire(); - // If a failure is encountered, the reference is returned to the caller - static already_AddRefed PostTimerEvent( - already_AddRefed aTimerRef); - void SetDelayInternal(uint32_t aDelay); - NS_DECL_THREADSAFE_ISUPPORTS NS_DECL_NSITIMER - int32_t GetGeneration() - { - return mGeneration; - } + virtual size_t SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) const override; + +private: + void SetDelayInternal(uint32_t aDelay); + + void Fire(); #ifdef MOZ_TASK_TRACER void GetTLSTraceInfo(); mozilla::tasktracer::TracedTaskCommon GetTracedTask(); #endif - virtual size_t SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) const override; + // If a failure is encountered, the reference is returned to the caller + static already_AddRefed PostTimerEvent( + already_AddRefed aTimerRef); + + int32_t GetGeneration() + { + return mGeneration; + } -private: enum class CallbackType : uint8_t { Unknown = 0, Interface = 1, @@ -114,10 +118,33 @@ private: union CallbackUnion { nsTimerCallbackFunc c; - nsITimerCallback* i; - nsIObserver* o; + // These refcounted references are managed manually, as they are in a union + nsITimerCallback* MOZ_OWNING_REF i; + nsIObserver* MOZ_OWNING_REF o; } mCallback; + void LogFiring(CallbackType aCallbackType, CallbackUnion aCallbackUnion); + + // |Name| is a tagged union type representing one of (a) nothing, (b) a + // string, or (c) a function. mozilla::Variant doesn't naturally handle the + // "nothing" case, so we define a dummy type and value (which is unused and + // so the exact value doesn't matter) for it. + typedef const int NameNothing; + typedef const char* NameString; + typedef nsTimerNameCallbackFunc NameFunc; + typedef mozilla::Variant Name; + static const NameNothing Nothing; + + nsresult InitWithFuncCallbackCommon(nsTimerCallbackFunc aFunc, + void* aClosure, + uint32_t aDelay, + uint32_t aType, + Name aName); + + // This is set by Init. It records the name (if there is one) for the timer, + // for use when logging timer firings. + Name mName; + // Some callers expect to be able to access the callback while the // timer is firing. nsCOMPtr mTimerCallbackWhileFiring;