Bug 1461001 - Implement frontend for console.timeLog; r=bgrins.
authorNicolas Chevobbe <nchevobbe@mozilla.com>
Mon, 21 May 2018 15:37:04 +0200
changeset 797953 8b9b40cc6aa6264123b231f7c2fe97dc56a41895
parent 797705 dc1868d255be744a7d2d462216be205086cc60af
push id110643
push userbmo:nchevobbe@mozilla.com
push dateTue, 22 May 2018 05:57:01 +0000
reviewersbgrins
bugs1461001
milestone62.0a1
Bug 1461001 - Implement frontend for console.timeLog; r=bgrins. The patch handles timeLog packets we receive from the backend. We add some stubs in order to have access to packets in mocha, and some mocha tests to make sure we render those messages as expected, be it when the timer exists or not. The BASE_PATH constant now makes use of MOZ_DEVELOPER_REPO_DIR environment variable in order to have an absolute path; without it the stubs couldn't be written to local files. MozReview-Commit-ID: Fx4DyRgOzcd
devtools/client/locales/en-US/webconsole.properties
devtools/client/webconsole/test/components/console-api-call.test.js
devtools/client/webconsole/test/fixtures/stub-generators/head.js
devtools/client/webconsole/test/fixtures/stub-generators/stub-snippets.js
devtools/client/webconsole/test/fixtures/stubs/consoleApi.js
devtools/client/webconsole/utils/messages.js
--- a/devtools/client/locales/en-US/webconsole.properties
+++ b/devtools/client/locales/en-US/webconsole.properties
@@ -30,16 +30,21 @@ webConsoleMoreInfoLabel=Learn More
 # anonymous. Test console.trace() in the webconsole.
 stacktrace.anonymousFunction=<anonymous>
 
 # LOCALIZATION NOTE (stacktrace.asyncStack): this string is used to
 # indicate that a given stack frame has an async parent.
 # %S is the "Async Cause" of the frame.
 stacktrace.asyncStack=(Async: %S)
 
+# LOCALIZATION NOTE (timeLog): this string is used to display the result of
+# the console.timeLog() call. Parameters: %1$S is the name of the timer, %2$S
+# is the number of milliseconds.
+timeLog=%1$S: %2$Sms
+
 # LOCALIZATION NOTE (timeEnd): this string is used to display the result of
 # the console.timeEnd() call. Parameters: %1$S is the name of the timer, %2$S
 # is the number of milliseconds.
 timeEnd=%1$S: %2$Sms
 
 # LOCALIZATION NOTE (consoleCleared): this string is displayed when receiving a
 # call to console.clear() to let the user know the previous messages of the
 # console have been removed programmatically.
--- a/devtools/client/webconsole/test/components/console-api-call.test.js
+++ b/devtools/client/webconsole/test/components/console-api-call.test.js
@@ -234,26 +234,47 @@ describe("ConsoleAPICall component:", ()
     it("shows an error if called again", () => {
       const message = stubPreparedMessages.get("timerAlreadyExists");
       const wrapper = render(ConsoleApiCall({ message, serviceContainer }));
 
       expect(wrapper.find(".message-body").text()).toBe("Timer “bar” already exists.");
     });
   });
 
