summaryrefslogtreecommitdiffstats
path: root/devtools/client/webconsole/net/test
diff options
context:
space:
mode:
Diffstat (limited to 'devtools/client/webconsole/net/test')
-rw-r--r--devtools/client/webconsole/net/test/mochitest/.eslintrc.js6
-rw-r--r--devtools/client/webconsole/net/test/mochitest/browser.ini22
-rw-r--r--devtools/client/webconsole/net/test/mochitest/browser_net_basic.js33
-rw-r--r--devtools/client/webconsole/net/test/mochitest/browser_net_cookies.js54
-rw-r--r--devtools/client/webconsole/net/test/mochitest/browser_net_headers.js40
-rw-r--r--devtools/client/webconsole/net/test/mochitest/browser_net_params.js69
-rw-r--r--devtools/client/webconsole/net/test/mochitest/browser_net_post.js88
-rw-r--r--devtools/client/webconsole/net/test/mochitest/browser_net_response.js86
-rw-r--r--devtools/client/webconsole/net/test/mochitest/head.js209
-rw-r--r--devtools/client/webconsole/net/test/mochitest/page_basic.html14
-rw-r--r--devtools/client/webconsole/net/test/mochitest/test-cookies.json1
-rw-r--r--devtools/client/webconsole/net/test/mochitest/test-cookies.json^headers^2
-rw-r--r--devtools/client/webconsole/net/test/mochitest/test.json1
-rw-r--r--devtools/client/webconsole/net/test/mochitest/test.json^headers^1
-rw-r--r--devtools/client/webconsole/net/test/mochitest/test.txt1
-rw-r--r--devtools/client/webconsole/net/test/mochitest/test.xml1
-rw-r--r--devtools/client/webconsole/net/test/mochitest/test.xml^headers^1
-rw-r--r--devtools/client/webconsole/net/test/unit/.eslintrc.js6
-rw-r--r--devtools/client/webconsole/net/test/unit/test_json-utils.js45
-rw-r--r--devtools/client/webconsole/net/test/unit/test_net-utils.js77
-rw-r--r--devtools/client/webconsole/net/test/unit/xpcshell.ini9
21 files changed, 766 insertions, 0 deletions
diff --git a/devtools/client/webconsole/net/test/mochitest/.eslintrc.js b/devtools/client/webconsole/net/test/mochitest/.eslintrc.js
new file mode 100644
index 000000000..76904829d
--- /dev/null
+++ b/devtools/client/webconsole/net/test/mochitest/.eslintrc.js
@@ -0,0 +1,6 @@
+"use strict";
+
+module.exports = {
+ // Extend from the shared list of defined globals for mochitests.
+ "extends": "../../../../../.eslintrc.mochitests.js",
+};
diff --git a/devtools/client/webconsole/net/test/mochitest/browser.ini b/devtools/client/webconsole/net/test/mochitest/browser.ini
new file mode 100644
index 000000000..9414414c6
--- /dev/null
+++ b/devtools/client/webconsole/net/test/mochitest/browser.ini
@@ -0,0 +1,22 @@
+[DEFAULT]
+tags = devtools
+subsuite = devtools
+support-files =
+ head.js
+ page_basic.html
+ test.json
+ test.json^headers^
+ test-cookies.json
+ test-cookies.json^headers^
+ test.txt
+ test.xml
+ test.xml^headers^
+ !/devtools/client/webconsole/test/head.js
+ !/devtools/client/framework/test/shared-head.js
+
+[browser_net_basic.js]
+[browser_net_cookies.js]
+[browser_net_headers.js]
+[browser_net_params.js]
+[browser_net_post.js]
+[browser_net_response.js]
diff --git a/devtools/client/webconsole/net/test/mochitest/browser_net_basic.js b/devtools/client/webconsole/net/test/mochitest/browser_net_basic.js
new file mode 100644
index 000000000..57273bec0
--- /dev/null
+++ b/devtools/client/webconsole/net/test/mochitest/browser_net_basic.js
@@ -0,0 +1,33 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+const TEST_PAGE_URL = URL_ROOT + "page_basic.html";
+const JSON_XHR_URL = URL_ROOT + "test.json";
+
+/**
+ * Basic test that generates XHR in the content and
+ * checks the related log in the Console panel can
+ * be expanded.
+ */
+add_task(function* () {
+ info("Test XHR Spy basic started");
+
+ let {hud} = yield addTestTab(TEST_PAGE_URL);
+
+ let netInfoBody = yield executeAndInspectXhr(hud, {
+ method: "GET",
+ url: JSON_XHR_URL
+ });
+
+ ok(netInfoBody, "The network details must be available");
+
+ // There should be at least two tabs: Headers and Response
+ ok(netInfoBody.querySelector(".tabs .tabs-menu-item.headers"),
+ "Headers tab must be available");
+ ok(netInfoBody.querySelector(".tabs .tabs-menu-item.response"),
+ "Response tab must be available");
+});
diff --git a/devtools/client/webconsole/net/test/mochitest/browser_net_cookies.js b/devtools/client/webconsole/net/test/mochitest/browser_net_cookies.js
new file mode 100644
index 000000000..cfd85c2ed
--- /dev/null
+++ b/devtools/client/webconsole/net/test/mochitest/browser_net_cookies.js
@@ -0,0 +1,54 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+const TEST_PAGE_URL = URL_ROOT + "page_basic.html";
+const JSON_XHR_URL = URL_ROOT + "test-cookies.json";
+
+/**
+ * This test generates XHR requests in the page, expands
+ * networks details in the Console panel and checks that
+ * Cookies are properly displayed.
+ */
+add_task(function* () {
+ info("Test XHR Spy cookies started");
+
+ let {hud} = yield addTestTab(TEST_PAGE_URL);
+
+ let netInfoBody = yield executeAndInspectXhr(hud, {
+ method: "GET",
+ url: JSON_XHR_URL
+ });
+
+ // Select "Cookies" tab
+ let tabBody = yield selectNetInfoTab(hud, netInfoBody, "cookies");
+
+ let requestCookieName = tabBody.querySelector(
+ ".netInfoGroup.requestCookies .netInfoParamName > span[title='bar']");
+
+ // Verify request cookies (name and value)
+ ok(requestCookieName, "Request Cookie name must exist");
+ is(requestCookieName.textContent, "bar",
+ "The cookie name must have proper value");
+
+ let requestCookieValue = requestCookieName.parentNode.nextSibling;
+ ok(requestCookieValue, "Request Cookie value must exist");
+ is(requestCookieValue.textContent, "foo",
+ "The cookie value must have proper value");
+
+ let responseCookieName = tabBody.querySelector(
+ ".netInfoGroup.responseCookies .netInfoParamName > span[title='test']");
+
+ // Verify response cookies (name and value)
+ ok(responseCookieName, "Response Cookie name must exist");
+ is(responseCookieName.textContent, "test",
+ "The cookie name must have proper value");
+
+ let responseCookieValue = responseCookieName.parentNode.nextSibling;
+ ok(responseCookieValue, "Response Cookie value must exist");
+ is(responseCookieValue.textContent, "abc",
+ "The cookie value must have proper value");
+});
diff --git a/devtools/client/webconsole/net/test/mochitest/browser_net_headers.js b/devtools/client/webconsole/net/test/mochitest/browser_net_headers.js
new file mode 100644
index 000000000..4a47074ee
--- /dev/null
+++ b/devtools/client/webconsole/net/test/mochitest/browser_net_headers.js
@@ -0,0 +1,40 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+const TEST_PAGE_URL = URL_ROOT + "page_basic.html";
+const JSON_XHR_URL = URL_ROOT + "test.json";
+
+/**
+ * This test generates XHR requests in the page, expands
+ * networks details in the Console panel and checks that
+ * HTTP headers are there.
+ */
+add_task(function* () {
+ info("Test XHR Spy headers started");
+
+ let {hud} = yield addTestTab(TEST_PAGE_URL);
+
+ let netInfoBody = yield executeAndInspectXhr(hud, {
+ method: "GET",
+ url: JSON_XHR_URL
+ });
+
+ // Select "Headers" tab
+ let tabBody = yield selectNetInfoTab(hud, netInfoBody, "headers");
+ let paramName = tabBody.querySelector(
+ ".netInfoParamName > span[title='Content-Type']");
+
+ // Verify "Content-Type" header (name and value)
+ ok(paramName, "Header name must exist");
+ is(paramName.textContent, "Content-Type",
+ "The header name must have proper value");
+
+ let paramValue = paramName.parentNode.nextSibling;
+ ok(paramValue, "Header value must exist");
+ is(paramValue.textContent, "application/json; charset=utf-8",
+ "The header value must have proper value");
+});
diff --git a/devtools/client/webconsole/net/test/mochitest/browser_net_params.js b/devtools/client/webconsole/net/test/mochitest/browser_net_params.js
new file mode 100644
index 000000000..d8b0e2c84
--- /dev/null
+++ b/devtools/client/webconsole/net/test/mochitest/browser_net_params.js
@@ -0,0 +1,69 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+const TEST_PAGE_URL = URL_ROOT + "page_basic.html";
+const JSON_XHR_URL = URL_ROOT + "test.json";
+
+/**
+ * This test generates XHR requests in the page, expands
+ * networks details in the Console panel and checks that
+ * HTTP parameters (query string) are there.
+ */
+add_task(function* () {
+ info("Test XHR Spy params started");
+
+ let {hud} = yield addTestTab(TEST_PAGE_URL);
+
+ let netInfoBody = yield executeAndInspectXhr(hud, {
+ method: "GET",
+ url: JSON_XHR_URL,
+ queryString: "?foo=bar"
+ });
+
+ // Check headers
+ let tabBody = yield selectNetInfoTab(hud, netInfoBody, "params");
+
+ let paramName = tabBody.querySelector(
+ ".netInfoParamName > span[title='foo']");
+
+ // Verify "Content-Type" header (name and value)
+ ok(paramName, "Header name must exist");
+ is(paramName.textContent, "foo",
+ "The param name must have proper value");
+
+ let paramValue = paramName.parentNode.nextSibling;
+ ok(paramValue, "param value must exist");
+ is(paramValue.textContent, "bar",
+ "The param value must have proper value");
+});
+
+/**
+ * Test URL parameters with the same name.
+ */
+add_task(function* () {
+ info("Test XHR Spy params started");
+
+ let {hud} = yield addTestTab(TEST_PAGE_URL);
+
+ let netInfoBody = yield executeAndInspectXhr(hud, {
+ method: "GET",
+ url: JSON_XHR_URL,
+ queryString: "?box[]=123&box[]=456"
+ });
+
+ // Check headers
+ let tabBody = yield selectNetInfoTab(hud, netInfoBody, "params");
+
+ let params = tabBody.querySelectorAll(
+ ".netInfoParamName > span[title='box[]']");
+ is(params.length, 2, "Two URI parameters must exist");
+
+ let values = tabBody.querySelectorAll(
+ ".netInfoParamValue > code");
+ is(values[0].textContent, 123, "First value must match");
+ is(values[1].textContent, 456, "Second value must match");
+});
diff --git a/devtools/client/webconsole/net/test/mochitest/browser_net_post.js b/devtools/client/webconsole/net/test/mochitest/browser_net_post.js
new file mode 100644
index 000000000..f6e776ef0
--- /dev/null
+++ b/devtools/client/webconsole/net/test/mochitest/browser_net_post.js
@@ -0,0 +1,88 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+const TEST_PAGE_URL = URL_ROOT + "page_basic.html";
+const JSON_XHR_URL = URL_ROOT + "test.json";
+
+const plainPostBody = "test-data";
+const jsonData = "{\"bar\": \"baz\"}";
+const jsonRendered = "bar\"baz\"";
+const xmlPostBody = "<xml><name>John</name></xml>";
+
+/**
+ * This test generates XHR requests in the page, expands
+ * networks details in the Console panel and checks that
+ * Post data are properly rendered.
+ */
+add_task(function* () {
+ info("Test XHR Spy post plain body started");
+
+ let {hud} = yield addTestTab(TEST_PAGE_URL);
+
+ let netInfoBody = yield executeAndInspectXhr(hud, {
+ method: "POST",
+ url: JSON_XHR_URL,
+ body: plainPostBody
+ });
+
+ // Check post body data
+ let tabBody = yield selectNetInfoTab(hud, netInfoBody, "post");
+ let postContent = tabBody.querySelector(
+ ".netInfoGroup.raw.opened .netInfoGroupContent");
+ is(postContent.textContent, plainPostBody,
+ "Post body must be properly rendered");
+});
+
+add_task(function* () {
+ info("Test XHR Spy post JSON body started");
+
+ let {hud} = yield addTestTab(TEST_PAGE_URL);
+
+ let netInfoBody = yield executeAndInspectXhr(hud, {
+ method: "POST",
+ url: JSON_XHR_URL,
+ body: jsonData,
+ requestHeaders: [{
+ name: "Content-Type",
+ value: "application/json"
+ }]
+ });
+
+ // Check post body data
+ let tabBody = yield selectNetInfoTab(hud, netInfoBody, "post");
+ let postContent = tabBody.querySelector(
+ ".netInfoGroup.json.opened .netInfoGroupContent");
+ is(postContent.textContent, jsonRendered,
+ "Post body must be properly rendered");
+
+ let rawPostContent = tabBody.querySelector(
+ ".netInfoGroup.raw.opened .netInfoGroupContent");
+ ok(!rawPostContent, "Raw response group must be collapsed");
+});
+
+add_task(function* () {
+ info("Test XHR Spy post XML body started");
+
+ let {hud} = yield addTestTab(TEST_PAGE_URL);
+
+ let netInfoBody = yield executeAndInspectXhr(hud, {
+ method: "POST",
+ url: JSON_XHR_URL,
+ body: xmlPostBody,
+ requestHeaders: [{
+ name: "Content-Type",
+ value: "application/xml"
+ }]
+ });
+
+ // Check post body data
+ let tabBody = yield selectNetInfoTab(hud, netInfoBody, "post");
+ let rawPostContent = tabBody.querySelector(
+ ".netInfoGroup.raw.opened .netInfoGroupContent");
+ is(rawPostContent.textContent, xmlPostBody,
+ "Raw response group must not be collapsed");
+});
diff --git a/devtools/client/webconsole/net/test/mochitest/browser_net_response.js b/devtools/client/webconsole/net/test/mochitest/browser_net_response.js
new file mode 100644
index 000000000..ec5543043
--- /dev/null
+++ b/devtools/client/webconsole/net/test/mochitest/browser_net_response.js
@@ -0,0 +1,86 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+const TEST_PAGE_URL = URL_ROOT + "page_basic.html";
+const TEXT_XHR_URL = URL_ROOT + "test.txt";
+const JSON_XHR_URL = URL_ROOT + "test.json";
+const XML_XHR_URL = URL_ROOT + "test.xml";
+
+const textResponseBody = "this is a response";
+const jsonResponseBody = "name\"John\"";
+
+// Individual tests below generate XHR request in the page, expand
+// network details in the Console panel and checks various types
+// of response bodies.
+
+/**
+ * Validate plain text response
+ */
+add_task(function* () {
+ info("Test XHR Spy respone plain body started");
+
+ let {hud} = yield addTestTab(TEST_PAGE_URL);
+
+ let netInfoBody = yield executeAndInspectXhr(hud, {
+ method: "GET",
+ url: TEXT_XHR_URL,
+ });
+
+ // Check response body data
+ let tabBody = yield selectNetInfoTab(hud, netInfoBody, "response");
+ let responseContent = tabBody.querySelector(
+ ".netInfoGroup.raw.opened .netInfoGroupContent");
+
+ ok(responseContent.textContent.indexOf(textResponseBody) > -1,
+ "Response body must be properly rendered");
+});
+
+/**
+ * Validate XML response
+ */
+add_task(function* () {
+ info("Test XHR Spy response XML body started");
+
+ let {hud} = yield addTestTab(TEST_PAGE_URL);
+
+ let netInfoBody = yield executeAndInspectXhr(hud, {
+ method: "GET",
+ url: XML_XHR_URL,
+ });
+
+ // Check response body data
+ let tabBody = yield selectNetInfoTab(hud, netInfoBody, "response");
+ let rawResponseContent = tabBody.querySelector(
+ ".netInfoGroup.raw.opened .netInfoGroupContent");
+ ok(rawResponseContent, "Raw response group must not be collapsed");
+});
+
+/**
+ * Validate JSON response
+ */
+add_task(function* () {
+ info("Test XHR Spy response JSON body started");
+
+ let {hud} = yield addTestTab(TEST_PAGE_URL);
+
+ let netInfoBody = yield executeAndInspectXhr(hud, {
+ method: "GET",
+ url: JSON_XHR_URL,
+ });
+
+ // Check response body data
+ let tabBody = yield selectNetInfoTab(hud, netInfoBody, "response");
+ let responseContent = tabBody.querySelector(
+ ".netInfoGroup.json .netInfoGroupContent");
+
+ is(responseContent.textContent, jsonResponseBody,
+ "Response body must be properly rendered");
+
+ let rawResponseContent = tabBody.querySelector(
+ ".netInfoGroup.raw.opened .netInfoGroupContent");
+ ok(!rawResponseContent, "Raw response group must be collapsed");
+});
diff --git a/devtools/client/webconsole/net/test/mochitest/head.js b/devtools/client/webconsole/net/test/mochitest/head.js
new file mode 100644
index 000000000..c01206948
--- /dev/null
+++ b/devtools/client/webconsole/net/test/mochitest/head.js
@@ -0,0 +1,209 @@
+/* vim: set ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+/* eslint no-unused-vars: [2, {"vars": "local", "args": "none"}] */
+/* import-globals-from ../../../test/head.js */
+
+"use strict";
+
+// Load Web Console head.js, it implements helper console test API
+Services.scriptloader.loadSubScript(
+ "chrome://mochitests/content/browser/devtools/client/webconsole/test/head.js", this);
+
+const FRAME_SCRIPT_UTILS_URL =
+ "chrome://devtools/content/shared/frame-script-utils.js";
+
+const NET_INFO_PREF = "devtools.webconsole.filter.networkinfo";
+const NET_XHR_PREF = "devtools.webconsole.filter.netxhr";
+
+// Enable XHR logging for the test
+Services.prefs.setBoolPref(NET_INFO_PREF, true);
+Services.prefs.setBoolPref(NET_XHR_PREF, true);
+
+registerCleanupFunction(() => {
+ Services.prefs.clearUserPref(NET_INFO_PREF, true);
+ Services.prefs.clearUserPref(NET_XHR_PREF, true);
+});
+
+// Use the old webconsole since the new one doesn't yet support
+// XHR spy. See Bug 1304794.
+Services.prefs.setBoolPref("devtools.webconsole.new-frontend-enabled", false);
+registerCleanupFunction(function* () {
+ Services.prefs.clearUserPref("devtools.webconsole.new-frontend-enabled");
+});
+
+/**
+ * Add a new test tab in the browser and load the given url.
+ * @param {String} url The url to be loaded in the new tab
+ * @return a promise that resolves to the tab object when the url is loaded
+ */
+function addTestTab(url) {
+ info("Adding a new JSON tab with URL: '" + url + "'");
+
+ return Task.spawn(function* () {
+ let tab = yield addTab(url);
+
+ // Load devtools/shared/frame-script-utils.js
+ loadCommonFrameScript(tab);
+
+ // Open the Console panel
+ let hud = yield openConsole();
+
+ return {
+ tab: tab,
+ browser: tab.linkedBrowser,
+ hud: hud
+ };
+ });
+}
+
+/**
+ *
+ * @param hud
+ * @param options
+ */
+function executeAndInspectXhr(hud, options) {
+ hud.jsterm.clearOutput();
+
+ options.queryString = options.queryString || "";
+
+ // Execute XHR in the content scope.
+ performRequestsInContent({
+ method: options.method,
+ url: options.url + options.queryString,
+ body: options.body,
+ nocache: options.nocache,
+ requestHeaders: options.requestHeaders
+ });
+
+ return Task.spawn(function* () {
+ // Wait till the appropriate Net log appears in the Console panel.
+ let rules = yield waitForMessages({
+ webconsole: hud,
+ messages: [{
+ text: options.url,
+ category: CATEGORY_NETWORK,
+ severity: SEVERITY_INFO,
+ isXhr: true,
+ }]
+ });
+
+ // The log is here, get its parent element (className: 'message').
+ let msg = [...rules[0].matched][0];
+ let body = msg.querySelector(".message-body");
+
+ // Open XHR HTTP details body and wait till the UI fetches
+ // all necessary data from the backend. All RPD requests
+ // needs to be finished before we can continue testing.
+ yield synthesizeMouseClickSoon(hud, body);
+ yield waitForBackend(msg);
+ let netInfoBody = body.querySelector(".netInfoBody");
+ ok(netInfoBody, "Net info body must exist");
+ return netInfoBody;
+ });
+}
+
+/**
+ * Wait till XHR data are fetched from the backend (i.e. there are
+ * no pending RDP requests.
+ */
+function waitForBackend(element) {
+ if (!element.hasAttribute("loading")) {
+ return;
+ }
+ return once(element, "netlog-no-pending-requests", true);
+}
+
+/**
+ * Select specific tab in XHR info body.
+ *
+ * @param netInfoBody The main XHR info body
+ * @param tabId Tab ID (possible values: 'headers', 'cookies', 'params',
+ * 'post', 'response');
+ *
+ * @returns Tab body element.
+ */
+function selectNetInfoTab(hud, netInfoBody, tabId) {
+ let tab = netInfoBody.querySelector(".tabs-menu-item." + tabId);
+ ok(tab, "Tab must exist " + tabId);
+
+ // Click to select specified tab and wait till its
+ // UI is populated with data from the backend.
+ // There must be no pending RDP requests before we can
+ // continue testing the UI.
+ return Task.spawn(function* () {
+ yield synthesizeMouseClickSoon(hud, tab);
+ let msg = getAncestorByClass(netInfoBody, "message");
+ yield waitForBackend(msg);
+ let tabBody = netInfoBody.querySelector("." + tabId + "TabBox");
+ ok(tabBody, "Tab body must exist");
+ return tabBody;
+ });
+}
+
+/**
+ * Return parent node with specified class.
+ *
+ * @param node A child element
+ * @param className Specified class name.
+ *
+ * @returns A parent element.
+ */
+function getAncestorByClass(node, className) {
+ for (let parent = node; parent; parent = parent.parentNode) {
+ if (parent.classList && parent.classList.contains(className)) {
+ return parent;
+ }
+ }
+ return null;
+}
+
+/**
+ * Synthesize asynchronous click event (with clean stack trace).
+ */
+function synthesizeMouseClickSoon(hud, element) {
+ return new Promise((resolve) => {
+ executeSoon(() => {
+ EventUtils.synthesizeMouse(element, 2, 2, {}, hud.iframeWindow);
+ resolve();
+ });
+ });
+}
+
+/**
+ * Execute XHR in the content scope.
+ */
+function performRequestsInContent(requests) {
+ info("Performing requests in the context of the content.");
+ return executeInContent("devtools:test:xhr", requests);
+}
+
+function executeInContent(name, data = {}, objects = {},
+ expectResponse = true) {
+ let mm = gBrowser.selectedBrowser.messageManager;
+
+ mm.sendAsyncMessage(name, data, objects);
+ if (expectResponse) {
+ return waitForContentMessage(name);
+ }
+
+ return Promise.resolve();
+}
+
+function waitForContentMessage(name) {
+ info("Expecting message " + name + " from content");
+
+ let mm = gBrowser.selectedBrowser.messageManager;
+
+ return new Promise((resolve) => {
+ mm.addMessageListener(name, function onMessage(msg) {
+ mm.removeMessageListener(name, onMessage);
+ resolve(msg.data);
+ });
+ });
+}
+
+function loadCommonFrameScript(tab) {
+ let browser = tab ? tab.linkedBrowser : gBrowser.selectedBrowser;
+ browser.messageManager.loadFrameScript(FRAME_SCRIPT_UTILS_URL, false);
+}
diff --git a/devtools/client/webconsole/net/test/mochitest/page_basic.html b/devtools/client/webconsole/net/test/mochitest/page_basic.html
new file mode 100644
index 000000000..da7158492
--- /dev/null
+++ b/devtools/client/webconsole/net/test/mochitest/page_basic.html
@@ -0,0 +1,14 @@
+<!-- Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ -->
+<!doctype html>
+<html>
+ <head>
+ <meta charset="utf-8"/>
+ <title>XHR Spy test page</title>
+ </head>
+ <body>
+ <script type="text/javascript">
+ document.cookie = "bar=foo";
+ </script>
+ </body>
+</html>
diff --git a/devtools/client/webconsole/net/test/mochitest/test-cookies.json b/devtools/client/webconsole/net/test/mochitest/test-cookies.json
new file mode 100644
index 000000000..b5e739025
--- /dev/null
+++ b/devtools/client/webconsole/net/test/mochitest/test-cookies.json
@@ -0,0 +1 @@
+{"name":"Cookies Test"}
diff --git a/devtools/client/webconsole/net/test/mochitest/test-cookies.json^headers^ b/devtools/client/webconsole/net/test/mochitest/test-cookies.json^headers^
new file mode 100644
index 000000000..94a8c0c69
--- /dev/null
+++ b/devtools/client/webconsole/net/test/mochitest/test-cookies.json^headers^
@@ -0,0 +1,2 @@
+Content-Type: application/json; charset=utf-8
+Set-Cookie: test=abc
diff --git a/devtools/client/webconsole/net/test/mochitest/test.json b/devtools/client/webconsole/net/test/mochitest/test.json
new file mode 100644
index 000000000..6548f8e3e
--- /dev/null
+++ b/devtools/client/webconsole/net/test/mochitest/test.json
@@ -0,0 +1 @@
+{"name":"John"}
diff --git a/devtools/client/webconsole/net/test/mochitest/test.json^headers^ b/devtools/client/webconsole/net/test/mochitest/test.json^headers^
new file mode 100644
index 000000000..6010bfd18
--- /dev/null
+++ b/devtools/client/webconsole/net/test/mochitest/test.json^headers^
@@ -0,0 +1 @@
+Content-Type: application/json; charset=utf-8
diff --git a/devtools/client/webconsole/net/test/mochitest/test.txt b/devtools/client/webconsole/net/test/mochitest/test.txt
new file mode 100644
index 000000000..af7014e11
--- /dev/null
+++ b/devtools/client/webconsole/net/test/mochitest/test.txt
@@ -0,0 +1 @@
+this is a response
diff --git a/devtools/client/webconsole/net/test/mochitest/test.xml b/devtools/client/webconsole/net/test/mochitest/test.xml
new file mode 100644
index 000000000..3749c8e5a
--- /dev/null
+++ b/devtools/client/webconsole/net/test/mochitest/test.xml
@@ -0,0 +1 @@
+<xml><name>John</name></xml>
diff --git a/devtools/client/webconsole/net/test/mochitest/test.xml^headers^ b/devtools/client/webconsole/net/test/mochitest/test.xml^headers^
new file mode 100644
index 000000000..10ecdf5f4
--- /dev/null
+++ b/devtools/client/webconsole/net/test/mochitest/test.xml^headers^
@@ -0,0 +1 @@
+Content-Type: application/xml; charset=utf-8
diff --git a/devtools/client/webconsole/net/test/unit/.eslintrc.js b/devtools/client/webconsole/net/test/unit/.eslintrc.js
new file mode 100644
index 000000000..54a9a6361
--- /dev/null
+++ b/devtools/client/webconsole/net/test/unit/.eslintrc.js
@@ -0,0 +1,6 @@
+"use strict";
+
+module.exports = {
+ // Extend from the common devtools xpcshell eslintrc config.
+ "extends": "../../../../../.eslintrc.xpcshell.js"
+};
diff --git a/devtools/client/webconsole/net/test/unit/test_json-utils.js b/devtools/client/webconsole/net/test/unit/test_json-utils.js
new file mode 100644
index 000000000..f8ccdf3aa
--- /dev/null
+++ b/devtools/client/webconsole/net/test/unit/test_json-utils.js
@@ -0,0 +1,45 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+var Cu = Components.utils;
+const { require } = Cu.import("resource://devtools/shared/Loader.jsm", {});
+const { parseJSONString, isJSON } = require("devtools/client/webconsole/net/utils/json");
+
+// Test data
+const simpleJson = '{"name":"John"}';
+const jsonInFunc = 'someFunc({"name":"John"})';
+
+const json1 = "{'a': 1}";
+const json2 = " {'a': 1}";
+const json3 = "\t {'a': 1}";
+const json4 = "\n\n\t {'a': 1}";
+const json5 = "\n\n\t ";
+
+const textMimeType = "text/plain";
+const jsonMimeType = "text/javascript";
+const unknownMimeType = "text/unknown";
+
+/**
+ * Testing API provided by webconsole/net/utils/json.js
+ */
+function run_test() {
+ // parseJSONString
+ equal(parseJSONString(simpleJson).name, "John");
+ equal(parseJSONString(jsonInFunc).name, "John");
+
+ // isJSON
+ equal(isJSON(textMimeType, json1), true);
+ equal(isJSON(textMimeType, json2), true);
+ equal(isJSON(jsonMimeType, json3), true);
+ equal(isJSON(jsonMimeType, json4), true);
+
+ equal(isJSON(unknownMimeType, json1), true);
+ equal(isJSON(textMimeType, json1), true);
+
+ equal(isJSON(unknownMimeType), false);
+ equal(isJSON(unknownMimeType, json5), false);
+}
diff --git a/devtools/client/webconsole/net/test/unit/test_net-utils.js b/devtools/client/webconsole/net/test/unit/test_net-utils.js
new file mode 100644
index 000000000..512ebcbc7
--- /dev/null
+++ b/devtools/client/webconsole/net/test/unit/test_net-utils.js
@@ -0,0 +1,77 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+var Cu = Components.utils;
+const { require } = Cu.import("resource://devtools/shared/Loader.jsm", {});
+const {
+ isImage,
+ isHTML,
+ getHeaderValue,
+ isURLEncodedRequest,
+ isMultiPartRequest
+} = require("devtools/client/webconsole/net/utils/net");
+
+// Test data
+const imageMimeTypes = ["image/jpeg", "image/jpg", "image/gif",
+ "image/png", "image/bmp"];
+
+const htmlMimeTypes = ["text/html", "text/xml", "application/xml",
+ "application/rss+xml", "application/atom+xml", "application/xhtml+xml",
+ "application/mathml+xml", "application/rdf+xml"];
+
+const headers = [{name: "headerName", value: "value1"}];
+
+const har1 = {
+ request: {
+ postData: {
+ text: "content-type: application/x-www-form-urlencoded"
+ }
+ }
+};
+
+const har2 = {
+ request: {
+ headers: [{
+ name: "content-type",
+ value: "application/x-www-form-urlencoded"
+ }]
+ }
+};
+
+const har3 = {
+ request: {
+ headers: [{
+ name: "content-type",
+ value: "multipart/form-data"
+ }]
+ }
+};
+
+/**
+ * Testing API provided by webconsole/net/utils/net.js
+ */
+function run_test() {
+ // isImage
+ imageMimeTypes.forEach(mimeType => {
+ ok(isImage(mimeType));
+ });
+
+ // isHTML
+ htmlMimeTypes.forEach(mimeType => {
+ ok(isHTML(mimeType));
+ });
+
+ // getHeaderValue
+ equal(getHeaderValue(headers, "headerName"), "value1");
+
+ // isURLEncodedRequest
+ ok(isURLEncodedRequest(har1));
+ ok(isURLEncodedRequest(har2));
+
+ // isMultiPartRequest
+ ok(isMultiPartRequest(har3));
+}
diff --git a/devtools/client/webconsole/net/test/unit/xpcshell.ini b/devtools/client/webconsole/net/test/unit/xpcshell.ini
new file mode 100644
index 000000000..d988a2ad0
--- /dev/null
+++ b/devtools/client/webconsole/net/test/unit/xpcshell.ini
@@ -0,0 +1,9 @@
+[DEFAULT]
+tags = devtools
+head =
+tail =
+firefox-appdir = browser
+skip-if = toolkit == 'android'
+
+[test_json-utils.js]
+[test_net-utils.js]