# HG changeset patch # User Jarda Snajdr Bug 1134073 - Part 3: Show network request cause and stacktrace in netmonitor - mochitests r=Honza diff --git a/devtools/client/netmonitor/test/browser.ini b/devtools/client/netmonitor/test/browser.ini index 8137f47..b8e4934 100644 --- a/devtools/client/netmonitor/test/browser.ini +++ b/devtools/client/netmonitor/test/browser.ini @@ -1,14 +1,15 @@ [DEFAULT] tags = devtools subsuite = devtools support-files = dropmarker.svg head.js + html_cause-test-page.html html_content-type-test-page.html html_content-type-without-cache-test-page.html html_cors-test-page.html html_custom-get-page.html html_single-get-page.html html_cyrillic-test-page.html html_filter-test-page.html html_infinite-get-page.html @@ -28,30 +29,34 @@ support-files = html_statistics-test-page.html html_status-codes-test-page.html html_api-calls-test-page.html html_copy-as-curl.html html_curl-utils.html sjs_content-type-test-server.sjs sjs_cors-test-server.sjs sjs_https-redirect-test-server.sjs + sjs_hsts-test-server.sjs sjs_simple-test-server.sjs sjs_sorting-test-server.sjs sjs_status-codes-test-server.sjs test-image.png service-workers/status-codes.html service-workers/status-codes-service-worker.js [browser_net_aaa_leaktest.js] [browser_net_accessibility-01.js] [browser_net_accessibility-02.js] skip-if = (toolkit == "cocoa" && e10s) # bug 1252254 [browser_net_api-calls.js] [browser_net_autoscroll.js] [browser_net_cached-status.js] +[browser_net_cause.js] +[browser_net_cause_redirect.js] +[browser_net_cause_service_worker.js] [browser_net_service-worker-status.js] [browser_net_charts-01.js] [browser_net_charts-02.js] [browser_net_charts-03.js] [browser_net_charts-04.js] [browser_net_charts-05.js] [browser_net_charts-06.js] [browser_net_charts-07.js] diff --git a/devtools/client/netmonitor/test/browser_net_cause.js b/devtools/client/netmonitor/test/browser_net_cause.js new file mode 100644 index 0000000..e911f66 --- /dev/null +++ b/devtools/client/netmonitor/test/browser_net_cause.js @@ -0,0 +1,58 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +/** + * Tests if request cause is reported correctly. + */ + +var test = Task.async(function* () { + const EXPECTED_REQUESTS = [ + // The document load is from JS function in e10s, native in non-e10s + [ "GET", CAUSE_URL, "document", "", gMultiProcessBrowser ? false : true ], + [ "GET", EXAMPLE_URL + "stylesheet_request", "stylesheet", CAUSE_URL, false ], + [ "GET", EXAMPLE_URL + "img_request", "img", CAUSE_URL, false ], + [ "GET", EXAMPLE_URL + "xhr_request", "xhr", CAUSE_URL, true ], + [ "POST", EXAMPLE_URL + "beacon_request", "beacon", CAUSE_URL, true ], + ]; + + // the initNetMonitor function clears the network request list after the + // page is loaded. That's why we first load a bogus page from SIMPLE_URL, + // and only then load the real thing from CAUSE_URL - we want to catch + // all the requests the page is making, not only the XHRs. + // We can't use about:blank here, because initNetMonitor checks that the + // page has actually made at least one request. + let [, debuggee, monitor] = yield initNetMonitor(SIMPLE_URL); + let { RequestsMenu } = monitor.panelWin.NetMonitorView; + RequestsMenu.lazyUpdate = false; + + debuggee.location = CAUSE_URL; + + yield waitForNetworkEvents(monitor, EXPECTED_REQUESTS.length); + + is(RequestsMenu.itemCount, EXPECTED_REQUESTS.length, + "All the page events should be recorded."); + + EXPECTED_REQUESTS.forEach((spec, i) => { + let [ method, url, causeType, causeUri, hasStack ] = spec; + + let requestItem = RequestsMenu.getItemAtIndex(i); + verifyRequestItemTarget(requestItem, + method, url, { cause: { type: causeType, loadingDocumentUri: causeUri } } + ); + + let { stacktrace } = requestItem.attachment.cause; + let stackLen = (stacktrace || []).length; + if (hasStack) { + ok(stacktrace, `Request #${i} has a stacktrace`); + ok(stackLen > 0, + `Request #${i} (${causeType}) has a stacktrace with ${stackLen} items`); + } else { + is(stackLen, 0, `Request #${i} (${causeType}) has an empty stacktrace`); + } + }); + + yield teardown(monitor); + finish(); +}); diff --git a/devtools/client/netmonitor/test/browser_net_cause_redirect.js b/devtools/client/netmonitor/test/browser_net_cause_redirect.js new file mode 100644 index 0000000..6b5bd4d --- /dev/null +++ b/devtools/client/netmonitor/test/browser_net_cause_redirect.js @@ -0,0 +1,50 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +/** + * Tests if request JS stack is property reported if the request is internally + * redirected without hitting the network (HSTS is one of such cases) + */ + +var test = Task.async(function* () { + const EXPECTED_REQUESTS = [ + // Request to HTTP URL, redirects to HTTPS, has callstack + { status: 302, hasStack: true }, + // Serves HTTPS, sets the Strict-Transport-Security header, no stack + { status: 200, hasStack: false }, + // Second request to HTTP redirects to HTTPS internally + { status: 200, hasStack: true }, + ]; + + let [, debuggee, monitor] = yield initNetMonitor(CUSTOM_GET_URL); + let { RequestsMenu } = monitor.panelWin.NetMonitorView; + RequestsMenu.lazyUpdate = false; + + debuggee.performRequests(2, HSTS_SJS); + yield waitForNetworkEvents(monitor, EXPECTED_REQUESTS.length); + + EXPECTED_REQUESTS.forEach(({status, hasStack}, i) => { + let { attachment } = RequestsMenu.getItemAtIndex(i); + + is(attachment.status, status, `Request #${i} has the expected status`); + + let { stacktrace } = attachment.cause; + + if (hasStack) { + ok(stacktrace, `Request #${i} has a stacktrace`); + let stackLen = (stacktrace || []).length; + ok(stackLen > 0, `Request #${i} has a stacktrace with ${stackLen} items`); + } else { + is((stacktrace || []).length, 0, `Request #${i} has an empty stacktrace`); + } + }); + + // Send a request to reset the HSTS policy to state before the test + debuggee.performRequests(1, HSTS_SJS + "?reset"); + yield waitForNetworkEvents(monitor, 1); + + yield teardown(monitor); + finish(); +}); diff --git a/devtools/client/netmonitor/test/browser_net_cause_service_worker.js b/devtools/client/netmonitor/test/browser_net_cause_service_worker.js new file mode 100644 index 0000000..e7be106 --- /dev/null +++ b/devtools/client/netmonitor/test/browser_net_cause_service_worker.js @@ -0,0 +1,63 @@ +/* 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 if requests intercepted by service workers have the correct stacktrace + */ + +// Service workers only work on https +const URL = EXAMPLE_URL.replace("http:", "https:"); +const TEST_URL = URL + "service-workers/status-codes.html"; + +var test = Task.async(function* () { + let [, debuggee, monitor] = yield initNetMonitor(TEST_URL, null, true); + loadCommonFrameScript(); + + info("Starting test... "); + + let { RequestsMenu } = monitor.panelWin. NetMonitorView; + + const EXPECTED_REQUESTS = [ + { + url: URL + "service-workers/test/200", + status: 200, + fromServiceWorker: true, + hasStack: true + } + ]; + + info("Registering the service worker..."); + yield debuggee.registerServiceWorker(); + + info("Performing requests..."); + debuggee.performXHRRequests(); + + yield waitForNetworkEvents(monitor, EXPECTED_REQUESTS.length); + + EXPECTED_REQUESTS.forEach((req, i) => { + let { attachment } = RequestsMenu.getItemAtIndex(i); + + is(attachment.url, req.url, `Request #${i} has the expected URL`); + is(attachment.status, req.status, `Request #${i} has the expected status`); + is(attachment.fromServiceWorker, req.fromServiceWorker, + `Request #${i} has the expected fromServiceWorker`); + + let { stacktrace } = attachment.cause; + + if (req.hasStack) { + ok(stacktrace, `Request #${i} has a stacktrace`); + let stackLen = (stacktrace || []).length; + ok(stackLen > 0, `Request #${i} has a stacktrace with ${stackLen} items`); + } else { + is((stacktrace || []).length, 0, `Request #${i} has an empty stacktrace`); + } + }); + + info("Unregistering the service worker..."); + yield debuggee.unregisterServiceWorker(); + + yield teardown(monitor); + finish(); +}); diff --git a/devtools/client/netmonitor/test/browser_net_image-tooltip.js b/devtools/client/netmonitor/test/browser_net_image-tooltip.js index f526cfb..c555710 100644 --- a/devtools/client/netmonitor/test/browser_net_image-tooltip.js +++ b/devtools/client/netmonitor/test/browser_net_image-tooltip.js @@ -18,42 +18,39 @@ add_task(function* test() { let onEvents = waitForNetworkEvents(monitor, 7); let onThumbnail = waitFor(monitor.panelWin, EVENTS.RESPONSE_IMAGE_THUMBNAIL_DISPLAYED); debuggee.performRequests(); yield onEvents; yield onThumbnail; info("Checking the image thumbnail after a few requests were made..."); - yield showTooltipAndVerify(RequestsMenu.items[5]); + yield showTooltipAndVerify(RequestsMenu.tooltip, RequestsMenu.items[5]); // 7 XHRs as before + 1 extra document reload onEvents = waitForNetworkEvents(monitor, 8); onThumbnail = waitFor(monitor.panelWin, EVENTS.RESPONSE_IMAGE_THUMBNAIL_DISPLAYED); info("Reloading the debuggee and performing all requests again..."); yield NetMonitorController.triggerActivity(ACTIVITY_TYPE.RELOAD.WITH_CACHE_ENABLED); debuggee.performRequests(); yield onEvents; yield onThumbnail; info("Checking the image thumbnail after a reload."); - yield showTooltipAndVerify(RequestsMenu.items[6]); + yield showTooltipAndVerify(RequestsMenu.tooltip, RequestsMenu.items[6]); yield teardown(monitor); finish(); /** * Show a tooltip on the {requestItem} and verify that it was displayed * with the expected content. */ - function* showTooltipAndVerify(requestItem) { - let { tooltip } = requestItem.attachment; - ok(tooltip, "There should be a tooltip instance for the image request."); - + function* showTooltipAndVerify(tooltip, requestItem) { let anchor = $(".requests-menu-file", requestItem.target); yield showTooltipOn(tooltip, anchor); info("Tooltip was successfully opened for the image request."); is(tooltip.content.querySelector("image").src, TEST_IMAGE_DATA_URI, "The tooltip's image content is displayed correctly."); } diff --git a/devtools/client/netmonitor/test/browser_net_service-worker-status.js b/devtools/client/netmonitor/test/browser_net_service-worker-status.js index 85f16f7..bbcc0ab 100644 --- a/devtools/client/netmonitor/test/browser_net_service-worker-status.js +++ b/devtools/client/netmonitor/test/browser_net_service-worker-status.js @@ -45,11 +45,14 @@ var test = Task.async(function* () { let item = RequestsMenu.getItemAtIndex(index); info("Verifying request #" + index); yield verifyRequestItemTarget(item, request.method, request.uri, request.details); index++; } + info("Unregistering the service worker..."); + yield debuggee.unregisterServiceWorker(); + yield teardown(monitor); finish(); }); diff --git a/devtools/client/netmonitor/test/head.js b/devtools/client/netmonitor/test/head.js index 75a1630..28b12b4 100644 --- a/devtools/client/netmonitor/test/head.js +++ b/devtools/client/netmonitor/test/head.js @@ -38,23 +38,28 @@ const FILTERING_URL = EXAMPLE_URL + "html_filter-test-page.html"; const INFINITE_GET_URL = EXAMPLE_URL + "html_infinite-get-page.html"; const CUSTOM_GET_URL = EXAMPLE_URL + "html_custom-get-page.html"; const SINGLE_GET_URL = EXAMPLE_URL + "html_single-get-page.html"; const STATISTICS_URL = EXAMPLE_URL + "html_statistics-test-page.html"; const CURL_URL = EXAMPLE_URL + "html_copy-as-curl.html"; const CURL_UTILS_URL = EXAMPLE_URL + "html_curl-utils.html"; const SEND_BEACON_URL = EXAMPLE_URL + "html_send-beacon.html"; const CORS_URL = EXAMPLE_URL + "html_cors-test-page.html"; +const CAUSE_URL = EXAMPLE_URL + "html_cause-test-page.html"; const SIMPLE_SJS = EXAMPLE_URL + "sjs_simple-test-server.sjs"; const CONTENT_TYPE_SJS = EXAMPLE_URL + "sjs_content-type-test-server.sjs"; const STATUS_CODES_SJS = EXAMPLE_URL + "sjs_status-codes-test-server.sjs"; const SORTING_SJS = EXAMPLE_URL + "sjs_sorting-test-server.sjs"; const HTTPS_REDIRECT_SJS = EXAMPLE_URL + "sjs_https-redirect-test-server.sjs"; const CORS_SJS_PATH = "/browser/devtools/client/netmonitor/test/sjs_cors-test-server.sjs"; +const HSTS_SJS = EXAMPLE_URL + "sjs_hsts-test-server.sjs"; + +const HSTS_BASE_URL = EXAMPLE_URL; +const HSTS_PAGE_URL = CUSTOM_GET_URL; const TEST_IMAGE = EXAMPLE_URL + "test-image.png"; const TEST_IMAGE_DATA_URI = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABGdBTUEAAK/INwWK6QAAABl0RVh0U29mdHdhcmUAQWRvYmUgSW1hZ2VSZWFkeXHJZTwAAAHWSURBVHjaYvz//z8DJQAggJiQOe/fv2fv7Oz8rays/N+VkfG/iYnJfyD/1+rVq7ffu3dPFpsBAAHEAHIBCJ85c8bN2Nj4vwsDw/8zQLwKiO8CcRoQu0DxqlWrdsHUwzBAAIGJmTNnPgYa9j8UqhFElwPxf2MIDeIrKSn9FwSJoRkAEEAM0DD4DzMAyPi/G+QKY4hh5WAXGf8PDQ0FGwJ22d27CjADAAIIrLmjo+MXA9R2kAHvGBA2wwx6B8W7od6CeQcggKCmCEL8bgwxYCbUIGTDVkHDBia+CuotgACCueD3TDQN75D4xmAvCoK9ARMHBzAw0AECiBHkAlC0Mdy7x9ABNA3obAZXIAa6iKEcGlMVQHwWyjYuL2d4v2cPg8vZswx7gHyAAAK7AOif7SAbOqCmn4Ha3AHFsIDtgPq/vLz8P4MSkJ2W9h8ggBjevXvHDo4FQUQg/kdypqCg4H8lUIACnQ/SOBMYI8bAsAJFPcj1AAEEjwVQqLpAbXmH5BJjqI0gi9DTAAgDBBCcAVLkgmQ7yKCZxpCQxqUZhAECCJ4XgMl493ug21ZD+aDAXH0WLM4A9MZPXJkJIIAwTAR5pQMalaCABQUULttBGCCAGCnNzgABBgAMJ5THwGvJLAAAAABJRU5ErkJggg=="; const FRAME_SCRIPT_UTILS_URL = "chrome://devtools/content/shared/frame-script-utils.js"; DevToolsUtils.testing = true; SimpleTest.registerCleanupFunction(() => { @@ -279,17 +284,17 @@ function verifyRequestItemTarget(aRequestItem, aMethod, aUrl, aData = {}) { let requestsMenu = aRequestItem.ownerView; let widgetIndex = requestsMenu.indexOfItem(aRequestItem); let visibleIndex = requestsMenu.visibleItems.indexOf(aRequestItem); info("Widget index of item: " + widgetIndex); info("Visible index of item: " + visibleIndex); - let { fuzzyUrl, status, statusText, type, fullMimeType, + let { fuzzyUrl, status, statusText, cause, type, fullMimeType, transferred, size, time, displayedStatus } = aData; let { attachment, target } = aRequestItem; let uri = Services.io.newURI(aUrl, null, null).QueryInterface(Ci.nsIURL); let unicodeUrl = NetworkHelper.convertToUnicode(unescape(aUrl)); let name = NetworkHelper.convertToUnicode(unescape(uri.fileName || uri.filePath || "/")); let query = NetworkHelper.convertToUnicode(unescape(uri.query)); let hostPort = uri.hostPort; @@ -331,16 +336,24 @@ function verifyRequestItemTarget(aRequestItem, aMethod, aUrl, aData = {}) { let tooltip = target.querySelector(".requests-menu-status").getAttribute("tooltiptext"); info("Displayed status: " + value); info("Displayed code: " + codeValue); info("Tooltip status: " + tooltip); is(value, displayedStatus ? displayedStatus : status, "The displayed status is correct."); is(codeValue, status, "The displayed status code is correct."); is(tooltip, status + " " + statusText, "The tooltip status is correct."); } + if (cause !== undefined) { + let value = target.querySelector(".requests-menu-cause").getAttribute("value"); + let tooltip = target.querySelector(".requests-menu-cause").getAttribute("tooltiptext"); + info("Displayed cause: " + value); + info("Tooltip cause: " + tooltip); + is(value, cause.type, "The displayed cause is correct."); + is(tooltip, cause.loadingDocumentUri, "The tooltip cause is correct.") + } if (type !== undefined) { let value = target.querySelector(".requests-menu-type").getAttribute("value"); let tooltip = target.querySelector(".requests-menu-type").getAttribute("tooltiptext"); info("Displayed type: " + value); info("Tooltip type: " + tooltip); is(value, type, "The displayed type is correct."); is(tooltip, fullMimeType, "The tooltip type is correct."); } diff --git a/devtools/client/netmonitor/test/html_cause-test-page.html b/devtools/client/netmonitor/test/html_cause-test-page.html new file mode 100644 index 0000000..40a6f40 --- /dev/null +++ b/devtools/client/netmonitor/test/html_cause-test-page.html @@ -0,0 +1,33 @@ + + + + + + + + + + Network Monitor test page + + + + +

