Bug 768096 - Web Console remote debugging protocol support - Part 5: tests; r=robcee
☠☠ backed out by 3d2e44c2fd29 ☠ ☠
authorMihai Sucan <mihai.sucan@gmail.com>
Wed, 26 Sep 2012 17:27:38 +0100
changeset 108507 89ab8685729df08d361ffa99000fd13f74cbbc40
parent 108506 9c70da27ec28f740cc2ae2f60394b17141ab452e
child 108508 3eb02ef25ea5a92bb3a85abdc6befc7d3f2d745f
push id82
push usershu@rfrn.org
push dateFri, 05 Oct 2012 13:20:22 +0000
reviewersrobcee
bugs768096
milestone18.0a1
Bug 768096 - Web Console remote debugging protocol support - Part 5: tests; r=robcee
toolkit/devtools/webconsole/Makefile.in
toolkit/devtools/webconsole/test/Makefile.in
toolkit/devtools/webconsole/test/common.js
toolkit/devtools/webconsole/test/data.json
toolkit/devtools/webconsole/test/network_requests_iframe.html
toolkit/devtools/webconsole/test/test_basics.html
toolkit/devtools/webconsole/test/test_cached_messages.html
toolkit/devtools/webconsole/test/test_consoleapi.html
toolkit/devtools/webconsole/test/test_jsterm.html
toolkit/devtools/webconsole/test/test_network_get.html
toolkit/devtools/webconsole/test/test_network_post.html
toolkit/devtools/webconsole/test/test_object_actor.html
toolkit/devtools/webconsole/test/test_page_errors.html
--- a/toolkit/devtools/webconsole/Makefile.in
+++ b/toolkit/devtools/webconsole/Makefile.in
@@ -4,14 +4,14 @@
 
 DEPTH = ../../..
 topsrcdir = @top_srcdir@
 srcdir = @srcdir@
 VPATH = @srcdir@
 
 include $(DEPTH)/config/autoconf.mk
 
