michael@0: /* Any copyright is dedicated to the Public Domain. michael@0: http://creativecommons.org/publicdomain/zero/1.0/ */ michael@0: "use strict"; michael@0: michael@0: const { classes: Cc, interfaces: Ci, utils: Cu, results: Cr } = Components; michael@0: michael@0: let { Services } = Cu.import("resource://gre/modules/Services.jsm", {}); michael@0: let { Task } = Cu.import("resource://gre/modules/Task.jsm", {}); michael@0: let { Promise: promise } = Cu.import("resource://gre/modules/Promise.jsm", {}); michael@0: let { gDevTools } = Cu.import("resource:///modules/devtools/gDevTools.jsm", {}); michael@0: let { devtools } = Cu.import("resource://gre/modules/devtools/Loader.jsm", {}); michael@0: let { CurlUtils } = Cu.import("resource:///modules/devtools/Curl.jsm", {}); michael@0: let TargetFactory = devtools.TargetFactory; michael@0: let Toolbox = devtools.Toolbox; michael@0: michael@0: const EXAMPLE_URL = "http://example.com/browser/browser/devtools/netmonitor/test/"; michael@0: michael@0: const SIMPLE_URL = EXAMPLE_URL + "html_simple-test-page.html"; michael@0: const NAVIGATE_URL = EXAMPLE_URL + "html_navigate-test-page.html"; michael@0: const CONTENT_TYPE_URL = EXAMPLE_URL + "html_content-type-test-page.html"; michael@0: const CONTENT_TYPE_WITHOUT_CACHE_URL = EXAMPLE_URL + "html_content-type-without-cache-test-page.html"; michael@0: const CYRILLIC_URL = EXAMPLE_URL + "html_cyrillic-test-page.html"; michael@0: const STATUS_CODES_URL = EXAMPLE_URL + "html_status-codes-test-page.html"; michael@0: const POST_DATA_URL = EXAMPLE_URL + "html_post-data-test-page.html"; michael@0: const POST_RAW_URL = EXAMPLE_URL + "html_post-raw-test-page.html"; michael@0: const POST_RAW_WITH_HEADERS_URL = EXAMPLE_URL + "html_post-raw-with-headers-test-page.html"; michael@0: const PARAMS_URL = EXAMPLE_URL + "html_params-test-page.html"; michael@0: const JSONP_URL = EXAMPLE_URL + "html_jsonp-test-page.html"; michael@0: const JSON_LONG_URL = EXAMPLE_URL + "html_json-long-test-page.html"; michael@0: const JSON_MALFORMED_URL = EXAMPLE_URL + "html_json-malformed-test-page.html"; michael@0: const JSON_CUSTOM_MIME_URL = EXAMPLE_URL + "html_json-custom-mime-test-page.html"; michael@0: const JSON_TEXT_MIME_URL = EXAMPLE_URL + "html_json-text-mime-test-page.html"; michael@0: const SORTING_URL = EXAMPLE_URL + "html_sorting-test-page.html"; michael@0: const FILTERING_URL = EXAMPLE_URL + "html_filter-test-page.html"; michael@0: const INFINITE_GET_URL = EXAMPLE_URL + "html_infinite-get-page.html"; michael@0: const CUSTOM_GET_URL = EXAMPLE_URL + "html_custom-get-page.html"; michael@0: const SINGLE_GET_URL = EXAMPLE_URL + "html_single-get-page.html"; michael@0: const STATISTICS_URL = EXAMPLE_URL + "html_statistics-test-page.html"; michael@0: const CURL_URL = EXAMPLE_URL + "html_copy-as-curl.html"; michael@0: const CURL_UTILS_URL = EXAMPLE_URL + "html_curl-utils.html"; michael@0: michael@0: const SIMPLE_SJS = EXAMPLE_URL + "sjs_simple-test-server.sjs"; michael@0: const CONTENT_TYPE_SJS = EXAMPLE_URL + "sjs_content-type-test-server.sjs"; michael@0: const STATUS_CODES_SJS = EXAMPLE_URL + "sjs_status-codes-test-server.sjs"; michael@0: const SORTING_SJS = EXAMPLE_URL + "sjs_sorting-test-server.sjs"; michael@0: michael@0: const TEST_IMAGE = EXAMPLE_URL + "test-image.png"; michael@0: 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=="; michael@0: michael@0: gDevTools.testing = true; michael@0: SimpleTest.registerCleanupFunction(() => { michael@0: gDevTools.testing = false; michael@0: }); michael@0: michael@0: // All tests are asynchronous. michael@0: waitForExplicitFinish(); michael@0: michael@0: const gEnableLogging = Services.prefs.getBoolPref("devtools.debugger.log"); michael@0: // To enable logging for try runs, just set the pref to true. michael@0: Services.prefs.setBoolPref("devtools.debugger.log", false); michael@0: michael@0: // Always reset some prefs to their original values after the test finishes. michael@0: const gDefaultFilters = Services.prefs.getCharPref("devtools.netmonitor.filters"); michael@0: michael@0: registerCleanupFunction(() => { michael@0: info("finish() was called, cleaning up..."); michael@0: michael@0: Services.prefs.setBoolPref("devtools.debugger.log", gEnableLogging); michael@0: Services.prefs.setCharPref("devtools.netmonitor.filters", gDefaultFilters); michael@0: }); michael@0: michael@0: function addTab(aUrl, aWindow) { michael@0: info("Adding tab: " + aUrl); michael@0: michael@0: let deferred = promise.defer(); michael@0: let targetWindow = aWindow || window; michael@0: let targetBrowser = targetWindow.gBrowser; michael@0: michael@0: targetWindow.focus(); michael@0: let tab = targetBrowser.selectedTab = targetBrowser.addTab(aUrl); michael@0: let browser = tab.linkedBrowser; michael@0: michael@0: browser.addEventListener("load", function onLoad() { michael@0: browser.removeEventListener("load", onLoad, true); michael@0: deferred.resolve(tab); michael@0: }, true); michael@0: michael@0: return deferred.promise; michael@0: } michael@0: michael@0: function removeTab(aTab, aWindow) { michael@0: info("Removing tab."); michael@0: michael@0: let targetWindow = aWindow || window; michael@0: let targetBrowser = targetWindow.gBrowser; michael@0: michael@0: targetBrowser.removeTab(aTab); michael@0: } michael@0: michael@0: function initNetMonitor(aUrl, aWindow) { michael@0: info("Initializing a network monitor pane."); michael@0: michael@0: return addTab(aUrl).then((aTab) => { michael@0: info("Net tab added successfully: " + aUrl); michael@0: michael@0: let deferred = promise.defer(); michael@0: let debuggee = aTab.linkedBrowser.contentWindow.wrappedJSObject; michael@0: let target = TargetFactory.forTab(aTab); michael@0: michael@0: gDevTools.showToolbox(target, "netmonitor").then((aToolbox) => { michael@0: info("Netork monitor pane shown successfully."); michael@0: michael@0: let monitor = aToolbox.getCurrentPanel(); michael@0: deferred.resolve([aTab, debuggee, monitor]); michael@0: }); michael@0: michael@0: return deferred.promise; michael@0: }); michael@0: } michael@0: michael@0: function restartNetMonitor(aMonitor, aNewUrl) { michael@0: info("Restarting the specified network monitor."); michael@0: michael@0: let deferred = promise.defer(); michael@0: let tab = aMonitor.target.tab; michael@0: let url = aNewUrl || tab.linkedBrowser.contentWindow.wrappedJSObject.location.href; michael@0: michael@0: aMonitor.once("destroyed", () => initNetMonitor(url).then(deferred.resolve)); michael@0: removeTab(tab); michael@0: michael@0: return deferred.promise; michael@0: } michael@0: michael@0: function teardown(aMonitor) { michael@0: info("Destroying the specified network monitor."); michael@0: michael@0: let deferred = promise.defer(); michael@0: let tab = aMonitor.target.tab; michael@0: michael@0: aMonitor.once("destroyed", () => executeSoon(deferred.resolve)); michael@0: removeTab(tab); michael@0: michael@0: return deferred.promise; michael@0: } michael@0: michael@0: function waitForNetworkEvents(aMonitor, aGetRequests, aPostRequests = 0) { michael@0: let deferred = promise.defer(); michael@0: michael@0: let panel = aMonitor.panelWin; michael@0: let genericEvents = 0; michael@0: let postEvents = 0; michael@0: michael@0: function onGenericEvent() { michael@0: genericEvents++; michael@0: maybeResolve(); michael@0: } michael@0: michael@0: function onPostEvent() { michael@0: postEvents++; michael@0: maybeResolve(); michael@0: } michael@0: michael@0: function maybeResolve() { michael@0: info("> Network events progress: " + michael@0: genericEvents + "/" + ((aGetRequests + aPostRequests) * 13) + ", " + michael@0: postEvents + "/" + (aPostRequests * 2)); michael@0: michael@0: // There are 15 updates which need to be fired for a request to be michael@0: // considered finished. RequestPostData isn't fired for non-POST requests. michael@0: if (genericEvents == (aGetRequests + aPostRequests) * 13 && michael@0: postEvents == aPostRequests * 2) { michael@0: michael@0: panel.off(panel.EVENTS.UPDATING_REQUEST_HEADERS, onGenericEvent); michael@0: panel.off(panel.EVENTS.RECEIVED_REQUEST_HEADERS, onGenericEvent); michael@0: panel.off(panel.EVENTS.UPDATING_REQUEST_COOKIES, onGenericEvent); michael@0: panel.off(panel.EVENTS.RECEIVED_REQUEST_COOKIES, onGenericEvent); michael@0: panel.off(panel.EVENTS.UPDATING_REQUEST_POST_DATA, onPostEvent); michael@0: panel.off(panel.EVENTS.RECEIVED_REQUEST_POST_DATA, onPostEvent); michael@0: panel.off(panel.EVENTS.UPDATING_RESPONSE_HEADERS, onGenericEvent); michael@0: panel.off(panel.EVENTS.RECEIVED_RESPONSE_HEADERS, onGenericEvent); michael@0: panel.off(panel.EVENTS.UPDATING_RESPONSE_COOKIES, onGenericEvent); michael@0: panel.off(panel.EVENTS.RECEIVED_RESPONSE_COOKIES, onGenericEvent); michael@0: panel.off(panel.EVENTS.STARTED_RECEIVING_RESPONSE, onGenericEvent); michael@0: panel.off(panel.EVENTS.UPDATING_RESPONSE_CONTENT, onGenericEvent); michael@0: panel.off(panel.EVENTS.RECEIVED_RESPONSE_CONTENT, onGenericEvent); michael@0: panel.off(panel.EVENTS.UPDATING_EVENT_TIMINGS, onGenericEvent); michael@0: panel.off(panel.EVENTS.RECEIVED_EVENT_TIMINGS, onGenericEvent); michael@0: michael@0: executeSoon(deferred.resolve); michael@0: } michael@0: } michael@0: michael@0: panel.on(panel.EVENTS.UPDATING_REQUEST_HEADERS, onGenericEvent); michael@0: panel.on(panel.EVENTS.RECEIVED_REQUEST_HEADERS, onGenericEvent); michael@0: panel.on(panel.EVENTS.UPDATING_REQUEST_COOKIES, onGenericEvent); michael@0: panel.on(panel.EVENTS.RECEIVED_REQUEST_COOKIES, onGenericEvent); michael@0: panel.on(panel.EVENTS.UPDATING_REQUEST_POST_DATA, onPostEvent); michael@0: panel.on(panel.EVENTS.RECEIVED_REQUEST_POST_DATA, onPostEvent); michael@0: panel.on(panel.EVENTS.UPDATING_RESPONSE_HEADERS, onGenericEvent); michael@0: panel.on(panel.EVENTS.RECEIVED_RESPONSE_HEADERS, onGenericEvent); michael@0: panel.on(panel.EVENTS.UPDATING_RESPONSE_COOKIES, onGenericEvent); michael@0: panel.on(panel.EVENTS.RECEIVED_RESPONSE_COOKIES, onGenericEvent); michael@0: panel.on(panel.EVENTS.STARTED_RECEIVING_RESPONSE, onGenericEvent); michael@0: panel.on(panel.EVENTS.UPDATING_RESPONSE_CONTENT, onGenericEvent); michael@0: panel.on(panel.EVENTS.RECEIVED_RESPONSE_CONTENT, onGenericEvent); michael@0: panel.on(panel.EVENTS.UPDATING_EVENT_TIMINGS, onGenericEvent); michael@0: panel.on(panel.EVENTS.RECEIVED_EVENT_TIMINGS, onGenericEvent); michael@0: michael@0: return deferred.promise; michael@0: } michael@0: michael@0: function verifyRequestItemTarget(aRequestItem, aMethod, aUrl, aData = {}) { michael@0: info("> Verifying: " + aMethod + " " + aUrl + " " + aData.toSource()); michael@0: // This bloats log sizes significantly in automation (bug 992485) michael@0: //info("> Request: " + aRequestItem.attachment.toSource()); michael@0: michael@0: let requestsMenu = aRequestItem.ownerView; michael@0: let widgetIndex = requestsMenu.indexOfItem(aRequestItem); michael@0: let visibleIndex = requestsMenu.visibleItems.indexOf(aRequestItem); michael@0: michael@0: info("Widget index of item: " + widgetIndex); michael@0: info("Visible index of item: " + visibleIndex); michael@0: michael@0: let { fuzzyUrl, status, statusText, type, fullMimeType, size, time } = aData; michael@0: let { attachment, target } = aRequestItem michael@0: michael@0: let uri = Services.io.newURI(aUrl, null, null).QueryInterface(Ci.nsIURL); michael@0: let name = uri.fileName || "/"; michael@0: let query = uri.query; michael@0: let hostPort = uri.hostPort; michael@0: michael@0: if (fuzzyUrl) { michael@0: ok(attachment.method.startsWith(aMethod), "The attached method is incorrect."); michael@0: ok(attachment.url.startsWith(aUrl), "The attached url is incorrect."); michael@0: } else { michael@0: is(attachment.method, aMethod, "The attached method is incorrect."); michael@0: is(attachment.url, aUrl, "The attached url is incorrect."); michael@0: } michael@0: michael@0: is(target.querySelector(".requests-menu-method").getAttribute("value"), michael@0: aMethod, "The displayed method is incorrect."); michael@0: michael@0: if (fuzzyUrl) { michael@0: ok(target.querySelector(".requests-menu-file").getAttribute("value").startsWith( michael@0: name + (query ? "?" + query : "")), "The displayed file is incorrect."); michael@0: ok(target.querySelector(".requests-menu-file").getAttribute("tooltiptext").startsWith( michael@0: name + (query ? "?" + query : "")), "The tooltip file is incorrect."); michael@0: } else { michael@0: is(target.querySelector(".requests-menu-file").getAttribute("value"), michael@0: name + (query ? "?" + query : ""), "The displayed file is incorrect."); michael@0: is(target.querySelector(".requests-menu-file").getAttribute("tooltiptext"), michael@0: name + (query ? "?" + query : ""), "The tooltip file is incorrect."); michael@0: } michael@0: michael@0: is(target.querySelector(".requests-menu-domain").getAttribute("value"), michael@0: hostPort, "The displayed domain is incorrect."); michael@0: is(target.querySelector(".requests-menu-domain").getAttribute("tooltiptext"), michael@0: hostPort, "The tooltip domain is incorrect."); michael@0: michael@0: if (status !== undefined) { michael@0: let value = target.querySelector(".requests-menu-status").getAttribute("code"); michael@0: let codeValue = target.querySelector(".requests-menu-status-code").getAttribute("value"); michael@0: let tooltip = target.querySelector(".requests-menu-status-and-method").getAttribute("tooltiptext"); michael@0: info("Displayed status: " + value); michael@0: info("Displayed code: " + codeValue); michael@0: info("Tooltip status: " + tooltip); michael@0: is(value, status, "The displayed status is incorrect."); michael@0: is(codeValue, status, "The displayed status code is incorrect."); michael@0: is(tooltip, status + " " + statusText, "The tooltip status is incorrect."); michael@0: } michael@0: if (type !== undefined) { michael@0: let value = target.querySelector(".requests-menu-type").getAttribute("value"); michael@0: let tooltip = target.querySelector(".requests-menu-type").getAttribute("tooltiptext"); michael@0: info("Displayed type: " + value); michael@0: info("Tooltip type: " + tooltip); michael@0: is(value, type, "The displayed type is incorrect."); michael@0: is(tooltip, fullMimeType, "The tooltip type is incorrect."); michael@0: } michael@0: if (size !== undefined) { michael@0: let value = target.querySelector(".requests-menu-size").getAttribute("value"); michael@0: let tooltip = target.querySelector(".requests-menu-size").getAttribute("tooltiptext"); michael@0: info("Displayed size: " + value); michael@0: info("Tooltip size: " + tooltip); michael@0: is(value, size, "The displayed size is incorrect."); michael@0: is(tooltip, size, "The tooltip size is incorrect."); michael@0: } michael@0: if (time !== undefined) { michael@0: let value = target.querySelector(".requests-menu-timings-total").getAttribute("value"); michael@0: let tooltip = target.querySelector(".requests-menu-timings-total").getAttribute("tooltiptext"); michael@0: info("Displayed time: " + value); michael@0: info("Tooltip time: " + tooltip); michael@0: ok(~~(value.match(/[0-9]+/)) >= 0, "The displayed time is incorrect."); michael@0: ok(~~(tooltip.match(/[0-9]+/)) >= 0, "The tooltip time is incorrect."); michael@0: } michael@0: michael@0: if (visibleIndex != -1) { michael@0: if (visibleIndex % 2 == 0) { michael@0: ok(aRequestItem.target.hasAttribute("even"), michael@0: "Unexpected 'even' attribute for " + aRequestItem.value); michael@0: ok(!aRequestItem.target.hasAttribute("odd"), michael@0: "Unexpected 'odd' attribute for " + aRequestItem.value); michael@0: } else { michael@0: ok(!aRequestItem.target.hasAttribute("even"), michael@0: "Unexpected 'even' attribute for " + aRequestItem.value); michael@0: ok(aRequestItem.target.hasAttribute("odd"), michael@0: "Unexpected 'odd' attribute for " + aRequestItem.value); michael@0: } michael@0: } michael@0: } michael@0: michael@0: /** michael@0: * Helper function for waiting for an event to fire before resolving a promise. michael@0: * Example: waitFor(aMonitor.panelWin, aMonitor.panelWin.EVENTS.TAB_UPDATED); michael@0: * michael@0: * @param object subject michael@0: * The event emitter object that is being listened to. michael@0: * @param string eventName michael@0: * The name of the event to listen to. michael@0: * @return object michael@0: * Returns a promise that resolves upon firing of the event. michael@0: */ michael@0: function waitFor (subject, eventName) { michael@0: let deferred = promise.defer(); michael@0: subject.once(eventName, deferred.resolve); michael@0: return deferred.promise; michael@0: } michael@0: michael@0: /** michael@0: * Tests if a button for a filter of given type is the only one checked. michael@0: * michael@0: * @param string aFilterType michael@0: * The type of the filter that should be the only one checked. michael@0: */ michael@0: function testFilterButtons(aMonitor, aFilterType) { michael@0: let doc = aMonitor.panelWin.document; michael@0: let target = doc.querySelector("#requests-menu-filter-" + aFilterType + "-button"); michael@0: let buttons = doc.querySelectorAll(".requests-menu-footer-button"); michael@0: michael@0: // Only target should be checked. michael@0: let checkStatus = [(button == target) ? 1 : 0 for (button of buttons)] michael@0: testFilterButtonsCustom(aMonitor, checkStatus); michael@0: } michael@0: michael@0: /** michael@0: * Tests if filter buttons have 'checked' attributes set correctly. michael@0: * michael@0: * @param array aIsChecked michael@0: * An array specifying if a button at given index should have a michael@0: * 'checked' attribute. For example, if the third item of the array michael@0: * evaluates to true, the third button should be checked. michael@0: */ michael@0: function testFilterButtonsCustom(aMonitor, aIsChecked) { michael@0: let doc = aMonitor.panelWin.document; michael@0: let buttons = doc.querySelectorAll(".requests-menu-footer-button"); michael@0: for (let i = 0; i < aIsChecked.length; i++) { michael@0: let button = buttons[i]; michael@0: if (aIsChecked[i]) { michael@0: is(button.hasAttribute("checked"), true, michael@0: "The " + button.id + " button should have a 'checked' attribute."); michael@0: } else { michael@0: is(button.hasAttribute("checked"), false, michael@0: "The " + button.id + " button should not have a 'checked' attribute."); michael@0: } michael@0: } michael@0: }