Bug 1245954 - Console StartTimer/StopTimer and IncrementCounter should run in the owning thread, r=bz

This commit is contained in:
Andrea Marchesini 2016-02-11 17:09:22 +00:00
parent 7761ceb26d
commit c02b7c4395
2 changed files with 266 additions and 73 deletions

View File

@ -84,6 +84,11 @@ public:
: mMethodName(Console::MethodLog)
, mPrivate(false)
, mTimeStamp(JS_Now() / PR_USEC_PER_MSEC)
, mStartTimerValue(0)
, mStartTimerStatus(false)
, mStopTimerDuration(0)
, mStopTimerStatus(false)
, mCountValue(MAX_PAGE_COUNTERS)
, mIDType(eUnknown)
, mOuterIDNumber(0)
, mInnerIDNumber(0)
@ -172,7 +177,34 @@ public:
Console::MethodName mMethodName;
bool mPrivate;
int64_t mTimeStamp;
DOMHighResTimeStamp mMonotonicTimer;
// These values are set in the owning thread and they contain the timestamp of
// when the new timer has started, the name of it and the status of the
// creation of it. If status is false, something went wrong. User
// DOMHighResTimeStamp instead mozilla::TimeStamp because we use
// monotonicTimer from Performance.now();
// They will be set on the owning thread and never touched again on that
// thread. They will be used on the main-thread in order to create a
// ConsoleTimerStart dictionary when console.time() is used.
DOMHighResTimeStamp mStartTimerValue;
nsString mStartTimerLabel;
bool mStartTimerStatus;
// These values are set in the owning thread and they contain the duration,
// the name and the status of the StopTimer method. If status is false,
// something went wrong. They will be set on the owning thread and never
// touched again on that thread. They will be used on the main-thread in order
// to create a ConsoleTimerEnd dictionary. This members are set when
// console.timeEnd() is called.
double mStopTimerDuration;
nsString mStopTimerLabel;
bool mStopTimerStatus;
// These 2 values are set by IncreaseCounter on the owning thread and they are
// used on the main-thread by CreateCounterValue. These members are set when
// console.count() is called.
nsString mCountLabel;
uint32_t mCountValue;
// The concept of outerID and innerID is misleading because when a
// ConsoleCallData is created from a window, these are the window IDs, but
@ -345,7 +377,7 @@ private:
void
RunWithWindow(nsPIDOMWindowInner* aWindow)
{
MOZ_ASSERT(NS_IsMainThread());
AssertIsOnMainThread();
AutoJSAPI jsapi;
MOZ_ASSERT(aWindow);
@ -367,7 +399,7 @@ private:
void
RunWindowless()
{
MOZ_ASSERT(NS_IsMainThread());
AssertIsOnMainThread();
WorkerPrivate* wp = mWorkerPrivate;
while (wp->GetParent()) {
@ -517,7 +549,7 @@ private:
RunConsole(JSContext* aCx, nsPIDOMWindowOuter* aOuterWindow,
nsPIDOMWindowInner* aInnerWindow) override
{
MOZ_ASSERT(NS_IsMainThread());
AssertIsOnMainThread();
MOZ_ASSERT(mCallData->mCopiedArguments.IsEmpty());
// The windows have to run in parallel.
@ -564,6 +596,8 @@ private:
void
ProcessCallData(JSContext* aCx)
{
AssertIsOnMainThread();
ClearException ce(aCx);
JS::Rooted<JS::Value> argumentsValue(aCx);
@ -658,7 +692,7 @@ private:
RunConsole(JSContext* aCx, nsPIDOMWindowOuter* aOuterWindow,
nsPIDOMWindowInner* aInnerWindow) override
{
MOZ_ASSERT(NS_IsMainThread());
AssertIsOnMainThread();
ClearException ce(aCx);
@ -793,7 +827,7 @@ NS_IMETHODIMP
Console::Observe(nsISupports* aSubject, const char* aTopic,
const char16_t* aData)
{
MOZ_ASSERT(NS_IsMainThread());
AssertIsOnMainThread();
if (strcmp(aTopic, "inner-window-destroyed")) {
return NS_OK;
@ -1120,6 +1154,8 @@ Console::Method(JSContext* aCx, MethodName aMethodName,
}
}
DOMHighResTimeStamp monotonicTimer;
// Monotonic timer for 'time' and 'timeEnd'
if (aMethodName == MethodTime ||
aMethodName == MethodTimeEnd ||
@ -1133,7 +1169,7 @@ Console::Method(JSContext* aCx, MethodName aMethodName,
return;
}
callData->mMonotonicTimer = performance->Now();
monotonicTimer = performance->Now();
nsDocShell* docShell = static_cast<nsDocShell*>(mWindow->GetDocShell());
RefPtr<TimelineConsumers> timelines = TimelineConsumers::Get();
@ -1175,12 +1211,36 @@ Console::Method(JSContext* aCx, MethodName aMethodName,
MOZ_ASSERT(workerPrivate);
TimeDuration duration =
mozilla::TimeStamp::Now() - workerPrivate->CreationTimeStamp();
mozilla::TimeStamp::Now() - workerPrivate->NowBaseTimeStamp();
callData->mMonotonicTimer = duration.ToMilliseconds();
monotonicTimer = duration.ToMilliseconds();
}
}
if (aMethodName == MethodTime && !aData.IsEmpty()) {
callData->mStartTimerStatus = StartTimer(aCx, aData[0],
monotonicTimer,
callData->mStartTimerLabel,
&callData->mStartTimerValue);
}
else if (aMethodName == MethodTimeEnd && !aData.IsEmpty()) {
callData->mStopTimerStatus = StopTimer(aCx, aData[0],
monotonicTimer,
callData->mStopTimerLabel,
&callData->mStopTimerDuration);
}
else if (aMethodName == MethodCount) {
ConsoleStackEntry frame;
if (callData->mTopStackFrame) {
frame = *callData->mTopStackFrame;
}
callData->mCountValue = IncreaseCounter(aCx, frame, aData,
callData->mCountLabel);
}
JS::Rooted<JSObject*> global(aCx, JS::CurrentGlobalOrNull(aCx));
if (NS_IsMainThread()) {
@ -1244,7 +1304,7 @@ Console::ProcessCallData(ConsoleCallData* aData, JS::Handle<JSObject*> aGlobal,
const Sequence<JS::Value>& aArguments)
{
MOZ_ASSERT(aData);
MOZ_ASSERT(NS_IsMainThread());
AssertIsOnMainThread();
ConsoleStackEntry frame;
if (aData->mTopStackFrame) {
@ -1322,15 +1382,20 @@ Console::ProcessCallData(ConsoleCallData* aData, JS::Handle<JSObject*> aGlobal,
}
else if (aData->mMethodName == MethodTime && !aArguments.IsEmpty()) {
event.mTimer = StartTimer(cx, aArguments[0], aData->mMonotonicTimer);
event.mTimer = CreateStartTimerValue(cx, aData->mStartTimerLabel,
aData->mStartTimerValue,
aData->mStartTimerStatus);
}
else if (aData->mMethodName == MethodTimeEnd && !aArguments.IsEmpty()) {
event.mTimer = StopTimer(cx, aArguments[0], aData->mMonotonicTimer);
event.mTimer = CreateStopTimerValue(cx, aData->mStopTimerLabel,
aData->mStopTimerDuration,
aData->mStopTimerStatus);
}
else if (aData->mMethodName == MethodCount) {
event.mCounter = IncreaseCounter(cx, frame, aArguments);
event.mCounter = CreateCounterValue(cx, aData->mCountLabel,
aData->mCountValue);
}
// We want to create a console event object and pass it to our
@ -1460,6 +1525,8 @@ Console::ProcessArguments(JSContext* aCx,
Sequence<JS::Value>& aSequence,
Sequence<JS::Value>& aStyles) const
{
AssertIsOnMainThread();
if (aData.IsEmpty()) {
return true;
}
@ -1697,7 +1764,7 @@ void
Console::MakeFormatString(nsCString& aFormat, int32_t aInteger,
int32_t aMantissa, char aCh) const
{
MOZ_ASSERT(NS_IsMainThread());
AssertIsOnMainThread();
aFormat.Append('%');
if (aInteger >= 0) {
@ -1717,6 +1784,8 @@ Console::ComposeGroupName(JSContext* aCx,
const Sequence<JS::Value>& aData,
nsAString& aName) const
{
AssertIsOnMainThread();
for (uint32_t i = 0; i < aData.Length(); ++i) {
if (i != 0) {
aName.AppendASCII(" ");
@ -1737,13 +1806,50 @@ Console::ComposeGroupName(JSContext* aCx,
}
}
JS::Value
bool
Console::StartTimer(JSContext* aCx, const JS::Value& aName,
DOMHighResTimeStamp aTimestamp)
DOMHighResTimeStamp aTimestamp,
nsAString& aTimerLabel,
DOMHighResTimeStamp* aTimerValue)
{
MOZ_ASSERT(NS_IsMainThread());
MOZ_ASSERT(aTimerValue);
*aTimerValue = 0;
if (mTimerRegistry.Count() >= MAX_PAGE_TIMERS) {
return false;
}
JS::Rooted<JS::Value> name(aCx, aName);
JS::Rooted<JSString*> jsString(aCx, JS::ToString(aCx, name));
if (!jsString) {
return false;
}
nsAutoJSString label;
if (!label.init(aCx, jsString)) {
return false;
}
DOMHighResTimeStamp entry;
if (!mTimerRegistry.Get(label, &entry)) {
mTimerRegistry.Put(label, aTimestamp);
} else {
aTimestamp = entry;
}
aTimerLabel = label;
*aTimerValue = aTimestamp;
return true;
}
JS::Value
Console::CreateStartTimerValue(JSContext* aCx, const nsAString& aTimerLabel,
DOMHighResTimeStamp aTimerValue,
bool aTimerStatus) const
{
AssertIsOnMainThread();
if (!aTimerStatus) {
RootedDictionary<ConsoleTimerError> error(aCx);
JS::Rooted<JS::Value> value(aCx);
@ -1756,27 +1862,8 @@ Console::StartTimer(JSContext* aCx, const JS::Value& aName,
RootedDictionary<ConsoleTimerStart> timer(aCx);
JS::Rooted<JS::Value> name(aCx, aName);
JS::Rooted<JSString*> jsString(aCx, JS::ToString(aCx, name));
if (!jsString) {
return JS::UndefinedValue();
}
nsAutoJSString key;
if (!key.init(aCx, jsString)) {
return JS::UndefinedValue();
}
timer.mName = key;
DOMHighResTimeStamp entry;
if (!mTimerRegistry.Get(key, &entry)) {
mTimerRegistry.Put(key, aTimestamp);
} else {
aTimestamp = entry;
}
timer.mStarted = aTimestamp;
timer.mName = aTimerLabel;
timer.mStarted = aTimerValue;
JS::Rooted<JS::Value> value(aCx);
if (!ToJSValue(aCx, timer, &value)) {
@ -1786,33 +1873,51 @@ Console::StartTimer(JSContext* aCx, const JS::Value& aName,
return value;
}
JS::Value
bool
Console::StopTimer(JSContext* aCx, const JS::Value& aName,
DOMHighResTimeStamp aTimestamp)
DOMHighResTimeStamp aTimestamp,
nsAString& aTimerLabel,
double* aTimerDuration)
{
MOZ_ASSERT(NS_IsMainThread());
MOZ_ASSERT(aTimerDuration);
*aTimerDuration = 0;
JS::Rooted<JS::Value> name(aCx, aName);
JS::Rooted<JSString*> jsString(aCx, JS::ToString(aCx, name));
if (!jsString) {
return JS::UndefinedValue();
return false;
}
nsAutoJSString key;
if (!key.init(aCx, jsString)) {
return JS::UndefinedValue();
return false;
}
DOMHighResTimeStamp entry;
if (!mTimerRegistry.Get(key, &entry)) {
return JS::UndefinedValue();
return false;
}
mTimerRegistry.Remove(key);
aTimerLabel = key;
*aTimerDuration = aTimestamp - entry;
return true;
}
JS::Value
Console::CreateStopTimerValue(JSContext* aCx, const nsAString& aLabel,
double aDuration, bool aStatus) const
{
AssertIsOnMainThread();
if (!aStatus) {
return JS::UndefinedValue();
}
RootedDictionary<ConsoleTimerEnd> timer(aCx);
timer.mName = key;
timer.mDuration = aTimestamp - entry;
timer.mName = aLabel;
timer.mDuration = aDuration;
JS::Rooted<JS::Value> value(aCx);
if (!ToJSValue(aCx, timer, &value)) {
@ -1826,6 +1931,8 @@ bool
Console::ArgumentsToValueList(const Sequence<JS::Value>& aData,
Sequence<JS::Value>& aSequence) const
{
AssertIsOnMainThread();
for (uint32_t i = 0; i < aData.Length(); ++i) {
if (!aSequence.AppendElement(aData[i], fallible)) {
return false;
@ -1835,12 +1942,11 @@ Console::ArgumentsToValueList(const Sequence<JS::Value>& aData,
return true;
}
JS::Value
uint32_t
Console::IncreaseCounter(JSContext* aCx, const ConsoleStackEntry& aFrame,
const Sequence<JS::Value>& aArguments)
const Sequence<JS::Value>& aArguments,
nsAString& aCountLabel)
{
MOZ_ASSERT(NS_IsMainThread());
ClearException ce(aCx);
nsAutoString key;
@ -1864,25 +1970,40 @@ Console::IncreaseCounter(JSContext* aCx, const ConsoleStackEntry& aFrame,
}
uint32_t count = 0;
if (!mCounterRegistry.Get(key, &count)) {
if (mCounterRegistry.Count() >= MAX_PAGE_COUNTERS) {
RootedDictionary<ConsoleCounterError> error(aCx);
JS::Rooted<JS::Value> value(aCx);
if (!ToJSValue(aCx, error, &value)) {
return JS::UndefinedValue();
}
return value;
}
if (!mCounterRegistry.Get(key, &count) &&
mCounterRegistry.Count() >= MAX_PAGE_COUNTERS) {
return MAX_PAGE_COUNTERS;
}
++count;
mCounterRegistry.Put(key, count);
aCountLabel = label;
return count;
}
JS::Value
Console::CreateCounterValue(JSContext* aCx, const nsAString& aCountLabel,
uint32_t aCountValue) const
{
AssertIsOnMainThread();
ClearException ce(aCx);
if (aCountValue == MAX_PAGE_COUNTERS) {
RootedDictionary<ConsoleCounterError> error(aCx);
JS::Rooted<JS::Value> value(aCx);
if (!ToJSValue(aCx, error, &value)) {
return JS::UndefinedValue();
}
return value;
}
RootedDictionary<ConsoleCounter> data(aCx);
data.mLabel = label;
data.mCount = count;
data.mLabel = aCountLabel;
data.mCount = aCountValue;
JS::Rooted<JS::Value> value(aCx);
if (!ToJSValue(aCx, data, &value)) {
@ -1909,7 +2030,7 @@ Console::ShouldIncludeStackTrace(MethodName aMethodName) const
JSObject*
Console::GetOrCreateSandbox(JSContext* aCx, nsIPrincipal* aPrincipal)
{
MOZ_ASSERT(NS_IsMainThread());
AssertIsOnMainThread();
if (!mSandbox) {
nsIXPConnect* xpc = nsContentUtils::XPConnect();

View File

@ -181,13 +181,67 @@ private:
ComposeGroupName(JSContext* aCx, const Sequence<JS::Value>& aData,
nsAString& aName) const;
JS::Value
// StartTimer is called on the owning thread and populates aTimerLabel and
// aTimerValue. It returns false if a JS exception is thrown or if
// the max number of timers is reached.
// * aCx - the JSContext rooting aName.
// * aName - this is (should be) the name of the timer as JS::Value.
// * aTimestamp - the monotonicTimer for this context (taken from
// window->performance.now() or from Now() -
// workerPrivate->NowBaseTimeStamp() in workers.
// * aTimerLabel - This label will be populated with the aName converted to a
// string.
// * aTimerValue - the StartTimer value stored into (or taken from)
// mTimerRegistry.
bool
StartTimer(JSContext* aCx, const JS::Value& aName,
DOMHighResTimeStamp aTimestamp);
DOMHighResTimeStamp aTimestamp,
nsAString& aTimerLabel,
DOMHighResTimeStamp* aTimerValue);
// CreateStartTimerValue is called on the main thread only and generates a
// ConsoleTimerStart dictionary exposed as JS::Value. If aTimerStatus is
// false, it generates a ConsoleTimerError instead. It's called only after
// the execution StartTimer on the owning thread.
// * aCx - this is the context that will root the returned value.
// * aTimerLabel - this label must be what StartTimer received as aTimerLabel.
// * aTimerValue - this is what StartTimer received as aTimerValue
// * aTimerStatus - the return value of StartTimer.
JS::Value
CreateStartTimerValue(JSContext* aCx, const nsAString& aTimerLabel,
DOMHighResTimeStamp aTimerValue,
bool aTimerStatus) const;
// StopTimer follows the same pattern as StartTimer: it runs on the
// owning thread and populates aTimerLabel and aTimerDuration, used by
// CreateStopTimerValue on the main thread. It returns false if a JS
// exception is thrown or if the aName timer doesn't exist in mTimerRegistry.
// * aCx - the JSContext rooting aName.
// * aName - this is (should be) the name of the timer as JS::Value.
// * aTimestamp - the monotonicTimer for this context (taken from
// window->performance.now() or from Now() -
// workerPrivate->NowBaseTimeStamp() in workers.
// * aTimerLabel - This label will be populated with the aName converted to a
// string.
// * aTimerDuration - the difference between aTimestamp and when the timer
// started (see StartTimer).
bool
StopTimer(JSContext* aCx, const JS::Value& aName,
DOMHighResTimeStamp aTimestamp);
DOMHighResTimeStamp aTimestamp,
nsAString& aTimerLabel,
double* aTimerDuration);
// Executed on the main thread and generates a ConsoleTimerEnd dictionary
// exposed as JS::Value, or a ConsoleTimerError dictionary if aTimerStatus is
// false. See StopTimer.
// * aCx - this is the context that will root the returned value.
// * aTimerLabel - this label must be what StopTimer received as aTimerLabel.
// * aTimerDuration - this is what StopTimer received as aTimerDuration
// * aTimerStatus - the return value of StopTimer.
JS::Value
CreateStopTimerValue(JSContext* aCx, const nsAString& aTimerLabel,
double aTimerDuration,
bool aTimerStatus) const;
// The method populates a Sequence from an array of JS::Value.
bool
@ -198,9 +252,29 @@ private:
ProfileMethod(JSContext* aCx, const nsAString& aAction,
const Sequence<JS::Value>& aData);
JS::Value
// This method follows the same pattern as StartTimer: its runs on the owning
// thread and populates aCountLabel, used by CreateCounterValue on the
// main thread. Returns MAX_PAGE_COUNTERS in case of error otherwise the
// incremented counter value.
// * aCx - the JSContext rooting aData.
// * aFrame - the first frame of ConsoleCallData.
// * aData - the arguments received by the console.count() method.
// * aCountLabel - the label that will be populated by this method.
uint32_t
IncreaseCounter(JSContext* aCx, const ConsoleStackEntry& aFrame,
const Sequence<JS::Value>& aArguments);
const Sequence<JS::Value>& aData,
nsAString& aCountLabel);
// Executed on the main thread and generates a ConsoleCounter dictionary as
// JS::Value. If aCountValue is == MAX_PAGE_COUNTERS it generates a
// ConsoleCounterError instead. See IncreaseCounter.
// * aCx - this is the context that will root the returned value.
// * aCountLabel - this label must be what IncreaseCounter received as
// aTimerLabel.
// * aCountValue - the return value of IncreaseCounter.
JS::Value
CreateCounterValue(JSContext* aCx, const nsAString& aCountLabel,
uint32_t aCountValue) const;
bool
ShouldIncludeStackTrace(MethodName aMethodName) const;
@ -214,15 +288,13 @@ private:
void
UnregisterConsoleCallData(ConsoleCallData* aData);
// All these nsCOMPtr are touched on main-thread only.
// All these nsCOMPtr are touched on main thread only.
nsCOMPtr<nsPIDOMWindowInner> mWindow;
nsCOMPtr<nsIConsoleAPIStorage> mStorage;
RefPtr<JSObjectHolder> mSandbox;
// Touched on main-thread only.
// Touched on the owner thread.
nsDataHashtable<nsStringHashKey, DOMHighResTimeStamp> mTimerRegistry;
// Touched on main-thread only.
nsDataHashtable<nsStringHashKey, uint32_t> mCounterRegistry;
// Raw pointers because ConsoleCallData manages its own