Bug 1510067 - change structure of console log messages reporting, create preview of objects and arrays r=twisniewski,Gijs
authorksenia <kberezina@mozilla.com>
Mon, 15 Jul 2019 20:34:38 +0000
changeset 483333 3be2257eb12188dc0ccab10b4247d7fc8ba5ab5b
parent 483332 a98b795c2b3cebd83441e275f23151c01ecbc063
child 483334 050b9d3ce6bec9a6b3777b79a0bafe7670416570
push id36313
push useropoprus@mozilla.com
push dateThu, 18 Jul 2019 21:50:51 +0000
treeherdermozilla-central@5fceb8c496bf [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerstwisniewski, Gijs
bugs1510067
milestone70.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1510067 - change structure of console log messages reporting, create preview of objects and arrays r=twisniewski,Gijs Differential Revision: https://phabricator.services.mozilla.com/D37675
browser/extensions/report-site-issue/experimentalAPIs/tabExtras.js
browser/extensions/report-site-issue/test/browser/browser_report_site_issue.js
browser/extensions/report-site-issue/test/browser/test.html
mobile/android/extensions/report-site-issue/experimentalAPIs/tabExtras.js
--- a/browser/extensions/report-site-issue/experimentalAPIs/tabExtras.js
+++ b/browser/extensions/report-site-issue/experimentalAPIs/tabExtras.js
@@ -9,16 +9,23 @@
 XPCOMUtils.defineLazyGlobalGetters(this, ["URL"]);
 
 function getInfoFrameScript(messageName) {
   /* eslint-env mozilla/frame-script */
 
   const { Services } = ChromeUtils.import(
     "resource://gre/modules/Services.jsm"
   );
+  const PREVIEW_MAX_ITEMS = 10;
+  const LOG_LEVEL_MAP = {
+    0: "debug",
+    1: "info",
+    2: "warn",
+    3: "error",
+  };
 
   function getInnerWindowId(window) {
     return window.windowUtils.currentInnerWindowID;
   }
 
   function getInnerWindowIDsForAllFrames(window) {
     const innerWindowID = getInnerWindowId(window);
     let ids = [innerWindowID];
@@ -35,32 +42,99 @@ function getInfoFrameScript(messageName)
   function getLoggedMessages(window, includePrivate = false) {
     const ids = getInnerWindowIDsForAllFrames(window);
     return getConsoleMessages(ids)
       .concat(getScriptErrors(ids, includePrivate))
       .sort((a, b) => a.timeStamp - b.timeStamp)
       .map(m => m.message);
   }
 
+  function getPreview(value) {
+    switch (typeof value) {
+      case "function":
+        return "function ()";
+
+      case "object":
+        if (value === null) {
+          return null;
+        }
+
+        if (Array.isArray(value)) {
+          return `(${value.length})[...]`;
+        }
+
+        return "{...}";
+
+      case "undefined":
+        return "undefined";
+
+      default:
+        return value;
+    }
+  }
+
+  function getArrayPreview(arr) {
+    const preview = [];
+    for (const value of arr) {
+      preview.push(getPreview(value));
+
+      if (preview.length === PREVIEW_MAX_ITEMS) {
+        break;
+      }
+    }
+
+    return preview;
+  }
+
+  function getObjectPreview(obj) {
+    const preview = {};
+    for (const key in obj) {
+      if (obj.hasOwnProperty(key)) {
+        preview[key] = getPreview(obj[key]);
+      }
+
+      if (Object.keys(preview).length === PREVIEW_MAX_ITEMS) {
+        break;
+      }
+    }
+
+    return preview;
+  }
+
+  function getArgs(value) {
+    if (typeof value === "object" && value !== null) {
+      if (Array.isArray(value)) {
+        return getArrayPreview(value);
+      }
+
+      return getObjectPreview(value);
+    }
+
+    return getPreview(value);
+  }
+
   function getConsoleMessages(windowIds) {
     const ConsoleAPIStorage = Cc[
       "@mozilla.org/consoleAPI-storage;1"
     ].getService(Ci.nsIConsoleAPIStorage);
     let messages = [];
     for (const id of windowIds) {
       messages = messages.concat(ConsoleAPIStorage.getEvents(id) || []);
     }
     return messages.map(evt => {
       const { columnNumber, filename, level, lineNumber, timeStamp } = evt;
-      const args = evt.arguments
-        .map(arg => {
-          return "" + arg;
-        })
-        .join(", ");
-      const message = `[console.${level}(${args}) ${filename}:${lineNumber}:${columnNumber}]`;
+      const args = evt.arguments.map(getArgs);
+
+      const message = {
+        level,
+        log: args,
+        uri: filename,
+        pos: `${lineNumber}:${columnNumber}`,
+      };
+
       return { timeStamp, message };
     });
   }
 
   function getScriptErrors(windowIds, includePrivate = false) {
     const messages = Services.console.getMessageArray() || [];
     return messages
       .filter(message => {
@@ -76,17 +150,31 @@ function getInfoFrameScript(messageName)
           return true;
         }
 
         // If this is not an nsIScriptError and we need to do window-based
         // filtering we skip this message.
         return false;
       })
       .map(error => {
-        const { timeStamp, message } = error;
+        const {
+          timeStamp,
+          errorMessage,
+          sourceName,
+          lineNumber,
+          columnNumber,
+          logLevel,
+        } = error;
+        const message = {
+          level: LOG_LEVEL_MAP[logLevel],
+          log: [errorMessage],
+          uri: sourceName,
+          pos: `${lineNumber}:${columnNumber}`,
+        };
+
         return { timeStamp, message };
       });
   }
 
   sendAsyncMessage(messageName, {
     hasMixedActiveContentBlocked: docShell.hasMixedActiveContentBlocked,
     hasMixedDisplayContentBlocked: docShell.hasMixedDisplayContentBlocked,
     hasTrackingContentBlocked: docShell.hasTrackingContentBlocked,
--- a/browser/extensions/report-site-issue/test/browser/browser_report_site_issue.js
+++ b/browser/extensions/report-site-issue/test/browser/browser_report_site_issue.js
@@ -70,16 +70,18 @@ add_task(async function test_opened_page
         return p[0] < fuzz && Math.abs(p[1] - 128) < fuzz && p[2] < fuzz;
       }
       ok(isPixelGreenFuzzy(getPixel(0, 0)), "The pixels were green");
     }
 
     let doc = content.document;
     let urlParam = doc.getElementById("url").innerText;
     let preview = doc.getElementById("screenshot-preview");
+    const URL =
+      "http://example.com/browser/browser/extensions/report-site-issue/test/browser/test.html";
     is(
       urlParam,
       args.TEST_PAGE,
       "Reported page is correctly added to the url param"
     );
 
     let docShell = content.docShell;
     is(
@@ -100,34 +102,76 @@ add_task(async function test_opened_page
 
     let detailsParam = doc.getElementById("details").innerText;
     const details = JSON.parse(detailsParam);
     ok(
       typeof details == "object",
       "Details param is a stringified JSON object."
     );
     ok(Array.isArray(details.consoleLog), "Details has a consoleLog array.");
-    ok(
-      details.consoleLog[0].match(
-        /console\.log\(null\)[\s\S]*test.html:\d+:\d+/m
-      ),
-      "Can handle degenerate console logs"
+
+    const log1 = details.consoleLog[0];
+    ok(log1.log[0] === null, "Can handle degenerate console logs");
+    ok(log1.level === "log", "Reports correct log level");
+    ok(log1.uri === URL, "Reports correct url");
+    ok(log1.pos === "7:13", "Reports correct line and column");
+
+    const log2 = details.consoleLog[1];
+    ok(log2.log[0] === "colored message", "Can handle fancy console logs");
+    ok(log2.level === "error", "Reports correct log level");
+    ok(log2.uri === URL, "Reports correct url");
+    ok(log2.pos === "8:13", "Reports correct line and column");
+
+    const log3 = details.consoleLog[2];
+    const loggedObject = log3.log[0];
+    is(loggedObject.testobj, "{...}", "Reports object inside object");
+    is(loggedObject.testnumber, 1, "Reports number inside object");
+    is(loggedObject.testArray, "(4)[...]", "Reports array inside object");
+    is(loggedObject.testUndf, "undefined", "Reports undefined inside object");
+    is(loggedObject.testNull, null, "Reports null inside object");
+    is(
+      loggedObject.testFunc,
+      undefined,
+      "Reports function inside object as undefined due to security reasons"
     );
+    is(loggedObject.testString, "string", "Reports string inside object");
+    is(loggedObject.c, "{...}", "Reports circular reference inside object");
+    is(
+      Object.keys(loggedObject).length,
+      10,
+      "Preview has 10 keys inside object"
+    );
+    ok(log3.level === "log", "Reports correct log level");
+    ok(log3.uri === URL, "Reports correct url");
+    ok(log3.pos === "23:13", "Reports correct line and column");
+
+    const log4 = details.consoleLog[3];
+    const loggedArray = log4.log[0];
+    is(loggedArray[0], "string", "Reports string inside array");
+    is(loggedArray[1], "{...}", "Reports object inside array");
+    is(loggedArray[2], null, "Reports null inside array");
+    is(loggedArray[3], 90, "Reports number inside array");
+    is(loggedArray[4], "undefined", "Reports undefined inside array");
+    is(
+      loggedArray[5],
+      "undefined",
+      "Reports function inside array as undefined due to security reasons"
+    );
+    is(loggedArray[6], "(4)[...]", "Reports array inside array");
+    is(loggedArray[7], "(8)[...]", "Reports circular array inside array");
+
+    const log5 = details.consoleLog[4];
     ok(
-      details.consoleLog[1].match(
-        /console\.error\(colored message\)[\s\S]*test.html:\d+:\d+/m
-      ),
-      "Can handle fancy console logs"
-    );
-    ok(
-      details.consoleLog[2].match(
-        /document\.access is undefined[\s\S]*test.html:\d+:\d+/m
-      ),
+      log5.log[0] === "TypeError: document.access is undefined",
       "Script errors are logged"
     );
+    ok(log5.level === "error", "Reports correct log level");
+    ok(log5.uri === URL, "Reports correct url");
+    ok(log5.pos === "35:5", "Reports correct line and column");
+
     ok(typeof details.buildID == "string", "Details has a buildID string.");
     ok(typeof details.channel == "string", "Details has a channel string.");
     ok(
       typeof details.hasTouchScreen == "boolean",
       "Details has a hasTouchScreen flag."
     );
     ok(
       typeof details.hasFastClick == "undefined",
--- a/browser/extensions/report-site-issue/test/browser/test.html
+++ b/browser/extensions/report-site-issue/test/browser/test.html
@@ -1,13 +1,39 @@
 <!DOCTYPE html>
 <meta charset="utf-8">
 <script>
-  /* eslint-disable no-console */
-  /* eslint-disable no-unused-expressions */
-  "use strict";
-  console.log(null);
-  console.error("%ccolored message", "background:green; color:white");
-  document.access.non.existent.property.to.trigger.error;
+    /* eslint-disable no-console */
+    /* eslint-disable no-unused-expressions */
+    "use strict";
+    console.log(null);
+    console.error("%ccolored message", "background:green; color:white");
+    const obj = {
+        testobj: {},
+        testnumber: 1,
+        testArray: [1, {}, 2, 555],
+        testUndf: undefined,
+        testNull: null,
+        testFunc() {},
+        testString: 'string',
+        prop1: 'prop1',
+        prop2: 'prop2'
+    };
+    obj.c = obj;
+    obj.prop3 = 'prop3';
+    obj.prop4 = 'prop4';
+    console.log(obj);
+    const arr = [
+        'string',
+        {test: 'obj'},
+        null,
+        90,
+        undefined,
+        function() {},
+        [1, {}, 2, 555]
+    ];
+    arr.push(arr);
+    console.log(arr);
+    document.access.non.existent.property.to.trigger.error;
 </script>
 <style>
   body {background: rgb(0, 128, 0);}
 </style>
--- a/mobile/android/extensions/report-site-issue/experimentalAPIs/tabExtras.js
+++ b/mobile/android/extensions/report-site-issue/experimentalAPIs/tabExtras.js
@@ -27,16 +27,23 @@ XPCOMUtils.defineLazyGetter(
   "GlobalEventDispatcher",
   () => EventDispatcher.instance
 );
 
 function getInfoFrameScript(messageName) {
   /* eslint-env mozilla/frame-script */
 
   ({ Services } = ChromeUtils.import("resource://gre/modules/Services.jsm"));
+  const PREVIEW_MAX_ITEMS = 10;
+  const LOG_LEVEL_MAP = {
+    0: "debug",
+    1: "info",
+    2: "warn",
+    3: "error",
+  };
 
   function getInnerWindowId(window) {
     return window.windowUtils.currentInnerWindowID;
   }
 
   function getInnerWindowIDsForAllFrames(window) {
     const innerWindowID = getInnerWindowId(window);
     let ids = [innerWindowID];
@@ -53,32 +60,99 @@ function getInfoFrameScript(messageName)
   function getLoggedMessages(window, includePrivate = false) {
     const ids = getInnerWindowIDsForAllFrames(window);
     return getConsoleMessages(ids)
       .concat(getScriptErrors(ids, includePrivate))
       .sort((a, b) => a.timeStamp - b.timeStamp)
       .map(m => m.message);
   }
 
+  function getPreview(value) {
+    switch (typeof value) {
+      case "function":
+        return "function ()";
+
+      case "object":
+        if (value === null) {
+          return null;
+        }
+
+        if (Array.isArray(value)) {
+          return `(${value.length})[...]`;
+        }
+
+        return "{...}";
+
+      case "undefined":
+        return "undefined";
+
+      default:
+        return value;
+    }
+  }
+
+  function getArrayPreview(arr) {
+    const preview = [];
+    for (const value of arr) {
+      preview.push(getPreview(value));
+
+      if (preview.length === PREVIEW_MAX_ITEMS) {
+        break;
+      }
+    }
+
+    return preview;
+  }
+
+  function getObjectPreview(obj) {
+    const preview = {};
+    for (const key in obj) {
+      if (obj.hasOwnProperty(key)) {
+        preview[key] = getPreview(obj[key]);
+      }
+
+      if (Object.keys(preview).length === PREVIEW_MAX_ITEMS) {
+        break;
+      }
+    }
+
+    return preview;
+  }
+
+  function getArgs(value) {
+    if (typeof value === "object" && value !== null) {
+      if (Array.isArray(value)) {
+        return getArrayPreview(value);
+      }
+
+      return getObjectPreview(value);
+    }
+
+    return getPreview(value);
+  }
+
   function getConsoleMessages(windowIds) {
     const ConsoleAPIStorage = Cc[
       "@mozilla.org/consoleAPI-storage;1"
     ].getService(Ci.nsIConsoleAPIStorage);
     let messages = [];
     for (const id of windowIds) {
       messages = messages.concat(ConsoleAPIStorage.getEvents(id) || []);
     }
     return messages.map(evt => {
       const { columnNumber, filename, level, lineNumber, timeStamp } = evt;
-      const args = evt.arguments
-        .map(arg => {
-          return "" + arg;
-        })
-        .join(", ");
-      const message = `[console.${level}(${args}) ${filename}:${lineNumber}:${columnNumber}]`;
+      const args = evt.arguments.map(getArgs);
+
+      const message = {
+        level,
+        log: args,
+        uri: filename,
+        pos: `${lineNumber}:${columnNumber}`,
+      };
+
       return { timeStamp, message };
     });
   }
 
   function getScriptErrors(windowIds, includePrivate = false) {
     const messages = Services.console.getMessageArray() || [];
     return messages
       .filter(message => {
@@ -94,17 +168,31 @@ function getInfoFrameScript(messageName)
           return true;
         }
 
         // If this is not an nsIScriptError and we need to do window-based
         // filtering we skip this message.
         return false;
       })
       .map(error => {
-        const { timeStamp, message } = error;
+        const {
+          timeStamp,
+          errorMessage,
+          sourceName,
+          lineNumber,
+          columnNumber,
+          logLevel,
+        } = error;
+        const message = {
+          level: LOG_LEVEL_MAP[logLevel],
+          log: [errorMessage],
+          uri: sourceName,
+          pos: `${lineNumber}:${columnNumber}`,
+        };
+
         return { timeStamp, message };
       });
   }
 
   sendAsyncMessage(messageName, {
     hasMixedActiveContentBlocked: docShell.hasMixedActiveContentBlocked,
     hasMixedDisplayContentBlocked: docShell.hasMixedDisplayContentBlocked,
     hasTrackingContentBlocked: docShell.hasTrackingContentBlocked,