Bug 1458466 - Implement Console.timeLog(optional DOMString label = "default"), r=bgrins

This commit is contained in:
Andrea Marchesini 2018-05-15 13:00:49 +02:00
parent 334f27e152
commit 325b9045d9
9 changed files with 208 additions and 52 deletions

View File

@ -108,6 +108,7 @@ method console.group
method console.groupCollapsed
method console.groupEnd
method console.time
method console.timeLog
method console.timeEnd
method console.exception
method console.timeStamp

View File

@ -94,8 +94,8 @@ public:
, mTimeStamp(JS_Now() / PR_USEC_PER_MSEC)
, mStartTimerValue(0)
, mStartTimerStatus(Console::eTimerUnknown)
, mStopTimerDuration(0)
, mStopTimerStatus(Console::eTimerUnknown)
, mLogTimerDuration(0)
, mLogTimerStatus(Console::eTimerUnknown)
, mCountValue(MAX_PAGE_COUNTERS)
, mIDType(eUnknown)
, mOuterIDNumber(0)
@ -221,14 +221,14 @@ public:
Console::TimerStatus 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,
// the name and the status of the LogTimer 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 in order to create a
// ConsoleTimerEnd dictionary. This members are set when
// console.timeEnd() is called.
double mStopTimerDuration;
nsString mStopTimerLabel;
Console::TimerStatus mStopTimerStatus;
// ConsoleTimerLogOrEnd dictionary. This members are set when
// console.timeEnd() or console.timeLog() are called.
double mLogTimerDuration;
nsString mLogTimerLabel;
Console::TimerStatus mLogTimerStatus;
// These 2 values are set by IncreaseCounter on the owning thread and they are
// used CreateCounterValue. These members are set when console.count() is
@ -1231,30 +1231,42 @@ Console::GroupEnd(const GlobalObject& aGlobal)
/* static */ void
Console::Time(const GlobalObject& aGlobal, const nsAString& aLabel)
{
StringMethod(aGlobal, aLabel, MethodTime, NS_LITERAL_STRING("time"));
StringMethod(aGlobal, aLabel, Sequence<JS::Value>(), MethodTime,
NS_LITERAL_STRING("time"));
}
/* static */ void
Console::TimeEnd(const GlobalObject& aGlobal, const nsAString& aLabel)
{
StringMethod(aGlobal, aLabel, MethodTimeEnd, NS_LITERAL_STRING("timeEnd"));
StringMethod(aGlobal, aLabel, Sequence<JS::Value>(), MethodTimeEnd,
NS_LITERAL_STRING("timeEnd"));
}
/* static */ void
Console::TimeLog(const GlobalObject& aGlobal, const nsAString& aLabel,
const Sequence<JS::Value>& aData)
{
StringMethod(aGlobal, aLabel, aData, MethodTimeLog,
NS_LITERAL_STRING("timeLog"));
}
/* static */ void
Console::StringMethod(const GlobalObject& aGlobal, const nsAString& aLabel,
MethodName aMethodName, const nsAString& aMethodString)
const Sequence<JS::Value>& aData, MethodName aMethodName,
const nsAString& aMethodString)
{
RefPtr<Console> console = GetConsole(aGlobal);
if (!console) {
return;
}
console->StringMethodInternal(aGlobal.Context(), aLabel, aMethodName,
console->StringMethodInternal(aGlobal.Context(), aLabel, aData, aMethodName,
aMethodString);
}
void
Console::StringMethodInternal(JSContext* aCx, const nsAString& aLabel,
const Sequence<JS::Value>& aData,
MethodName aMethodName,
const nsAString& aMethodString)
{
@ -1272,6 +1284,12 @@ Console::StringMethodInternal(JSContext* aCx, const nsAString& aLabel,
return;
}
for (uint32_t i = 0; i < aData.Length(); ++i) {
if (!data.AppendElement(aData[i], fallible)) {
return;
}
}
MethodInternal(aCx, aMethodName, aMethodString, data);
}
@ -1422,7 +1440,8 @@ Console::Assert(const GlobalObject& aGlobal, bool aCondition,
/* static */ void
Console::Count(const GlobalObject& aGlobal, const nsAString& aLabel)
{
StringMethod(aGlobal, aLabel, MethodCount, NS_LITERAL_STRING("count"));
StringMethod(aGlobal, aLabel, Sequence<JS::Value>(), MethodCount,
NS_LITERAL_STRING("count"));
}
namespace {
@ -1573,8 +1592,9 @@ Console::MethodInternal(JSContext* aCx, MethodName aMethodName,
DOMHighResTimeStamp monotonicTimer;
// Monotonic timer for 'time' and 'timeEnd'
// Monotonic timer for 'time', 'timeLog' and 'timeEnd'
if ((aMethodName == MethodTime ||
aMethodName == MethodTimeLog ||
aMethodName == MethodTimeEnd ||
aMethodName == MethodTimeStamp) &&
!MonotonicTimer(aCx, aMethodName, aData, &monotonicTimer)) {
@ -1589,10 +1609,19 @@ Console::MethodInternal(JSContext* aCx, MethodName aMethodName,
}
else if (aMethodName == MethodTimeEnd && !aData.IsEmpty()) {
callData->mStopTimerStatus = StopTimer(aCx, aData[0],
monotonicTimer,
callData->mStopTimerLabel,
&callData->mStopTimerDuration);
callData->mLogTimerStatus = LogTimer(aCx, aData[0],
monotonicTimer,
callData->mLogTimerLabel,
&callData->mLogTimerDuration,
true /* Cancel timer */);
}
else if (aMethodName == MethodTimeLog && !aData.IsEmpty()) {
callData->mLogTimerStatus = LogTimer(aCx, aData[0],
monotonicTimer,
callData->mLogTimerLabel,
&callData->mLogTimerDuration,
false /* Cancel timer */);
}
else if (aMethodName == MethodCount) {
@ -1863,10 +1892,11 @@ Console::PopulateConsoleNotificationInTheTargetScope(JSContext* aCx,
aData->mStartTimerStatus);
}
else if (aData->mMethodName == MethodTimeEnd && !aArguments.IsEmpty()) {
event.mTimer = CreateStopTimerValue(aCx, aData->mStopTimerLabel,
aData->mStopTimerDuration,
aData->mStopTimerStatus);
else if ((aData->mMethodName == MethodTimeEnd ||
aData->mMethodName == MethodTimeLog) && !aArguments.IsEmpty()) {
event.mTimer = CreateLogOrEndTimerValue(aCx, aData->mLogTimerLabel,
aData->mLogTimerDuration,
aData->mLogTimerStatus);
}
else if (aData->mMethodName == MethodCount) {
@ -2321,10 +2351,11 @@ Console::CreateStartTimerValue(JSContext* aCx, const nsAString& aTimerLabel,
}
Console::TimerStatus
Console::StopTimer(JSContext* aCx, const JS::Value& aName,
DOMHighResTimeStamp aTimestamp,
nsAString& aTimerLabel,
double* aTimerDuration)
Console::LogTimer(JSContext* aCx, const JS::Value& aName,
DOMHighResTimeStamp aTimestamp,
nsAString& aTimerLabel,
double* aTimerDuration,
bool aCancelTimer)
{
AssertIsOnOwningThread();
MOZ_ASSERT(aTimerDuration);
@ -2345,9 +2376,17 @@ Console::StopTimer(JSContext* aCx, const JS::Value& aName,
aTimerLabel = key;
DOMHighResTimeStamp value = 0;
if (!mTimerRegistry.Remove(key, &value)) {
NS_WARNING("mTimerRegistry entry not found");
return eTimerDoesntExist;
if (aCancelTimer) {
if (!mTimerRegistry.Remove(key, &value)) {
NS_WARNING("mTimerRegistry entry not found");
return eTimerDoesntExist;
}
} else {
if (!mTimerRegistry.Get(key, &value)) {
NS_WARNING("mTimerRegistry entry not found");
return eTimerDoesntExist;
}
}
*aTimerDuration = aTimestamp - value;
@ -2355,14 +2394,14 @@ Console::StopTimer(JSContext* aCx, const JS::Value& aName,
}
JS::Value
Console::CreateStopTimerValue(JSContext* aCx, const nsAString& aLabel,
double aDuration, TimerStatus aStatus) const
Console::CreateLogOrEndTimerValue(JSContext* aCx, const nsAString& aLabel,
double aDuration, TimerStatus aStatus) const
{
if (aStatus != eTimerDone) {
return CreateTimerError(aCx, aLabel, aStatus);
}
RootedDictionary<ConsoleTimerEnd> timer(aCx);
RootedDictionary<ConsoleTimerLogOrEnd> timer(aCx);
timer.mName = aLabel;
timer.mDuration = aDuration;
@ -2996,6 +3035,7 @@ Console::WebIDLLogLevelToInteger(ConsoleLogLevel aLevel) const
case ConsoleLogLevel::Info: return 3;
case ConsoleLogLevel::Clear: return 3;
case ConsoleLogLevel::Trace: return 3;
case ConsoleLogLevel::TimeLog: return 3;
case ConsoleLogLevel::TimeEnd: return 3;
case ConsoleLogLevel::Time: return 3;
case ConsoleLogLevel::Group: return 3;
@ -3033,6 +3073,7 @@ Console::InternalLogLevelToInteger(MethodName aName) const
case MethodGroupCollapsed: return 3;
case MethodGroupEnd: return 3;
case MethodTime: return 3;
case MethodTimeLog: return 3;
case MethodTimeEnd: return 3;
case MethodTimeStamp: return 3;
case MethodAssert: return 3;

View File

@ -96,6 +96,10 @@ public:
static void
Time(const GlobalObject& aGlobal, const nsAString& aLabel);
static void
TimeLog(const GlobalObject& aGlobal, const nsAString& aLabel,
const Sequence<JS::Value>& aData);
static void
TimeEnd(const GlobalObject& aGlobal, const nsAString& aLabel);
@ -159,6 +163,7 @@ private:
MethodGroupCollapsed,
MethodGroupEnd,
MethodTime,
MethodTimeLog,
MethodTimeEnd,
MethodTimeStamp,
MethodAssert,
@ -193,10 +198,12 @@ private:
static void
StringMethod(const GlobalObject& aGlobal, const nsAString& aLabel,
MethodName aMethodName, const nsAString& aMethodString);
const Sequence<JS::Value>& aData, MethodName aMethodName,
const nsAString& aMethodString);
void
StringMethodInternal(JSContext* aCx, const nsAString& aLabel,
const Sequence<JS::Value>& aData,
MethodName aMethodName, const nsAString& aMethodString);
// This method must receive aCx and aArguments in the same JSCompartment.
@ -317,9 +324,9 @@ private:
CreateStartTimerValue(JSContext* aCx, const nsAString& aTimerLabel,
TimerStatus aTimerStatus) const;
// StopTimer follows the same pattern as StartTimer: it runs on the
// LogTimer follows the same pattern as StartTimer: it runs on the
// owning thread and populates aTimerLabel and aTimerDuration, used by
// CreateStopTimerValue.
// CreateLogOrEndTimerValue.
// * 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
@ -328,22 +335,24 @@ private:
// string.
// * aTimerDuration - the difference between aTimestamp and when the timer
// started (see StartTimer).
// * aCancelTimer - if true, the timer is removed from the table.
TimerStatus
StopTimer(JSContext* aCx, const JS::Value& aName,
DOMHighResTimeStamp aTimestamp,
nsAString& aTimerLabel,
double* aTimerDuration);
LogTimer(JSContext* aCx, const JS::Value& aName,
DOMHighResTimeStamp aTimestamp,
nsAString& aTimerLabel,
double* aTimerDuration,
bool aCancelTimer);
// This method generates a ConsoleTimerEnd dictionary exposed as JS::Value, or
// a ConsoleTimerError dictionary if aTimerStatus is false. See StopTimer.
// a ConsoleTimerError dictionary if aTimerStatus is false. See LogTimer.
// * 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.
// * aTimerLabel - this label must be what LogTimer received as aTimerLabel.
// * aTimerDuration - this is what LogTimer received as aTimerDuration
// * aTimerStatus - the return value of LogTimer.
JS::Value
CreateStopTimerValue(JSContext* aCx, const nsAString& aTimerLabel,
double aTimerDuration,
TimerStatus aTimerStatus) const;
CreateLogOrEndTimerValue(JSContext* aCx, const nsAString& aTimerLabel,
double aTimerDuration,
TimerStatus aTimerStatus) const;
// The method populates a Sequence from an array of JS::Value.
bool

View File

@ -147,14 +147,24 @@ ConsoleInstance::GroupEnd(JSContext* aCx)
void
ConsoleInstance::Time(JSContext* aCx, const nsAString& aLabel)
{
mConsole->StringMethodInternal(aCx, aLabel, Console::MethodTime,
mConsole->StringMethodInternal(aCx, aLabel, Sequence<JS::Value>(),
Console::MethodTime,
NS_LITERAL_STRING("time"));
}
void
ConsoleInstance::TimeLog(JSContext* aCx, const nsAString& aLabel,
const Sequence<JS::Value>& aData)
{
mConsole->StringMethodInternal(aCx, aLabel, aData, Console::MethodTimeLog,
NS_LITERAL_STRING("timeLog"));
}
void
ConsoleInstance::TimeEnd(JSContext* aCx, const nsAString& aLabel)
{
mConsole->StringMethodInternal(aCx, aLabel, Console::MethodTimeEnd,
mConsole->StringMethodInternal(aCx, aLabel, Sequence<JS::Value>(),
Console::MethodTimeEnd,
NS_LITERAL_STRING("timeEnd"));
}
@ -201,7 +211,8 @@ ConsoleInstance::Assert(JSContext* aCx, bool aCondition,
void
ConsoleInstance::Count(JSContext* aCx, const nsAString& aLabel)
{
mConsole->StringMethodInternal(aCx, aLabel, Console::MethodCount,
mConsole->StringMethodInternal(aCx, aLabel, Sequence<JS::Value>(),
Console::MethodCount,
NS_LITERAL_STRING("count"));
}

View File

@ -74,6 +74,10 @@ public:
void
Time(JSContext* aCx, const nsAString& aLabel);
void
TimeLog(JSContext* aCx, const nsAString& aLabel,
const Sequence<JS::Value>& aData);
void
TimeEnd(JSContext* aCx, const nsAString& aLabel);

View File

@ -10,3 +10,4 @@ support-files =
[test_console_binding.html]
[test_console_proto.html]
[test_devtools_pref.html]
[test_timer.html]

View File

@ -0,0 +1,84 @@
<!DOCTYPE HTML>
<html>
<head>
<meta charset="utf-8">
<title>Test for timeStart/timeLog/timeEnd in console</title>
<script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
</head>
<body>
<script type="application/javascript">
SimpleTest.waitForExplicitFinish();
function consoleListener(cb) {
return new Promise(resolve => {
let observer = {
observe: function listener(aSubject, aTopic, aData) {
var obj = aSubject.wrappedJSObject;
if (obj.arguments[0] == 'test' && cb(obj)) {
SpecialPowers.removeObserver(observer, "console-api-log-event");
resolve();
}
}
};
SpecialPowers.addObserver(observer, "console-api-log-event");
});
}
// Timer creation:
async function runTest() {
var cl = consoleListener(function(obj) {
return ("timer" in obj) &&
("name" in obj.timer) &&
obj.timer.name == 'test';
});
console.time('test');
await cl;
ok(true, "Console.time received!");
// Timer check:
cl = consoleListener(obj => {
return ("timer" in obj) &&
("name" in obj.timer) &&
obj.timer.name == 'test' &&
("duration" in obj.timer) &&
obj.timer.duration > 0 &&
obj.arguments[1] == 1 &&
obj.arguments[2] == 2 &&
obj.arguments[3] == 3 &&
obj.arguments[4] == 4;
});
console.timeLog('test', 1, 2, 3, 4);
await cl;
ok(true, "Console.timeLog received!");
// Time deleted:
cl = consoleListener(obj => {
return ("timer" in obj) &&
("name" in obj.timer) &&
obj.timer.name == 'test' &&
("duration" in obj.timer) &&
obj.timer.duration > 0;
});
console.timeEnd('test');
await cl;
ok(true, "Console.timeEnd received!");
// Here an error:
cl = consoleListener(obj => {
return ("timer" in obj) &&
("name" in obj.timer) &&
obj.timer.name == 'test' &&
("error" in obj.timer);
});
console.timeLog('test');
await cl;
ok(true, "Console.timeLog with error received!");
}
runTest().then(SimpleTest.finish);
</script>
</body>
</html>

View File

@ -32,6 +32,7 @@ function doTest() {
"groupCollapsed": "function",
"groupEnd": "function",
"time": "function",
"timeLog": "function",
"timeEnd": "function",
"profile": "function",
"profileEnd": "function",

View File

@ -54,6 +54,8 @@ namespace console {
[UseCounter]
void time(optional DOMString label = "default");
[UseCounter]
void timeLog(optional DOMString label = "default", any... data);
[UseCounter]
void timeEnd(optional DOMString label = "default");
// Mozilla only or Webcompat methods
@ -121,7 +123,7 @@ dictionary ConsoleTimerStart {
DOMString name = "";
};
dictionary ConsoleTimerEnd {
dictionary ConsoleTimerLogOrEnd {
DOMString name = "";
double duration = 0;
};
@ -165,6 +167,7 @@ interface ConsoleInstance {
// Timing
void time(optional DOMString label = "default");
void timeLog(optional DOMString label = "default", any... data);
void timeEnd(optional DOMString label = "default");
// Mozilla only or Webcompat methods
@ -179,8 +182,9 @@ interface ConsoleInstance {
callback ConsoleInstanceDumpCallback = void (DOMString message);
enum ConsoleLogLevel {
"All", "Debug", "Log", "Info", "Clear", "Trace", "TimeEnd", "Time", "Group",
"GroupEnd", "Profile", "ProfileEnd", "Dir", "Dirxml", "Warn", "Error", "Off"
"All", "Debug", "Log", "Info", "Clear", "Trace", "TimeLog", "TimeEnd", "Time",
"Group", "GroupEnd", "Profile", "ProfileEnd", "Dir", "Dirxml", "Warn", "Error",
"Off"
};
dictionary ConsoleInstanceOptions {