diff options
Diffstat (limited to 'devtools/shared/webconsole/test')
47 files changed, 5641 insertions, 0 deletions
diff --git a/devtools/shared/webconsole/test/chrome.ini b/devtools/shared/webconsole/test/chrome.ini new file mode 100644 index 000000000..ae867b821 --- /dev/null +++ b/devtools/shared/webconsole/test/chrome.ini @@ -0,0 +1,41 @@ +[DEFAULT] +tags = devtools +support-files = + common.js + data.json + data.json^headers^ + helper_serviceworker.js + network_requests_iframe.html + sandboxed_iframe.html + console-test-worker.js + !/browser/base/content/test/general/browser_star_hsts.sjs + !/browser/base/content/test/general/pinning_headers.sjs + +[test_basics.html] +[test_bug819670_getter_throws.html] +[test_cached_messages.html] +[test_commands_other.html] +[test_commands_registration.html] +[test_consoleapi.html] +[test_consoleapi_innerID.html] +[test_console_serviceworker.html] +[test_console_serviceworker_cached.html] +[test_console_styling.html] +[test_file_uri.html] +[test_reflow.html] +[test_jsterm.html] +[test_jsterm_autocomplete.html] +[test_jsterm_cd_iframe.html] +[test_jsterm_last_result.html] +[test_jsterm_queryselector.html] +[test_network_get.html] +[test_network_longstring.html] +[test_network_post.html] +[test_network_security-hpkp.html] +[test_network_security-hsts.html] +[test_nsiconsolemessage.html] +[test_object_actor.html] +[test_object_actor_native_getters.html] +[test_object_actor_native_getters_lenient_this.html] +[test_page_errors.html] +[test_throw.html] diff --git a/devtools/shared/webconsole/test/common.js b/devtools/shared/webconsole/test/common.js new file mode 100644 index 000000000..b6649bc44 --- /dev/null +++ b/devtools/shared/webconsole/test/common.js @@ -0,0 +1,345 @@ +/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */ +/* vim: set ft= javascript ts=2 et sw=2 tw=80: */ +/* 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/. */ + +"use strict"; + +var {classes: Cc, interfaces: Ci, utils: Cu} = Components; + +const XHTML_NS = "http://www.w3.org/1999/xhtml"; + +// This gives logging to stdout for tests +var {console} = Cu.import("resource://gre/modules/Console.jsm", {}); + +var {require} = Cu.import("resource://devtools/shared/Loader.jsm", {}); +var Services = require("Services"); +var WebConsoleUtils = require("devtools/client/webconsole/utils").Utils; +var {Task} = require("devtools/shared/task"); + +var ConsoleAPIStorage = Cc["@mozilla.org/consoleAPI-storage;1"] + .getService(Ci.nsIConsoleAPIStorage); +var {DebuggerServer} = require("devtools/server/main"); +var {DebuggerClient, ObjectClient} = require("devtools/shared/client/main"); + +var {ConsoleServiceListener, ConsoleAPIListener} = + require("devtools/server/actors/utils/webconsole-utils"); + +function initCommon() +{ + // Services.prefs.setBoolPref("devtools.debugger.log", true); +} + +function initDebuggerServer() +{ + if (!DebuggerServer.initialized) { + DebuggerServer.init(); + DebuggerServer.addBrowserActors(); + } + DebuggerServer.allowChromeProcess = true; +} + +function connectToDebugger(aCallback) +{ + initCommon(); + initDebuggerServer(); + + let transport = DebuggerServer.connectPipe(); + let client = new DebuggerClient(transport); + + let dbgState = { dbgClient: client }; + client.connect().then(response => aCallback(dbgState, response)); +} + +function attachConsole(aListeners, aCallback) { + _attachConsole(aListeners, aCallback); +} +function attachConsoleToTab(aListeners, aCallback) { + _attachConsole(aListeners, aCallback, true); +} +function attachConsoleToWorker(aListeners, aCallback) { + _attachConsole(aListeners, aCallback, true, true); +} + +function _attachConsole(aListeners, aCallback, aAttachToTab, aAttachToWorker) +{ + function _onAttachConsole(aState, aResponse, aWebConsoleClient) + { + if (aResponse.error) { + console.error("attachConsole failed: " + aResponse.error + " " + + aResponse.message); + } + + aState.client = aWebConsoleClient; + + aCallback(aState, aResponse); + } + + connectToDebugger(function _onConnect(aState, aResponse) { + if (aResponse.error) { + console.error("client.connect() failed: " + aResponse.error + " " + + aResponse.message); + aCallback(aState, aResponse); + return; + } + + if (aAttachToTab) { + aState.dbgClient.listTabs(function _onListTabs(aResponse) { + if (aResponse.error) { + console.error("listTabs failed: " + aResponse.error + " " + + aResponse.message); + aCallback(aState, aResponse); + return; + } + let tab = aResponse.tabs[aResponse.selected]; + aState.dbgClient.attachTab(tab.actor, function (response, tabClient) { + if (aAttachToWorker) { + let workerName = "console-test-worker.js#" + new Date().getTime(); + var worker = new Worker(workerName); + // Keep a strong reference to the Worker to avoid it being + // GCd during the test (bug 1237492). + aState._worker_ref = worker; + worker.addEventListener("message", function listener() { + worker.removeEventListener("message", listener); + tabClient.listWorkers(function (response) { + let worker = response.workers.filter(w => w.url == workerName)[0]; + if (!worker) { + console.error("listWorkers failed. Unable to find the " + + "worker actor\n"); + return; + } + tabClient.attachWorker(worker.actor, function (response, workerClient) { + if (!workerClient || response.error) { + console.error("attachWorker failed. No worker client or " + + " error: " + response.error); + return; + } + workerClient.attachThread({}, function (aResponse) { + aState.actor = workerClient.consoleActor; + aState.dbgClient.attachConsole(workerClient.consoleActor, aListeners, + _onAttachConsole.bind(null, aState)); + }); + }); + }); + }); + } else { + aState.actor = tab.consoleActor; + aState.dbgClient.attachConsole(tab.consoleActor, aListeners, + _onAttachConsole.bind(null, aState)); + } + }); + }); + } else { + aState.dbgClient.getProcess().then(response => { + aState.dbgClient.attachTab(response.form.actor, function () { + let consoleActor = response.form.consoleActor; + aState.actor = consoleActor; + aState.dbgClient.attachConsole(consoleActor, aListeners, + _onAttachConsole.bind(null, aState)); + }); + }); + } + }); +} + +function closeDebugger(aState, aCallback) +{ + aState.dbgClient.close().then(aCallback); + aState.dbgClient = null; + aState.client = null; +} + +function checkConsoleAPICalls(consoleCalls, expectedConsoleCalls) +{ + is(consoleCalls.length, expectedConsoleCalls.length, + "received correct number of console calls"); + expectedConsoleCalls.forEach(function (aMessage, aIndex) { + info("checking received console call #" + aIndex); + checkConsoleAPICall(consoleCalls[aIndex], expectedConsoleCalls[aIndex]); + }); +} + +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]; + checkValue(name, value, expected); + } +} + +function checkValue(aName, aValue, aExpected) +{ + if (aExpected === null) { + ok(!aValue, "'" + aName + "' is null"); + } + else if (aValue === undefined) { + ok(false, "'" + aName + "' is undefined"); + } + else if (aValue === null) { + ok(false, "'" + aName + "' is null"); + } + else if (typeof aExpected == "string" || typeof aExpected == "number" || + typeof aExpected == "boolean") { + is(aValue, aExpected, "property '" + aName + "'"); + } + else if (aExpected instanceof RegExp) { + ok(aExpected.test(aValue), aName + ": " + aExpected + " matched " + aValue); + } + else if (Array.isArray(aExpected)) { + info("checking array for property '" + aName + "'"); + checkObject(aValue, aExpected); + } + else if (typeof aExpected == "object") { + info("checking object for property '" + aName + "'"); + checkObject(aValue, aExpected); + } +} + +function checkHeadersOrCookies(aArray, aExpected) +{ + let foundHeaders = {}; + + for (let elem of aArray) { + if (!(elem.name in aExpected)) { + continue; + } + foundHeaders[elem.name] = true; + info("checking value of header " + elem.name); + checkValue(elem.name, elem.value, aExpected[elem.name]); + } + + for (let header in aExpected) { + if (!(header in foundHeaders)) { + ok(false, header + " was not found"); + } + } +} + +function checkRawHeaders(aText, aExpected) +{ + let headers = aText.split(/\r\n|\n|\r/); + let arr = []; + for (let header of headers) { + let index = header.indexOf(": "); + if (index < 0) { + continue; + } + arr.push({ + name: header.substr(0, index), + value: header.substr(index + 2) + }); + } + + checkHeadersOrCookies(arr, aExpected); +} + +var gTestState = {}; + +function runTests(aTests, aEndCallback) +{ + function* driver() + { + let lastResult, sendToNext; + for (let i = 0; i < aTests.length; i++) { + gTestState.index = i; + let fn = aTests[i]; + info("will run test #" + i + ": " + fn.name); + lastResult = fn(sendToNext, lastResult); + sendToNext = yield lastResult; + } + yield aEndCallback(sendToNext, lastResult); + } + gTestState.driver = driver(); + return gTestState.driver.next(); +} + +function nextTest(aMessage) +{ + return gTestState.driver.next(aMessage); +} + +function withFrame(url) { + return new Promise(resolve => { + let iframe = document.createElement("iframe"); + iframe.onload = function () { + resolve(iframe); + }; + iframe.src = url; + document.body.appendChild(iframe); + }); +} + +function navigateFrame(iframe, url) { + return new Promise(resolve => { + iframe.onload = function () { + resolve(iframe); + }; + iframe.src = url; + }); +} + +function forceReloadFrame(iframe) { + return new Promise(resolve => { + iframe.onload = function () { + resolve(iframe); + }; + iframe.contentWindow.location.reload(true); + }); +} + +function withActiveServiceWorker(win, url, scope) { + let opts = {}; + if (scope) { + opts.scope = scope; + } + return win.navigator.serviceWorker.register(url, opts).then(swr => { + if (swr.active) { + return swr; + } + + // Unfortunately we can't just use navigator.serviceWorker.ready promise + // here. If the service worker is for a scope that does not cover the window + // then the ready promise will never resolve. Instead monitor the service + // workers state change events to determine when its activated. + return new Promise(resolve => { + let sw = swr.waiting || swr.installing; + sw.addEventListener("statechange", function stateHandler(evt) { + if (sw.state === "activated") { + sw.removeEventListener("statechange", stateHandler); + resolve(swr); + } + }); + }); + }); +} + +function messageServiceWorker(win, scope, message) { + return win.navigator.serviceWorker.getRegistration(scope).then(swr => { + return new Promise(resolve => { + win.navigator.serviceWorker.onmessage = evt => { + resolve(); + }; + let sw = swr.active || swr.waiting || swr.installing; + sw.postMessage({ type: "PING", message: message }); + }); + }); +} + +function unregisterServiceWorker(win) { + return win.navigator.serviceWorker.ready.then(swr => { + return swr.unregister(); + }); +} diff --git a/devtools/shared/webconsole/test/console-test-worker.js b/devtools/shared/webconsole/test/console-test-worker.js new file mode 100644 index 000000000..881eab0b8 --- /dev/null +++ b/devtools/shared/webconsole/test/console-test-worker.js @@ -0,0 +1,16 @@ +"use strict"; + +function f() { + var a = 1; + var b = 2; + var c = 3; +} + +self.onmessage = function (event) { + if (event.data == "ping") { + f(); + postMessage("pong"); + } +}; + +postMessage("load"); diff --git a/devtools/shared/webconsole/test/data.json b/devtools/shared/webconsole/test/data.json new file mode 100644 index 000000000..d46085c12 --- /dev/null +++ b/devtools/shared/webconsole/test/data.json @@ -0,0 +1,3 @@ +{ id: "test JSON data", myArray: [ "foo", "bar", "baz", "biff" ], + veryLong: "foo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo bar" +} diff --git a/devtools/shared/webconsole/test/data.json^headers^ b/devtools/shared/webconsole/test/data.json^headers^ new file mode 100644 index 000000000..bb6b45500 --- /dev/null +++ b/devtools/shared/webconsole/test/data.json^headers^ @@ -0,0 +1,3 @@ +Content-Type: application/json +x-very-long: Lorem ipsum dolor sit amet, consectetur adipiscing elit. Suspendisse a ipsum massa. Phasellus at elit dictum libero laoreet sagittis. Phasellus condimentum ultricies imperdiet. Nam eu ligula justo, ut tincidunt quam. Etiam sollicitudin, tortor sed egestas blandit, sapien sem tincidunt nulla, eu luctus libero odio quis leo. Nam elit massa, mattis quis blandit ac, facilisis vitae arcu. Donec vitae dictum neque. Proin ornare nisl at lectus commodo iaculis eget eget est. Quisque scelerisque vestibulum quam sed interdum. +x-very-short: hello world diff --git a/devtools/shared/webconsole/test/helper_serviceworker.js b/devtools/shared/webconsole/test/helper_serviceworker.js new file mode 100644 index 000000000..633f3e09b --- /dev/null +++ b/devtools/shared/webconsole/test/helper_serviceworker.js @@ -0,0 +1,19 @@ +console.log("script evaluation"); + +addEventListener("install", function (evt) { + console.log("install event"); +}); + +addEventListener("activate", function (evt) { + console.log("activate event"); +}); + +addEventListener("fetch", function (evt) { + console.log("fetch event: " + evt.request.url); + evt.respondWith(new Response("Hello world")); +}); + +addEventListener("message", function (evt) { + console.log("message event: " + evt.data.message); + evt.source.postMessage({ type: "PONG" }); +}); diff --git a/devtools/shared/webconsole/test/network_requests_iframe.html b/devtools/shared/webconsole/test/network_requests_iframe.html new file mode 100644 index 000000000..9147b6720 --- /dev/null +++ b/devtools/shared/webconsole/test/network_requests_iframe.html @@ -0,0 +1,61 @@ +<!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"><!-- + var setAllowAllCookies = false; + + function makeXhr(aMethod, aUrl, aRequestBody, aCallback) { + // On the first call, allow all cookies and set cookies, then resume the actual test + if (!setAllowAllCookies) + SpecialPowers.pushPrefEnv({"set": [["network.cookie.cookieBehavior", 0]]}, function () { + setAllowAllCookies = true; + setCookies(); + makeXhrCallback(aMethod, aUrl, aRequestBody, aCallback); + }); + else + makeXhrCallback(aMethod, aUrl, aRequestBody, aCallback); + } + + function makeXhrCallback(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) { + var body = "Hello world! " + (new Array(50)).join("foobaz barr"); + makeXhr('post', 'data.json', body, aCallback); + } + + function setCookies() { + document.cookie = "foobar=fooval"; + document.cookie = "omgfoo=bug768096"; + document.cookie = "badcookie=bug826798=st3fan"; + } + // --></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> diff --git a/devtools/shared/webconsole/test/sandboxed_iframe.html b/devtools/shared/webconsole/test/sandboxed_iframe.html new file mode 100644 index 000000000..55a6224b5 --- /dev/null +++ b/devtools/shared/webconsole/test/sandboxed_iframe.html @@ -0,0 +1,8 @@ +<html> +<head><title>Sandboxed iframe</title></head> +<body> + <iframe id="sandboxed-iframe" + sandbox="allow-scripts" + srcdoc="<script>var foobarObject = {bug1051224: 'sandboxed'};</script>"></iframe> +</body> +</html> diff --git a/devtools/shared/webconsole/test/test_basics.html b/devtools/shared/webconsole/test/test_basics.html new file mode 100644 index 000000000..fa54557ae --- /dev/null +++ b/devtools/shared/webconsole/test/test_basics.html @@ -0,0 +1,80 @@ +<!DOCTYPE HTML> +<html lang="en"> +<head> + <meta charset="utf8"> + <title>Basic Web Console Actor tests</title> + <script type="text/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + <script type="text/javascript;version=1.8" src="common.js"></script> + <!-- Any copyright is dedicated to the Public Domain. + - http://creativecommons.org/publicdomain/zero/1.0/ --> +</head> +<body> +<p>Basic Web Console Actor tests</p> + +<script class="testbody" type="text/javascript;version=1.8"> +SimpleTest.waitForExplicitFinish(); + +function startTest() +{ + removeEventListener("load", startTest); + + attachConsoleToTab(["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" }; + attachConsoleToTab(["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_; + 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() { + attachConsoleToTab(["ConsoleAPI"], onStartConsoleAPI); + }); +} + +function onStartConsoleAPI(aState, aResponse) +{ + is(aResponse.startedListeners.length, 1, "startedListeners.length"); + is(aResponse.startedListeners[0], "ConsoleAPI", "startedListeners: ConsoleAPI"); + ok(aResponse.nativeConsoleAPI, "nativeConsoleAPI"); + + top.console = top.console_; + delete top.console_; + + closeDebugger(aState, function() { + SimpleTest.finish(); + }); +} + +addEventListener("load", startTest); +</script> +</body> +</html> diff --git a/devtools/shared/webconsole/test/test_bug819670_getter_throws.html b/devtools/shared/webconsole/test/test_bug819670_getter_throws.html new file mode 100644 index 000000000..1b45c2d88 --- /dev/null +++ b/devtools/shared/webconsole/test/test_bug819670_getter_throws.html @@ -0,0 +1,76 @@ +<!DOCTYPE HTML> +<html lang="en"> +<head> + <meta charset="utf8"> + <title>Test for Bug 819670 - Web console object inspection does not handle native getters throwing very well</title> + <script type="text/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + <script type="text/javascript;version=1.8" src="common.js"></script> + <!-- Any copyright is dedicated to the Public Domain. + - http://creativecommons.org/publicdomain/zero/1.0/ --> +</head> +<body> +<p>Test for Bug 819670 - Web console object inspection does not handle native getters throwing very well</p> + +<script class="testbody" type="text/javascript;version=1.8"> +SimpleTest.waitForExplicitFinish(); + +function startTest() +{ + removeEventListener("load", startTest); + attachConsoleToTab([], onAttach); +} + +function onAttach(aState, aResponse) +{ + onEvaluate = onEvaluate.bind(null, aState); + aState.client.evaluateJS("document.__proto__", onEvaluate); +} + +function onEvaluate(aState, aResponse) +{ + checkObject(aResponse, { + from: aState.actor, + input: "document.__proto__", + result: { + type: "object", + actor: /[a-z]/, + }, + }); + + ok(!aResponse.exception, "no eval exception"); + ok(!aResponse.helperResult, "no helper result"); + + onInspect = onInspect.bind(null, aState); + let client = new ObjectClient(aState.dbgClient, aResponse.result); + client.getPrototypeAndProperties(onInspect); +} + +function onInspect(aState, aResponse) +{ + ok(!aResponse.error, "no response error"); + + let expectedProps = { + "addBroadcastListenerFor": { value: { type: "object" } }, + "commandDispatcher": { get: { type: "object" } }, + "getBoxObjectFor": { value: { type: "object" } }, + "getElementsByAttribute": { value: { type: "object" } }, + }; + + let props = aResponse.ownProperties; + ok(props, "response properties available"); + + if (props) { + ok(Object.keys(props).length > Object.keys(expectedProps).length, + "number of enumerable properties"); + checkObject(props, expectedProps); + } + + closeDebugger(aState, function() { + SimpleTest.finish(); + }); +} + +addEventListener("load", startTest); +</script> +</body> +</html> diff --git a/devtools/shared/webconsole/test/test_cached_messages.html b/devtools/shared/webconsole/test/test_cached_messages.html new file mode 100644 index 000000000..210543ca3 --- /dev/null +++ b/devtools/shared/webconsole/test/test_cached_messages.html @@ -0,0 +1,230 @@ +<!DOCTYPE HTML> +<html lang="en"> +<head> + <meta charset="utf8"> + <title>Test for cached messages</title> + <script type="text/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + <script type="text/javascript;version=1.8" src="common.js"></script> + <!-- Any copyright is dedicated to the Public Domain. + - http://creativecommons.org/publicdomain/zero/1.0/ --> +</head> +<body> +<p>Test for cached messages</p> + +<script class="testbody" type="application/javascript;version=1.8"> +let expectedConsoleCalls = []; +let expectedPageErrors = []; + +function doPageErrors() +{ + 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: "chrome 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 doConsoleCalls() +{ + ConsoleAPIStorage.clearEvents(); + + 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 class="testbody" type="text/javascript;version=1.8"> +SimpleTest.waitForExplicitFinish(); + +let consoleAPIListener, consoleServiceListener; +let consoleAPICalls = 0; +let pageErrors = 0; + +let handlers = { + onConsoleAPICall: function onConsoleAPICall(aMessage) + { + for (let msg of expectedConsoleCalls) { + if (msg.functionName == aMessage.functionName && + msg.filename.test(aMessage.filename)) { + consoleAPICalls++; + break; + } + } + if (consoleAPICalls == expectedConsoleCalls.length) { + checkConsoleAPICache(); + } + }, + + onConsoleServiceMessage: function onConsoleServiceMessage(aMessage) + { + if (!(aMessage instanceof Ci.nsIScriptError)) { + return; + } + for (let msg of expectedPageErrors) { + if (msg.category == aMessage.category && + msg.errorMessage.test(aMessage.errorMessage)) { + pageErrors++; + break; + } + } + if (pageErrors == expectedPageErrors.length) { + testPageErrors(); + } + }, +}; + +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; + info("cached console messages: " + msgs.length); + + ok(msgs.length >= expectedConsoleCalls.length, + "number of cached console messages"); + + for (let msg of msgs) { + for (let expected of expectedConsoleCalls) { + if (expected.functionName == msg.functionName && + expected.filename.test(msg.filename)) { + expectedConsoleCalls.splice(expectedConsoleCalls.indexOf(expected)); + checkConsoleAPICall(msg, expected); + break; + } + } + } + + is(expectedConsoleCalls.length, 0, "all expected messages have been found"); + + closeDebugger(aState, function() { + consoleServiceListener = new ConsoleServiceListener(null, handlers); + consoleServiceListener.init(); + doPageErrors(); + }); +} + +function testPageErrors() +{ + consoleServiceListener.destroy(); + consoleServiceListener = null; + attachConsole(["PageError"], onAttach2); +} + +function onAttach2(aState, aResponse) +{ + aState.client.getCachedMessages(["PageError"], + onCachedPageErrors.bind(null, aState)); +} + +function onCachedPageErrors(aState, aResponse) +{ + let msgs = aResponse.messages; + info("cached page errors: " + msgs.length); + + ok(msgs.length >= expectedPageErrors.length, + "number of cached page errors"); + + for (let msg of msgs) { + for (let expected of expectedPageErrors) { + if (expected.category == msg.category && + expected.errorMessage.test(msg.errorMessage)) { + expectedPageErrors.splice(expectedPageErrors.indexOf(expected)); + checkObject(msg, expected); + break; + } + } + } + + is(expectedPageErrors.length, 0, "all expected messages have been found"); + + closeDebugger(aState, function() { + SimpleTest.finish(); + }); +} + +addEventListener("load", startTest); +</script> +</body> +</html> diff --git a/devtools/shared/webconsole/test/test_commands_other.html b/devtools/shared/webconsole/test/test_commands_other.html new file mode 100644 index 000000000..47d1142c9 --- /dev/null +++ b/devtools/shared/webconsole/test/test_commands_other.html @@ -0,0 +1,83 @@ +<!DOCTYPE HTML> +<html lang="en"> +<head> + <meta charset="utf8"> + <title>Test for the other command helpers</title> + <script type="text/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + <script type="text/javascript;version=1.8" src="common.js"></script> + <!-- Any copyright is dedicated to the Public Domain. + - http://creativecommons.org/publicdomain/zero/1.0/ --> +</head> +<body> +<p>Test for the querySelector / querySelectorAll helpers</p> + +<script class="testbody" type="text/javascript;version=1.8"> +SimpleTest.waitForExplicitFinish(); +let gState; +let gWin; +let tests; + +function evaluateJS(input) { + return new Promise((resolve) => gState.client.evaluateJS(input, resolve)); +} + +function startTest() { + info ("Content window opened, attaching console to it"); + + let systemPrincipal = Cc["@mozilla.org/systemprincipal;1"].createInstance(Ci.nsIPrincipal); + ok (!gWin.document.nodePrincipal.equals(systemPrincipal), + "The test document is not using the system principal"); + + attachConsoleToTab([], state => { + gState = state; + runTests(tests, testEnd); + }); +} + +tests = [ + Task.async(function* keys() { + let response = yield evaluateJS("keys({foo: 'bar'})"); + checkObject(response, { + from: gState.actor, + result: { + class: "Array", + preview: { + items: ["foo"] + } + } + }); + nextTest(); + }), + Task.async(function* values() { + let response = yield evaluateJS("values({foo: 'bar'})"); + checkObject(response, { + from: gState.actor, + result: { + class: "Array", + preview: { + items: ["bar"] + } + } + }); + nextTest(); + }), +]; + +function testEnd() { + gWin.close(); + gWin = null; + closeDebugger(gState, function() { + gState = null; + SimpleTest.finish(); + }); +} + +window.onload = function() { + // Open a content window to test XRay functionality on built in functions. + gWin = window.open("data:text/html,"); + info ("Waiting for content window to load"); + gWin.onload = startTest; +} +</script> +</body> +</html> diff --git a/devtools/shared/webconsole/test/test_commands_registration.html b/devtools/shared/webconsole/test/test_commands_registration.html new file mode 100644 index 000000000..2a68f7cdc --- /dev/null +++ b/devtools/shared/webconsole/test/test_commands_registration.html @@ -0,0 +1,191 @@ +<!DOCTYPE HTML> +<html lang="en"> +<head> + <meta charset="utf8"> + <title>Test for Web Console commands registration.</title> + <script type="text/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + <script type="text/javascript;version=1.8" src="common.js"></script> + <!-- Any copyright is dedicated to the Public Domain. + - http://creativecommons.org/publicdomain/zero/1.0/ --> +</head> +<body> +<p>Test for Web Console commands registration.</p> +<p id="quack"></p> + +<script class="testbody" type="text/javascript;version=1.8"> +SimpleTest.waitForExplicitFinish(); + +let gState; +let tests; + +let {WebConsoleCommands} = require("devtools/server/actors/utils/webconsole-utils"); + +function evaluateJS(input) { + return new Promise((resolve) => gState.client.evaluateJS(input, resolve)); +} + +function* evaluateJSAndCheckResult(input, result) { + let response = yield evaluateJS(input); + checkObject(response, {result}); +} + +function startTest() +{ + removeEventListener("load", startTest); + + attachConsoleToTab(["PageError"], onAttach); +} + +function onAttach(aState, aResponse) +{ + gState = aState; + + runTests(tests, testEnd); +} + +tests = [ + Task.async(function* registerNewCommand() { + let win; + WebConsoleCommands.register("setFoo", (owner, value) => { + owner.window.foo = value; + return "ok"; + }); + + ok(WebConsoleCommands.hasCommand("setFoo"), + "The command should be registered"); + + let command = "setFoo('bar')"; + let response = yield evaluateJS(command); + + checkObject(response, { + from: gState.actor, + input: command, + result: "ok" + }); + is(top.foo, "bar", "top.foo should equal to 'bar'"); + nextTest(); + }), + + Task.async(function* wrapCommand() { + let origKeys = WebConsoleCommands.getCommand("keys"); + + let newKeys = (...args) => { + let [owner, arg0] = args; + if (arg0 === ">o_/") { + return "bang!"; + } + else { + return origKeys(...args); + } + }; + + WebConsoleCommands.register("keys", newKeys); + is(WebConsoleCommands.getCommand("keys"), newKeys, + "the keys() command should have been replaced"); + + let response = yield evaluateJS("keys('>o_/')"); + checkObject(response, { + from: gState.actor, + result: "bang!" + }); + + response = yield evaluateJS("keys({foo: 'bar'})"); + checkObject(response, { + from: gState.actor, + result: { + class: "Array", + preview: { + items: ["foo"] + } + } + }); + + WebConsoleCommands.register("keys", origKeys); + is(WebConsoleCommands.getCommand("keys"), origKeys, + "the keys() command should be restored"); + nextTest(); + }), + + Task.async(function* unregisterCommand() { + WebConsoleCommands.unregister("setFoo"); + + let response = yield evaluateJS("setFoo"); + + checkObject(response, { + from: gState.actor, + input: "setFoo", + result: { + type: "undefined" + }, + exceptionMessage: /setFoo is not defined/ + }); + nextTest(); + }), + + Task.async(function* registerAccessor() { + WebConsoleCommands.register("$foo", { + get(owner) { + let foo = owner.window.frames[0].window.document.getElementById("quack"); + return owner.makeDebuggeeValue(foo); + } + }); + let command = "$foo.textContent = '>o_/'"; + let response = yield evaluateJS(command); + + checkObject(response, { + from: gState.actor, + input: command, + result: ">o_/" + }); + is(document.getElementById("quack").textContent, ">o_/", + "#foo textContent should equal to \">o_/\""); + WebConsoleCommands.unregister("$foo"); + ok(!WebConsoleCommands.hasCommand("$foo"), "$foo should be unregistered"); + nextTest(); + }), + + Task.async(function* unregisterAfterOverridingTwice() { + WebConsoleCommands.register("keys", (owner, obj) => "command 1"); + info("checking the value of the first override"); + yield evaluateJSAndCheckResult("keys('foo');", "command 1"); + + let orig = WebConsoleCommands.getCommand("keys"); + WebConsoleCommands.register("keys", (owner, obj) => { + if (obj === "quack") + return "bang!"; + return orig(owner, obj); + }); + + info("checking the values after the second override"); + yield evaluateJSAndCheckResult("keys({});", "command 1"); + yield evaluateJSAndCheckResult("keys('quack');", "bang!"); + + WebConsoleCommands.unregister("keys"); + + info("checking the value after unregistration (should restore " + + "the original command)"); + yield evaluateJSAndCheckResult("keys({});", { + class: "Array", + preview: {items: []} + }); + nextTest(); + + }) +]; + +function testEnd() +{ + // If this is the first run, reload the page and do it again. + // Otherwise, end the test. + delete top.foo; + closeDebugger(gState, function() { + gState = null; + SimpleTest.finish(); + }); +} + +addEventListener("load", startTest); +</script> +</body> +</html> + diff --git a/devtools/shared/webconsole/test/test_console_serviceworker.html b/devtools/shared/webconsole/test/test_console_serviceworker.html new file mode 100644 index 000000000..83d728d92 --- /dev/null +++ b/devtools/shared/webconsole/test/test_console_serviceworker.html @@ -0,0 +1,157 @@ +<!DOCTYPE HTML> +<html lang="en"> +<head> + <meta charset="utf8"> + <title>Test for the Console API and Service Workers</title> + <script type="text/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + <script type="text/javascript;version=1.8" src="common.js"></script> + <!-- Any copyright is dedicated to the Public Domain. + - http://creativecommons.org/publicdomain/zero/1.0/ --> +</head> +<body> +<p>Test for the Console API and Service Workers</p> + +<script class="testbody" type="text/javascript;version=1.8"> +SimpleTest.waitForExplicitFinish(); + +let BASE_URL = "https://example.com/chrome/devtools/shared/webconsole/test/"; +let SERVICE_WORKER_URL = BASE_URL + "helper_serviceworker.js"; +let SCOPE = BASE_URL + "foo/"; +let NONSCOPE_FRAME_URL = BASE_URL + "sandboxed_iframe.html"; +let SCOPE_FRAME_URL = SCOPE + "fake.html"; +let SCOPE_FRAME_URL2 = SCOPE + "whatsit.html"; +let MESSAGE = 'Tic Tock'; + +let expectedConsoleCalls = [ + { + level: "log", + filename: /helper_serviceworker/, + arguments: ['script evaluation'], + }, + { + level: "log", + filename: /helper_serviceworker/, + arguments: ['install event'], + }, + { + level: "log", + filename: /helper_serviceworker/, + arguments: ['activate event'], + }, + { + level: "log", + filename: /helper_serviceworker/, + arguments: ['fetch event: ' + SCOPE_FRAME_URL], + }, + { + level: "log", + filename: /helper_serviceworker/, + arguments: ['fetch event: ' + SCOPE_FRAME_URL2], + }, + { + level: "log", + filename: /helper_serviceworker/, + arguments: ['message event: ' + MESSAGE], + }, +]; +let consoleCalls = []; + +let startTest = Task.async(function*() { + removeEventListener("load", startTest); + + yield new Promise(resolve => { + SpecialPowers.pushPrefEnv({"set": [ + ["dom.serviceWorkers.enabled", true], + ["devtools.webconsole.filter.serviceworkers", true] + ]}, resolve); + }); + + attachConsoleToTab(["ConsoleAPI"], onAttach); +}); +addEventListener("load", startTest); + +let onAttach = Task.async(function*(state, response) { + onConsoleAPICall = onConsoleAPICall.bind(null, state); + state.dbgClient.addListener("consoleAPICall", onConsoleAPICall); + + let currentFrame; + try { + // First, we need a frame from which to register our script. This + // will not trigger any console calls. + info("Loading a non-scope frame from which to register a service worker."); + currentFrame = yield withFrame(NONSCOPE_FRAME_URL); + + // Now register the service worker and wait for it to become + // activate. This should trigger 3 console calls; 1 for script + // evaluation, 1 for the install event, and 1 for the activate + // event. These console calls are received because we called + // register(), not because we are in scope for the worker. + info("Registering the service worker"); + yield withActiveServiceWorker(currentFrame.contentWindow, + SERVICE_WORKER_URL, SCOPE); + ok(!currentFrame.contentWindow.navigator.serviceWorker.controller, + 'current frame should not be controlled'); + + // Now that the service worker is activate, lets navigate our frame. + // This will trigger 1 more console call for the fetch event. + info("Service worker registered. Navigating frame."); + yield navigateFrame(currentFrame, SCOPE_FRAME_URL); + ok(currentFrame.contentWindow.navigator.serviceWorker.controller, + 'navigated frame should be controlled'); + + // We now have a controlled frame. Lets perform a non-navigation fetch. + // This should produce another console call for the fetch event. + info("Frame navigated. Calling fetch()."); + yield currentFrame.contentWindow.fetch(SCOPE_FRAME_URL2); + + // Now force refresh our controlled frame. This will cause the frame + // to bypass the service worker and become an uncontrolled frame. It + // also happens to make the frame display a 404 message because the URL + // does not resolve to a real resource. This is ok, as we really only + // care about the frame being non-controlled, but still having a location + // that matches our service worker scope so we can provide its not + // incorrectly getting console calls. + info("Completed fetch(). Force refreshing to get uncontrolled frame."); + yield forceReloadFrame(currentFrame); + ok(!currentFrame.contentWindow.navigator.serviceWorker.controller, + 'current frame should not be controlled after force refresh'); + is(currentFrame.contentWindow.location.toString(), SCOPE_FRAME_URL, + 'current frame should still have in-scope location URL even though it got 404'); + + // Now postMessage() the service worker to trigger its message event + // handler. This will generate 1 or 2 to console.log() statements + // depending on if the worker thread needs to spin up again. Although we + // don't have a controlled or registering document in both cases, we still + // could get console calls since we only flush reports when the channel is + // finally destroyed. + info("Completed force refresh. Messaging service worker."); + yield messageServiceWorker(currentFrame.contentWindow, SCOPE, MESSAGE); + + info("Done messaging service worker. Unregistering service worker."); + yield unregisterServiceWorker(currentFrame.contentWindow); + + info('Service worker unregistered. Checking console calls.'); + state.dbgClient.removeListener("consoleAPICall", onConsoleAPICall); + checkConsoleAPICalls(consoleCalls, expectedConsoleCalls); + } catch(error) { + ok(false, 'unexpected error: ' + error); + } finally { + if (currentFrame) { + currentFrame.remove(); + currentFrame = null; + } + consoleCalls = []; + closeDebugger(state, function() { + SimpleTest.finish(); + }); + } +}); + +function onConsoleAPICall(state, type, packet) { + info("received message level: " + packet.message.level); + is(packet.from, state.actor, "console API call actor"); + consoleCalls.push(packet.message); +} +</script> +</body> +</html> diff --git a/devtools/shared/webconsole/test/test_console_serviceworker_cached.html b/devtools/shared/webconsole/test/test_console_serviceworker_cached.html new file mode 100644 index 000000000..5aab64d7f --- /dev/null +++ b/devtools/shared/webconsole/test/test_console_serviceworker_cached.html @@ -0,0 +1,117 @@ +<!DOCTYPE HTML> +<html lang="en"> +<head> + <meta charset="utf8"> + <title>Test for getCachedMessages and Service Workers</title> + <script type="text/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + <script type="text/javascript;version=1.8" src="common.js"></script> + <!-- Any copyright is dedicated to the Public Domain. + - http://creativecommons.org/publicdomain/zero/1.0/ --> +</head> +<body> +<p>Test for getCachedMessages and Service Workers</p> + +<script class="testbody" type="text/javascript;version=1.8"> +SimpleTest.waitForExplicitFinish(); + +let BASE_URL = "https://example.com/chrome/devtools/shared/webconsole/test/"; +let SERVICE_WORKER_URL = BASE_URL + "helper_serviceworker.js"; +let FRAME_URL = BASE_URL + "sandboxed_iframe.html"; + +let firstTabExpectedCalls = [ + { + level: "log", + filename: /helper_serviceworker/, + arguments: ['script evaluation'], + }, + { + level: "log", + filename: /helper_serviceworker/, + arguments: ['install event'], + }, + { + level: "log", + filename: /helper_serviceworker/, + arguments: ['activate event'], + }, +]; + +let secondTabExpectedCalls = [ + { + level: "log", + filename: /helper_serviceworker/, + arguments: ['fetch event: ' + FRAME_URL], + } +]; + +let startTest = Task.async(function*() { + removeEventListener("load", startTest); + + yield new Promise(resolve => { + SpecialPowers.pushPrefEnv({"set": [ + ["dom.serviceWorkers.enabled", true], + ["devtools.webconsole.filter.serviceworkers", true] + ]}, resolve); + }); + + info("Adding a tab and attaching a service worker"); + let tab1 = yield addTab(FRAME_URL); + let swr = yield withActiveServiceWorker(tab1.linkedBrowser.contentWindow, + SERVICE_WORKER_URL); + + yield new Promise(resolve => { + info("Attaching console to tab 1"); + attachConsoleToTab(["ConsoleAPI"], function(state) { + state.client.getCachedMessages(["ConsoleAPI"], function(calls) { + checkConsoleAPICalls(calls.messages, firstTabExpectedCalls); + closeDebugger(state, resolve); + }); + }); + }); + + // Because this tab is being added after the original messages happened, + // they shouldn't show up in a call to getCachedMessages. + // However, there is a fetch event which is logged due to loading the tab. + info("Adding a new tab at the same URL"); + let tab2 = yield addTab(FRAME_URL); + yield new Promise(resolve => { + info("Attaching console to tab 2"); + attachConsoleToTab(["ConsoleAPI"], function(state) { + state.client.getCachedMessages(["ConsoleAPI"], function(calls) { + checkConsoleAPICalls(calls.messages, secondTabExpectedCalls); + closeDebugger(state, resolve); + }); + }); + }); + + yield swr.unregister(); + + SimpleTest.finish(); +}); +addEventListener("load", startTest); + +// This test needs to add tabs that are controlled by a service worker +// so use some special powers to dig around and find gBrowser +let {gBrowser} = SpecialPowers._getTopChromeWindow(SpecialPowers.window.get()); + +SimpleTest.registerCleanupFunction(() => { + while (gBrowser.tabs.length > 1) { + gBrowser.removeCurrentTab(); + } +}); + +function addTab(url) { + info("Adding a new tab with URL: '" + url + "'"); + return new Promise(resolve => { + let tab = gBrowser.selectedTab = gBrowser.addTab(url); + gBrowser.selectedBrowser.addEventListener("load", function onload() { + gBrowser.selectedBrowser.removeEventListener("load", onload, true); + info("URL '" + url + "' loading complete"); + resolve(tab); + }, true); + }); +} + +</script> +</body> +</html> diff --git a/devtools/shared/webconsole/test/test_console_styling.html b/devtools/shared/webconsole/test/test_console_styling.html new file mode 100644 index 000000000..97d21bcb9 --- /dev/null +++ b/devtools/shared/webconsole/test/test_console_styling.html @@ -0,0 +1,126 @@ +<!DOCTYPE HTML> +<html lang="en"> +<head> + <meta charset="utf8"> + <title>Test for console.log styling with %c</title> + <script type="text/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + <script type="text/javascript;version=1.8" src="common.js"></script> + <!-- Any copyright is dedicated to the Public Domain. + - http://creativecommons.org/publicdomain/zero/1.0/ --> +</head> +<body> +<p>Test for console.log styling with %c</p> + +<script class="testbody" type="text/javascript;version=1.8"> +SimpleTest.waitForExplicitFinish(); + +let expectedConsoleCalls = []; + +function doConsoleCalls(aState) +{ + top.console.log("%cOne formatter with no styles"); + top.console.log("%cOne formatter", "color: red"); + top.console.log("%cTwo formatters%cEach with an arg", + "color: red", "background: red"); + top.console.log("%c%cTwo formatters next to each other", + "color: red", "background: red"); + top.console.log("%c%c%cThree formatters next to each other", + "color: red", "background: red", "font-size: 150%"); + top.console.log("%c%cTwo formatters%cWith a third separated", + "color: red", "background: red", "font-size: 150%"); + top.console.log("%cOne formatter", "color: red", + "Second arg with no styles"); + top.console.log("%cOne formatter", "color: red", + "%cSecond formatter is ignored", "background: blue") + + expectedConsoleCalls = [ + { + level: "log", + styles: /^$/, + arguments: ["%cOne formatter with no styles"], + }, + { + level: "log", + styles: /^color: red$/, + arguments: ["One formatter"], + }, + { + level: "log", + styles: /^color: red,background: red$/, + arguments: ["Two formatters", "Each with an arg"], + }, + { + level: "log", + styles: /^background: red$/, + arguments: ["Two formatters next to each other"], + }, + { + level: "log", + styles: /^font-size: 150%$/, + arguments: ["Three formatters next to each other"], + }, + { + level: "log", + styles: /^background: red,font-size: 150%$/, + arguments: ["Two formatters", "With a third separated"], + }, + { + level: "log", + styles: /^color: red$/, + arguments: ["One formatter", "Second arg with no styles"], + }, + { + level: "log", + styles: /^color: red$/, + arguments: ["One formatter", + "%cSecond formatter is ignored", + "background: blue"], + }, + ]; +} + +function startTest() +{ + removeEventListener("load", startTest); + + attachConsoleToTab(["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) +{ + info("received message level: " + aPacket.message.level); + is(aPacket.from, aState.actor, "console API call actor"); + + consoleCalls.push(aPacket.message); + if (consoleCalls.length != expectedConsoleCalls.length) { + return; + } + + aState.dbgClient.removeListener("consoleAPICall", onConsoleAPICall); + + expectedConsoleCalls.forEach(function(aMessage, aIndex) { + info("checking received console call #" + aIndex); + checkConsoleAPICall(consoleCalls[aIndex], expectedConsoleCalls[aIndex]); + }); + + + consoleCalls = []; + + closeDebugger(aState, function() { + SimpleTest.finish(); + }); +} + +addEventListener("load", startTest); +</script> +</body> +</html> diff --git a/devtools/shared/webconsole/test/test_consoleapi.html b/devtools/shared/webconsole/test/test_consoleapi.html new file mode 100644 index 000000000..848db9cb6 --- /dev/null +++ b/devtools/shared/webconsole/test/test_consoleapi.html @@ -0,0 +1,233 @@ +<!DOCTYPE HTML> +<html lang="en"> +<head> + <meta charset="utf8"> + <title>Test for the Console API</title> + <script type="text/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + <script type="text/javascript;version=1.8" src="common.js"></script> + <!-- Any copyright is dedicated to the Public Domain. + - http://creativecommons.org/publicdomain/zero/1.0/ --> +</head> +<body> +<p>Test for the Console API</p> + +<script class="testbody" type="text/javascript;version=1.8"> +SimpleTest.waitForExplicitFinish(); + +let expectedConsoleCalls = []; + +function doConsoleCalls(aState) +{ + let longString = (new Array(DebuggerServer.LONG_STRING_LENGTH + 2)).join("a"); + + top.console.log("foobarBaz-log", undefined); + + top.console.log("Float from not a number: %f", "foo"); + top.console.log("Float from string: %f", "1.2"); + top.console.log("Float from number: %f", 1.3); + + top.console.info("foobarBaz-info", null); + top.console.warn("foobarBaz-warn", top.document.documentElement); + top.console.debug(null); + top.console.trace(); + top.console.dir(top.document, top.location); + top.console.log("foo", longString); + + let sandbox = new Cu.Sandbox(null, { invisibleToDebugger: true }); + let sandboxObj = sandbox.eval("new Object"); + top.console.log(sandboxObj); + + function fromAsmJS() { + top.console.error("foobarBaz-asmjs-error", undefined); + } + + (function(global, foreign) { + "use asm"; + var fromAsmJS = foreign.fromAsmJS; + function inAsmJS2() { fromAsmJS() } + function inAsmJS1() { inAsmJS2() } + return inAsmJS1 + })(null, { fromAsmJS:fromAsmJS })(); + + expectedConsoleCalls = [ + { + level: "log", + filename: /test_consoleapi/, + functionName: "doConsoleCalls", + timeStamp: /^\d+$/, + arguments: ["foobarBaz-log", { type: "undefined" }], + }, + { + level: "log", + arguments: ["Float from not a number: NaN"], + }, + { + level: "log", + arguments: ["Float from string: 1.200000"], + }, + { + level: "log", + arguments: ["Float from number: 1.300000"], + }, + { + level: "info", + filename: /test_consoleapi/, + functionName: "doConsoleCalls", + timeStamp: /^\d+$/, + arguments: ["foobarBaz-info", { type: "null" }], + }, + { + level: "warn", + filename: /test_consoleapi/, + functionName: "doConsoleCalls", + timeStamp: /^\d+$/, + arguments: ["foobarBaz-warn", { type: "object", actor: /[a-z]/ }], + }, + { + level: "debug", + filename: /test_consoleapi/, + functionName: "doConsoleCalls", + timeStamp: /^\d+$/, + arguments: [{ type: "null" }], + }, + { + level: "trace", + filename: /test_consoleapi/, + functionName: "doConsoleCalls", + timeStamp: /^\d+$/, + stacktrace: [ + { + filename: /test_consoleapi/, + functionName: "doConsoleCalls", + }, + { + filename: /test_consoleapi/, + functionName: "onAttach", + }, + ], + }, + { + level: "dir", + filename: /test_consoleapi/, + functionName: "doConsoleCalls", + timeStamp: /^\d+$/, + arguments: [ + { + type: "object", + actor: /[a-z]/, + class: "XULDocument", + }, + { + type: "object", + actor: /[a-z]/, + class: "Location", + } + ], + }, + { + level: "log", + filename: /test_consoleapi/, + functionName: "doConsoleCalls", + timeStamp: /^\d+$/, + arguments: [ + "foo", + { + type: "longString", + initial: longString.substring(0, + DebuggerServer.LONG_STRING_INITIAL_LENGTH), + length: longString.length, + actor: /[a-z]/, + }, + ], + }, + { + level: "log", + filename: /test_consoleapi/, + functionName: "doConsoleCalls", + timeStamp: /^\d+$/, + arguments: [ + { + type: "object", + actor: /[a-z]/, + class: "Object", + }, + ], + }, + { + level: "error", + filename: /test_consoleapi/, + functionName: "fromAsmJS", + timeStamp: /^\d+$/, + arguments: ["foobarBaz-asmjs-error", { type: "undefined" }], + + stacktrace: [ + { + filename: /test_consoleapi/, + functionName: "fromAsmJS", + }, + { + filename: /test_consoleapi/, + functionName: "inAsmJS2", + }, + { + filename: /test_consoleapi/, + functionName: "inAsmJS1", + }, + { + filename: /test_consoleapi/, + functionName: "doConsoleCalls", + }, + { + filename: /test_consoleapi/, + functionName: "onAttach", + }, + ], + }, + ]; +} + +function startTest() +{ + removeEventListener("load", startTest); + + attachConsoleToTab(["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) +{ + info("received message level: " + aPacket.message.level); + is(aPacket.from, aState.actor, "console API call actor"); + + consoleCalls.push(aPacket.message); + if (consoleCalls.length != expectedConsoleCalls.length) { + return; + } + + aState.dbgClient.removeListener("consoleAPICall", onConsoleAPICall); + + expectedConsoleCalls.forEach(function(aMessage, aIndex) { + info("checking received console call #" + aIndex); + checkConsoleAPICall(consoleCalls[aIndex], expectedConsoleCalls[aIndex]); + }); + + + consoleCalls = []; + + closeDebugger(aState, function() { + SimpleTest.finish(); + }); +} + +addEventListener("load", startTest); +</script> +</body> +</html> diff --git a/devtools/shared/webconsole/test/test_consoleapi_innerID.html b/devtools/shared/webconsole/test/test_consoleapi_innerID.html new file mode 100644 index 000000000..b477a36be --- /dev/null +++ b/devtools/shared/webconsole/test/test_consoleapi_innerID.html @@ -0,0 +1,164 @@ +<!DOCTYPE HTML> +<html lang="en"> +<head> + <meta charset="utf8"> + <title>Test for the innerID property of the Console API</title> + <script type="text/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + <script type="text/javascript;version=1.8" src="common.js"></script> + <!-- Any copyright is dedicated to the Public Domain. + - http://creativecommons.org/publicdomain/zero/1.0/ --> +</head> +<body> +<p>Test for the Console API</p> + +<script class="testbody" type="text/javascript;version=1.8"> +SimpleTest.waitForExplicitFinish(); + +let expectedConsoleCalls = []; + +function doConsoleCalls(aState) +{ + let { ConsoleAPI } = Cu.import("resource://gre/modules/Console.jsm", {}); + let console = new ConsoleAPI({ + innerID: window.QueryInterface(Ci.nsIInterfaceRequestor) + .getInterface(Ci.nsIDOMWindowUtils) + .currentInnerWindowID + }); + + let longString = (new Array(DebuggerServer.LONG_STRING_LENGTH + 2)).join("a"); + + console.log("foobarBaz-log", undefined); + console.info("foobarBaz-info", null); + console.warn("foobarBaz-warn", top.document.documentElement); + console.debug(null); + console.trace(); + console.dir(top.document, top.location); + console.log("foo", longString); + + expectedConsoleCalls = [ + { + level: "log", + filename: /test_consoleapi/, + functionName: "doConsoleCalls", + timeStamp: /^\d+$/, + arguments: ["foobarBaz-log", { type: "undefined" }], + }, + { + level: "info", + filename: /test_consoleapi/, + functionName: "doConsoleCalls", + timeStamp: /^\d+$/, + arguments: ["foobarBaz-info", { type: "null" }], + }, + { + level: "warn", + filename: /test_consoleapi/, + functionName: "doConsoleCalls", + timeStamp: /^\d+$/, + arguments: ["foobarBaz-warn", { type: "object", actor: /[a-z]/ }], + }, + { + level: "debug", + filename: /test_consoleapi/, + functionName: "doConsoleCalls", + timeStamp: /^\d+$/, + arguments: [{ type: "null" }], + }, + { + level: "trace", + filename: /test_consoleapi/, + functionName: "doConsoleCalls", + timeStamp: /^\d+$/, + stacktrace: [ + { + filename: /test_consoleapi/, + functionName: "doConsoleCalls", + }, + { + filename: /test_consoleapi/, + functionName: "onAttach", + }, + ], + }, + { + level: "dir", + filename: /test_consoleapi/, + functionName: "doConsoleCalls", + timeStamp: /^\d+$/, + arguments: [ + { + type: "object", + actor: /[a-z]/, + class: "XULDocument", + }, + { + type: "object", + actor: /[a-z]/, + class: "Location", + } + ], + }, + { + level: "log", + filename: /test_consoleapi/, + functionName: "doConsoleCalls", + timeStamp: /^\d+$/, + arguments: [ + "foo", + { + type: "longString", + initial: longString.substring(0, + DebuggerServer.LONG_STRING_INITIAL_LENGTH), + length: longString.length, + actor: /[a-z]/, + }, + ], + }, + ]; +} + +function startTest() +{ + removeEventListener("load", startTest); + + attachConsoleToTab(["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) +{ + info("received message level: " + aPacket.message.level); + is(aPacket.from, aState.actor, "console API call actor"); + + consoleCalls.push(aPacket.message); + if (consoleCalls.length != expectedConsoleCalls.length) { + return; + } + + aState.dbgClient.removeListener("consoleAPICall", onConsoleAPICall); + + expectedConsoleCalls.forEach(function(aMessage, aIndex) { + info("checking received console call #" + aIndex); + checkConsoleAPICall(consoleCalls[aIndex], expectedConsoleCalls[aIndex]); + }); + + + consoleCalls = []; + + closeDebugger(aState, function() { + SimpleTest.finish(); + }); +} + +addEventListener("load", startTest); +</script> +</body> +</html> diff --git a/devtools/shared/webconsole/test/test_file_uri.html b/devtools/shared/webconsole/test/test_file_uri.html new file mode 100644 index 000000000..f5aada5b1 --- /dev/null +++ b/devtools/shared/webconsole/test/test_file_uri.html @@ -0,0 +1,106 @@ +<!DOCTYPE HTML> +<html lang="en"> +<head> + <meta charset="utf8"> + <title>Test for file activity tracking</title> + <script type="text/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + <script type="text/javascript;version=1.8" src="common.js"></script> + <!-- Any copyright is dedicated to the Public Domain. + - http://creativecommons.org/publicdomain/zero/1.0/ --> +</head> +<body> +<p>Test for file activity tracking</p> + +<script class="testbody" type="text/javascript;version=1.8"> +SimpleTest.waitForExplicitFinish(); + +Cu.import("resource://gre/modules/NetUtil.jsm"); +Cu.import("resource://gre/modules/FileUtils.jsm"); + +let gState; +let gTmpFile; + +function doFileActivity() +{ + info("doFileActivity"); + let fileContent = "<p>hello world from bug 798764"; + + gTmpFile = FileUtils.getFile("TmpD", ["bug798764.html"]); + gTmpFile.createUnique(Ci.nsIFile.NORMAL_FILE_TYPE, FileUtils.PERMS_FILE); + + let fout = FileUtils.openSafeFileOutputStream(gTmpFile, + FileUtils.MODE_WRONLY | FileUtils.MODE_CREATE | FileUtils.MODE_TRUNCATE); + + let converter = Cc["@mozilla.org/intl/scriptableunicodeconverter"]. + createInstance(Ci.nsIScriptableUnicodeConverter); + converter.charset = "UTF-8"; + let fileContentStream = converter.convertToInputStream(fileContent); + + NetUtil.asyncCopy(fileContentStream, fout, addIframe); +} + +function addIframe(aStatus) +{ + ok(Components.isSuccessCode(aStatus), + "the temporary file was saved successfully"); + + let iframe = document.createElement("iframe"); + iframe.src = NetUtil.newURI(gTmpFile).spec; + document.body.appendChild(iframe); +} + +function startTest() +{ + removeEventListener("load", startTest); + + attachConsole(["FileActivity"], onAttach); +} + +function onAttach(aState, aResponse) +{ + gState = aState; + gState.dbgClient.addListener("fileActivity", onFileActivity); + doFileActivity(); +} + +function onFileActivity(aType, aPacket) +{ + is(aPacket.from, gState.actor, "fileActivity actor"); + + gState.dbgClient.removeListener("fileActivity", onFileActivity); + + info("aPacket.uri: " + aPacket.uri); + ok(/\bbug798764\b.*\.html$/.test(aPacket.uri), "file URI match"); + + testEnd(); +} + +function testEnd() +{ + if (gTmpFile) { + SimpleTest.executeSoon(function() { + try { + gTmpFile.remove(false); + } + catch (ex if (ex.name == "NS_ERROR_FILE_IS_LOCKED")) { + // Sometimes remove() throws because the file is not unlocked soon + // enough. + } + gTmpFile = null; + }); + } + + if (gState) { + closeDebugger(gState, function() { + gState = null; + SimpleTest.finish(); + }); + } else { + SimpleTest.finish(); + } +} + +addEventListener("load", startTest); +</script> +</body> +</html> diff --git a/devtools/shared/webconsole/test/test_jsterm.html b/devtools/shared/webconsole/test/test_jsterm.html new file mode 100644 index 000000000..b6eefad4b --- /dev/null +++ b/devtools/shared/webconsole/test/test_jsterm.html @@ -0,0 +1,309 @@ +<!DOCTYPE HTML> +<html lang="en"> +<head> + <meta charset="utf8"> + <title>Test for JavaScript terminal functionality</title> + <script type="text/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + <script type="text/javascript;version=1.8" src="common.js"></script> + <!-- Any copyright is dedicated to the Public Domain. + - http://creativecommons.org/publicdomain/zero/1.0/ --> +</head> +<body> +<p>Test for JavaScript terminal functionality</p> + +<iframe id="content-iframe" src="http://example.com/chrome/devtools/shared/webconsole/test/sandboxed_iframe.html"></iframe> + +<script class="testbody" type="text/javascript;version=1.8"> +SimpleTest.waitForExplicitFinish(); + +let gState; + +let {MAX_AUTOCOMPLETE_ATTEMPTS,MAX_AUTOCOMPLETIONS} = require("devtools/shared/webconsole/js-property-provider"); + +// This test runs all of its assertions twice - once with +// evaluateJS and once with evaluateJSAsync. +let evaluatingSync = true; +function evaluateJS(input, options = {}) { + return new Promise((resolve, reject) => { + if (evaluatingSync) { + gState.client.evaluateJS(input, resolve, options); + } else { + gState.client.evaluateJSAsync(input, resolve, options); + } + }); +} + +function startTest() +{ + removeEventListener("load", startTest); + + attachConsoleToTab(["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; + top.foobarObject.strfoo = "foobarz"; + top.foobarObject.omgstr = "foobarz" + + (new Array(DebuggerServer.LONG_STRING_LENGTH * 2)).join("abb"); + + top.largeObject1 = Object.create(null); + for (let i = 0; i < MAX_AUTOCOMPLETE_ATTEMPTS + 1; i++) { + top.largeObject1['a' + i] = i; + } + + top.largeObject2 = Object.create(null); + for (let i = 0; i < MAX_AUTOCOMPLETIONS * 2; i++) { + top.largeObject2['a' + i] = i; + } + + gState = aState; + + let tests = [doSimpleEval, doWindowEval, doEvalWithException, + doEvalWithHelper, doEvalString, doEvalLongString, + doEvalWithBinding, doEvalWithBindingFrame, + forceLexicalInit].map(t => { + return Task.async(t); + }); + + runTests(tests, testEnd); +} + +function* doSimpleEval() { + info("test eval '2+2'"); + let response = yield evaluateJS("2+2"); + checkObject(response, { + from: gState.actor, + input: "2+2", + result: 4, + }); + + ok(!response.exception, "no eval exception"); + ok(!response.helperResult, "no helper result"); + + nextTest(); +} + +function* doWindowEval() { + info("test eval 'document'"); + let response = yield evaluateJS("document"); + checkObject(response, { + from: gState.actor, + input: "document", + result: { + type: "object", + class: "XULDocument", + actor: /[a-z]/, + }, + }); + + ok(!response.exception, "no eval exception"); + ok(!response.helperResult, "no helper result"); + + nextTest(); +} + +function* doEvalWithException() { + info("test eval with exception"); + let response = yield evaluateJS("window.doTheImpossible()"); + checkObject(response, { + from: gState.actor, + input: "window.doTheImpossible()", + result: { + type: "undefined", + }, + exceptionMessage: /doTheImpossible/, + }); + + ok(response.exception, "js eval exception"); + ok(!response.helperResult, "no helper result"); + + nextTest(); +} + +function* doEvalWithHelper() { + info("test eval with helper"); + let response = yield evaluateJS("clear()"); + checkObject(response, { + from: gState.actor, + input: "clear()", + result: { + type: "undefined", + }, + helperResult: { type: "clearOutput" }, + }); + + ok(!response.exception, "no eval exception"); + + nextTest(); +} + +function* doEvalString() { + let response = yield evaluateJS("window.foobarObject.strfoo"); + checkObject(response, { + from: gState.actor, + input: "window.foobarObject.strfoo", + result: "foobarz", + }); + + nextTest(); +} + +function* doEvalLongString() { + let response = yield evaluateJS("window.foobarObject.omgstr"); + let str = top.foobarObject.omgstr; + let initial = str.substring(0, DebuggerServer.LONG_STRING_INITIAL_LENGTH); + + checkObject(response, { + from: gState.actor, + input: "window.foobarObject.omgstr", + result: { + type: "longString", + initial: initial, + length: str.length, + }, + }); + + nextTest(); +} + +function* doEvalWithBinding() { + let response = yield evaluateJS("document;"); + let documentActor = response.result.actor; + + info("running a command with _self as document using bindObjectActor"); + let bindObjectSame = yield evaluateJS("_self === document", { + bindObjectActor: documentActor + }); + checkObject(bindObjectSame, { + result: true + }); + + info("running a command with _self as document using selectedObjectActor"); + let selectedObjectSame = yield evaluateJS("_self === document", { + selectedObjectActor: documentActor + }); + checkObject(selectedObjectSame, { + result: true + }); + + nextTest(); +} + +function* doEvalWithBindingFrame() { + let frameWin = top.document.querySelector("iframe").contentWindow; + frameWin.fooFrame = { bar: 1 }; + + let response = yield evaluateJS( + "document.querySelector('iframe').contentWindow.fooFrame" + ); + let iframeObjectActor = response.result.actor; + ok(iframeObjectActor, "There is an actor associated with the response"); + + let bindObjectGlobal = yield evaluateJS("this.temp0 = _self;", { + bindObjectActor: iframeObjectActor + }); + ok(!top.temp0, + "Global doesn't match the top global with bindObjectActor"); + ok(frameWin.temp0 && frameWin.temp0.bar === 1, + "Global matches the object's global with bindObjectActor"); + + let selectedObjectGlobal = yield evaluateJS("this.temp1 = _self;", { + selectedObjectActor: iframeObjectActor + }); + ok(top.temp1 && top.temp1.bar === 1, + "Global matches the top global with bindObjectActor"); + ok(!frameWin.temp1, + "Global doesn't match the object's global with bindObjectActor"); + + nextTest() +} + +function* forceLexicalInit() { + info("test that failed let/const bindings are initialized to undefined"); + + const testData = [ + { + stmt: "let foopie = wubbalubadubdub", + vars: ["foopie"] + }, + { + stmt: "let {z, w={n}=null} = {}", + vars: ["z", "w"] + }, + { + stmt: "let [a, b, c] = null", + vars: ["a", "b", "c"] + }, + { + stmt: "const nein1 = rofl, nein2 = copter", + vars: ["nein1", "nein2"] + }, + { + stmt: "const {ha} = null", + vars: ["ha"] + }, + { + stmt: "const [haw=[lame]=null] = []", + vars: ["haw"] + }, + { + stmt: "const [rawr, wat=[lame]=null] = []", + vars: ["rawr", "haw"] + }, + { + stmt: "let {zzz: xyz=99, zwz: wb} = nexistepas()", + vars: ["xyz", "wb"] + }, + { + stmt: "let {c3pdoh=101} = null", + vars: ["c3pdoh"] + } + ]; + + for (let data of testData) { + let response = yield evaluateJS(data.stmt); + checkObject(response, { + from: gState.actor, + input: data.stmt, + result: undefined, + }); + ok(response.exception, "expected exception"); + for (let varName of data.vars) { + let response2 = yield evaluateJS(varName); + checkObject(response2, { + from: gState.actor, + input: varName, + result: undefined, + }); + ok(!response2.exception, "unexpected exception"); + } + } + + nextTest(); +} + +function testEnd() +{ + // If this is the first run, reload the page and do it again. + // Otherwise, end the test. + closeDebugger(gState, function() { + gState = null; + if (evaluatingSync) { + evaluatingSync = false; + startTest(); + } else { + SimpleTest.finish(); + } + }); +} + +addEventListener("load", startTest); +</script> +</body> +</html> diff --git a/devtools/shared/webconsole/test/test_jsterm_autocomplete.html b/devtools/shared/webconsole/test/test_jsterm_autocomplete.html new file mode 100644 index 000000000..0e32e1a63 --- /dev/null +++ b/devtools/shared/webconsole/test/test_jsterm_autocomplete.html @@ -0,0 +1,183 @@ +<!DOCTYPE HTML> +<html lang="en"> +<head> + <meta charset="utf8"> + <title>Test for JavaScript terminal functionality</title> + <script type="text/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + <script type="text/javascript;version=1.8" src="common.js"></script> + <!-- Any copyright is dedicated to the Public Domain. + - http://creativecommons.org/publicdomain/zero/1.0/ --> +</head> +<body> +<p>Test for JavaScript terminal autocomplete functionality</p> + +<script class="testbody" type="text/javascript;version=1.8"> +SimpleTest.waitForExplicitFinish(); + +let gState; +let {MAX_AUTOCOMPLETE_ATTEMPTS,MAX_AUTOCOMPLETIONS} = require("devtools/shared/webconsole/js-property-provider"); + +function evaluateJS(input, options = {}) { + return new Promise((resolve, reject) => { + gState.client.evaluateJSAsync(input, resolve, options); + }); +} + +function autocompletePromise(str, cursor, frameActor) { + return new Promise(resolve => { + gState.client.autocomplete(str, cursor, resolve, frameActor); + }); +} + +// This test runs all of its assertions twice - once with +// the tab as a target and once with a worker +let runningInTab = true; +function startTest({worker}) { + if (worker) { + attachConsoleToWorker(["PageError"], onAttach); + } else { + attachConsoleToTab(["PageError"], onAttach); + } +}; + +let onAttach = Task.async(function*(aState, response) { + gState = aState; + + let longStrLength = DebuggerServer.LONG_STRING_LENGTH; + + // Set up the global variables needed to test autocompletion + // in the target. + let script = ` + // This is for workers so autocomplete acts the same + if (!this.window) { + window = this; + } + + window.foobarObject = Object.create(null); + window.foobarObject.foo = 1; + window.foobarObject.foobar = 2; + window.foobarObject.foobaz = 3; + window.foobarObject.omg = 4; + window.foobarObject.omgfoo = 5; + window.foobarObject.strfoo = "foobarz"; + window.foobarObject.omgstr = "foobarz" + + (new Array(${longStrLength})).join("abb"); + window.largeObject1 = Object.create(null); + for (let i = 0; i < ${MAX_AUTOCOMPLETE_ATTEMPTS + 1}; i++) { + window.largeObject1['a' + i] = i; + } + + window.largeObject2 = Object.create(null); + for (let i = 0; i < ${MAX_AUTOCOMPLETIONS * 2}; i++) { + window.largeObject2['a' + i] = i; + } + `; + + yield evaluateJS(script); + + let tests = [doAutocomplete1, doAutocomplete2, doAutocomplete3, + doAutocomplete4, doAutocompleteLarge1, + doAutocompleteLarge2].map(t => { + return Task.async(t); + }); + + runTests(tests, testEnd); +}); + +function* doAutocomplete1() { + info("test autocomplete for 'window.foo'"); + let response = yield autocompletePromise("window.foo", 10); + let matches = response.matches; + + is(response.matchProp, "foo", "matchProp"); + is(matches.length, 1, "matches.length"); + is(matches[0], "foobarObject", "matches[0]"); + + nextTest(); +} + +function* doAutocomplete2() { + info("test autocomplete for 'window.foobarObject.'"); + let response = yield autocompletePromise("window.foobarObject.", 20); + let matches = response.matches; + + ok(!response.matchProp, "matchProp"); + is(matches.length, 7, "matches.length"); + checkObject(matches, + ["foo", "foobar", "foobaz", "omg", "omgfoo", "omgstr", "strfoo"]); + + nextTest(); +} + +function* doAutocomplete3() { + // Check that completion suggestions are offered inside the string. + info("test autocomplete for 'dump(window.foobarObject.)'"); + let response = yield autocompletePromise("dump(window.foobarObject.)", 25); + let matches = response.matches; + + ok(!response.matchProp, "matchProp"); + is(matches.length, 7, "matches.length"); + checkObject(matches, + ["foo", "foobar", "foobaz", "omg", "omgfoo", "omgstr", "strfoo"]); + + nextTest(); +} + +function* doAutocomplete4() { + // Check that completion requests can have no suggestions. + info("test autocomplete for 'dump(window.foobarObject.)'"); + let response = yield autocompletePromise("dump(window.foobarObject.)", 26); + ok(!response.matchProp, "matchProp"); + is(response.matches.length, 0, "matches.length"); + + nextTest(); +} + +function* doAutocompleteLarge1() { + // Check that completion requests with too large objects will + // have no suggestions. + info("test autocomplete for 'window.largeObject1.'"); + let response = yield autocompletePromise("window.largeObject1.", 20); + ok(!response.matchProp, "matchProp"); + info (response.matches.join("|")); + is(response.matches.length, 0, "Bailed out with too many properties"); + + nextTest(); +} + +function* doAutocompleteLarge2() { + // Check that completion requests with pretty large objects will + // have MAX_AUTOCOMPLETIONS suggestions + info("test autocomplete for 'window.largeObject2.'"); + let response = yield autocompletePromise("window.largeObject2.", 20); + ok(!response.matchProp, "matchProp"); + is(response.matches.length, MAX_AUTOCOMPLETIONS, "matches.length is MAX_AUTOCOMPLETIONS"); + + nextTest(); +} + +function testEnd() +{ + // If this is the first run, reload the page and do it again + // in a worker. Otherwise, end the test. + closeDebugger(gState, function() { + gState = null; + if (runningInTab) { + runningInTab = false; + startTest({ + worker: true + }); + } else { + SimpleTest.finish(); + } + }); +} + +addEventListener("load", () => { + startTest({ + worker: false + }); +}); +</script> +</body> +</html> diff --git a/devtools/shared/webconsole/test/test_jsterm_cd_iframe.html b/devtools/shared/webconsole/test/test_jsterm_cd_iframe.html new file mode 100644 index 000000000..7207a00a1 --- /dev/null +++ b/devtools/shared/webconsole/test/test_jsterm_cd_iframe.html @@ -0,0 +1,223 @@ +<!DOCTYPE HTML> +<html lang="en"> +<head> + <meta charset="utf8"> + <title>Test for the cd() function</title> + <script type="text/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + <script type="text/javascript;version=1.8" src="common.js"></script> + <!-- Any copyright is dedicated to the Public Domain. + - http://creativecommons.org/publicdomain/zero/1.0/ --> +</head> +<body> +<p>Test for the cd() function</p> + +<iframe id="content-iframe" src="http://example.com/chrome/devtools/shared/webconsole/test/sandboxed_iframe.html"></iframe> + +<script class="testbody" type="text/javascript;version=1.8"> +SimpleTest.waitForExplicitFinish(); + +let gState; + +function startTest() +{ + removeEventListener("load", startTest); + + attachConsoleToTab([], onAttach); +} + +function onAttach(aState, aResponse) +{ + top.foobarObject = Object.create(null); + top.foobarObject.bug609872 = "parent"; + + window.foobarObject = Object.create(null); + window.foobarObject.bug609872 = "child"; + + gState = aState; + + let tests = [doCheckParent, doCdIframe, doCheckIframe, + doCdContentIframe, + doCdSandboxedIframe, doCheckSandboxedIframe, + doCdParent, + doCdParent, + doCheckParent2]; + runTests(tests, testEnd); +} + +function doCheckParent() +{ + info("check parent window"); + gState.client.evaluateJS("window.foobarObject.bug609872", + onFooObjectFromParent); +} + +function onFooObjectFromParent(aResponse) +{ + checkObject(aResponse, { + from: gState.actor, + input: "window.foobarObject.bug609872", + result: "parent", + }); + + ok(!aResponse.exception, "no eval exception"); + ok(!aResponse.helperResult, "no helper result"); + + nextTest(); +} + +function doCdIframe() +{ + info("test cd('iframe')"); + gState.client.evaluateJS("cd('iframe')", onCdIframe); +} + +function onCdIframe(aResponse) +{ + checkObject(aResponse, { + from: gState.actor, + input: "cd('iframe')", + result: { type: "undefined" }, + helperResult: { type: "cd" }, + }); + + ok(!aResponse.exception, "no eval exception"); + + nextTest(); +} + +function doCheckIframe() +{ + info("check foobarObject from the iframe"); + gState.client.evaluateJS("window.foobarObject.bug609872", + onFooObjectFromIframe); +} + +function onFooObjectFromIframe(aResponse) +{ + checkObject(aResponse, { + from: gState.actor, + input: "window.foobarObject.bug609872", + result: "child", + }); + + ok(!aResponse.exception, "no js eval exception"); + ok(!aResponse.helperResult, "no helper result"); + + nextTest(); +} + +function doCdContentIframe() +{ + info("test cd('#content-iframe')"); + gState.client.evaluateJS("cd('#content-iframe')", onCdContentIframe); +} + +function onCdContentIframe(aResponse) +{ + checkObject(aResponse, { + from: gState.actor, + input: "cd('#content-iframe')", + result: { type: "undefined" }, + helperResult: { type: "cd" }, + }); + + ok(!aResponse.exception, "no eval exception"); + + nextTest(); +} +function doCdSandboxedIframe() +{ + // Don't use string to ensure we don't get security exception + // when passing a content window reference. + let cmd = "cd(document.getElementById('sandboxed-iframe').contentWindow)"; + info("test " + cmd); + gState.client.evaluateJS(cmd, onCdSandboxedIframe.bind(null, cmd)); +} + +function onCdSandboxedIframe(cmd, aResponse) +{ + checkObject(aResponse, { + from: gState.actor, + input: cmd, + result: { type: "undefined" }, + helperResult: { type: "cd" }, + }); + + ok(!aResponse.exception, "no eval exception"); + + nextTest(); +} + +function doCheckSandboxedIframe() +{ + info("check foobarObject from the sandboxed iframe"); + gState.client.evaluateJS("window.foobarObject.bug1051224", + onFooObjectFromSandboxedIframe); +} + +function onFooObjectFromSandboxedIframe(aResponse) +{ + checkObject(aResponse, { + from: gState.actor, + input: "window.foobarObject.bug1051224", + result: "sandboxed", + }); + + ok(!aResponse.exception, "no js eval exception"); + ok(!aResponse.helperResult, "no helper result"); + + nextTest(); +} + +function doCdParent() +{ + info("test cd() back to parent"); + gState.client.evaluateJS("cd()", onCdParent); +} + +function onCdParent(aResponse) +{ + checkObject(aResponse, { + from: gState.actor, + input: "cd()", + result: { type: "undefined" }, + helperResult: { type: "cd" }, + }); + + ok(!aResponse.exception, "no eval exception"); + + nextTest(); +} + +function doCheckParent2() +{ + gState.client.evaluateJS("window.foobarObject.bug609872", + onFooObjectFromParent2); +} + +function onFooObjectFromParent2(aResponse) +{ + checkObject(aResponse, { + from: gState.actor, + input: "window.foobarObject.bug609872", + result: "parent", + }); + + ok(!aResponse.exception, "no eval exception"); + ok(!aResponse.helperResult, "no helper result"); + + nextTest(); +} + +function testEnd() +{ + closeDebugger(gState, function() { + gState = null; + SimpleTest.finish(); + }); +} + +addEventListener("load", startTest); +</script> +</body> +</html> diff --git a/devtools/shared/webconsole/test/test_jsterm_last_result.html b/devtools/shared/webconsole/test/test_jsterm_last_result.html new file mode 100644 index 000000000..e90ce14ed --- /dev/null +++ b/devtools/shared/webconsole/test/test_jsterm_last_result.html @@ -0,0 +1,130 @@ +<!DOCTYPE HTML> +<html lang="en"> +<head> + <meta charset="utf8"> + <title>Test for the $_ getter</title> + <script type="text/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + <script type="text/javascript;version=1.8" src="common.js"></script> + <!-- Any copyright is dedicated to the Public Domain. + - http://creativecommons.org/publicdomain/zero/1.0/ --> +</head> +<body> +<p>Test for the $_ getter</p> + +<iframe id="content-iframe" src="http://example.com/chrome/devtools/shared/webconsole/test/sandboxed_iframe.html"></iframe> + +<script class="testbody" type="text/javascript;version=1.8"> +SimpleTest.waitForExplicitFinish(); +let gState; + +function evaluateJS(input, callback) { + return new Promise((resolve, reject) => { + gState.client.evaluateJSAsync(input, response => { + if (callback) { + callback(response); + } + resolve(response); + }); + }); +} + +function startTest() +{ + removeEventListener("load", startTest); + attachConsoleToTab([], state => { + gState = state; + let tests = [checkUndefinedResult,checkAdditionResult,checkObjectResult]; + runTests(tests, testEnd); + }); +} + +let checkUndefinedResult = Task.async(function*() { + info ("$_ returns undefined if nothing has evaluated yet"); + let response = yield evaluateJS("$_"); + basicResultCheck(response, "$_", undefined); + nextTest(); +}); + +let checkAdditionResult = Task.async(function*() { + info ("$_ returns last value and performs basic arithmetic"); + let response = yield evaluateJS("2+2"); + basicResultCheck(response, "2+2", 4); + + response = yield evaluateJS("$_"); + basicResultCheck(response, "$_", 4); + + response = yield evaluateJS("$_ + 2"); + basicResultCheck(response, "$_ + 2", 6); + + response = yield evaluateJS("$_ + 4"); + basicResultCheck(response, "$_ + 4", 10); + + nextTest(); +}); + +let checkObjectResult = Task.async(function*() { + info ("$_ has correct references to objects"); + + let response = yield evaluateJS("var foo = {bar:1}; foo;"); + basicResultCheck(response, "var foo = {bar:1}; foo;", { + type: "object", + class: "Object", + actor: /[a-z]/, + }); + checkObject(response.result.preview.ownProperties, { + bar: { + value: 1 + } + }); + + response = yield evaluateJS("$_"); + basicResultCheck(response, "$_", { + type: "object", + class: "Object", + actor: /[a-z]/, + }); + checkObject(response.result.preview.ownProperties, { + bar: { + value: 1 + } + }); + + top.foo.bar = 2; + + response = yield evaluateJS("$_"); + basicResultCheck(response, "$_", { + type: "object", + class: "Object", + actor: /[a-z]/, + }); + checkObject(response.result.preview.ownProperties, { + bar: { + value: 2 + } + }); + + nextTest(); +}); + +function basicResultCheck(response, input, output) { + checkObject(response, { + from: gState.actor, + input: input, + result: output, + }); + ok(!response.exception, "no eval exception"); + ok(!response.helperResult, "no helper result"); +} + +function testEnd() +{ + closeDebugger(gState, function() { + gState = null; + SimpleTest.finish(); + }); +} + +addEventListener("load", startTest); +</script> +</body> +</html> diff --git a/devtools/shared/webconsole/test/test_jsterm_queryselector.html b/devtools/shared/webconsole/test/test_jsterm_queryselector.html new file mode 100644 index 000000000..b75ee399c --- /dev/null +++ b/devtools/shared/webconsole/test/test_jsterm_queryselector.html @@ -0,0 +1,134 @@ +<!DOCTYPE HTML> +<html lang="en"> +<head> + <meta charset="utf8"> + <title>Test for the querySelector / querySelectorAll helpers</title> + <script type="text/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + <script type="text/javascript;version=1.8" src="common.js"></script> + <!-- Any copyright is dedicated to the Public Domain. + - http://creativecommons.org/publicdomain/zero/1.0/ --> +</head> +<body> +<p>Test for the querySelector / querySelectorAll helpers</p> + +<script class="testbody" type="text/javascript;version=1.8"> +SimpleTest.waitForExplicitFinish(); +let gState; +let gWin; + +function evaluateJS(input) { + return new Promise((resolve) => gState.client.evaluateJS(input, resolve)); +} + +function startTest() { + info ("Content window opened, attaching console to it"); + + let systemPrincipal = Cc["@mozilla.org/systemprincipal;1"].createInstance(Ci.nsIPrincipal); + ok (!gWin.document.nodePrincipal.equals(systemPrincipal), + "The test document is not using the system principal"); + + attachConsoleToTab([], state => { + gState = state; + let tests = [ + setupWindow, + checkQuerySelector, + checkQuerySelectorAll, + checkQuerySelectorAllNotExist, + checkQuerySelectorAllException + ]; + runTests(tests, testEnd); + }); +} + +let setupWindow = Task.async(function*() { + info ("Shimming window functions for the content privileged tab"); + yield evaluateJS("document.querySelector = function() { throw 'should not call qS'; }"); + yield evaluateJS("document.querySelectorAll = function() { throw 'should not call qSA'; }"); + nextTest(); +}); + +let checkQuerySelector = Task.async(function*() { + info ("$ returns an DOMNode"); + let response = yield evaluateJS("$('body')"); + basicResultCheck(response, "$('body')", { + type: "object", + class: "HTMLBodyElement", + preview: { + kind: "DOMNode", + nodeName: "body" + } + }); + nextTest(); +}); + +let checkQuerySelectorAll = Task.async(function*() { + info ("$$ returns an array"); + let response = yield evaluateJS("$$('body')"); + basicResultCheck(response, "$$('body')", { + type: "object", + class: "Array", + preview: { + length: 1 + } + }); + nextTest(); +}); + +let checkQuerySelectorAllNotExist = Task.async(function*() { + info ("$$ returns an array even if query yields no results"); + let response = yield evaluateJS("$$('foobar')"); + basicResultCheck(response, "$$('foobar')", { + type: "object", + class: "Array", + preview: { + length: 0 + } + }); + nextTest(); +}); + +let checkQuerySelectorAllException = Task.async(function*() { + info ("$$ returns an exception if an invalid selector was provided"); + let response = yield evaluateJS("$$(':foo')"); + checkObject(response, { + input: "$$(':foo')", + exceptionMessage: "SyntaxError: ':foo' is not a valid selector", + exception: { + preview: { + kind: "DOMException", + name: "SyntaxError", + message: "':foo' is not a valid selector" + } + } + }); + nextTest(); +}); + +function basicResultCheck(response, input, output) { + checkObject(response, { + from: gState.actor, + input: input, + result: output, + }); + ok(!response.exception, "no eval exception"); + ok(!response.helperResult, "no helper result"); +} + +function testEnd() { + gWin.close(); + gWin = null; + closeDebugger(gState, function() { + gState = null; + SimpleTest.finish(); + }); +} + +window.onload = function() { + // Open a content window to test XRay functionality on built in functions. + gWin = window.open("data:text/html,"); + info ("Waiting for content window to load"); + gWin.onload = startTest; +} +</script> +</body> +</html> diff --git a/devtools/shared/webconsole/test/test_network_get.html b/devtools/shared/webconsole/test/test_network_get.html new file mode 100644 index 000000000..710c9b0d7 --- /dev/null +++ b/devtools/shared/webconsole/test/test_network_get.html @@ -0,0 +1,260 @@ +<!DOCTYPE HTML> +<html lang="en"> +<head> + <meta charset="utf8"> + <title>Test for the network actor (GET request)</title> + <script type="text/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + <script type="text/javascript;version=1.8" src="common.js"></script> + <!-- Any copyright is dedicated to the Public Domain. + - http://creativecommons.org/publicdomain/zero/1.0/ --> +</head> +<body> +<p>Test for the network actor (GET request)</p> + +<iframe src="http://example.com/chrome/devtools/shared/webconsole/test/network_requests_iframe.html"></iframe> + +<script class="testbody" type="text/javascript;version=1.8"> +SimpleTest.waitForExplicitFinish(); + +function startTest() +{ + removeEventListener("load", startTest); + attachConsoleToTab(["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.wrappedJSObject.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: 3, + }; + break; + case "requestPostData": + ok(false, "got unexpected requestPostData"); + break; + case "responseStart": + expectedPacket = { + response: { + httpVersion: /^HTTP\/\d\.\d$/, + status: "200", + statusText: "OK", + headersSize: /^\d+$/, + discardResponseBody: false, + }, + }; + break; + case "securityInfo": + expectedPacket = { + state: "insecure", + }; + break; + case "responseCookies": + expectedPacket = { + cookies: 0, + }; + break; + case "responseContent": + expectedPacket = { + mimeType: "application/json", + contentSize: 1070, + 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"); + ok(!!aResponse.rawHeaders, "request rawHeaders available"); + + checkHeadersOrCookies(aResponse.headers, { + Referer: /network_requests_iframe\.html/, + Cookie: /bug768096/, + }); + + checkRawHeaders(aResponse.rawHeaders, { + 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, 3, "request cookies length"); + + checkHeadersOrCookies(aResponse.cookies, { + foobar: "fooval", + omgfoo: "bug768096", + badcookie: "bug826798=st3fan", + }); + + 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 not 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"); + ok(!!aResponse.rawHeaders, "response rawHeaders available"); + + checkHeadersOrCookies(aResponse.headers, { + "Content-Type": /^application\/(json|octet-stream)$/, + "Content-Length": /^\d+$/, + }); + + checkRawHeaders(aResponse.rawHeaders, { + "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, "response content text"); + ok(!aResponse.contentDiscarded, "response content was not 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> diff --git a/devtools/shared/webconsole/test/test_network_longstring.html b/devtools/shared/webconsole/test/test_network_longstring.html new file mode 100644 index 000000000..d55136896 --- /dev/null +++ b/devtools/shared/webconsole/test/test_network_longstring.html @@ -0,0 +1,293 @@ +<!DOCTYPE HTML> +<html lang="en"> +<head> + <meta charset="utf8"> + <title>Test that the network actor uses the LongStringActor</title> + <script type="text/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + <script type="text/javascript;version=1.8" src="common.js"></script> + <!-- Any copyright is dedicated to the Public Domain. + - http://creativecommons.org/publicdomain/zero/1.0/ --> +</head> +<body> +<p>Test that the network actor uses the LongStringActor</p> + +<iframe src="http://example.com/chrome/devtools/shared/webconsole/test/network_requests_iframe.html"></iframe> + +<script class="testbody" type="text/javascript;version=1.8"> +SimpleTest.waitForExplicitFinish(); + +function startTest() +{ + removeEventListener("load", startTest); + + attachConsoleToTab(["NetworkActivity"], onAttach); +} + +function onAttach(aState, aResponse) +{ + info("set long string length"); + + window.ORIGINAL_LONG_STRING_LENGTH = DebuggerServer.LONG_STRING_LENGTH; + window.ORIGINAL_LONG_STRING_INITIAL_LENGTH = + DebuggerServer.LONG_STRING_INITIAL_LENGTH; + + DebuggerServer.LONG_STRING_LENGTH = 400; + DebuggerServer.LONG_STRING_INITIAL_LENGTH = 400; + + 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.wrappedJSObject.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: 3, + }; + 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 "securityInfo": + expectedPacket = { + state: "insecure", + }; + break; + case "responseCookies": + expectedPacket = { + cookies: 0, + }; + break; + case "responseContent": + expectedPacket = { + mimeType: "application/json", + 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, 3, "request cookies length"); + + checkHeadersOrCookies(aResponse.cookies, { + foobar: "fooval", + omgfoo: "bug768096", + badcookie: "bug826798=st3fan", + }); + + onRequestPostData = onRequestPostData.bind(null, aState); + aState.client.getRequestPostData(aState.netActor, + onRequestPostData); +} + +function onRequestPostData(aState, aResponse) +{ + info("checking request POST data"); + + checkObject(aResponse, { + postData: { + text: { + type: "longString", + initial: /^Hello world! foobaz barr.+foobaz barrfo$/, + length: 552, + actor: /[a-z]/, + }, + }, + postDataDiscarded: false, + }); + + is(aResponse.postData.text.initial.length, + DebuggerServer.LONG_STRING_INITIAL_LENGTH, "postData text initial length"); + + 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+$/, + "x-very-short": "hello world", + "x-very-long": { + "type": "longString", + "length": 521, + "initial": /^Lorem ipsum.+\. Donec vitae d$/, + "actor": /[a-z]/, + }, + }); + + 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: { + type: "longString", + initial: /^\{ id: "test JSON data"(.|\r|\n)+ barfoo ba$/g, + length: 1070, + actor: /[a-z]/, + }, + }, + contentDiscarded: false, + }); + + is(aResponse.content.text.initial.length, + DebuggerServer.LONG_STRING_INITIAL_LENGTH, "content initial length"); + + 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() { + DebuggerServer.LONG_STRING_LENGTH = ORIGINAL_LONG_STRING_LENGTH; + DebuggerServer.LONG_STRING_INITIAL_LENGTH = ORIGINAL_LONG_STRING_INITIAL_LENGTH; + + SimpleTest.finish(); + }); +} + +addEventListener("load", startTest); +</script> +</body> +</html> diff --git a/devtools/shared/webconsole/test/test_network_post.html b/devtools/shared/webconsole/test/test_network_post.html new file mode 100644 index 000000000..d96b9b0b7 --- /dev/null +++ b/devtools/shared/webconsole/test/test_network_post.html @@ -0,0 +1,272 @@ +<!DOCTYPE HTML> +<html lang="en"> +<head> + <meta charset="utf8"> + <title>Test for the network actor (POST request)</title> + <script type="text/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + <script type="text/javascript;version=1.8" src="common.js"></script> + <!-- Any copyright is dedicated to the Public Domain. + - http://creativecommons.org/publicdomain/zero/1.0/ --> +</head> +<body> +<p>Test for the network actor (POST request)</p> + +<iframe src="http://example.com/chrome/devtools/shared/webconsole/test/network_requests_iframe.html"></iframe> + +<script class="testbody" type="text/javascript;version=1.8"> +SimpleTest.waitForExplicitFinish(); + +function startTest() +{ + removeEventListener("load", startTest); + + attachConsoleToTab(["NetworkActivity"], onAttach); +} + +function onAttach(aState, aResponse) +{ + 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.wrappedJSObject.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: 3, + }; + 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 "securityInfo": + expectedPacket = { + state: "insecure", + }; + break; + case "responseCookies": + expectedPacket = { + cookies: 0, + }; + break; + case "responseContent": + expectedPacket = { + mimeType: "application/json", + 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"); + ok(!!aResponse.rawHeaders.length, "request rawHeaders available"); + + checkHeadersOrCookies(aResponse.headers, { + Referer: /network_requests_iframe\.html/, + Cookie: /bug768096/, + }); + + checkRawHeaders(aResponse.rawHeaders, { + 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, 3, "request cookies length"); + + checkHeadersOrCookies(aResponse.cookies, { + foobar: "fooval", + omgfoo: "bug768096", + badcookie: "bug826798=st3fan", + }); + + 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! foobaz barr.+foobaz barr$/, + }, + postDataDiscarded: false, + }); + + is(aResponse.postData.text.length, 552, "postData text length"); + + 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"); + ok(!!aResponse.rawHeaders, "response rawHeaders available"); + + checkHeadersOrCookies(aResponse.headers, { + "Content-Type": /^application\/(json|octet-stream)$/, + "Content-Length": /^\d+$/, + }); + + checkRawHeaders(aResponse.rawHeaders, { + "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> diff --git a/devtools/shared/webconsole/test/test_network_security-hpkp.html b/devtools/shared/webconsole/test/test_network_security-hpkp.html new file mode 100644 index 000000000..55e2621a8 --- /dev/null +++ b/devtools/shared/webconsole/test/test_network_security-hpkp.html @@ -0,0 +1,108 @@ +<!DOCTYPE HTML> +<html lang="en"> +<head> + <meta charset="utf8"> + <title>Test for the network actor (HPKP detection)</title> + <script type="text/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + <script type="text/javascript;version=1.8" src="common.js"></script> + <!-- Any copyright is dedicated to the Public Domain. + - http://creativecommons.org/publicdomain/zero/1.0/ --> +</head> +<body> +<p>Test for the network actor (HPKP detection)</p> + +<iframe src="https://example.com/chrome/devtools/shared/webconsole/test/network_requests_iframe.html"></iframe> + +<script class="testbody" type="text/javascript;version=1.8"> +SimpleTest.waitForExplicitFinish(); + +let gCurrentTestCase = -1; +const HPKP_PREF = "security.cert_pinning.process_headers_from_non_builtin_roots"; + +// Static pins tested by unit/test_security-info-static-hpkp.js. +const TEST_CASES = [ + { + desc: "no Public Key Pinning", + url: "https://example.com", + usesPinning: false, + }, + { + desc: "dynamic Public Key Pinning with this request", + url: "https://include-subdomains.pinning-dynamic.example.com/" + + "browser/browser/base/content/test/general/pinning_headers.sjs", + usesPinning: true, + }, + { + desc: "dynamic Public Key Pinning with previous request", + url: "https://include-subdomains.pinning-dynamic.example.com/", + usesPinning: true, + } +]; + +function startTest() +{ + // Need to enable this pref or pinning headers are rejected due test + // certificate. + Services.prefs.setBoolPref(HPKP_PREF, true); + SimpleTest.registerCleanupFunction(() => { + Services.prefs.setBoolPref(HPKP_PREF, false); + + // Reset pinning state. + let gSSService = Cc["@mozilla.org/ssservice;1"] + .getService(Ci.nsISiteSecurityService); + + let gIOService = Cc["@mozilla.org/network/io-service;1"] + .getService(Ci.nsIIOService); + for (let {url} of TEST_CASES) { + let uri = gIOService.newURI(url, null, null); + gSSService.removeState(Ci.nsISiteSecurityService.HEADER_HPKP, uri, 0); + } + }); + + info("Test detection of Public Key Pinning."); + removeEventListener("load", startTest); + attachConsoleToTab(["NetworkActivity"], onAttach); +} + +function onAttach(aState, aResponse) +{ + onNetworkEventUpdate = onNetworkEventUpdate.bind(null, aState); + aState.dbgClient.addListener("networkEventUpdate", onNetworkEventUpdate); + + runNextCase(aState); +} + +function runNextCase(aState) { + gCurrentTestCase++; + if (gCurrentTestCase === TEST_CASES.length) { + info("Tests ran. Cleaning up."); + closeDebugger(aState, SimpleTest.finish); + return; + } + + let { desc, url } = TEST_CASES[gCurrentTestCase]; + info("Testing site with " + desc); + + let iframe = document.querySelector("iframe").contentWindow; + iframe.wrappedJSObject.makeXhrCallback("GET", url); +} + +function onNetworkEventUpdate(aState, aType, aPacket) +{ + function onSecurityInfo(packet) { + let data = TEST_CASES[gCurrentTestCase]; + is(packet.securityInfo.hpkp, data.usesPinning, + "Public Key Pinning detected correctly."); + + runNextCase(aState); + } + + if (aPacket.updateType === "securityInfo") { + aState.client.getSecurityInfo(aPacket.from, onSecurityInfo); + } +} + +addEventListener("load", startTest); +</script> +</body> +</html> diff --git a/devtools/shared/webconsole/test/test_network_security-hsts.html b/devtools/shared/webconsole/test/test_network_security-hsts.html new file mode 100644 index 000000000..f69244d8d --- /dev/null +++ b/devtools/shared/webconsole/test/test_network_security-hsts.html @@ -0,0 +1,100 @@ +<!DOCTYPE HTML> +<html lang="en"> +<head> + <meta charset="utf8"> + <title>Test for the network actor (HSTS detection)</title> + <script type="text/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + <script type="text/javascript;version=1.8" src="common.js"></script> + <!-- Any copyright is dedicated to the Public Domain. + - http://creativecommons.org/publicdomain/zero/1.0/ --> +</head> +<body> +<p>Test for the network actor (HSTS detection)</p> + +<iframe src="https://example.com/chrome/devtools/shared/webconsole/test/network_requests_iframe.html"></iframe> + +<script class="testbody" type="text/javascript;version=1.8"> +SimpleTest.waitForExplicitFinish(); + +let gCurrentTestCase = -1; +const TEST_CASES = [ + { + desc: "no HSTS", + url: "https://example.com", + usesHSTS: false, + }, + { + desc: "HSTS from this response", + url: "https://example.com/"+ + "browser/browser/base/content/test/general/browser_star_hsts.sjs", + usesHSTS: true, + }, + { + desc: "stored HSTS from previous response", + url: "https://example.com/", + usesHSTS: true, + } +]; + +function startTest() +{ + + SimpleTest.registerCleanupFunction(() => { + // Reset HSTS state. + let gSSService = Cc["@mozilla.org/ssservice;1"] + .getService(Ci.nsISiteSecurityService); + + let gIOService = Cc["@mozilla.org/network/io-service;1"] + .getService(Ci.nsIIOService); + + let uri = gIOService.newURI(TEST_CASES[0].url, null, null); + gSSService.removeState(Ci.nsISiteSecurityService.HEADER_HSTS, uri, 0); + }); + + info("Test detection of HTTP Strict Transport Security."); + removeEventListener("load", startTest); + attachConsoleToTab(["NetworkActivity"], onAttach); +} + +function onAttach(aState, aResponse) +{ + onNetworkEventUpdate = onNetworkEventUpdate.bind(null, aState); + aState.dbgClient.addListener("networkEventUpdate", onNetworkEventUpdate); + + runNextCase(aState); +} + +function runNextCase(aState) { + gCurrentTestCase++; + if (gCurrentTestCase === TEST_CASES.length) { + info("Tests ran. Cleaning up."); + closeDebugger(aState, SimpleTest.finish); + return; + } + + let { desc, url } = TEST_CASES[gCurrentTestCase]; + info("Testing site with " + desc); + + let iframe = document.querySelector("iframe").contentWindow; + iframe.wrappedJSObject.makeXhrCallback("GET", url); +} + +function onNetworkEventUpdate(aState, aType, aPacket) +{ + function onSecurityInfo(packet) { + let data = TEST_CASES[gCurrentTestCase]; + is(packet.securityInfo.hsts, data.usesHSTS, + "Strict Transport Security detected correctly."); + + runNextCase(aState); + } + + if (aPacket.updateType === "securityInfo") { + aState.client.getSecurityInfo(aPacket.from, onSecurityInfo); + } +} + +addEventListener("load", startTest); +</script> +</body> +</html> diff --git a/devtools/shared/webconsole/test/test_nsiconsolemessage.html b/devtools/shared/webconsole/test/test_nsiconsolemessage.html new file mode 100644 index 000000000..ef8b8067e --- /dev/null +++ b/devtools/shared/webconsole/test/test_nsiconsolemessage.html @@ -0,0 +1,74 @@ +<!DOCTYPE HTML> +<html lang="en"> +<head> + <meta charset="utf8"> + <title>Test for nsIConsoleMessages</title> + <script type="text/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + <script type="text/javascript;version=1.8" src="common.js"></script> + <!-- Any copyright is dedicated to the Public Domain. + - http://creativecommons.org/publicdomain/zero/1.0/ --> +</head> +<body> +<p>Make sure that nsIConsoleMessages are logged. See bug 859756.</p> + +<script class="testbody" type="text/javascript;version=1.8"> +"use strict"; +SimpleTest.waitForExplicitFinish(); + +let expectedMessages = []; + +function startTest() +{ + removeEventListener("load", startTest); + attachConsole(["PageError"], onAttach); +} + +function onAttach(aState, aResponse) +{ + onLogMessage = onLogMessage.bind(null, aState); + aState.dbgClient.addListener("logMessage", onLogMessage); + + expectedMessages = [{ + message: "hello world! bug859756", + timeStamp: /^\d+$/, + }]; + + Services.console.logStringMessage("hello world! bug859756"); + + info("waiting for messages"); +} + +let receivedMessages = []; + +function onLogMessage(aState, aType, aPacket) +{ + is(aPacket.from, aState.actor, "packet actor"); + info("received message: " + aPacket.message); + + let found = false; + for (let expected of expectedMessages) { + if (expected.message == aPacket.message) { + found = true; + break; + } + } + if (!found) { + return; + } + + receivedMessages.push(aPacket); + if (receivedMessages.length != expectedMessages.length) { + return; + } + + aState.dbgClient.removeListener("logMessage", onLogMessage); + + checkObject(receivedMessages, expectedMessages); + + closeDebugger(aState, () => SimpleTest.finish()); +} + +addEventListener("load", startTest); +</script> +</body> +</html> diff --git a/devtools/shared/webconsole/test/test_object_actor.html b/devtools/shared/webconsole/test/test_object_actor.html new file mode 100644 index 000000000..09176a5aa --- /dev/null +++ b/devtools/shared/webconsole/test/test_object_actor.html @@ -0,0 +1,178 @@ +<!DOCTYPE HTML> +<html lang="en"> +<head> + <meta charset="utf8"> + <title>Test for the object actor</title> + <script type="text/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + <script type="text/javascript;version=1.8" src="common.js"></script> + <!-- Any copyright is dedicated to the Public Domain. + - http://creativecommons.org/publicdomain/zero/1.0/ --> +</head> +<body> +<p>Test for the object actor</p> + +<script class="testbody" type="text/javascript;version=1.8"> +SimpleTest.waitForExplicitFinish(); + +let expectedProps = []; + +function startTest() +{ + removeEventListener("load", startTest); + + attachConsoleToTab(["ConsoleAPI"], onAttach); +} + +function onAttach(aState, aResponse) +{ + onConsoleCall = onConsoleCall.bind(null, aState); + aState.dbgClient.addListener("consoleAPICall", onConsoleCall); + + let longString = (new Array(DebuggerServer.LONG_STRING_LENGTH + 3)).join("\u0629"); + + // Here we put the objects in the correct window, to avoid having them all + // wrapped by proxies for cross-compartment access. + + let foobarObject = top.Object.create(null); + foobarObject.tamarbuta = longString; + foobarObject.foo = 1; + foobarObject.foobar = "hello"; + foobarObject.omg = null; + foobarObject.testfoo = false; + foobarObject.notInspectable = top.Object.create(null); + foobarObject.omgfn = new top.Function("return 'myResult'"); + foobarObject.abArray = new top.Array("a", "b"); + foobarObject.foobaz = top.document; + + top.Object.defineProperty(foobarObject, "getterAndSetter", { + enumerable: true, + get: new top.Function("return 'foo';"), + set: new top.Function("1+2"), + }); + + foobarObject.longStringObj = top.Object.create(null); + foobarObject.longStringObj.toSource = new top.Function("'" + longString + "'"); + foobarObject.longStringObj.toString = new top.Function("'" + longString + "'"); + foobarObject.longStringObj.boom = "explode"; + + top.wrappedJSObject.foobarObject = foobarObject; + top.console.log("hello", top.wrappedJSObject.foobarObject); + + expectedProps = { + "abArray": { + value: { + type: "object", + class: "Array", + actor: /[a-z]/, + }, + }, + "foo": { + configurable: true, + enumerable: true, + writable: true, + value: 1, + }, + "foobar": { + value: "hello", + }, + "foobaz": { + value: { + type: "object", + class: "XULDocument", + actor: /[a-z]/, + }, + }, + "getterAndSetter": { + get: { + type: "object", + class: "Function", + actor: /[a-z]/, + }, + set: { + type: "object", + class: "Function", + actor: /[a-z]/, + }, + }, + "longStringObj": { + value: { + type: "object", + class: "Object", + actor: /[a-z]/, + }, + }, + "notInspectable": { + value: { + type: "object", + class: "Object", + actor: /[a-z]/, + }, + }, + "omg": { + value: { type: "null" }, + }, + "omgfn": { + value: { + type: "object", + class: "Function", + actor: /[a-z]/, + }, + }, + "tamarbuta": { + value: { + type: "longString", + initial: longString.substring(0, + DebuggerServer.LONG_STRING_INITIAL_LENGTH), + length: longString.length, + }, + }, + "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]/, + }], + }); + + aState.dbgClient.removeListener("consoleAPICall", onConsoleCall); + + info("inspecting object properties"); + let args = aPacket.message.arguments; + onProperties = onProperties.bind(null, aState); + + let client = new ObjectClient(aState.dbgClient, args[1]); + client.getPrototypeAndProperties(onProperties); +} + +function onProperties(aState, aResponse) +{ + let props = aResponse.ownProperties; + is(Object.keys(props).length, Object.keys(expectedProps).length, + "number of enumerable properties"); + checkObject(props, expectedProps); + + expectedProps = []; + + closeDebugger(aState, function() { + SimpleTest.finish(); + }); +} + +addEventListener("load", startTest); +</script> +</body> +</html> diff --git a/devtools/shared/webconsole/test/test_object_actor_native_getters.html b/devtools/shared/webconsole/test/test_object_actor_native_getters.html new file mode 100644 index 000000000..e22eb8cd5 --- /dev/null +++ b/devtools/shared/webconsole/test/test_object_actor_native_getters.html @@ -0,0 +1,106 @@ +<!DOCTYPE HTML> +<html lang="en"> +<head> + <meta charset="utf8"> + <title>Test for the native getters in object actors</title> + <script type="text/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + <script type="text/javascript;version=1.8" src="common.js"></script> + <!-- Any copyright is dedicated to the Public Domain. + - http://creativecommons.org/publicdomain/zero/1.0/ --> +</head> +<body> +<p>Test for the native getters in object actors</p> + +<script class="testbody" type="text/javascript;version=1.8"> +SimpleTest.waitForExplicitFinish(); + +let expectedProps = []; +let expectedSafeGetters = []; + +function startTest() +{ + removeEventListener("load", startTest); + + attachConsoleToTab(["ConsoleAPI"], onAttach); +} + +function onAttach(aState, aResponse) +{ + onConsoleCall = onConsoleCall.bind(null, aState); + aState.dbgClient.addListener("consoleAPICall", onConsoleCall); + + top.console.log("hello", document); + + expectedProps = { + "location": { + get: { + type: "object", + class: "Function", + actor: /[a-z]/, + }, + }, + }; + + expectedSafeGetters = { + "title": { + getterValue: /native getters in object actors/, + getterPrototypeLevel: 2, + }, + "styleSheets": { + getterValue: /\[object Object\]/, + getterPrototypeLevel: 2, + }, + }; +} + +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]/, + }], + }); + + aState.dbgClient.removeListener("consoleAPICall", onConsoleCall); + + info("inspecting object properties"); + let args = aPacket.message.arguments; + onProperties = onProperties.bind(null, aState); + + let client = new ObjectClient(aState.dbgClient, args[1]); + client.getPrototypeAndProperties(onProperties); +} + +function onProperties(aState, aResponse) +{ + let props = aResponse.ownProperties; + let keys = Object.keys(props); + info(keys.length + " ownProperties: " + keys); + + ok(keys.length >= Object.keys(expectedProps).length, "number of properties"); + + info("check ownProperties"); + checkObject(props, expectedProps); + info("check safeGetterValues"); + checkObject(aResponse.safeGetterValues, expectedSafeGetters); + + expectedProps = []; + expectedSafeGetters = []; + + closeDebugger(aState, function() { + SimpleTest.finish(); + }); +} + +addEventListener("load", startTest); +</script> +</body> +</html> diff --git a/devtools/shared/webconsole/test/test_object_actor_native_getters_lenient_this.html b/devtools/shared/webconsole/test/test_object_actor_native_getters_lenient_this.html new file mode 100644 index 000000000..c4197a5b8 --- /dev/null +++ b/devtools/shared/webconsole/test/test_object_actor_native_getters_lenient_this.html @@ -0,0 +1,79 @@ +<!DOCTYPE HTML> +<html lang="en"> +<head> + <meta charset="utf8"> + <title>Test that WebIDL attributes with the LenientThis extended attribute + do not appear in the wrong objects</title> + <script type="text/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + <script type="text/javascript;version=1.8" src="common.js"></script> + <!-- Any copyright is dedicated to the Public Domain. + - http://creativecommons.org/publicdomain/zero/1.0/ --> +</head> +<body> +<p>Test for the native getters in object actors</p> + +<script class="testbody" type="text/javascript;version=1.8"> +SimpleTest.waitForExplicitFinish(); + +function startTest() +{ + removeEventListener("load", startTest); + + attachConsoleToTab(["ConsoleAPI"], onAttach); +} + +function onAttach(aState, aResponse) +{ + onConsoleCall = onConsoleCall.bind(null, aState); + aState.dbgClient.addListener("consoleAPICall", onConsoleCall); + + let docAsProto = Object.create(document); + + top.console.log("hello", docAsProto); +} + +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]/, + }], + }); + + aState.dbgClient.removeListener("consoleAPICall", onConsoleCall); + + info("inspecting object properties"); + let args = aPacket.message.arguments; + onProperties = onProperties.bind(null, aState); + + let client = new ObjectClient(aState.dbgClient, args[1]); + client.getPrototypeAndProperties(onProperties); +} + +function onProperties(aState, aResponse) +{ + let props = aResponse.ownProperties; + let keys = Object.keys(props); + info(keys.length + " ownProperties: " + keys); + + is(keys.length, 0, "number of properties"); + keys = Object.keys(aResponse.safeGetterValues); + is(keys.length, 0, "number of safe getters"); + + closeDebugger(aState, function() { + SimpleTest.finish(); + }); +} + +addEventListener("load", startTest); +</script> +</body> +</html> diff --git a/devtools/shared/webconsole/test/test_page_errors.html b/devtools/shared/webconsole/test/test_page_errors.html new file mode 100644 index 000000000..19e5ba4b4 --- /dev/null +++ b/devtools/shared/webconsole/test/test_page_errors.html @@ -0,0 +1,186 @@ +<!DOCTYPE HTML> +<html lang="en"> +<head> + <meta charset="utf8"> + <title>Test for page errors</title> + <script type="text/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + <script type="text/javascript;version=1.8" src="common.js"></script> + <!-- Any copyright is dedicated to the Public Domain. + - http://creativecommons.org/publicdomain/zero/1.0/ --> +</head> +<body> +<p>Test for page errors</p> + +<script class="testbody" type="text/javascript;version=1.8"> +SimpleTest.waitForExplicitFinish(); + +let expectedPageErrors = []; + +function doPageErrors() +{ + expectedPageErrors = { + "document.body.style.color = 'fooColor';": { + errorMessage: /fooColor/, + errorMessageName: undefined, + sourceName: /test_page_errors/, + category: "CSS Parser", + timeStamp: /^\d+$/, + error: false, + warning: true, + exception: false, + strict: false, + }, + "document.doTheImpossible();": { + errorMessage: /doTheImpossible/, + errorMessageName: undefined, + sourceName: /test_page_errors/, + category: "chrome javascript", + timeStamp: /^\d+$/, + error: false, + warning: false, + exception: true, + strict: false, + }, + "(42).toString(0);": { + errorMessage: /radix/, + errorMessageName: "JSMSG_BAD_RADIX", + sourceName: /test_page_errors/, + category: "chrome javascript", + timeStamp: /^\d+$/, + error: false, + warning: false, + exception: true, + strict: false, + }, + "'use strict'; (Object.freeze({name: 'Elsa', score: 157})).score = 0;": { + errorMessage: /read.only/, + errorMessageName: "JSMSG_READ_ONLY", + sourceName: /test_page_errors/, + category: "chrome javascript", + timeStamp: /^\d+$/, + error: false, + warning: false, + exception: true, + }, + "([]).length = -1": { + errorMessage: /array length/, + errorMessageName: "JSMSG_BAD_ARRAY_LENGTH", + sourceName: /test_page_errors/, + category: "chrome javascript", + timeStamp: /^\d+$/, + error: false, + warning: false, + exception: true, + }, + "'abc'.repeat(-1);": { + errorMessage: /repeat count.*non-negative/, + errorMessageName: "JSMSG_NEGATIVE_REPETITION_COUNT", + sourceName: /test_page_errors/, + category: "chrome javascript", + timeStamp: /^\d+$/, + error: false, + warning: false, + exception: true, + }, + "'a'.repeat(2e28);": { + errorMessage: /repeat count.*less than infinity/, + errorMessageName: "JSMSG_RESULTING_STRING_TOO_LARGE", + sourceName: /test_page_errors/, + category: "chrome javascript", + timeStamp: /^\d+$/, + error: false, + warning: false, + exception: true, + }, + "77.1234.toExponential(-1);": { + errorMessage: /out of range/, + errorMessageName: "JSMSG_PRECISION_RANGE", + sourceName: /test_page_errors/, + category: "chrome javascript", + timeStamp: /^\d+$/, + error: false, + warning: false, + exception: true, + }, + "var f = Function('x y', 'return x + y;');": { + errorMessage: /malformed formal/, + errorMessageName: "JSMSG_BAD_FORMAL", + sourceName: /test_page_errors/, + category: "chrome javascript", + timeStamp: /^\d+$/, + error: false, + warning: false, + exception: true, + }, + "function a() { return; 1 + 1; }": { + errorMessage: /unreachable code/, + errorMessageName: "JSMSG_STMT_AFTER_RETURN", + sourceName: /test_page_errors/, + category: "chrome javascript", + timeStamp: /^\d+$/, + error: false, + warning: true, + exception: false, + }, + }; + + let container = document.createElement("script"); + for (let stmt of Object.keys(expectedPageErrors)) { + if (expectedPageErrors[stmt].exception) { + SimpleTest.expectUncaughtException(); + } + info("starting stmt: " + stmt); + container = document.createElement("script"); + document.body.appendChild(container); + container.textContent = stmt; + document.body.removeChild(container); + info("ending stmt: " + stmt); + } +} + +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) +{ + if (!aPacket.pageError.sourceName.includes("test_page_errors")) { + info("Ignoring error from unknown source: " + aPacket.pageError.sourceName); + return; + } + + is(aPacket.from, aState.actor, "page error actor"); + + pageErrors.push(aPacket.pageError); + if (pageErrors.length != Object.keys(expectedPageErrors).length) { + return; + } + + aState.dbgClient.removeListener("pageError", onPageError); + + Object.values(expectedPageErrors).forEach(function(aMessage, aIndex) { + info("checking received page error #" + aIndex); + checkObject(pageErrors[aIndex], Object.values(expectedPageErrors)[aIndex]); + }); + + closeDebugger(aState, function() { + SimpleTest.finish(); + }); +} + +addEventListener("load", startTest); +</script> +</body> +</html> diff --git a/devtools/shared/webconsole/test/test_reflow.html b/devtools/shared/webconsole/test/test_reflow.html new file mode 100644 index 000000000..2ac2ca509 --- /dev/null +++ b/devtools/shared/webconsole/test/test_reflow.html @@ -0,0 +1,94 @@ +<!DOCTYPE HTML> +<html lang="en"> +<head> + <meta charset="utf8"> + <title>Test for the Reflow Activity</title> + <script type="text/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + <script type="text/javascript;version=1.8" src="common.js"></script> + <!-- Any copyright is dedicated to the Public Domain. + - http://creativecommons.org/publicdomain/zero/1.0/ --> +</head> +<body> +<p>Test for reflow events</p> + +<script class="testbody" type="text/javascript;version=1.8"> +SimpleTest.waitForExplicitFinish(); + +let client; + +function generateReflow() +{ + top.document.documentElement.style.display = "none"; + top.document.documentElement.getBoundingClientRect(); + top.document.documentElement.style.display = "block"; +} + +function startTest() +{ + removeEventListener("load", startTest); + attachConsoleToTab(["ReflowActivity"], onAttach); +} + +function onAttach(aState, aResponse) +{ + client = aState.dbgClient; + + onReflowActivity = onReflowActivity.bind(null, aState); + client.addListener("reflowActivity", onReflowActivity); + generateReflow(); +} + +// We are expecting 3 reflow events. +let expectedEvents = [ + { + interruptible: false, + sourceURL: "chrome://mochitests/content/chrome/devtools/shared/webconsole/test/test_reflow.html", + functionName: "generateReflow" + }, + { + interruptible: true, + sourceURL: null, + functionName: null + }, + { + interruptible: true, + sourceURL: null, + functionName: null + }, +]; + +let receivedEvents = []; + + +function onReflowActivity(aState, aType, aPacket) +{ + info("packet: " + aPacket.message); + receivedEvents.push(aPacket); + if (receivedEvents.length == expectedEvents.length) { + checkEvents(); + finish(aState); + } +} + +function checkEvents() { + for (let i = 0; i < expectedEvents.length; i++) { + let a = expectedEvents[i]; + let b = receivedEvents[i]; + for (let key in a) { + is(a[key], b[key], "field " + key + " is valid"); + } + } +} + +function finish(aState) { + client.removeListener("reflowActivity", onReflowActivity); + closeDebugger(aState, function() { + SimpleTest.finish(); + }); +} + +addEventListener("load", startTest); + +</script> +</body> +</html> diff --git a/devtools/shared/webconsole/test/test_throw.html b/devtools/shared/webconsole/test/test_throw.html new file mode 100644 index 000000000..7d7ea7b31 --- /dev/null +++ b/devtools/shared/webconsole/test/test_throw.html @@ -0,0 +1,93 @@ +<!DOCTYPE HTML> +<html lang="en"> +<head> + <meta charset="utf8"> + <title>Web Console throw tests</title> + <script type="text/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + <script type="text/javascript;version=1.8" src="common.js"></script> + <!-- Any copyright is dedicated to the Public Domain. + - http://creativecommons.org/publicdomain/zero/1.0/ --> +</head> +<body> +<p>Web Console throw tests</p> + +<script class="testbody" type="text/javascript;version=1.8"> +SimpleTest.waitForExplicitFinish(); + +function startTest() +{ + removeEventListener("load", startTest); + attachConsoleToTab([], onAttach); +} + +function onAttach(aState, aResponse) +{ + let tests = []; + + let falsyValues = ["-0", "null", "undefined", "Infinity", "-Infinity", "NaN"]; + falsyValues.forEach(function(value) { + tests.push(function() { + aState.client.evaluateJS("throw " + value + ";", function(aResponse) { + let type = aResponse.exception.type; + is(type, value, "exception.type for throw " + value); + nextTest(); + }); + }); + }); + + let identityTestValues = [false, 0]; + identityTestValues.forEach(function(value) { + tests.push(function() { + aState.client.evaluateJS("throw " + value + ";", function(aResponse) { + let exception = aResponse.exception; + is(exception, value, "response.exception for throw " + value); + nextTest(); + }); + }); + }); + + let longString = Array(DebuggerServer.LONG_STRING_LENGTH + 1).join("a"), + shortedString = longString.substring(0, + DebuggerServer.LONG_STRING_INITIAL_LENGTH + ); + tests.push(function() { + aState.client.evaluateJS("throw '" + longString + "';", function(aResponse) { + is(aResponse.exception.initial, shortedString, + "exception.initial for throw longString" + ); + is(aResponse.exceptionMessage.initial, shortedString, + "exceptionMessage.initial for throw longString" + ); + nextTest(); + }); + }); + + let symbolTestValues = [ + ["Symbol.iterator", "Symbol(Symbol.iterator)"], + ["Symbol('foo')", "Symbol(foo)"], + ["Symbol()", "Symbol()"], + ]; + symbolTestValues.forEach(function([expr, message]) { + tests.push(function() { + aState.client.evaluateJS("throw " + expr + ";", function(aResponse) { + is(aResponse.exceptionMessage, message, + "response.exception for throw " + expr); + nextTest(); + }); + }); + }); + + runTests(tests, endTest.bind(null, aState)); +} + +function endTest(aState) +{ + closeDebugger(aState, function() { + SimpleTest.finish(); + }); +} + +addEventListener("load", startTest); +</script> +</body> +</html> diff --git a/devtools/shared/webconsole/test/unit/.eslintrc.js b/devtools/shared/webconsole/test/unit/.eslintrc.js new file mode 100644 index 000000000..59adf410a --- /dev/null +++ b/devtools/shared/webconsole/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/shared/webconsole/test/unit/test_js_property_provider.js b/devtools/shared/webconsole/test/unit/test_js_property_provider.js new file mode 100644 index 000000000..c360cf96d --- /dev/null +++ b/devtools/shared/webconsole/test/unit/test_js_property_provider.js @@ -0,0 +1,170 @@ +/* -*- js-indent-level: 2; indent-tabs-mode: nil -*- */ +// Any copyright is dedicated to the Public Domain. +// http://creativecommons.org/publicdomain/zero/1.0/ + +"use strict"; +const { require } = Components.utils.import("resource://devtools/shared/Loader.jsm", {}); +const { FallibleJSPropertyProvider: JSPropertyProvider } = + require("devtools/shared/webconsole/js-property-provider"); + +Components.utils.import("resource://gre/modules/jsdebugger.jsm"); +addDebuggerToGlobal(this); + +function run_test() { + const testArray = `var testArray = [ + {propA: "A"}, + { + propB: "B", + propC: [ + "D" + ] + }, + [ + {propE: "E"} + ] + ]`; + + const testObject = 'var testObject = {"propA": [{"propB": "B"}]}'; + const testHyphenated = 'var testHyphenated = {"prop-A": "res-A"}'; + const testLet = "let foobar = {a: ''}; const blargh = {a: 1};"; + + let sandbox = Components.utils.Sandbox("http://example.com"); + let dbg = new Debugger; + let dbgObject = dbg.addDebuggee(sandbox); + let dbgEnv = dbgObject.asEnvironment(); + Components.utils.evalInSandbox(testArray, sandbox); + Components.utils.evalInSandbox(testObject, sandbox); + Components.utils.evalInSandbox(testHyphenated, sandbox); + Components.utils.evalInSandbox(testLet, sandbox); + + do_print("Running tests with dbgObject"); + runChecks(dbgObject, null); + + do_print("Running tests with dbgEnv"); + runChecks(null, dbgEnv); + +} + +function runChecks(dbgObject, dbgEnv) { + do_print("Test that suggestions are given for 'this'"); + let results = JSPropertyProvider(dbgObject, dbgEnv, "t"); + test_has_result(results, "this"); + + if (dbgObject != null) { + do_print("Test that suggestions are given for 'this.'"); + results = JSPropertyProvider(dbgObject, dbgEnv, "this."); + test_has_result(results, "testObject"); + + do_print("Test that no suggestions are given for 'this.this'"); + results = JSPropertyProvider(dbgObject, dbgEnv, "this.this"); + test_has_no_results(results); + } + + do_print("Testing lexical scope issues (Bug 1207868)"); + results = JSPropertyProvider(dbgObject, dbgEnv, "foobar"); + test_has_result(results, "foobar"); + + results = JSPropertyProvider(dbgObject, dbgEnv, "foobar."); + test_has_result(results, "a"); + + results = JSPropertyProvider(dbgObject, dbgEnv, "blargh"); + test_has_result(results, "blargh"); + + results = JSPropertyProvider(dbgObject, dbgEnv, "blargh."); + test_has_result(results, "a"); + + do_print("Test that suggestions are given for 'foo[n]' where n is an integer."); + results = JSPropertyProvider(dbgObject, dbgEnv, "testArray[0]."); + test_has_result(results, "propA"); + + do_print("Test that suggestions are given for multidimensional arrays."); + results = JSPropertyProvider(dbgObject, dbgEnv, "testArray[2][0]."); + test_has_result(results, "propE"); + + do_print("Test that suggestions are given for nested arrays."); + results = JSPropertyProvider(dbgObject, dbgEnv, "testArray[1].propC[0]."); + test_has_result(results, "indexOf"); + + do_print("Test that suggestions are given for literal arrays."); + results = JSPropertyProvider(dbgObject, dbgEnv, "[1,2,3]."); + test_has_result(results, "indexOf"); + + do_print("Test that suggestions are given for literal arrays with newlines."); + results = JSPropertyProvider(dbgObject, dbgEnv, "[1,2,3,\n4\n]."); + test_has_result(results, "indexOf"); + + do_print("Test that suggestions are given for literal strings."); + results = JSPropertyProvider(dbgObject, dbgEnv, "'foo'."); + test_has_result(results, "charAt"); + results = JSPropertyProvider(dbgObject, dbgEnv, '"foo".'); + test_has_result(results, "charAt"); + results = JSPropertyProvider(dbgObject, dbgEnv, "`foo`."); + test_has_result(results, "charAt"); + results = JSPropertyProvider(dbgObject, dbgEnv, "'[1,2,3]'."); + test_has_result(results, "charAt"); + + do_print("Test that suggestions are not given for syntax errors."); + results = JSPropertyProvider(dbgObject, dbgEnv, "'foo\""); + do_check_null(results); + results = JSPropertyProvider(dbgObject, dbgEnv, "[1,',2]"); + do_check_null(results); + results = JSPropertyProvider(dbgObject, dbgEnv, "'[1,2]."); + do_check_null(results); + results = JSPropertyProvider(dbgObject, dbgEnv, "'foo'.."); + do_check_null(results); + + do_print("Test that suggestions are not given without a dot."); + results = JSPropertyProvider(dbgObject, dbgEnv, "'foo'"); + test_has_no_results(results); + results = JSPropertyProvider(dbgObject, dbgEnv, "[1,2,3]"); + test_has_no_results(results); + results = JSPropertyProvider(dbgObject, dbgEnv, "[1,2,3].\n'foo'"); + test_has_no_results(results); + + do_print("Test that suggestions are not given for numeric literals."); + results = JSPropertyProvider(dbgObject, dbgEnv, "1."); + do_check_null(results); + + do_print("Test that suggestions are not given for index that's out of bounds."); + results = JSPropertyProvider(dbgObject, dbgEnv, "testArray[10]."); + do_check_null(results); + + do_print("Test that no suggestions are given if an index is not numerical somewhere in the chain."); + results = JSPropertyProvider(dbgObject, dbgEnv, "testArray[0]['propC'][0]."); + do_check_null(results); + + results = JSPropertyProvider(dbgObject, dbgEnv, "testObject['propA'][0]."); + do_check_null(results); + + results = JSPropertyProvider(dbgObject, dbgEnv, "testArray[0]['propC']."); + do_check_null(results); + + results = JSPropertyProvider(dbgObject, dbgEnv, "testArray[][1]."); + do_check_null(results); + + do_print("Test that suggestions are not given if there is an hyphen in the chain."); + results = JSPropertyProvider(dbgObject, dbgEnv, "testHyphenated['prop-A']."); + do_check_null(results); +} + +/** + * A helper that ensures an empty array of results were found. + * @param Object aResults + * The results returned by JSPropertyProvider. + */ +function test_has_no_results(aResults) { + do_check_neq(aResults, null); + do_check_eq(aResults.matches.length, 0); +} +/** + * A helper that ensures (required) results were found. + * @param Object aResults + * The results returned by JSPropertyProvider. + * @param String aRequiredSuggestion + * A suggestion that must be found from the results. + */ +function test_has_result(aResults, aRequiredSuggestion) { + do_check_neq(aResults, null); + do_check_true(aResults.matches.length > 0); + do_check_true(aResults.matches.indexOf(aRequiredSuggestion) !== -1); +} diff --git a/devtools/shared/webconsole/test/unit/test_network_helper.js b/devtools/shared/webconsole/test/unit/test_network_helper.js new file mode 100644 index 000000000..3a43ff432 --- /dev/null +++ b/devtools/shared/webconsole/test/unit/test_network_helper.js @@ -0,0 +1,47 @@ +/* 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", {}); + +Object.defineProperty(this, "NetworkHelper", { + get: function () { + return require("devtools/shared/webconsole/network-helper"); + }, + configurable: true, + writeable: false, + enumerable: true +}); + +function run_test() { + test_isTextMimeType(); +} + +function test_isTextMimeType() { + do_check_eq(NetworkHelper.isTextMimeType("text/plain"), true); + do_check_eq(NetworkHelper.isTextMimeType("application/javascript"), true); + do_check_eq(NetworkHelper.isTextMimeType("application/json"), true); + do_check_eq(NetworkHelper.isTextMimeType("text/css"), true); + do_check_eq(NetworkHelper.isTextMimeType("text/html"), true); + do_check_eq(NetworkHelper.isTextMimeType("image/svg+xml"), true); + do_check_eq(NetworkHelper.isTextMimeType("application/xml"), true); + + // Test custom JSON subtype + do_check_eq(NetworkHelper.isTextMimeType("application/vnd.tent.posts-feed.v0+json"), true); + do_check_eq(NetworkHelper.isTextMimeType("application/vnd.tent.posts-feed.v0-json"), true); + // Test custom XML subtype + do_check_eq(NetworkHelper.isTextMimeType("application/vnd.tent.posts-feed.v0+xml"), true); + do_check_eq(NetworkHelper.isTextMimeType("application/vnd.tent.posts-feed.v0-xml"), false); + // Test case-insensitive + do_check_eq(NetworkHelper.isTextMimeType("application/vnd.BIG-CORP+json"), true); + // Test non-text type + do_check_eq(NetworkHelper.isTextMimeType("image/png"), false); + // Test invalid types + do_check_eq(NetworkHelper.isTextMimeType("application/foo-+json"), false); + do_check_eq(NetworkHelper.isTextMimeType("application/-foo+json"), false); + do_check_eq(NetworkHelper.isTextMimeType("application/foo--bar+json"), false); + + // Test we do not cause internal errors with unoptimized regex. Bug 961097 + do_check_eq(NetworkHelper.isTextMimeType("application/vnd.google.safebrowsing-chunk"), false); +} diff --git a/devtools/shared/webconsole/test/unit/test_security-info-certificate.js b/devtools/shared/webconsole/test/unit/test_security-info-certificate.js new file mode 100644 index 000000000..be223d87e --- /dev/null +++ b/devtools/shared/webconsole/test/unit/test_security-info-certificate.js @@ -0,0 +1,68 @@ +/* vim: set ft=javascript ts=2 et sw=2 tw=80: */ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ +"use strict"; + +// Tests that NetworkHelper.parseCertificateInfo parses certificate information +// correctly. + +const { require } = Components.utils.import("resource://devtools/shared/Loader.jsm", {}); + +Object.defineProperty(this, "NetworkHelper", { + get: function () { + return require("devtools/shared/webconsole/network-helper"); + }, + configurable: true, + writeable: false, + enumerable: true +}); + +var Ci = Components.interfaces; +const DUMMY_CERT = { + commonName: "cn", + organization: "o", + organizationalUnit: "ou", + issuerCommonName: "issuerCN", + issuerOrganization: "issuerO", + issuerOrganizationUnit: "issuerOU", + sha256Fingerprint: "qwertyuiopoiuytrewq", + sha1Fingerprint: "qwertyuiop", + validity: { + notBeforeLocalDay: "yesterday", + notAfterLocalDay: "tomorrow", + } +}; + +function run_test() { + do_print("Testing NetworkHelper.parseCertificateInfo."); + + let result = NetworkHelper.parseCertificateInfo(DUMMY_CERT); + + // Subject + equal(result.subject.commonName, DUMMY_CERT.commonName, + "Common name is correct."); + equal(result.subject.organization, DUMMY_CERT.organization, + "Organization is correct."); + equal(result.subject.organizationUnit, DUMMY_CERT.organizationUnit, + "Organizational unit is correct."); + + // Issuer + equal(result.issuer.commonName, DUMMY_CERT.issuerCommonName, + "Common name of the issuer is correct."); + equal(result.issuer.organization, DUMMY_CERT.issuerOrganization, + "Organization of the issuer is correct."); + equal(result.issuer.organizationUnit, DUMMY_CERT.issuerOrganizationUnit, + "Organizational unit of the issuer is correct."); + + // Validity + equal(result.validity.start, DUMMY_CERT.validity.notBeforeLocalDay, + "Start of the validity period is correct."); + equal(result.validity.end, DUMMY_CERT.validity.notAfterLocalDay, + "End of the validity period is correct."); + + // Fingerprints + equal(result.fingerprint.sha1, DUMMY_CERT.sha1Fingerprint, + "Certificate SHA1 fingerprint is correct."); + equal(result.fingerprint.sha256, DUMMY_CERT.sha256Fingerprint, + "Certificate SHA256 fingerprint is correct."); +} diff --git a/devtools/shared/webconsole/test/unit/test_security-info-parser.js b/devtools/shared/webconsole/test/unit/test_security-info-parser.js new file mode 100644 index 000000000..a5682e209 --- /dev/null +++ b/devtools/shared/webconsole/test/unit/test_security-info-parser.js @@ -0,0 +1,64 @@ +/* vim: set ft=javascript ts=2 et sw=2 tw=80: */ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ +"use strict"; + +// Test that NetworkHelper.parseSecurityInfo returns correctly formatted object. + +const { require } = Components.utils.import("resource://devtools/shared/Loader.jsm", {}); +Components.utils.import("resource://gre/modules/XPCOMUtils.jsm"); + +Object.defineProperty(this, "NetworkHelper", { + get: function () { + return require("devtools/shared/webconsole/network-helper"); + }, + configurable: true, + writeable: false, + enumerable: true +}); + +var Ci = Components.interfaces; +const wpl = Ci.nsIWebProgressListener; +const MockCertificate = { + commonName: "cn", + organization: "o", + organizationalUnit: "ou", + issuerCommonName: "issuerCN", + issuerOrganization: "issuerO", + issuerOrganizationUnit: "issuerOU", + sha256Fingerprint: "qwertyuiopoiuytrewq", + sha1Fingerprint: "qwertyuiop", + validity: { + notBeforeLocalDay: "yesterday", + notAfterLocalDay: "tomorrow", + } +}; + +const MockSecurityInfo = { + QueryInterface: XPCOMUtils.generateQI([Ci.nsITransportSecurityInfo, + Ci.nsISSLStatusProvider]), + securityState: wpl.STATE_IS_SECURE, + errorCode: 0, + SSLStatus: { + cipherSuite: "TLS_ECDH_ECDSA_WITH_AES_128_GCM_SHA256", + protocolVersion: 3, // TLS_VERSION_1_2 + serverCert: MockCertificate, + } +}; + +function run_test() { + let result = NetworkHelper.parseSecurityInfo(MockSecurityInfo, {}); + + equal(result.state, "secure", "State is correct."); + + equal(result.cipherSuite, MockSecurityInfo.cipherSuite, + "Cipher suite is correct."); + + equal(result.protocolVersion, "TLSv1.2", "Protocol version is correct."); + + deepEqual(result.cert, NetworkHelper.parseCertificateInfo(MockCertificate), + "Certificate information is correct."); + + equal(result.hpkp, false, "HPKP is false when URI is not available."); + equal(result.hsts, false, "HSTS is false when URI is not available."); +} diff --git a/devtools/shared/webconsole/test/unit/test_security-info-protocol-version.js b/devtools/shared/webconsole/test/unit/test_security-info-protocol-version.js new file mode 100644 index 000000000..b84002131 --- /dev/null +++ b/devtools/shared/webconsole/test/unit/test_security-info-protocol-version.js @@ -0,0 +1,54 @@ +/* vim: set ft=javascript ts=2 et sw=2 tw=80: */ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ +"use strict"; + +// Tests that NetworkHelper.formatSecurityProtocol returns correct +// protocol version strings. + +const { require } = Components.utils.import("resource://devtools/shared/Loader.jsm", {}); + +Object.defineProperty(this, "NetworkHelper", { + get: function () { + return require("devtools/shared/webconsole/network-helper"); + }, + configurable: true, + writeable: false, + enumerable: true +}); + +var Ci = Components.interfaces; +const TEST_CASES = [ + { + description: "TLS_VERSION_1", + input: 1, + expected: "TLSv1" + }, { + description: "TLS_VERSION_1.1", + input: 2, + expected: "TLSv1.1" + }, { + description: "TLS_VERSION_1.2", + input: 3, + expected: "TLSv1.2" + }, { + description: "TLS_VERSION_1.3", + input: 4, + expected: "TLSv1.3" + }, { + description: "invalid version", + input: -1, + expected: "Unknown" + }, +]; + +function run_test() { + do_print("Testing NetworkHelper.formatSecurityProtocol."); + + for (let {description, input, expected} of TEST_CASES) { + do_print("Testing " + description); + + equal(NetworkHelper.formatSecurityProtocol(input), expected, + "Got the expected protocol string."); + } +} diff --git a/devtools/shared/webconsole/test/unit/test_security-info-state.js b/devtools/shared/webconsole/test/unit/test_security-info-state.js new file mode 100644 index 000000000..efa493a95 --- /dev/null +++ b/devtools/shared/webconsole/test/unit/test_security-info-state.js @@ -0,0 +1,100 @@ +/* vim: set ft=javascript ts=2 et sw=2 tw=80: */ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ +"use strict"; + +// Tests that security info parser gives correct general security state for +// different cases. + +const { require } = Components.utils.import("resource://devtools/shared/Loader.jsm", {}); +Components.utils.import("resource://gre/modules/XPCOMUtils.jsm"); + +Object.defineProperty(this, "NetworkHelper", { + get: function () { + return require("devtools/shared/webconsole/network-helper"); + }, + configurable: true, + writeable: false, + enumerable: true +}); + +var Ci = Components.interfaces; +const wpl = Ci.nsIWebProgressListener; +const MockSecurityInfo = { + QueryInterface: XPCOMUtils.generateQI([Ci.nsITransportSecurityInfo, + Ci.nsISSLStatusProvider]), + securityState: wpl.STATE_IS_BROKEN, + errorCode: 0, + SSLStatus: { + protocolVersion: 3, // nsISSLStatus.TLS_VERSION_1_2 + cipherSuite: "TLS_ECDH_ECDSA_WITH_AES_128_GCM_SHA256", + } +}; + +function run_test() { + test_nullSecurityInfo(); + test_insecureSecurityInfoWithNSSError(); + test_insecureSecurityInfoWithoutNSSError(); + test_brokenSecurityInfo(); + test_secureSecurityInfo(); +} + +/** + * Test that undefined security information is returns "insecure". + */ +function test_nullSecurityInfo() { + let result = NetworkHelper.parseSecurityInfo(null, {}); + equal(result.state, "insecure", + "state == 'insecure' when securityInfo was undefined"); +} + +/** + * Test that STATE_IS_INSECURE with NSSError returns "broken" + */ +function test_insecureSecurityInfoWithNSSError() { + MockSecurityInfo.securityState = wpl.STATE_IS_INSECURE; + + // Taken from security/manager/ssl/tests/unit/head_psm.js. + MockSecurityInfo.errorCode = -8180; + + let result = NetworkHelper.parseSecurityInfo(MockSecurityInfo, {}); + equal(result.state, "broken", + "state == 'broken' if securityState contains STATE_IS_INSECURE flag AND " + + "errorCode is NSS error."); + + MockSecurityInfo.errorCode = 0; +} + +/** + * Test that STATE_IS_INSECURE without NSSError returns "insecure" + */ +function test_insecureSecurityInfoWithoutNSSError() { + MockSecurityInfo.securityState = wpl.STATE_IS_INSECURE; + + let result = NetworkHelper.parseSecurityInfo(MockSecurityInfo, {}); + equal(result.state, "insecure", + "state == 'insecure' if securityState contains STATE_IS_INSECURE flag BUT " + + "errorCode is not NSS error."); +} + +/** + * Test that STATE_IS_SECURE returns "secure" + */ +function test_secureSecurityInfo() { + MockSecurityInfo.securityState = wpl.STATE_IS_SECURE; + + let result = NetworkHelper.parseSecurityInfo(MockSecurityInfo, {}); + equal(result.state, "secure", + "state == 'secure' if securityState contains STATE_IS_SECURE flag"); +} + +/** + * Test that STATE_IS_BROKEN returns "weak" + */ +function test_brokenSecurityInfo() { + MockSecurityInfo.securityState = wpl.STATE_IS_BROKEN; + + let result = NetworkHelper.parseSecurityInfo(MockSecurityInfo, {}); + equal(result.state, "weak", + "state == 'weak' if securityState contains STATE_IS_BROKEN flag"); +} diff --git a/devtools/shared/webconsole/test/unit/test_security-info-static-hpkp.js b/devtools/shared/webconsole/test/unit/test_security-info-static-hpkp.js new file mode 100644 index 000000000..b76fa141a --- /dev/null +++ b/devtools/shared/webconsole/test/unit/test_security-info-static-hpkp.js @@ -0,0 +1,47 @@ +/* vim: set ft=javascript ts=2 et sw=2 tw=80: */ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ +"use strict"; + +// Test that NetworkHelper.parseSecurityInfo correctly detects static hpkp pins + +const { require } = Components.utils.import("resource://devtools/shared/Loader.jsm", {}); +Components.utils.import("resource://gre/modules/XPCOMUtils.jsm"); +const Services = require("Services"); + +Object.defineProperty(this, "NetworkHelper", { + get: function () { + return require("devtools/shared/webconsole/network-helper"); + }, + configurable: true, + writeable: false, + enumerable: true +}); + +var Ci = Components.interfaces; +const wpl = Ci.nsIWebProgressListener; + +const MockSecurityInfo = { + QueryInterface: XPCOMUtils.generateQI([Ci.nsITransportSecurityInfo, + Ci.nsISSLStatusProvider]), + securityState: wpl.STATE_IS_SECURE, + errorCode: 0, + SSLStatus: { + cipherSuite: "TLS_ECDH_ECDSA_WITH_AES_128_GCM_SHA256", + protocolVersion: 3, // TLS_VERSION_1_2 + serverCert: { + validity: {} + }, + } +}; + +const MockHttpInfo = { + hostname: "include-subdomains.pinning.example.com", + private: false, +}; + +function run_test() { + Services.prefs.setIntPref("security.cert_pinning.enforcement_level", 1); + let result = NetworkHelper.parseSecurityInfo(MockSecurityInfo, MockHttpInfo); + equal(result.hpkp, true, "Static HPKP detected."); +} diff --git a/devtools/shared/webconsole/test/unit/test_security-info-weakness-reasons.js b/devtools/shared/webconsole/test/unit/test_security-info-weakness-reasons.js new file mode 100644 index 000000000..f91d8049e --- /dev/null +++ b/devtools/shared/webconsole/test/unit/test_security-info-weakness-reasons.js @@ -0,0 +1,47 @@ +/* vim: set ft=javascript ts=2 et sw=2 tw=80: */ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ +"use strict"; + +// Tests that NetworkHelper.getReasonsForWeakness returns correct reasons for +// weak requests. + +const { require } = Components.utils.import("resource://devtools/shared/Loader.jsm", {}); + +Object.defineProperty(this, "NetworkHelper", { + get: function () { + return require("devtools/shared/webconsole/network-helper"); + }, + configurable: true, + writeable: false, + enumerable: true +}); + +var Ci = Components.interfaces; +const wpl = Ci.nsIWebProgressListener; +const TEST_CASES = [ + { + description: "weak cipher", + input: wpl.STATE_IS_BROKEN | wpl.STATE_USES_WEAK_CRYPTO, + expected: ["cipher"] + }, { + description: "only STATE_IS_BROKEN flag", + input: wpl.STATE_IS_BROKEN, + expected: [] + }, { + description: "only STATE_IS_SECURE flag", + input: wpl.STATE_IS_SECURE, + expected: [] + }, +]; + +function run_test() { + do_print("Testing NetworkHelper.getReasonsForWeakness."); + + for (let {description, input, expected} of TEST_CASES) { + do_print("Testing " + description); + + deepEqual(NetworkHelper.getReasonsForWeakness(input), expected, + "Got the expected reasons for weakness."); + } +} diff --git a/devtools/shared/webconsole/test/unit/test_throttle.js b/devtools/shared/webconsole/test/unit/test_throttle.js new file mode 100644 index 000000000..fa8b26b61 --- /dev/null +++ b/devtools/shared/webconsole/test/unit/test_throttle.js @@ -0,0 +1,140 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +const Cu = Components.utils; +const Cc = Components.classes; +const Ci = Components.interfaces; +const { require } = Cu.import("resource://devtools/shared/Loader.jsm", {}); +const promise = require("promise"); +const { NetworkThrottleManager } = + require("devtools/shared/webconsole/throttle"); +const nsIScriptableInputStream = Ci.nsIScriptableInputStream; + +function TestStreamListener() { + this.state = "initial"; +} +TestStreamListener.prototype = { + onStartRequest: function() { + this.setState("start"); + }, + + onStopRequest: function() { + this.setState("stop"); + }, + + onDataAvailable: function(request, context, inputStream, offset, count) { + const sin = Components.classes["@mozilla.org/scriptableinputstream;1"] + .createInstance(nsIScriptableInputStream); + sin.init(inputStream); + this.data = sin.read(count); + this.setState("data"); + }, + + setState: function(state) { + this.state = state; + if (this._deferred) { + this._deferred.resolve(state); + this._deferred = null; + } + }, + + onStateChanged: function() { + if (!this._deferred) { + this._deferred = promise.defer(); + } + return this._deferred.promise; + } +}; + +function TestChannel() { + this.state = "initial"; + this.testListener = new TestStreamListener(); + this._throttleQueue = null; +} +TestChannel.prototype = { + QueryInterface: function() { + return this; + }, + + get throttleQueue() { + return this._throttleQueue; + }, + + set throttleQueue(q) { + this._throttleQueue = q; + this.state = "throttled"; + }, + + setNewListener: function(listener) { + this.listener = listener; + this.state = "listener"; + return this.testListener; + }, +}; + +add_task(function*() { + let throttler = new NetworkThrottleManager({ + roundTripTimeMean: 1, + roundTripTimeMax: 1, + downloadBPSMean: 500, + downloadBPSMax: 500, + uploadBPSMean: 500, + uploadBPSMax: 500, + }); + + let uploadChannel = new TestChannel(); + throttler.manageUpload(uploadChannel); + equal(uploadChannel.state, "throttled", + "NetworkThrottleManager set throttleQueue"); + + let downloadChannel = new TestChannel(); + let testListener = downloadChannel.testListener; + + let listener = throttler.manage(downloadChannel); + equal(downloadChannel.state, "listener", + "NetworkThrottleManager called setNewListener"); + + equal(testListener.state, "initial", "test listener in initial state"); + + // This method must be passed through immediately. + listener.onStartRequest(null, null); + equal(testListener.state, "start", "test listener started"); + + const TEST_INPUT = "hi bob"; + + let testStream = Cc["@mozilla.org/storagestream;1"] + .createInstance(Ci.nsIStorageStream); + testStream.init(512, 512); + let out = testStream.getOutputStream(0); + out.write(TEST_INPUT, TEST_INPUT.length); + out.close(); + let testInputStream = testStream.newInputStream(0); + + let activityDistributor = + Cc["@mozilla.org/network/http-activity-distributor;1"] + .getService(Ci.nsIHttpActivityDistributor); + let activitySeen = false; + listener.addActivityCallback(() => activitySeen = true, null, null, null, + activityDistributor + .ACTIVITY_SUBTYPE_RESPONSE_COMPLETE, + null, TEST_INPUT.length, null); + + // onDataAvailable is required to immediately read the data. + listener.onDataAvailable(null, null, testInputStream, 0, 6); + equal(testInputStream.available(), 0, "no more data should be available"); + equal(testListener.state, "start", + "test listener should not have received data"); + equal(activitySeen, false, "activity not distributed yet"); + + let newState = yield testListener.onStateChanged(); + equal(newState, "data", "test listener received data"); + equal(testListener.data, TEST_INPUT, "test listener received all the data"); + equal(activitySeen, true, "activity has been distributed"); + + let onChange = testListener.onStateChanged(); + listener.onStopRequest(null, null, null); + newState = yield onChange; + equal(newState, "stop", "onStateChanged reported"); +}); diff --git a/devtools/shared/webconsole/test/unit/xpcshell.ini b/devtools/shared/webconsole/test/unit/xpcshell.ini new file mode 100644 index 000000000..083950834 --- /dev/null +++ b/devtools/shared/webconsole/test/unit/xpcshell.ini @@ -0,0 +1,17 @@ +[DEFAULT] +tags = devtools +head = +tail = +firefox-appdir = browser +skip-if = toolkit == 'android' +support-files = + +[test_js_property_provider.js] +[test_network_helper.js] +[test_security-info-certificate.js] +[test_security-info-parser.js] +[test_security-info-protocol-version.js] +[test_security-info-state.js] +[test_security-info-static-hpkp.js] +[test_security-info-weakness-reasons.js] +[test_throttle.js] |