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 114696 89ab8685729df08d361ffa99000fd13f74cbbc40
parent 114695 9c70da27ec28f740cc2ae2f60394b17141ab452e
child 114697 3eb02ef25ea5a92bb3a85abdc6befc7d3f2d745f
push id1708
push userakeybl@mozilla.com
push dateMon, 19 Nov 2012 21:10:21 +0000
treeherdermozilla-beta@27b14fe50103 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersrobcee
bugs768096
milestone18.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 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>