Bug 993520: Add innerID option to ConsoleAPI to allow creating consoles for specific DOM windows. r=msucan
authorDave Townsend <dtownsend@oxymoronical.com>
Tue, 15 Apr 2014 10:35:30 -0700
changeset 197077 865af0ed2de9b9cdc8e1cbef251b70ffc1f12598
parent 197076 6f2321b72086bfe47a921a047c1734d43a1f9ce7
child 197078 f2cc37938434da36318ae1d31054d8c4b00cc1c4
push id3624
push userasasaki@mozilla.com
push dateMon, 09 Jun 2014 21:49:01 +0000
treeherdermozilla-beta@b1a5da15899a [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmsucan
bugs993520
milestone31.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 993520: Add innerID option to ConsoleAPI to allow creating consoles for specific DOM windows. r=msucan
toolkit/devtools/Console.jsm
toolkit/devtools/webconsole/test/chrome.ini
toolkit/devtools/webconsole/test/test_consoleapi_innerID.html
toolkit/devtools/webconsole/utils.js
--- a/toolkit/devtools/Console.jsm
+++ b/toolkit/devtools/Console.jsm
@@ -461,17 +461,17 @@ function dumpMessage(aConsole, aLevel, a
  */
 function createDumper(aLevel) {
   return function() {
     if (!shouldLog(aLevel, this.maxLogLevel)) {
       return;
     }
     let args = Array.prototype.slice.call(arguments, 0);
     let frame = getStack(Components.stack.caller, 1)[0];
-    sendConsoleAPIMessage(aLevel, frame, args);
+    sendConsoleAPIMessage(this, aLevel, frame, args);
     let data = args.map(function(arg) {
       return stringify(arg, true);
     });
     dumpMessage(this, aLevel, data.join(" "));
   };
 }
 
 /**
@@ -488,47 +488,49 @@ function createDumper(aLevel) {
 function createMultiLineDumper(aLevel) {
   return function() {
     if (!shouldLog(aLevel, this.maxLogLevel)) {
       return;
     }
     dumpMessage(this, aLevel, "");
     let args = Array.prototype.slice.call(arguments, 0);
     let frame = getStack(Components.stack.caller, 1)[0];
-    sendConsoleAPIMessage(aLevel, frame, args);
+    sendConsoleAPIMessage(this, aLevel, frame, args);
     args.forEach(function(arg) {
       this.dump(log(arg));
     }, this);
   };
 }
 
 /**
  * Send a Console API message. This function will send a console-api-log-event
  * notification through the nsIObserverService.
  *
+ * @param {object} aConsole
+ *        The instance of ConsoleAPI performing the logging.
  * @param {string} aLevel
  *        Message severity level. This is usually the name of the console method
  *        that was called.
  * @param {object} aFrame
  *        The youngest stack frame coming from Components.stack, as formatted by
  *        getStack().
  * @param {array} aArgs
  *        The arguments given to the console method.
  * @param {object} aOptions
  *        Object properties depend on the console method that was invoked:
  *        - timer: for time() and timeEnd(). Holds the timer information.
  *        - groupName: for group(), groupCollapsed() and groupEnd().
  *        - stacktrace: for trace(). Holds the array of stack frames as given by
  *        getStack().
  */
-function sendConsoleAPIMessage(aLevel, aFrame, aArgs, aOptions = {})
+function sendConsoleAPIMessage(aConsole, aLevel, aFrame, aArgs, aOptions = {})
 {
   let consoleEvent = {
     ID: "jsm",
-    innerID: aFrame.filename,
+    innerID: aConsole.innerID || aFrame.filename,
     level: aLevel,
     filename: aFrame.filename,
     lineNumber: aFrame.lineNumber,
     functionName: aFrame.functionName,
     timeStamp: Date.now(),
     arguments: aArgs,
   };
 
@@ -573,25 +575,28 @@ function sendConsoleAPIMessage(aLevel, a
  *        - maxLogLevel {string} : String identifier (See LOG_LEVELS for
  *                            possible values) that allows to filter which
  *                            messages are logged based on their log level.
  *                            If falsy value, all messages will be logged.
  *                            If wrong value that doesn't match any key of
  *                            LOG_LEVELS, no message will be logged
  *        - dump {function} : An optional function to intercept all strings
  *                            written to stdout
+ *        - innerID {string}: An ID representing the source of the message.
+ *                            Normally the inner ID of a DOM window.
  * @return {object}
  *        A console API instance object
  */
 function ConsoleAPI(aConsoleOptions = {}) {
   // Normalize console options to set default values
   // in order to avoid runtime checks on each console method call.
   this.dump = aConsoleOptions.dump || dump;
   this.prefix = aConsoleOptions.prefix || "";
   this.maxLogLevel = aConsoleOptions.maxLogLevel || "all";
+  this.innerID = aConsoleOptions.innerID || null;
 
   // Bind all the functions to this object.
   for (let prop in this) {
     if (typeof(this[prop]) === "function") {
       this[prop] = this[prop].bind(this);
     }
   }
 }
@@ -605,17 +610,17 @@ ConsoleAPI.prototype = {
   exception: createMultiLineDumper("error"),
 
   trace: function Console_trace() {
     if (!shouldLog("trace", this.maxLogLevel)) {
       return;
     }
     let args = Array.prototype.slice.call(arguments, 0);
     let trace = getStack(Components.stack.caller);
-    sendConsoleAPIMessage("trace", trace[0], args,
+    sendConsoleAPIMessage(this, "trace", trace[0], args,
                           { stacktrace: trace });
     dumpMessage(this, "trace", "\n" + formatTrace(trace));
   },
   clear: function Console_clear() {},
 
   dir: createMultiLineDumper("dir"),
   dirxml: createMultiLineDumper("dirxml"),
   group: createDumper("group"),
@@ -623,28 +628,28 @@ ConsoleAPI.prototype = {
 
   time: function Console_time() {
     if (!shouldLog("time", this.maxLogLevel)) {
       return;
     }
     let args = Array.prototype.slice.call(arguments, 0);
     let frame = getStack(Components.stack.caller, 1)[0];
     let timer = startTimer(args[0]);
-    sendConsoleAPIMessage("time", frame, args, { timer: timer });
+    sendConsoleAPIMessage(this, "time", frame, args, { timer: timer });
     dumpMessage(this, "time",
                 "'" + timer.name + "' @ " + (new Date()));
   },
 
   timeEnd: function Console_timeEnd() {
     if (!shouldLog("timeEnd", this.maxLogLevel)) {
       return;
     }
     let args = Array.prototype.slice.call(arguments, 0);
     let frame = getStack(Components.stack.caller, 1)[0];
     let timer = stopTimer(args[0]);
-    sendConsoleAPIMessage("timeEnd", frame, args, { timer: timer });
+    sendConsoleAPIMessage(this, "timeEnd", frame, args, { timer: timer });
     dumpMessage(this, "timeEnd",
                 "'" + timer.name + "' " + timer.duration + "ms");
   },
 };
 
 this.console = new ConsoleAPI();
 this.ConsoleAPI = ConsoleAPI;
--- a/toolkit/devtools/webconsole/test/chrome.ini
+++ b/toolkit/devtools/webconsole/test/chrome.ini
@@ -4,16 +4,17 @@ support-files =
   data.json
   data.json^headers^
   network_requests_iframe.html
 
 [test_basics.html]
 [test_bug819670_getter_throws.html]
 [test_cached_messages.html]
 [test_consoleapi.html]
+[test_consoleapi_innerID.html]
 [test_file_uri.html]
 [test_reflow.html]
 [test_jsterm.html]
 [test_network_get.html]
 [test_network_longstring.html]
 [test_network_post.html]
 [test_nsiconsolemessage.html]
 [test_object_actor.html]
new file mode 100644
--- /dev/null
+++ b/toolkit/devtools/webconsole/test/test_consoleapi_innerID.html
@@ -0,0 +1,164 @@
+<!DOCTYPE HTML>
+<html lang="en">
+<head>
+  <meta charset="utf8">
+  <title>Test for the innerID property of the Console API</title>
+  <script type="text/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+  <script type="text/javascript;version=1.8" src="common.js"></script>
+  <!-- Any copyright is dedicated to the Public Domain.
+     - http://creativecommons.org/publicdomain/zero/1.0/ -->
+</head>
+<body>
+<p>Test for the Console API</p>
+
+<script class="testbody" type="text/javascript;version=1.8">
+SimpleTest.waitForExplicitFinish();
+
+let expectedConsoleCalls = [];
+
+function doConsoleCalls(aState)
+{
+  let { ConsoleAPI } = Cu.import("resource://gre/modules/devtools/Console.jsm", {});
+  let console = new ConsoleAPI({
+    innerID: window.QueryInterface(Ci.nsIInterfaceRequestor)
+                   .getInterface(Ci.nsIDOMWindowUtils)
+                   .currentInnerWindowID
+  });
+
+  let longString = (new Array(DebuggerServer.LONG_STRING_LENGTH + 2)).join("a");
+
+  console.log("foobarBaz-log", undefined);
+  console.info("foobarBaz-info", null);
+  console.warn("foobarBaz-warn", top.document.documentElement);
+  console.debug(null);
+  console.trace();
+  console.dir(top.document, top.location);
+  console.log("foo", longString);
+
+  expectedConsoleCalls = [
+    {
+      level: "log",
+      filename: /test_consoleapi/,
+      functionName: "doConsoleCalls",
+      timeStamp: /^\d+$/,
+      arguments: ["foobarBaz-log", { type: "undefined" }],
+    },
+    {
+      level: "info",
+      filename: /test_consoleapi/,
+      functionName: "doConsoleCalls",
+      timeStamp: /^\d+$/,
+      arguments: ["foobarBaz-info", { type: "null" }],
+    },
+    {
+      level: "warn",
+      filename: /test_consoleapi/,
+      functionName: "doConsoleCalls",
+      timeStamp: /^\d+$/,
+      arguments: ["foobarBaz-warn", { type: "object", actor: /[a-z]/ }],
+    },
+    {
+      level: "debug",
+      filename: /test_consoleapi/,
+      functionName: "doConsoleCalls",
+      timeStamp: /^\d+$/,
+      arguments: [{ type: "null" }],
+    },
+    {
+      level: "trace",
+      filename: /test_consoleapi/,
+      functionName: "doConsoleCalls",
+      timeStamp: /^\d+$/,
+      stacktrace: [
+        {
+          filename: /test_consoleapi/,
+          functionName: "doConsoleCalls",
+        },
+        {
+          filename: /test_consoleapi/,
+          functionName: "onAttach",
+        },
+      ],
+    },
+    {
+      level: "dir",
+      filename: /test_consoleapi/,
+      functionName: "doConsoleCalls",
+      timeStamp: /^\d+$/,
+      arguments: [
+        {
+          type: "object",
+          actor: /[a-z]/,
+          class: "XULDocument",
+        },
+        {
+          type: "object",
+          actor: /[a-z]/,
+          class: "Location",
+        }
+      ],
+    },
+    {
+      level: "log",
+      filename: /test_consoleapi/,
+      functionName: "doConsoleCalls",
+      timeStamp: /^\d+$/,
+      arguments: [
+        "foo",
+        {
+          type: "longString",
+          initial: longString.substring(0,
+            DebuggerServer.LONG_STRING_INITIAL_LENGTH),
+          length: longString.length,
+          actor: /[a-z]/,
+        },
+      ],
+    },
+  ];
+}
+
+function startTest()
+{
+  removeEventListener("load", startTest);
+
+  attachConsole(["ConsoleAPI"], onAttach, true);
+}
+
+function onAttach(aState, aResponse)
+{
+  onConsoleAPICall = onConsoleAPICall.bind(null, aState);
+  aState.dbgClient.addListener("consoleAPICall", onConsoleAPICall);
+  doConsoleCalls(aState.actor);
+}
+
+let consoleCalls = [];
+
+function onConsoleAPICall(aState, aType, aPacket)
+{
+  info("received message level: " + aPacket.message.level);
+  is(aPacket.from, aState.actor, "console API call actor");
+
+  consoleCalls.push(aPacket.message);
+  if (consoleCalls.length != expectedConsoleCalls.length) {
+    return;
+  }
+
+  aState.dbgClient.removeListener("consoleAPICall", onConsoleAPICall);
+
+  expectedConsoleCalls.forEach(function(aMessage, aIndex) {
+    info("checking received console call #" + aIndex);
+    checkConsoleAPICall(consoleCalls[aIndex], expectedConsoleCalls[aIndex]);
+  });
+
+
+  consoleCalls = [];
+
+  closeDebugger(aState, function() {
+    SimpleTest.finish();
+  });
+}
+
+addEventListener("load", startTest);
+</script>
+</body>
+</html>
--- a/toolkit/devtools/webconsole/utils.js
+++ b/toolkit/devtools/webconsole/utils.js
@@ -1344,17 +1344,17 @@ ConsoleAPIListener.prototype =
   observe: function CAL_observe(aMessage, aTopic)
   {
     if (!this.owner) {
       return;
     }
 
     let apiMessage = aMessage.wrappedJSObject;
     if (this.window) {
-      let msgWindow = Services.wm.getOuterWindowWithId(apiMessage.ID);
+      let msgWindow = Services.wm.getCurrentInnerWindowWithId(apiMessage.innerID);
       if (!msgWindow || !this.layoutHelpers.isIncludedInTopLevelWindow(msgWindow)) {
         // Not the same window!
         return;
       }
     }
 
     this.owner.onConsoleAPICall(apiMessage);
   },