Request cause test

+ + + + diff --git a/devtools/client/netmonitor/test/service-workers/status-codes-service-worker.js b/devtools/client/netmonitor/test/service-workers/status-codes-service-worker.js index 6d43875..3c70c7d 100644 --- a/devtools/client/netmonitor/test/service-workers/status-codes-service-worker.js +++ b/devtools/client/netmonitor/test/service-workers/status-codes-service-worker.js @@ -1,8 +1,15 @@ /* 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/ */ -addEventListener("fetch", function (event) { +"use strict"; + +self.addEventListener("activate", event => { + // start controlling the already loaded page + event.waitUntil(self.clients.claim()); +}); + +self.addEventListener("fetch", event => { let response = new Response("Service worker response"); event.respondWith(response); }); diff --git a/devtools/client/netmonitor/test/service-workers/status-codes.html b/devtools/client/netmonitor/test/service-workers/status-codes.html index 1cef820..4e8f633 100644 --- a/devtools/client/netmonitor/test/service-workers/status-codes.html +++ b/devtools/client/netmonitor/test/service-workers/status-codes.html @@ -19,21 +19,54 @@ return new Promise(done => { let iframe = document.createElement("iframe"); iframe.setAttribute("src", url); document.documentElement.appendChild(iframe); iframe.contentWindow.onload = done; }); } + let swRegistration; + function registerServiceWorker() { - return navigator.serviceWorker.register("status-codes-service-worker.js") - .then(() => navigator.serviceWorker.ready); + let sw = navigator.serviceWorker; + return sw.register("status-codes-service-worker.js") + .then(registration => { + swRegistration = registration; + console.log("Registered, scope is:", registration.scope); + return sw.ready; + }).then(() => { + // wait until the page is controlled + return new Promise(resolve => { + if (sw.controller) { + resolve(); + } else { + sw.addEventListener('controllerchange', function onControllerChange() { + sw.removeEventListener('controllerchange', onControllerChange); + resolve(); + }); + } + }); + }).catch(err => { + console.error("Registration failed"); + }); + } + + function unregisterServiceWorker() { + return swRegistration.unregister(); } function performRequests() { return get("test/200"); } + function performXHRRequests() { + return new Promise(done => { + let xhr = new XMLHttpRequest(); + xhr.open("GET", "test/200", true); + xhr.onreadystatechange = done; + xhr.send(null); + }); + } diff --git a/devtools/client/netmonitor/test/sjs_hsts-test-server.sjs b/devtools/client/netmonitor/test/sjs_hsts-test-server.sjs new file mode 100644 index 0000000..c571588 --- /dev/null +++ b/devtools/client/netmonitor/test/sjs_hsts-test-server.sjs @@ -0,0 +1,22 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +function handleRequest(request, response) { + response.setHeader("Cache-Control", "no-cache, no-store, must-revalidate"); + response.setHeader("Pragma", "no-cache"); + response.setHeader("Expires", "0"); + + if (request.queryString === "reset") { + // Reset the HSTS policy, prevent influencing other tests + response.setStatusLine(request.httpVersion, 200, "OK"); + response.setHeader("Strict-Transport-Security", "max-age=0"); + response.write("Resetting HSTS"); + } else if (request.scheme === "http") { + response.setStatusLine(request.httpVersion, 302, "Found"); + response.setHeader("Location", "https://" + request.host + request.path); + } else { + response.setStatusLine(request.httpVersion, 200, "OK"); + response.setHeader("Strict-Transport-Security", "max-age=100"); + response.write("Page was accessed over HTTPS!"); + } +}