-#TEST_DIRS += tests
+TEST_DIRS += test
 
 include $(topsrcdir)/config/rules.mk
 
 libs::
 	$(INSTALL) $(IFLAGS1) $(srcdir)/*.jsm $(FINAL_TARGET)/modules/devtools
new file mode 100644
--- /dev/null
+++ b/toolkit/devtools/webconsole/test/Makefile.in
@@ -0,0 +1,28 @@
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+DEPTH          = @DEPTH@
+topsrcdir      = @top_srcdir@
+srcdir         = @srcdir@
+VPATH          = @srcdir@
+relativesrcdir = @relativesrcdir@
+
+include $(DEPTH)/config/autoconf.mk
+
+# Mochitest tests
+MOCHITEST_FILES = \
+    test_basics.html \
+    test_cached_messages.html \
+    test_page_errors.html \
+    test_consoleapi.html \
+    test_jsterm.html \
+    test_object_actor.html \
+    test_network_get.html \
+    test_network_post.html \
+    network_requests_iframe.html \
+    data.json \
+    common.js \
+    $(NULL)
+
+include $(topsrcdir)/config/rules.mk
new file mode 100644
--- /dev/null
+++ b/toolkit/devtools/webconsole/test/common.js
@@ -0,0 +1,128 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+netscape.security.PrivilegeManager.enablePrivilege("UniversalXPConnect");
+
+const {classes: Cc, interfaces: Ci, utils: Cu} = Components;
+
+Cu.import("resource://gre/modules/Services.jsm");
+Cu.import("resource://gre/modules/devtools/dbg-server.jsm");
+Cu.import("resource://gre/modules/devtools/dbg-client.jsm");
+Cu.import("resource://gre/modules/devtools/WebConsoleUtils.jsm");
+
+function initCommon()
+{
+  // Always log packets when running tests.
+  Services.prefs.setBoolPref("devtools.debugger.log", true);
+}
+
+function initDebuggerServer()
+{
+  if (!DebuggerServer.initialized) {
+    DebuggerServer.init();
+    DebuggerServer.addBrowserActors();
+  }
+}
+
+function connectToDebugger(aCallback)
+{
+  initCommon();
+  initDebuggerServer();
+
+  let transport = DebuggerServer.connectPipe();
+  let client = new DebuggerClient(transport);
+
+  let dbgState = { dbgClient: client };
+  client.connect(aCallback.bind(null, dbgState));
+}
+
+function attachConsole(aListeners, aCallback)
+{
+  function _onAttachConsole(aState, aResponse, aWebConsoleClient)
+  {
+    if (aResponse.error) {
+      Cu.reportError("attachConsole failed: " + aResponse.error + " " +
+                     aResponse.message);
+    }
+
+    aState.client = aWebConsoleClient;
+
+    aCallback(aState, aResponse);
+  }
+
+  connectToDebugger(function _onConnect(aState) {
+    aState.dbgClient.listTabs(function _onListTabs(aResponse) {
+      if (aResponse.error) {
+        Cu.reportError("listTabs failed: " + aResponse.error + " " +
+                       aResponse.message);
+        aCallback(aState, aResponse);
+        return;
+      }
+      let tab = aResponse.tabs[aResponse.selected];
+      aState.actor = tab.consoleActor;
+      aState.dbgClient.attachConsole(tab.consoleActor, aListeners,
+                                     _onAttachConsole.bind(null, aState));
+    });
+  });
+}
+
+function closeDebugger(aState, aCallback)
+{
+  aState.dbgClient.close(aCallback);
+  aState.dbgClient = null;
+  aState.client = null;
+}
+
+function checkConsoleAPICall(aCall, aExpected)
+{
+  if (aExpected.level != "trace" && aExpected.arguments) {
+    is(aCall.arguments.length, aExpected.arguments.length,
+       "number of arguments");
+  }
+
+  checkObject(aCall, aExpected);
+}
+
+function checkObject(aObject, aExpected)
+{
+  for (let name of Object.keys(aExpected))
+  {
+    let expected = aExpected[name];
+    let value = aObject[name];
+    if (value === undefined) {
+      ok(false, "'" + name + "' is undefined");
+    }
+    else if (typeof expected == "string" ||
+        typeof expected == "number" ||
+        typeof expected == "boolean") {
+      is(value, expected, "property '" + name + "'");
+    }
+    else if (expected instanceof RegExp) {
+      ok(expected.test(value), name + ": " + expected);
+    }
+    else if (Array.isArray(expected)) {
+      info("checking array for property '" + name + "'");
+      checkObject(value, expected);
+    }
+    else if (typeof expected == "object") {
+      info("checking object for property '" + name + "'");
+      checkObject(value, expected);
+    }
+  }
+}
+
+function checkHeadersOrCookies(aArray, aExpected)
+{
+  for (let elem of aArray) {
+    if (!(elem.name in aExpected)) {
+      continue;
+    }
+    let expected = aExpected[elem.name];
+    if (expected instanceof RegExp) {
+      ok(expected.test(elem.value), elem.name + ": " + expected);
+    }
+    else {
+      is(elem.value, expected, elem.name);
+    }
+  }
+}
new file mode 100644
--- /dev/null
+++ b/toolkit/devtools/webconsole/test/data.json
@@ -0,0 +1,1 @@
+{ id: "test JSON data", myArray: [ "foo", "bar", "baz", "biff" ] }
\ No newline at end of file
new file mode 100644
--- /dev/null
+++ b/toolkit/devtools/webconsole/test/network_requests_iframe.html
@@ -0,0 +1,43 @@
+<!DOCTYPE HTML>
+<html>
+  <head>
+    <meta charset="utf-8">
+    <title>Console HTTP test page</title>
+    <!-- Any copyright is dedicated to the Public Domain.
+       - http://creativecommons.org/publicdomain/zero/1.0/ -->
+    <script type="text/javascript"><!--
+      function makeXhr(aMethod, aUrl, aRequestBody, aCallback) {
+        var xmlhttp = new XMLHttpRequest();
+        xmlhttp.open(aMethod, aUrl, true);
+        if (aCallback) {
+          xmlhttp.onreadystatechange = function() {
+            if (xmlhttp.readyState == 4) {
+              aCallback();
+            }
+          };
+        }
+        xmlhttp.send(aRequestBody);
+      }
+
+      function testXhrGet(aCallback) {
+        makeXhr('get', 'data.json', null, aCallback);
+      }
+
+      function testXhrPost(aCallback) {
+        makeXhr('post', 'data.json', "Hello world!", aCallback);
+      }
+
+      document.cookie = "foobar=fooval";
+      document.cookie = "omgfoo=bug768096";
+    // --></script>
+  </head>
+  <body>
+    <h1>Web Console HTTP Logging Testpage</h1>
+    <h2>This page is used to test the HTTP logging.</h2>
+
+    <form action="?" method="post">
+      <input name="name" type="text" value="foo bar"><br>
+      <input name="age" type="text" value="144"><br>
+    </form>
+  </body>
+</html>
new file mode 100644
--- /dev/null
+++ b/toolkit/devtools/webconsole/test/test_basics.html
@@ -0,0 +1,69 @@
+<!DOCTYPE HTML>
+<html lang="en">
+<head>
+  <meta charset="utf8">
+  <title>Basic Web Console Actor tests</title>
+  <script type="text/javascript" src="/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>Basic Web Console Actor tests</p>
+
+<script type="text/javascript;version=1.8">
+netscape.security.PrivilegeManager.enablePrivilege("UniversalXPConnect");
+SimpleTest.waitForExplicitFinish();
+
+function startTest()
+{
+  removeEventListener("load", startTest);
+
+  attachConsole(["PageError"], onStartPageError);
+}
+
+function onStartPageError(aState, aResponse)
+{
+  is(aResponse.startedListeners.length, 1, "startedListeners.length");
+  is(aResponse.startedListeners[0], "PageError", "startedListeners: PageError");
+  ok(aResponse.nativeConsoleAPI, "nativeConsoleAPI");
+
+  closeDebugger(aState, function() {
+    top.console_ = top.console;
+    top.console = { lolz: "foo" };
+    attachConsole(["PageError", "ConsoleAPI", "foo"],
+                  onStartPageErrorAndConsoleAPI);
+  });
+}
+
+function onStartPageErrorAndConsoleAPI(aState, aResponse)
+{
+  let startedListeners = aResponse.startedListeners;
+  is(startedListeners.length, 2, "startedListeners.length");
+  isnot(startedListeners.indexOf("PageError"), -1, "startedListeners: PageError");
+  isnot(startedListeners.indexOf("ConsoleAPI"), -1,
+        "startedListeners: ConsoleAPI");
+  is(startedListeners.indexOf("foo"), -1, "startedListeners: no foo");
+  ok(!aResponse.nativeConsoleAPI, "!nativeConsoleAPI");
+
+  top.console = top.console_;
+  delete top.console_;
+
+  aState.client.stopListeners(["ConsoleAPI", "foo"],
+                              onStopConsoleAPI.bind(null, aState));
+}
+
+function onStopConsoleAPI(aState, aResponse)
+{
+  is(aResponse.stoppedListeners.length, 1, "stoppedListeners.length");
+  is(aResponse.stoppedListeners[0], "ConsoleAPI", "stoppedListeners: ConsoleAPI");
+
+  closeDebugger(aState, function() {
+    SimpleTest.finish();
+  });
+}
+
+addEventListener("load", startTest);
+</script>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/toolkit/devtools/webconsole/test/test_cached_messages.html
@@ -0,0 +1,179 @@
+<!DOCTYPE HTML>
+<html lang="en">
+<head>
+  <meta charset="utf8">
+  <title>Test for cached messages</title>
+  <script type="text/javascript" src="/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 cached messages</p>
+
+<script type="application/javascript;version=1.8">
+let expectedConsoleCalls = [];
+let expectedPageErrors = [];
+
+(function() {
+  Services.console.reset();
+
+  expectedPageErrors = [
+    {
+      _type: "PageError",
+      errorMessage: /fooColor/,
+      sourceName: /.+/,
+      category: "CSS Parser",
+      timeStamp: /^\d+$/,
+      error: false,
+      warning: true,
+      exception: false,
+      strict: false,
+    },
+    {
+      _type: "PageError",
+      errorMessage: /doTheImpossible/,
+      sourceName: /.+/,
+      category: "content javascript",
+      timeStamp: /^\d+$/,
+      error: false,
+      warning: false,
+      exception: true,
+      strict: false,
+    },
+  ];
+
+  let container = top.document.createElement("script");
+  top.document.body.appendChild(container);
+  container.textContent = "document.body.style.color = 'fooColor';";
+  top.document.body.removeChild(container);
+
+  container = top.document.createElement("script");
+  top.document.body.appendChild(container);
+  container.textContent = "document.doTheImpossible();";
+  top.document.body.removeChild(container);
+})();
+
+function doConsoleCalls()
+{
+  top.console.log("foobarBaz-log", undefined);
+  top.console.info("foobarBaz-info", null);
+  top.console.warn("foobarBaz-warn", document.body);
+
+  expectedConsoleCalls = [
+    {
+      _type: "ConsoleAPI",
+      level: "log",
+      filename: /test_cached_messages/,
+      functionName: "doConsoleCalls",
+      timeStamp: /^\d+$/,
+      arguments: ["foobarBaz-log", { type: "undefined" }],
+    },
+    {
+      _type: "ConsoleAPI",
+      level: "info",
+      filename: /test_cached_messages/,
+      functionName: "doConsoleCalls",
+      timeStamp: /^\d+$/,
+      arguments: ["foobarBaz-info", { type: "null" }],
+    },
+    {
+      _type: "ConsoleAPI",
+      level: "warn",
+      filename: /test_cached_messages/,
+      functionName: "doConsoleCalls",
+      timeStamp: /^\d+$/,
+      arguments: ["foobarBaz-warn", { type: "object", actor: /[a-z]/ }],
+    },
+  ];
+}
+</script>
+
+<script type="text/javascript;version=1.8">
+netscape.security.PrivilegeManager.enablePrivilege("UniversalXPConnect");
+SimpleTest.waitForExplicitFinish();
+
+let consoleAPIListener;
+
+let consoleAPICalls = 0;
+
+let handlers = {
+  onConsoleAPICall: function onConsoleAPICall()
+  {
+    consoleAPICalls++;
+    if (consoleAPICalls == expectedConsoleCalls.length) {
+      checkConsoleAPICache();
+    }
+  },
+};
+
+function startTest()
+{
+  removeEventListener("load", startTest);
+
+  consoleAPIListener = new ConsoleAPIListener(top, handlers);
+  consoleAPIListener.init();
+
+  doConsoleCalls();
+}
+
+function checkConsoleAPICache()
+{
+  consoleAPIListener.destroy();
+  consoleAPIListener = null;
+  attachConsole(["ConsoleAPI"], onAttach1);
+}
+
+function onAttach1(aState, aResponse)
+{
+  aState.client.getCachedMessages(["ConsoleAPI"],
+                                  onCachedConsoleAPI.bind(null, aState));
+}
+
+function onCachedConsoleAPI(aState, aResponse)
+{
+  let msgs = aResponse.messages;
+
+  is(msgs.length, expectedConsoleCalls.length,
+     "number of cached console messages");
+
+  expectedConsoleCalls.forEach(function(aMessage, aIndex) {
+    info("checking received cached message #" + aIndex);
+    checkConsoleAPICall(msgs[aIndex], expectedConsoleCalls[aIndex]);
+  });
+
+  closeDebugger(aState, testPageErrors);
+}
+
+function testPageErrors()
+{
+  attachConsole(["PageError"], onAttach2);
+}
+
+function onAttach2(aState, aResponse)
+{
+  aState.client.getCachedMessages(["PageError"],
+                                  onCachedPageErrors.bind(null, aState));
+}
+
+function onCachedPageErrors(aState, aResponse)
+{
+  let msgs = aResponse.messages;
+
+  is(msgs.length, expectedPageErrors.length,
+     "number of cached page errors");
+
+  expectedPageErrors.forEach(function(aMessage, aIndex) {
+    info("checking received cached message #" + aIndex);
+    checkObject(msgs[aIndex], expectedPageErrors[aIndex]);
+  });
+
+  closeDebugger(aState, function() {
+    SimpleTest.finish();
+  });
+}
+
+addEventListener("load", startTest);
+</script>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/toolkit/devtools/webconsole/test/test_consoleapi.html
@@ -0,0 +1,148 @@
+<!DOCTYPE HTML>
+<html lang="en">
+<head>
+  <meta charset="utf8">
+  <title>Test for the Console API</title>
+  <script type="text/javascript" src="/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 type="text/javascript;version=1.8">
+netscape.security.PrivilegeManager.enablePrivilege("UniversalXPConnect");
+SimpleTest.waitForExplicitFinish();
+
+let expectedConsoleCalls = [];
+
+function doConsoleCalls(aState)
+{
+  console.log("foobarBaz-log", undefined);
+  console.info("foobarBaz-info", null);
+  console.warn("foobarBaz-warn", document.body);
+  console.debug(null);
+  console.trace();
+  console.dir(document, window);
+
+  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+$/,
+      arguments: [
+        {
+          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]/,
+          className: "HTMLDocument",
+        },
+        {
+          type: "object",
+          actor: /[a-z]/,
+          className: "Window",
+        }
+      ],
+      objectProperties: [
+        {
+          name: "ATTRIBUTE_NODE",
+          value: 2,
+        },
+        {
+          name: "CDATA_SECTION_NODE",
+          value: 4,
+        }, // ...
+      ],
+    },
+  ];
+}
+
+function startTest()
+{
+  removeEventListener("load", startTest);
+
+  attachConsole(["ConsoleAPI"], onAttach);
+}
+
+function onAttach(aState, aResponse)
+{
+  onConsoleAPICall = onConsoleAPICall.bind(null, aState);
+  aState.dbgClient.addListener("consoleAPICall", onConsoleAPICall);
+  doConsoleCalls(aState.actor);
+}
+
+let consoleCalls = [];
+
+function onConsoleAPICall(aState, aType, aPacket)
+{
+  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>
new file mode 100644
--- /dev/null
+++ b/toolkit/devtools/webconsole/test/test_jsterm.html
@@ -0,0 +1,146 @@
+<!DOCTYPE HTML>
+<html lang="en">
+<head>
+  <meta charset="utf8">
+  <title>Test for JavaScript terminal functionality</title>
+  <script type="text/javascript" src="/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 JavaScript terminal functionality</p>
+
+<script type="text/javascript;version=1.8">
+netscape.security.PrivilegeManager.enablePrivilege("UniversalXPConnect");
+SimpleTest.waitForExplicitFinish();
+
+function startTest()
+{
+  removeEventListener("load", startTest);
+
+  attachConsole(["PageError"], onAttach);
+}
+
+function onAttach(aState, aResponse)
+{
+  top.foobarObject = Object.create(null);
+  top.foobarObject.foo = 1;
+  top.foobarObject.foobar = 2;
+  top.foobarObject.foobaz = 3;
+  top.foobarObject.omg = 4;
+  top.foobarObject.omgfoo = 5;
+
+  info("test autocomplete for 'window.foo'");
+  onAutocomplete1 = onAutocomplete1.bind(null, aState);
+  aState.client.autocomplete("window.foo", 0, onAutocomplete1);
+}
+
+function onAutocomplete1(aState, aResponse)
+{
+  let matches = aResponse.matches;
+
+  is(aResponse.matchProp, "foo", "matchProp");
+  is(matches.length, 1, "matches.length");
+  is(matches[0], "foobarObject", "matches[0]");
+
+  info("test autocomplete for 'window.foobarObject.'");
+
+  onAutocomplete2 = onAutocomplete2.bind(null, aState);
+  aState.client.autocomplete("window.foobarObject.", 0, onAutocomplete2);
+}
+
+function onAutocomplete2(aState, aResponse)
+{
+  let matches = aResponse.matches;
+
+  ok(!aResponse.matchProp, "matchProp");
+  is(matches.length, 5, "matches.length");
+  checkObject(matches, ["foo", "foobar", "foobaz", "omg", "omgfoo"]);
+
+  info("test eval '2+2'");
+
+  onEval1 = onEval1.bind(null, aState);
+  aState.client.evaluateJS("2+2", onEval1);
+}
+
+function onEval1(aState, aResponse)
+{
+  checkObject(aResponse, {
+    from: aState.actor,
+    input: "2+2",
+    result: 4,
+  });
+
+  ok(!aResponse.error, "no js error");
+  ok(!aResponse.helperResult, "no helper result");
+
+  info("test eval 'window'");
+  onEval2 = onEval2.bind(null, aState);
+  aState.client.evaluateJS("window", onEval2);
+}
+
+function onEval2(aState, aResponse)
+{
+  checkObject(aResponse, {
+    from: aState.actor,
+    input: "window",
+    result: {
+      type: "object",
+      className: "Window",
+      actor: /[a-z]/,
+    },
+  });
+
+  ok(!aResponse.error, "no js error");
+  ok(!aResponse.helperResult, "no helper result");
+
+  info("test eval with exception");
+
+  onEvalWithException = onEvalWithException.bind(null, aState);
+  aState.client.evaluateJS("window.doTheImpossible()",
+                           onEvalWithException);
+}
+
+function onEvalWithException(aState, aResponse)
+{
+  checkObject(aResponse, {
+    from: aState.actor,
+    input: "window.doTheImpossible()",
+    result: {
+      type: "undefined",
+    },
+    errorMessage: /doTheImpossible/,
+  });
+
+  ok(aResponse.error, "js error object");
+  ok(!aResponse.helperResult, "no helper result");
+
+  info("test eval with helper");
+
+  onEvalWithHelper = onEvalWithHelper.bind(null, aState);
+  aState.client.evaluateJS("clear()", onEvalWithHelper);
+}
+
+function onEvalWithHelper(aState, aResponse)
+{
+  checkObject(aResponse, {
+    from: aState.actor,
+    input: "clear()",
+    result: {
+      type: "undefined",
+    },
+    helperResult: { type: "clearOutput" },
+  });
+
+  ok(!aResponse.error, "no js error");
+
+  closeDebugger(aState, function() {
+    SimpleTest.finish();
+  });
+}
+
+addEventListener("load", startTest);
+</script>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/toolkit/devtools/webconsole/test/test_network_get.html
@@ -0,0 +1,243 @@
+<!DOCTYPE HTML>
+<html lang="en">
+<head>
+  <meta charset="utf8">
+  <title>Test for the network actor (GET request)</title>
+  <script type="text/javascript" src="/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 network actor (GET request)</p>
+
+<iframe src="/tests/toolkit/devtools/webconsole/test/network_requests_iframe.html"></iframe>
+
+<script type="text/javascript;version=1.8">
+netscape.security.PrivilegeManager.enablePrivilege("UniversalXPConnect");
+SimpleTest.waitForExplicitFinish();
+
+function startTest()
+{
+  removeEventListener("load", startTest);
+  attachConsole(["NetworkActivity"], onAttach);
+}
+
+function onAttach(aState, aResponse)
+{
+  info("test network GET request");
+
+  onNetworkEvent = onNetworkEvent.bind(null, aState);
+  aState.dbgClient.addListener("networkEvent", onNetworkEvent);
+  onNetworkEventUpdate = onNetworkEventUpdate.bind(null, aState);
+  aState.dbgClient.addListener("networkEventUpdate", onNetworkEventUpdate);
+
+  let iframe = document.querySelector("iframe").contentWindow;
+  iframe.testXhrGet();
+}
+
+function onNetworkEvent(aState, aType, aPacket)
+{
+  is(aPacket.from, aState.actor, "network event actor");
+
+  info("checking the network event packet");
+
+  let netActor = aPacket.eventActor;
+
+  checkObject(netActor, {
+    actor: /[a-z]/,
+    startedDateTime: /^\d+\-\d+\-\d+T.+$/,
+    url: /data\.json/,
+    method: "GET",
+  });
+
+  aState.netActor = netActor.actor;
+
+  aState.dbgClient.removeListener("networkEvent", onNetworkEvent);
+}
+
+let updates = [];
+
+function onNetworkEventUpdate(aState, aType, aPacket)
+{
+  info("received networkEventUpdate " + aPacket.updateType);
+  is(aPacket.from, aState.netActor, "networkEventUpdate actor");
+
+  updates.push(aPacket.updateType);
+
+  let expectedPacket = null;
+
+  switch (aPacket.updateType) {
+    case "requestHeaders":
+    case "responseHeaders":
+      ok(aPacket.headers > 0, "headers > 0");
+      ok(aPacket.headersSize > 0, "headersSize > 0");
+      break;
+    case "requestCookies":
+      expectedPacket = {
+        cookies: 2,
+      };
+      break;
+    case "requestPostData":
+      ok(false, "got unexpected requestPostData");
+      break;
+    case "responseStart":
+      expectedPacket = {
+        response: {
+          httpVersion: /^HTTP\/\d\.\d$/,
+          status: 200,
+          statusText: "OK",
+          headersSize: /^\d+$/,
+          discardResponseBody: true,
+        },
+      };
+      break;
+    case "responseCookies":
+      expectedPacket = {
+        cookies: 0,
+      };
+      break;
+    case "responseContent":
+      expectedPacket = {
+        mimeType: /^application\/(json|octet-stream)$/,
+        contentSize: 0,
+        discardResponseBody: true,
+      };
+      break;
+    case "eventTimings":
+      expectedPacket = {
+        totalTime: /^\d+$/,
+      };
+      break;
+    default:
+      ok(false, "unknown network event update type: " +
+         aPacket.updateType);
+      return;
+  }
+
+  if (expectedPacket) {
+    info("checking the packet content");
+    checkObject(aPacket, expectedPacket);
+  }
+
+  if (updates.indexOf("responseContent") > -1 &&
+      updates.indexOf("eventTimings") > -1) {
+    aState.dbgClient.removeListener("networkEventUpdate",
+                                    onNetworkEvent);
+
+    onRequestHeaders = onRequestHeaders.bind(null, aState);
+    aState.client.getRequestHeaders(aState.netActor,
+                                    onRequestHeaders);
+  }
+}
+
+function onRequestHeaders(aState, aResponse)
+{
+  info("checking request headers");
+
+  ok(aResponse.headers.length > 0, "request headers > 0");
+  ok(aResponse.headersSize > 0, "request headersSize > 0");
+
+  checkHeadersOrCookies(aResponse.headers, {
+    Referer: /network_requests_iframe\.html/,
+    Cookie: /bug768096/,
+  });
+
+  onRequestCookies = onRequestCookies.bind(null, aState);
+  aState.client.getRequestCookies(aState.netActor,
+                                  onRequestCookies);
+}
+
+function onRequestCookies(aState, aResponse)
+{
+  info("checking request cookies");
+
+  is(aResponse.cookies.length, 2, "request cookies length");
+
+  checkHeadersOrCookies(aResponse.cookies, {
+    foobar: "fooval",
+    omgfoo: "bug768096",
+  });
+
+  onRequestPostData = onRequestPostData.bind(null, aState);
+  aState.client.getRequestPostData(aState.netActor,
+                                   onRequestPostData);
+}
+
+function onRequestPostData(aState, aResponse)
+{
+  info("checking request POST data");
+
+  ok(!aResponse.postData.text, "no request POST data");
+  ok(aResponse.postDataDiscarded, "request POST data was discarded");
+
+  onResponseHeaders = onResponseHeaders.bind(null, aState);
+  aState.client.getResponseHeaders(aState.netActor,
+                                   onResponseHeaders);
+}
+
+function onResponseHeaders(aState, aResponse)
+{
+  info("checking response headers");
+
+  ok(aResponse.headers.length > 0, "response headers > 0");
+  ok(aResponse.headersSize > 0, "response headersSize > 0");
+
+  checkHeadersOrCookies(aResponse.headers, {
+    "Content-Type": /^application\/(json|octet-stream)$/,
+    "Content-Length": /^\d+$/,
+  });
+
+  onResponseCookies = onResponseCookies.bind(null, aState);
+  aState.client.getResponseCookies(aState.netActor,
+                                  onResponseCookies);
+}
+
+function onResponseCookies(aState, aResponse)
+{
+  info("checking response cookies");
+
+  is(aResponse.cookies.length, 0, "response cookies length");
+
+  onResponseContent = onResponseContent.bind(null, aState);
+  aState.client.getResponseContent(aState.netActor,
+                                   onResponseContent);
+}
+
+function onResponseContent(aState, aResponse)
+{
+  info("checking response content");
+
+  ok(!aResponse.content.text, "no response content");
+  ok(aResponse.contentDiscarded, "response content was discarded");
+
+  onEventTimings = onEventTimings.bind(null, aState);
+  aState.client.getEventTimings(aState.netActor,
+                                onEventTimings);
+}
+
+function onEventTimings(aState, aResponse)
+{
+  info("checking event timings");
+
+  checkObject(aResponse, {
+    timings: {
+      blocked: /^-1|\d+$/,
+      dns: /^-1|\d+$/,
+      connect: /^-1|\d+$/,
+      send: /^-1|\d+$/,
+      wait: /^-1|\d+$/,
+      receive: /^-1|\d+$/,
+    },
+    totalTime: /^\d+$/,
+  });
+
+  closeDebugger(aState, function() {
+    SimpleTest.finish();
+  });
+}
+
+addEventListener("load", startTest);
+</script>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/toolkit/devtools/webconsole/test/test_network_post.html
@@ -0,0 +1,267 @@
+<!DOCTYPE HTML>
+<html lang="en">
+<head>
+  <meta charset="utf8">
+  <title>Test for the network actor (POST request)</title>
+  <script type="text/javascript" src="/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 network actor (POST request)</p>
+
+<iframe src="/tests/toolkit/devtools/webconsole/test/network_requests_iframe.html"></iframe>
+
+<script type="text/javascript;version=1.8">
+netscape.security.PrivilegeManager.enablePrivilege("UniversalXPConnect");
+SimpleTest.waitForExplicitFinish();
+
+function startTest()
+{
+  removeEventListener("load", startTest);
+
+  attachConsole(["NetworkActivity"], onAttach);
+}
+
+function onAttach(aState, aResponse)
+{
+  info("enable network request and response body logging");
+
+  onSetPreferences = onSetPreferences.bind(null, aState);
+  aState.client.setPreferences({
+    "NetworkMonitor.saveRequestAndResponseBodies": true,
+  }, onSetPreferences);
+}
+
+function onSetPreferences(aState, aResponse)
+{
+  is(aResponse.updated.length, 1, "updated prefs length");
+  is(aResponse.updated[0], "NetworkMonitor.saveRequestAndResponseBodies",
+     "updated prefs length");
+
+  info("test network POST request");
+
+  onNetworkEvent = onNetworkEvent.bind(null, aState);
+  aState.dbgClient.addListener("networkEvent", onNetworkEvent);
+  onNetworkEventUpdate = onNetworkEventUpdate.bind(null, aState);
+  aState.dbgClient.addListener("networkEventUpdate", onNetworkEventUpdate);
+
+  let iframe = document.querySelector("iframe").contentWindow;
+  iframe.testXhrPost();
+}
+
+function onNetworkEvent(aState, aType, aPacket)
+{
+  is(aPacket.from, aState.actor, "network event actor");
+
+  info("checking the network event packet");
+
+  let netActor = aPacket.eventActor;
+
+  checkObject(netActor, {
+    actor: /[a-z]/,
+    startedDateTime: /^\d+\-\d+\-\d+T.+$/,
+    url: /data\.json/,
+    method: "POST",
+  });
+
+  aState.netActor = netActor.actor;
+
+  aState.dbgClient.removeListener("networkEvent", onNetworkEvent);
+}
+
+let updates = [];
+
+function onNetworkEventUpdate(aState, aType, aPacket)
+{
+  info("received networkEventUpdate " + aPacket.updateType);
+  is(aPacket.from, aState.netActor, "networkEventUpdate actor");
+
+  updates.push(aPacket.updateType);
+
+  let expectedPacket = null;
+
+  switch (aPacket.updateType) {
+    case "requestHeaders":
+    case "responseHeaders":
+      ok(aPacket.headers > 0, "headers > 0");
+      ok(aPacket.headersSize > 0, "headersSize > 0");
+      break;
+    case "requestCookies":
+      expectedPacket = {
+        cookies: 2,
+      };
+      break;
+    case "requestPostData":
+      ok(aPacket.dataSize > 0, "dataSize > 0");
+      ok(!aPacket.discardRequestBody, "discardRequestBody");
+      break;
+    case "responseStart":
+      expectedPacket = {
+        response: {
+          httpVersion: /^HTTP\/\d\.\d$/,
+          status: 200,
+          statusText: "OK",
+          headersSize: /^\d+$/,
+          discardResponseBody: false,
+        },
+      };
+      break;
+    case "responseCookies":
+      expectedPacket = {
+        cookies: 0,
+      };
+      break;
+    case "responseContent":
+      expectedPacket = {
+        mimeType: /^application\/(json|octet-stream)$/,
+        contentSize: /^\d+$/,
+        discardResponseBody: false,
+      };
+      break;
+    case "eventTimings":
+      expectedPacket = {
+        totalTime: /^\d+$/,
+      };
+      break;
+    default:
+      ok(false, "unknown network event update type: " +
+         aPacket.updateType);
+      return;
+  }
+
+  if (expectedPacket) {
+    info("checking the packet content");
+    checkObject(aPacket, expectedPacket);
+  }
+
+  if (updates.indexOf("responseContent") > -1 &&
+      updates.indexOf("eventTimings") > -1) {
+    aState.dbgClient.removeListener("networkEventUpdate",
+                                    onNetworkEvent);
+
+    onRequestHeaders = onRequestHeaders.bind(null, aState);
+    aState.client.getRequestHeaders(aState.netActor,
+                                    onRequestHeaders);
+  }
+}
+
+function onRequestHeaders(aState, aResponse)
+{
+  info("checking request headers");
+
+  ok(aResponse.headers.length > 0, "request headers > 0");
+  ok(aResponse.headersSize > 0, "request headersSize > 0");
+
+  checkHeadersOrCookies(aResponse.headers, {
+    Referer: /network_requests_iframe\.html/,
+    Cookie: /bug768096/,
+  });
+
+  onRequestCookies = onRequestCookies.bind(null, aState);
+  aState.client.getRequestCookies(aState.netActor,
+                                  onRequestCookies);
+}
+
+function onRequestCookies(aState, aResponse)
+{
+  info("checking request cookies");
+
+  is(aResponse.cookies.length, 2, "request cookies length");
+
+  checkHeadersOrCookies(aResponse.cookies, {
+    foobar: "fooval",
+    omgfoo: "bug768096",
+  });
+
+  onRequestPostData = onRequestPostData.bind(null, aState);
+  aState.client.getRequestPostData(aState.netActor,
+                                   onRequestPostData);
+}
+
+function onRequestPostData(aState, aResponse)
+{
+  info("checking request POST data");
+
+  checkObject(aResponse, {
+    postData: {
+      text: "Hello world!",
+    },
+    postDataDiscarded: false,
+  });
+
+  onResponseHeaders = onResponseHeaders.bind(null, aState);
+  aState.client.getResponseHeaders(aState.netActor,
+                                   onResponseHeaders);
+}
+
+function onResponseHeaders(aState, aResponse)
+{
+  info("checking response headers");
+
+  ok(aResponse.headers.length > 0, "response headers > 0");
+  ok(aResponse.headersSize > 0, "response headersSize > 0");
+
+  checkHeadersOrCookies(aResponse.headers, {
+    "Content-Type": /^application\/(json|octet-stream)$/,
+    "Content-Length": /^\d+$/,
+  });
+
+  onResponseCookies = onResponseCookies.bind(null, aState);
+  aState.client.getResponseCookies(aState.netActor,
+                                  onResponseCookies);
+}
+
+function onResponseCookies(aState, aResponse)
+{
+  info("checking response cookies");
+
+  is(aResponse.cookies.length, 0, "response cookies length");
+
+  onResponseContent = onResponseContent.bind(null, aState);
+  aState.client.getResponseContent(aState.netActor,
+                                   onResponseContent);
+}
+
+function onResponseContent(aState, aResponse)
+{
+  info("checking response content");
+
+  checkObject(aResponse, {
+    content: {
+      text: /"test JSON data"/,
+    },
+    contentDiscarded: false,
+  });
+
+  onEventTimings = onEventTimings.bind(null, aState);
+  aState.client.getEventTimings(aState.netActor,
+                                onEventTimings);
+}
+
+function onEventTimings(aState, aResponse)
+{
+  info("checking event timings");
+
+  checkObject(aResponse, {
+    timings: {
+      blocked: /^-1|\d+$/,
+      dns: /^-1|\d+$/,
+      connect: /^-1|\d+$/,
+      send: /^-1|\d+$/,
+      wait: /^-1|\d+$/,
+      receive: /^-1|\d+$/,
+    },
+    totalTime: /^\d+$/,
+  });
+
+  closeDebugger(aState, function() {
+    SimpleTest.finish();
+  });
+}
+
+addEventListener("load", startTest);
+</script>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/toolkit/devtools/webconsole/test/test_object_actor.html
@@ -0,0 +1,172 @@
+<!DOCTYPE HTML>
+<html lang="en">
+<head>
+  <meta charset="utf8">
+  <title>Test for the object actor</title>
+  <script type="text/javascript" src="/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 object actor</p>
+
+<script type="text/javascript;version=1.8">
+netscape.security.PrivilegeManager.enablePrivilege("UniversalXPConnect");
+SimpleTest.waitForExplicitFinish();
+
+let expectedProps = [];
+
+function startTest()
+{
+  removeEventListener("load", startTest);
+
+  attachConsole(["ConsoleAPI"], onAttach);
+}
+
+function onAttach(aState, aResponse)
+{
+  onConsoleCall = onConsoleCall.bind(null, aState);
+  aState.dbgClient.addListener("consoleAPICall", onConsoleCall);
+
+  window.foobarObject = Object.create(null);
+  foobarObject.foo = 1;
+  foobarObject.foobar = "hello";
+  foobarObject.foobaz = document;
+  foobarObject.omg = null;
+  foobarObject.testfoo = false;
+  foobarObject.notInspectable = {};
+  foobarObject.omgfn = function _omgfn() {
+    return "myResult";
+  };
+  foobarObject.abArray = ["a", "b"];
+
+  Object.defineProperty(foobarObject, "getterAndSetter", {
+    enumerable: true,
+    get: function fooGet() { return "foo"; },
+    set: function fooSet() { 1+2 },
+  });
+
+  console.log("hello", foobarObject);
+
+  expectedProps = [
+    {
+      name: "abArray",
+      value: {
+        type: "object",
+        className: "Array",
+        actor: /[a-z]/,
+        inspectable: true,
+      },
+    },
+    {
+      name: "foo",
+      configurable: true,
+      enumerable: true,
+      writable: true,
+      value: 1,
+    },
+    {
+      name: "foobar",
+      value: "hello",
+    },
+    {
+      name: "foobaz",
+      value: {
+        type: "object",
+        className: "HTMLDocument",
+        displayString: /\[object HTMLDocument/,
+        inspectable: true,
+        actor: /[a-z]/,
+      },
+    },
+    {
+      name: "getterAndSetter",
+      get: {
+        type: "function",
+        className: "function",
+        displayString: /function fooGet/,
+        actor: /[a-z]/,
+        inspectable: false,
+      },
+      set: {
+        type: "function",
+        className: "function",
+        displayString: /function fooSet/,
+        actor: /[a-z]/,
+        inspectable: false,
+      },
+    },
+    {
+      name: "notInspectable",
+      value: {
+        type: "object",
+        className: "Object",
+        actor: /[a-z]/,
+        inspectable: false,
+      },
+    },
+    {
+      name: "omg",
+      value: { type: "null" },
+    },
+    {
+      name: "omgfn",
+      value: {
+        type: "function",
+        className: "function",
+        displayString: /function _omgfn/,
+        actor: /[a-z]/,
+        inspectable: false,
+      },
+    },
+    {
+      name: "testfoo",
+      value: false,
+    },
+  ];
+}
+
+function onConsoleCall(aState, aType, aPacket)
+{
+  is(aPacket.from, aState.actor, "console API call actor");
+
+  info("checking the console API call packet");
+
+  checkConsoleAPICall(aPacket.message, {
+    level: "log",
+    filename: /test_object_actor/,
+    functionName: "onAttach",
+    arguments: ["hello", {
+      type: "object",
+      actor: /[a-z]/,
+      inspectable: true,
+    }],
+  });
+
+  aState.dbgClient.removeListener("consoleAPICall", onConsoleCall);
+
+  info("inspecting object properties");
+  let args = aPacket.message.arguments;
+  onProperties = onProperties.bind(null, aState);
+  aState.client.inspectObjectProperties(args[1].actor, onProperties);
+}
+
+function onProperties(aState, aResponse)
+{
+  let props = aResponse.properties;
+  is(props.length, expectedProps.length,
+     "number of enumerable properties");
+  checkObject(props, expectedProps);
+
+  expectedProps = [];
+
+  closeDebugger(aState, function() {
+    SimpleTest.finish();
+  });
+}
+
+addEventListener("load", startTest);
+</script>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/toolkit/devtools/webconsole/test/test_page_errors.html
@@ -0,0 +1,98 @@
+<!DOCTYPE HTML>
+<html lang="en">
+<head>
+  <meta charset="utf8">
+  <title>Test for page errors</title>
+  <script type="text/javascript" src="/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 page errors</p>
+
+<script type="text/javascript;version=1.8">
+netscape.security.PrivilegeManager.enablePrivilege("UniversalXPConnect");
+SimpleTest.waitForExplicitFinish();
+
+let expectedPageErrors = [];
+
+function doPageErrors()
+{
+  expectedPageErrors = [
+    {
+      errorMessage: /fooColor/,
+      sourceName: /test_page_errors/,
+      category: "CSS Parser",
+      timeStamp: /^\d+$/,
+      error: false,
+      warning: true,
+      exception: false,
+      strict: false,
+    },
+    {
+      errorMessage: /doTheImpossible/,
+      sourceName: /test_page_errors/,
+      category: "content javascript",
+      timeStamp: /^\d+$/,
+      error: false,
+      warning: false,
+      exception: true,
+      strict: false,
+    },
+  ];
+
+  let container = document.createElement("script");
+  document.body.appendChild(container);
+  container.textContent = "document.body.style.color = 'fooColor';";
+  document.body.removeChild(container);
+
+  SimpleTest.expectUncaughtException();
+
+  container = document.createElement("script");
+  document.body.appendChild(container);
+  container.textContent = "document.doTheImpossible();";
+  document.body.removeChild(container);
+}
+
+function startTest()
+{
+  removeEventListener("load", startTest);
+
+  attachConsole(["PageError"], onAttach);
+}
+
+function onAttach(aState, aResponse)
+{
+  onPageError = onPageError.bind(null, aState);
+  aState.dbgClient.addListener("pageError", onPageError);
+  doPageErrors();
+}
+
+let pageErrors = [];
+
+function onPageError(aState, aType, aPacket)
+{
+  is(aPacket.from, aState.actor, "page error actor");
+
+  pageErrors.push(aPacket.pageError);
+  if (pageErrors.length != expectedPageErrors.length) {
+    return;
+  }
+
+  aState.dbgClient.removeListener("pageError", onPageError);
+
+  expectedPageErrors.forEach(function(aMessage, aIndex) {
+    info("checking received page error #" + aIndex);
+    checkObject(pageErrors[aIndex], expectedPageErrors[aIndex]);
+  });
+
+  closeDebugger(aState, function() {
+    SimpleTest.finish();
+  });
+}
+
+addEventListener("load", startTest);
+</script>
+</body>
+</html>