+  describe("console.timeLog", () => {
+    it("renders as expected", () => {
+      let message = stubPreparedMessages.get("console.timeLog('bar') - 1");
+      let wrapper = render(ConsoleApiCall({ message, serviceContainer }));
+
+      expect(wrapper.find(".message-body").text()).toBe(message.parameters[0]);
+      expect(wrapper.find(".message-body").text()).toMatch(/^bar: \d+(\.\d+)?ms$/);
+
+      message = stubPreparedMessages.get("console.timeLog('bar') - 2");
+      wrapper = render(ConsoleApiCall({ message, serviceContainer }));
+      expect(wrapper.find(".message-body").text())
+        .toMatch(/^bar: \d+(\.\d+)?ms second call Object \{ state\: 1 \}$/);
+    });
+    it("shows an error if the timer doesn't exist", () => {
+      const message = stubPreparedMessages.get("timeLog.timerDoesntExist");
+      const wrapper = render(ConsoleApiCall({ message, serviceContainer }));
+
+      expect(wrapper.find(".message-body").text()).toBe("Timer “bar” doesn’t exist.");
+    });
+  });
+
   describe("console.timeEnd", () => {
     it("renders as expected", () => {
       const message = stubPreparedMessages.get("console.timeEnd('bar')");
       const wrapper = render(ConsoleApiCall({ message, serviceContainer }));
 
       expect(wrapper.find(".message-body").text()).toBe(message.messageText);
       expect(wrapper.find(".message-body").text()).toMatch(/^bar: \d+(\.\d+)?ms$/);
     });
     it("shows an error if the timer doesn't exist", () => {
-      const message = stubPreparedMessages.get("timerDoesntExist");
+      const message = stubPreparedMessages.get("timeEnd.timerDoesntExist");
       const wrapper = render(ConsoleApiCall({ message, serviceContainer }));
 
       expect(wrapper.find(".message-body").text()).toBe("Timer “bar” doesn’t exist.");
     });
   });
 
   describe("console.trace", () => {
     it("renders", () => {
--- a/devtools/client/webconsole/test/fixtures/stub-generators/head.js
+++ b/devtools/client/webconsole/test/fixtures/stub-generators/head.js
@@ -21,17 +21,18 @@ const { stubPackets } = require("devtool
 const {
   consoleApi,
   cssMessage,
   evaluationResult,
   networkEvent,
   pageError,
 } = require("devtools/client/webconsole/test/fixtures/stub-generators/stub-snippets.js");
 
-const BASE_PATH = "../../../devtools/client/webconsole/test/fixtures";
+const BASE_PATH = env.get("MOZ_DEVELOPER_REPO_DIR") +
+                  "/devtools/client/webconsole/test/fixtures";
 
 let cachedPackets = {};
 
 function getCleanedPacket(key, packet) {
   if (Object.keys(cachedPackets).includes(key)) {
     return cachedPackets[key];
   }
 
@@ -69,17 +70,17 @@ function getCleanedPacket(key, packet) {
       res.actor = existingPacket.actor;
     }
 
     if (res.message) {
       // Clean timeStamp on the message prop.
       res.message.timeStamp = existingPacket.message.timeStamp;
       if (res.message.timer) {
         // Clean timer properties on the message.
-        // Those properties are found on console.time and console.timeEnd calls,
+        // Those properties are found on console.time, timeLog and timeEnd calls,
         // and those time can vary, which is why we need to clean them.
         if ("duration" in res.message.timer) {
           res.message.timer.duration = existingPacket.message.timer.duration;
         }
       }
 
       if (Array.isArray(res.message.arguments)) {
         res.message.arguments = res.message.arguments.map((argument, i) => {
--- a/devtools/client/webconsole/test/fixtures/stub-generators/stub-snippets.js
+++ b/devtools/client/webconsole/test/fixtures/stub-generators/stub-snippets.js
@@ -54,23 +54,33 @@ function testStacktraceFiltering() {
 function foo() {
   testStacktraceFiltering()
 }
 
 foo()
 `});
 
 consoleApi.set("console.time('bar')", {
-  keys: ["console.time('bar')", "timerAlreadyExists",
-         "console.timeEnd('bar')", "timerDoesntExist"],
+  keys: [
+    "console.time('bar')",
+    "timerAlreadyExists",
+    "console.timeLog('bar') - 1",
+    "console.timeLog('bar') - 2",
+    "console.timeEnd('bar')",
+    "timeEnd.timerDoesntExist",
+    "timeLog.timerDoesntExist",
+  ],
   code: `
 console.time("bar");
 console.time("bar");
+console.timeLog("bar");
+console.timeLog("bar", "second call", {state: 1});
 console.timeEnd("bar");
 console.timeEnd("bar");
+console.timeLog("bar");
 `});
 
 consoleApi.set("console.table('bar')", {
   keys: ["console.table('bar')"],
   code: `
 console.table('bar');
 `});
 
--- a/devtools/client/webconsole/test/fixtures/stubs/consoleApi.js
+++ b/devtools/client/webconsole/test/fixtures/stubs/consoleApi.js
@@ -868,57 +868,164 @@ stubPreparedMessages.set(`timerAlreadyEx
   "exceptionDocURL": null,
   "userProvidedStyles": [],
   "notes": null,
   "indent": 0,
   "prefix": "",
   "private": false
 }));
 
-stubPreparedMessages.set(`console.timeEnd('bar')`, new ConsoleMessage({
+stubPreparedMessages.set(`console.timeLog('bar') - 1`, new ConsoleMessage({
   "id": "1",
   "allowRepeating": true,
   "source": "console-api",
-  "timeStamp": 1502884924759,
-  "type": "timeEnd",
+  "timeStamp": 1526920999996,
+  "type": "timeLog",
   "helperType": null,
   "level": "log",
-  "messageText": "bar: 1.21ms",
-  "parameters": null,
-  "repeatId": "{\"frame\":{\"source\":\"http://example.com/browser/devtools/client/webconsole/test/fixtures/stub-generators/test-console-api.html\",\"line\":4,\"column\":1},\"groupId\":null,\"indent\":0,\"level\":\"log\",\"messageText\":\"bar: 1.21ms\",\"parameters\":null,\"source\":\"console-api\",\"type\":\"timeEnd\",\"userProvidedStyles\":[],\"private\":false}",
+  "messageText": null,
+  "parameters": [
+    "bar: 1ms"
+  ],
+  "repeatId": "{\"frame\":{\"source\":\"http://example.com/browser/devtools/client/webconsole/test/fixtures/stub-generators/test-console-api.html\",\"line\":4,\"column\":1},\"groupId\":null,\"indent\":0,\"level\":\"log\",\"messageText\":null,\"parameters\":[\"bar: 1ms\"],\"source\":\"console-api\",\"type\":\"timeLog\",\"userProvidedStyles\":[],\"private\":false}",
   "stacktrace": null,
   "frame": {
     "source": "http://example.com/browser/devtools/client/webconsole/test/fixtures/stub-generators/test-console-api.html",
     "line": 4,
     "column": 1
   },
   "groupId": null,
   "exceptionDocURL": null,
   "userProvidedStyles": [],
   "notes": null,
   "indent": 0,
   "prefix": "",
   "private": false
 }));
 
-stubPreparedMessages.set(`timerDoesntExist`, new ConsoleMessage({
+stubPreparedMessages.set(`console.timeLog('bar') - 2`, new ConsoleMessage({
+  "id": "1",
+  "allowRepeating": true,
+  "source": "console-api",
+  "timeStamp": 1526920999996,
+  "type": "timeLog",
+  "helperType": null,
+  "level": "log",
+  "messageText": null,
+  "parameters": [
+    "bar: 1ms",
+    "second call",
+    {
+      "type": "object",
+      "actor": "server1.conn0.child1/obj34",
+      "class": "Object",
+      "extensible": true,
+      "frozen": false,
+      "sealed": false,
+      "ownPropertyLength": 1,
+      "preview": {
+        "kind": "Object",
+        "ownProperties": {
+          "state": {
+            "configurable": true,
+            "enumerable": true,
+            "writable": true,
+            "value": 1
+          }
+        },
+        "ownSymbols": [],
+        "ownPropertiesLength": 1,
+        "ownSymbolsLength": 0,
+        "safeGetterValues": {}
+      }
+    }
+  ],
+  "repeatId": "{\"frame\":{\"source\":\"http://example.com/browser/devtools/client/webconsole/test/fixtures/stub-generators/test-console-api.html\",\"line\":5,\"column\":1},\"groupId\":null,\"indent\":0,\"level\":\"log\",\"messageText\":null,\"parameters\":[\"bar: 1ms\",\"second call\",{\"type\":\"object\",\"actor\":\"server1.conn0.child1/obj34\",\"class\":\"Object\",\"extensible\":true,\"frozen\":false,\"sealed\":false,\"ownPropertyLength\":1,\"preview\":{\"kind\":\"Object\",\"ownProperties\":{\"state\":{\"configurable\":true,\"enumerable\":true,\"writable\":true,\"value\":1}},\"ownSymbols\":[],\"ownPropertiesLength\":1,\"ownSymbolsLength\":0,\"safeGetterValues\":{}}}],\"source\":\"console-api\",\"type\":\"timeLog\",\"userProvidedStyles\":[],\"private\":false}",
+  "stacktrace": null,
+  "frame": {
+    "source": "http://example.com/browser/devtools/client/webconsole/test/fixtures/stub-generators/test-console-api.html",
+    "line": 5,
+    "column": 1
+  },
+  "groupId": null,
+  "exceptionDocURL": null,
+  "userProvidedStyles": [],
+  "notes": null,
+  "indent": 0,
+  "prefix": "",
+  "private": false
+}));
+
+stubPreparedMessages.set(`console.timeEnd('bar')`, new ConsoleMessage({
   "id": "1",
   "allowRepeating": true,
   "source": "console-api",
   "timeStamp": 1502884924759,
   "type": "timeEnd",
   "helperType": null,
+  "level": "log",
+  "messageText": "bar: 1.21ms",
+  "parameters": null,
+  "repeatId": "{\"frame\":{\"source\":\"http://example.com/browser/devtools/client/webconsole/test/fixtures/stub-generators/test-console-api.html\",\"line\":6,\"column\":1},\"groupId\":null,\"indent\":0,\"level\":\"log\",\"messageText\":\"bar: 1.21ms\",\"parameters\":null,\"source\":\"console-api\",\"type\":\"timeEnd\",\"userProvidedStyles\":[],\"private\":false}",
+  "stacktrace": null,
+  "frame": {
+    "source": "http://example.com/browser/devtools/client/webconsole/test/fixtures/stub-generators/test-console-api.html",
+    "line": 6,
+    "column": 1
+  },
+  "groupId": null,
+  "exceptionDocURL": null,
+  "userProvidedStyles": [],
+  "notes": null,
+  "indent": 0,
+  "prefix": "",
+  "private": false
+}));
+
+stubPreparedMessages.set(`timeEnd.timerDoesntExist`, new ConsoleMessage({
+  "id": "1",
+  "allowRepeating": true,
+  "source": "console-api",
+  "timeStamp": 1526920999998,
+  "type": "timeEnd",
+  "helperType": null,
   "level": "warn",
   "messageText": "Timer “bar” doesn’t exist.",
   "parameters": null,
-  "repeatId": "{\"frame\":{\"source\":\"http://example.com/browser/devtools/client/webconsole/test/fixtures/stub-generators/test-console-api.html\",\"line\":5,\"column\":1},\"groupId\":null,\"indent\":0,\"level\":\"warn\",\"messageText\":\"Timer “bar” doesn’t exist.\",\"parameters\":null,\"source\":\"console-api\",\"type\":\"timeEnd\",\"userProvidedStyles\":[],\"private\":false}",
+  "repeatId": "{\"frame\":{\"source\":\"http://example.com/browser/devtools/client/webconsole/test/fixtures/stub-generators/test-console-api.html\",\"line\":7,\"column\":1},\"groupId\":null,\"indent\":0,\"level\":\"warn\",\"messageText\":\"Timer “bar” doesn’t exist.\",\"parameters\":null,\"source\":\"console-api\",\"type\":\"timeEnd\",\"userProvidedStyles\":[],\"private\":false}",
   "stacktrace": null,
   "frame": {
     "source": "http://example.com/browser/devtools/client/webconsole/test/fixtures/stub-generators/test-console-api.html",
-    "line": 5,
+    "line": 7,
+    "column": 1
+  },
+  "groupId": null,
+  "exceptionDocURL": null,
+  "userProvidedStyles": [],
+  "notes": null,
+  "indent": 0,
+  "prefix": "",
+  "private": false
+}));
+
+stubPreparedMessages.set(`timeLog.timerDoesntExist`, new ConsoleMessage({
+  "id": "1",
+  "allowRepeating": true,
+  "source": "console-api",
+  "timeStamp": 1526920999999,
+  "type": "timeLog",
+  "helperType": null,
+  "level": "warn",
+  "messageText": "Timer “bar” doesn’t exist.",
+  "parameters": null,
+  "repeatId": "{\"frame\":{\"source\":\"http://example.com/browser/devtools/client/webconsole/test/fixtures/stub-generators/test-console-api.html\",\"line\":8,\"column\":1},\"groupId\":null,\"indent\":0,\"level\":\"warn\",\"messageText\":\"Timer “bar” doesn’t exist.\",\"parameters\":null,\"source\":\"console-api\",\"type\":\"timeLog\",\"userProvidedStyles\":[],\"private\":false}",
+  "stacktrace": null,
+  "frame": {
+    "source": "http://example.com/browser/devtools/client/webconsole/test/fixtures/stub-generators/test-console-api.html",
+    "line": 8,
     "column": 1
   },
   "groupId": null,
   "exceptionDocURL": null,
   "userProvidedStyles": [],
   "notes": null,
   "indent": 0,
   "prefix": "",
@@ -2419,62 +2526,171 @@ stubPackets.set(`timerAlreadyExists`, {
       "name": "bar"
     },
     "workerType": "none",
     "styles": [],
     "category": "webdev"
   }
 });
 
+stubPackets.set(`console.timeLog('bar') - 1`, {
+  "from": "server1.conn0.child1/consoleActor2",
+  "type": "consoleAPICall",
+  "message": {
+    "addonId": "",
+    "arguments": [
+      "bar"
+    ],
+    "columnNumber": 1,
+    "counter": null,
+    "filename": "http://example.com/browser/devtools/client/webconsole/test/fixtures/stub-generators/test-console-api.html",
+    "functionName": "triggerPacket",
+    "groupName": "",
+    "level": "timeLog",
+    "lineNumber": 4,
+    "prefix": "",
+    "private": false,
+    "timeStamp": 1526920999996,
+    "timer": {
+      "duration": 1,
+      "name": "bar"
+    },
+    "workerType": "none",
+    "styles": [],
+    "category": "webdev"
+  }
+});
+
+stubPackets.set(`console.timeLog('bar') - 2`, {
+  "from": "server1.conn0.child1/consoleActor2",
+  "type": "consoleAPICall",
+  "message": {
+    "addonId": "",
+    "arguments": [
+      "bar",
+      "second call",
+      {
+        "type": "object",
+        "actor": "server1.conn0.child1/obj34",
+        "class": "Object",
+        "extensible": true,
+        "frozen": false,
+        "sealed": false,
+        "ownPropertyLength": 1,
+        "preview": {
+          "kind": "Object",
+          "ownProperties": {
+            "state": {
+              "configurable": true,
+              "enumerable": true,
+              "writable": true,
+              "value": 1
+            }
+          },
+          "ownSymbols": [],
+          "ownPropertiesLength": 1,
+          "ownSymbolsLength": 0,
+          "safeGetterValues": {}
+        }
+      }
+    ],
+    "columnNumber": 1,
+    "counter": null,
+    "filename": "http://example.com/browser/devtools/client/webconsole/test/fixtures/stub-generators/test-console-api.html",
+    "functionName": "triggerPacket",
+    "groupName": "",
+    "level": "timeLog",
+    "lineNumber": 5,
+    "prefix": "",
+    "private": false,
+    "timeStamp": 1526920999996,
+    "timer": {
+      "duration": 1,
+      "name": "bar"
+    },
+    "workerType": "none",
+    "styles": [],
+    "category": "webdev"
+  }
+});
+
 stubPackets.set(`console.timeEnd('bar')`, {
   "from": "server1.conn0.child1/consoleActor2",
   "type": "consoleAPICall",
   "message": {
     "addonId": "",
     "arguments": [
       "bar"
     ],
     "columnNumber": 1,
     "counter": null,
     "filename": "http://example.com/browser/devtools/client/webconsole/test/fixtures/stub-generators/test-console-api.html",
     "functionName": "triggerPacket",
     "groupName": "",
     "level": "timeEnd",
-    "lineNumber": 4,
+    "lineNumber": 6,
     "prefix": "",
     "private": false,
     "timeStamp": 1502884924759,
     "timer": {
       "duration": 1.2149999999999181,
       "name": "bar"
     },
     "workerType": "none",
     "styles": [],
     "category": "webdev"
   }
 });
 
-stubPackets.set(`timerDoesntExist`, {
+stubPackets.set(`timeEnd.timerDoesntExist`, {
   "from": "server1.conn0.child1/consoleActor2",
   "type": "consoleAPICall",
   "message": {
     "addonId": "",
     "arguments": [
       "bar"
     ],
     "columnNumber": 1,
     "counter": null,
     "filename": "http://example.com/browser/devtools/client/webconsole/test/fixtures/stub-generators/test-console-api.html",
     "functionName": "triggerPacket",
     "groupName": "",
     "level": "timeEnd",
-    "lineNumber": 5,
+    "lineNumber": 7,
     "prefix": "",
     "private": false,
-    "timeStamp": 1502884924759,
+    "timeStamp": 1526920999998,
+    "timer": {
+      "error": "timerDoesntExist",
+      "name": "bar"
+    },
+    "workerType": "none",
+    "styles": [],
+    "category": "webdev"
+  }
+});
+
+stubPackets.set(`timeLog.timerDoesntExist`, {
+  "from": "server1.conn0.child1/consoleActor2",
+  "type": "consoleAPICall",
+  "message": {
+    "addonId": "",
+    "arguments": [
+      "bar"
+    ],
+    "columnNumber": 1,
+    "counter": null,
+    "filename": "http://example.com/browser/devtools/client/webconsole/test/fixtures/stub-generators/test-console-api.html",
+    "functionName": "triggerPacket",
+    "groupName": "",
+    "level": "timeLog",
+    "lineNumber": 8,
+    "prefix": "",
+    "private": false,
+    "timeStamp": 1526920999999,
     "timer": {
       "error": "timerDoesntExist",
       "name": "bar"
     },
     "workerType": "none",
     "styles": [],
     "category": "webdev"
   }
--- a/devtools/client/webconsole/utils/messages.js
+++ b/devtools/client/webconsole/utils/messages.js
@@ -101,26 +101,36 @@ function transformConsoleAPICallPacket(p
       if (timer && timer.error) {
         messageText = l10n.getFormatStr(timer.error, [timer.name]);
         level = MESSAGE_LEVEL.WARN;
       } else {
         // We don't show anything for console.time calls to match Chrome's behaviour.
         type = MESSAGE_TYPE.NULL_MESSAGE;
       }
       break;
+    case "timeLog":
     case "timeEnd":
-      parameters = null;
       if (timer && timer.error) {
+        parameters = null;
         messageText = l10n.getFormatStr(timer.error, [timer.name]);
         level = MESSAGE_LEVEL.WARN;
       } else if (timer) {
-        // We show the duration to users when calls console.timeEnd() is called,
+        // We show the duration to users when calls console.timeLog/timeEnd is called,
         // if corresponding console.time() was called before.
         let duration = Math.round(timer.duration * 100) / 100;
-        messageText = l10n.getFormatStr("timeEnd", [timer.name, duration]);
+        if (type === "timeEnd") {
+          messageText = l10n.getFormatStr("timeEnd", [timer.name, duration]);
+          parameters = null;
+        } else if (type === "timeLog") {
+          const [, ...rest] = parameters;
+          parameters = [
+            l10n.getFormatStr("timeLog", [timer.name, duration]),
+            ...rest,
+          ];
+        }
       } else {
         // If the `timer` property does not exists, we don't output anything.
         type = MESSAGE_TYPE.NULL_MESSAGE;
       }
       break;
     case "table":
       const supportedClasses = [
         "Array", "Object", "Map", "Set", "WeakMap", "WeakSet"];