summaryrefslogtreecommitdiffstats
path: root/devtools/client/netmonitor/test
diff options
context:
space:
mode:
Diffstat (limited to 'devtools/client/netmonitor/test')
-rw-r--r--devtools/client/netmonitor/test/.eslintrc.js6
-rw-r--r--devtools/client/netmonitor/test/browser.ini156
-rw-r--r--devtools/client/netmonitor/test/browser_net_aaa_leaktest.js28
-rw-r--r--devtools/client/netmonitor/test/browser_net_accessibility-01.js87
-rw-r--r--devtools/client/netmonitor/test/browser_net_accessibility-02.js130
-rw-r--r--devtools/client/netmonitor/test/browser_net_api-calls.js39
-rw-r--r--devtools/client/netmonitor/test/browser_net_autoscroll.js75
-rw-r--r--devtools/client/netmonitor/test/browser_net_brotli.js91
-rw-r--r--devtools/client/netmonitor/test/browser_net_cached-status.js111
-rw-r--r--devtools/client/netmonitor/test/browser_net_cause.js147
-rw-r--r--devtools/client/netmonitor/test/browser_net_cause_redirect.js57
-rw-r--r--devtools/client/netmonitor/test/browser_net_charts-01.js73
-rw-r--r--devtools/client/netmonitor/test/browser_net_charts-02.js49
-rw-r--r--devtools/client/netmonitor/test/browser_net_charts-03.js106
-rw-r--r--devtools/client/netmonitor/test/browser_net_charts-04.js75
-rw-r--r--devtools/client/netmonitor/test/browser_net_charts-05.js61
-rw-r--r--devtools/client/netmonitor/test/browser_net_charts-06.js47
-rw-r--r--devtools/client/netmonitor/test/browser_net_charts-07.js63
-rw-r--r--devtools/client/netmonitor/test/browser_net_clear.js77
-rw-r--r--devtools/client/netmonitor/test/browser_net_complex-params.js195
-rw-r--r--devtools/client/netmonitor/test/browser_net_content-type.js255
-rw-r--r--devtools/client/netmonitor/test/browser_net_copy_as_curl.js88
-rw-r--r--devtools/client/netmonitor/test/browser_net_copy_headers.js72
-rw-r--r--devtools/client/netmonitor/test/browser_net_copy_image_as_data_uri.js35
-rw-r--r--devtools/client/netmonitor/test/browser_net_copy_params.js98
-rw-r--r--devtools/client/netmonitor/test/browser_net_copy_response.js35
-rw-r--r--devtools/client/netmonitor/test/browser_net_copy_svg_image_as_data_uri.js37
-rw-r--r--devtools/client/netmonitor/test/browser_net_copy_url.js31
-rw-r--r--devtools/client/netmonitor/test/browser_net_cors_requests.js33
-rw-r--r--devtools/client/netmonitor/test/browser_net_curl-utils.js228
-rw-r--r--devtools/client/netmonitor/test/browser_net_cyrillic-01.js45
-rw-r--r--devtools/client/netmonitor/test/browser_net_cyrillic-02.js44
-rw-r--r--devtools/client/netmonitor/test/browser_net_details-no-duplicated-content.js172
-rw-r--r--devtools/client/netmonitor/test/browser_net_filter-01.js264
-rw-r--r--devtools/client/netmonitor/test/browser_net_filter-02.js200
-rw-r--r--devtools/client/netmonitor/test/browser_net_filter-03.js185
-rw-r--r--devtools/client/netmonitor/test/browser_net_filter-04.js63
-rw-r--r--devtools/client/netmonitor/test/browser_net_footer-summary.js75
-rw-r--r--devtools/client/netmonitor/test/browser_net_frame.js221
-rw-r--r--devtools/client/netmonitor/test/browser_net_html-preview.js62
-rw-r--r--devtools/client/netmonitor/test/browser_net_icon-preview.js71
-rw-r--r--devtools/client/netmonitor/test/browser_net_image-tooltip.js101
-rw-r--r--devtools/client/netmonitor/test/browser_net_json-long.js98
-rw-r--r--devtools/client/netmonitor/test/browser_net_json-malformed.js77
-rw-r--r--devtools/client/netmonitor/test/browser_net_json_custom_mime.js90
-rw-r--r--devtools/client/netmonitor/test/browser_net_json_text_mime.js90
-rw-r--r--devtools/client/netmonitor/test/browser_net_jsonp.js111
-rw-r--r--devtools/client/netmonitor/test/browser_net_large-response.js55
-rw-r--r--devtools/client/netmonitor/test/browser_net_leak_on_tab_close.js17
-rw-r--r--devtools/client/netmonitor/test/browser_net_open_request_in_tab.js37
-rw-r--r--devtools/client/netmonitor/test/browser_net_page-nav.js69
-rw-r--r--devtools/client/netmonitor/test/browser_net_pane-collapse.js72
-rw-r--r--devtools/client/netmonitor/test/browser_net_pane-toggle.js74
-rw-r--r--devtools/client/netmonitor/test/browser_net_persistent_logs.js51
-rw-r--r--devtools/client/netmonitor/test/browser_net_post-data-01.js166
-rw-r--r--devtools/client/netmonitor/test/browser_net_post-data-02.js73
-rw-r--r--devtools/client/netmonitor/test/browser_net_post-data-03.js98
-rw-r--r--devtools/client/netmonitor/test/browser_net_post-data-04.js74
-rw-r--r--devtools/client/netmonitor/test/browser_net_prefs-and-l10n.js54
-rw-r--r--devtools/client/netmonitor/test/browser_net_prefs-reload.js215
-rw-r--r--devtools/client/netmonitor/test/browser_net_raw_headers.js70
-rw-r--r--devtools/client/netmonitor/test/browser_net_reload-button.js25
-rw-r--r--devtools/client/netmonitor/test/browser_net_reload-markers.js35
-rw-r--r--devtools/client/netmonitor/test/browser_net_req-resp-bodies.js68
-rw-r--r--devtools/client/netmonitor/test/browser_net_resend.js174
-rw-r--r--devtools/client/netmonitor/test/browser_net_resend_cors.js80
-rw-r--r--devtools/client/netmonitor/test/browser_net_resend_headers.js67
-rw-r--r--devtools/client/netmonitor/test/browser_net_security-details.js102
-rw-r--r--devtools/client/netmonitor/test/browser_net_security-error.js70
-rw-r--r--devtools/client/netmonitor/test/browser_net_security-icon-click.js57
-rw-r--r--devtools/client/netmonitor/test/browser_net_security-redirect.js38
-rw-r--r--devtools/client/netmonitor/test/browser_net_security-state.js119
-rw-r--r--devtools/client/netmonitor/test/browser_net_security-tab-deselect.js46
-rw-r--r--devtools/client/netmonitor/test/browser_net_security-tab-visibility.js121
-rw-r--r--devtools/client/netmonitor/test/browser_net_security-warnings.js56
-rw-r--r--devtools/client/netmonitor/test/browser_net_send-beacon-other-tab.js34
-rw-r--r--devtools/client/netmonitor/test/browser_net_send-beacon.js31
-rw-r--r--devtools/client/netmonitor/test/browser_net_service-worker-status.js87
-rw-r--r--devtools/client/netmonitor/test/browser_net_simple-init.js93
-rw-r--r--devtools/client/netmonitor/test/browser_net_simple-request-data.js247
-rw-r--r--devtools/client/netmonitor/test/browser_net_simple-request-details.js261
-rw-r--r--devtools/client/netmonitor/test/browser_net_simple-request.js72
-rw-r--r--devtools/client/netmonitor/test/browser_net_sort-01.js230
-rw-r--r--devtools/client/netmonitor/test/browser_net_sort-02.js272
-rw-r--r--devtools/client/netmonitor/test/browser_net_sort-03.js209
-rw-r--r--devtools/client/netmonitor/test/browser_net_statistics-01.js63
-rw-r--r--devtools/client/netmonitor/test/browser_net_statistics-02.js42
-rw-r--r--devtools/client/netmonitor/test/browser_net_statistics-03.js45
-rw-r--r--devtools/client/netmonitor/test/browser_net_status-codes.js213
-rw-r--r--devtools/client/netmonitor/test/browser_net_streaming-response.js69
-rw-r--r--devtools/client/netmonitor/test/browser_net_throttle.js57
-rw-r--r--devtools/client/netmonitor/test/browser_net_timeline_ticks.js142
-rw-r--r--devtools/client/netmonitor/test/browser_net_timing-division.js61
-rw-r--r--devtools/client/netmonitor/test/browser_net_truncate.js44
-rw-r--r--devtools/client/netmonitor/test/dropmarker.svg6
-rw-r--r--devtools/client/netmonitor/test/head.js518
-rw-r--r--devtools/client/netmonitor/test/html_api-calls-test-page.html46
-rw-r--r--devtools/client/netmonitor/test/html_brotli-test-page.html38
-rw-r--r--devtools/client/netmonitor/test/html_cause-test-page.html48
-rw-r--r--devtools/client/netmonitor/test/html_content-type-test-page.html48
-rw-r--r--devtools/client/netmonitor/test/html_content-type-without-cache-test-page.html52
-rw-r--r--devtools/client/netmonitor/test/html_copy-as-curl.html30
-rw-r--r--devtools/client/netmonitor/test/html_cors-test-page.html31
-rw-r--r--devtools/client/netmonitor/test/html_curl-utils.html102
-rw-r--r--devtools/client/netmonitor/test/html_custom-get-page.html44
-rw-r--r--devtools/client/netmonitor/test/html_cyrillic-test-page.html39
-rw-r--r--devtools/client/netmonitor/test/html_filter-test-page.html60
-rw-r--r--devtools/client/netmonitor/test/html_frame-subdocument.html48
-rw-r--r--devtools/client/netmonitor/test/html_frame-test-page.html49
-rw-r--r--devtools/client/netmonitor/test/html_image-tooltip-test-page.html26
-rw-r--r--devtools/client/netmonitor/test/html_infinite-get-page.html41
-rw-r--r--devtools/client/netmonitor/test/html_json-custom-mime-test-page.html38
-rw-r--r--devtools/client/netmonitor/test/html_json-long-test-page.html38
-rw-r--r--devtools/client/netmonitor/test/html_json-malformed-test-page.html38
-rw-r--r--devtools/client/netmonitor/test/html_json-text-mime-test-page.html38
-rw-r--r--devtools/client/netmonitor/test/html_jsonp-test-page.html40
-rw-r--r--devtools/client/netmonitor/test/html_navigate-test-page.html18
-rw-r--r--devtools/client/netmonitor/test/html_params-test-page.html67
-rw-r--r--devtools/client/netmonitor/test/html_post-data-test-page.html77
-rw-r--r--devtools/client/netmonitor/test/html_post-json-test-page.html39
-rw-r--r--devtools/client/netmonitor/test/html_post-raw-test-page.html40
-rw-r--r--devtools/client/netmonitor/test/html_post-raw-with-headers-test-page.html45
-rw-r--r--devtools/client/netmonitor/test/html_send-beacon.html23
-rw-r--r--devtools/client/netmonitor/test/html_simple-test-page.html18
-rw-r--r--devtools/client/netmonitor/test/html_single-get-page.html36
-rw-r--r--devtools/client/netmonitor/test/html_sorting-test-page.html18
-rw-r--r--devtools/client/netmonitor/test/html_statistics-test-page.html40
-rw-r--r--devtools/client/netmonitor/test/html_status-codes-test-page.html55
-rw-r--r--devtools/client/netmonitor/test/service-workers/status-codes-service-worker.js15
-rw-r--r--devtools/client/netmonitor/test/service-workers/status-codes.html59
-rw-r--r--devtools/client/netmonitor/test/sjs_content-type-test-server.sjs273
-rw-r--r--devtools/client/netmonitor/test/sjs_cors-test-server.sjs17
-rw-r--r--devtools/client/netmonitor/test/sjs_hsts-test-server.sjs22
-rw-r--r--devtools/client/netmonitor/test/sjs_https-redirect-test-server.sjs19
-rw-r--r--devtools/client/netmonitor/test/sjs_simple-test-server.sjs17
-rw-r--r--devtools/client/netmonitor/test/sjs_sorting-test-server.sjs26
-rw-r--r--devtools/client/netmonitor/test/sjs_status-codes-test-server.sjs56
-rw-r--r--devtools/client/netmonitor/test/sjs_truncate-test-server.sjs18
-rw-r--r--devtools/client/netmonitor/test/test-image.pngbin0 -> 580 bytes
139 files changed, 11425 insertions, 0 deletions
diff --git a/devtools/client/netmonitor/test/.eslintrc.js b/devtools/client/netmonitor/test/.eslintrc.js
new file mode 100644
index 000000000..8d15a76d9
--- /dev/null
+++ b/devtools/client/netmonitor/test/.eslintrc.js
@@ -0,0 +1,6 @@
+"use strict";
+
+module.exports = {
+ // Extend from the shared list of defined globals for mochitests.
+ "extends": "../../../.eslintrc.mochitests.js"
+};
diff --git a/devtools/client/netmonitor/test/browser.ini b/devtools/client/netmonitor/test/browser.ini
new file mode 100644
index 000000000..5dfe9012d
--- /dev/null
+++ b/devtools/client/netmonitor/test/browser.ini
@@ -0,0 +1,156 @@
+[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_brotli-test-page.html
+ html_image-tooltip-test-page.html
+ html_cors-test-page.html
+ html_custom-get-page.html
+ html_cyrillic-test-page.html
+ html_frame-test-page.html
+ html_frame-subdocument.html
+ html_filter-test-page.html
+ html_infinite-get-page.html
+ html_json-custom-mime-test-page.html
+ html_json-long-test-page.html
+ html_json-malformed-test-page.html
+ html_json-text-mime-test-page.html
+ html_jsonp-test-page.html
+ html_navigate-test-page.html
+ html_params-test-page.html
+ html_post-data-test-page.html
+ html_post-json-test-page.html
+ html_post-raw-test-page.html
+ html_post-raw-with-headers-test-page.html
+ html_simple-test-page.html
+ html_single-get-page.html
+ html_send-beacon.html
+ html_sorting-test-page.html
+ 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
+ sjs_truncate-test-server.sjs
+ test-image.png
+ service-workers/status-codes.html
+ service-workers/status-codes-service-worker.js
+ !/devtools/client/framework/test/shared-head.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]
+skip-if = true # Bug 1309191 - replace with rewritten version in React
+[browser_net_cached-status.js]
+[browser_net_cause.js]
+[browser_net_cause_redirect.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]
+[browser_net_clear.js]
+[browser_net_complex-params.js]
+[browser_net_content-type.js]
+[browser_net_brotli.js]
+[browser_net_curl-utils.js]
+[browser_net_copy_image_as_data_uri.js]
+subsuite = clipboard
+[browser_net_copy_svg_image_as_data_uri.js]
+subsuite = clipboard
+[browser_net_copy_url.js]
+subsuite = clipboard
+[browser_net_copy_params.js]
+subsuite = clipboard
+[browser_net_copy_response.js]
+subsuite = clipboard
+[browser_net_copy_headers.js]
+subsuite = clipboard
+[browser_net_copy_as_curl.js]
+subsuite = clipboard
+[browser_net_cors_requests.js]
+[browser_net_cyrillic-01.js]
+[browser_net_cyrillic-02.js]
+[browser_net_details-no-duplicated-content.js]
+skip-if = (os == 'linux' && e10s && debug) # Bug 1242204
+[browser_net_frame.js]
+[browser_net_filter-01.js]
+[browser_net_filter-02.js]
+[browser_net_filter-03.js]
+[browser_net_filter-04.js]
+[browser_net_footer-summary.js]
+[browser_net_html-preview.js]
+[browser_net_icon-preview.js]
+[browser_net_image-tooltip.js]
+[browser_net_json-long.js]
+[browser_net_json-malformed.js]
+[browser_net_json_custom_mime.js]
+[browser_net_json_text_mime.js]
+[browser_net_jsonp.js]
+[browser_net_large-response.js]
+[browser_net_leak_on_tab_close.js]
+[browser_net_open_request_in_tab.js]
+[browser_net_page-nav.js]
+[browser_net_pane-collapse.js]
+[browser_net_pane-toggle.js]
+[browser_net_post-data-01.js]
+[browser_net_post-data-02.js]
+[browser_net_post-data-03.js]
+[browser_net_post-data-04.js]
+[browser_net_prefs-and-l10n.js]
+[browser_net_prefs-reload.js]
+[browser_net_raw_headers.js]
+[browser_net_reload-button.js]
+[browser_net_reload-markers.js]
+[browser_net_req-resp-bodies.js]
+[browser_net_resend_cors.js]
+[browser_net_resend_headers.js]
+[browser_net_resend.js]
+[browser_net_security-details.js]
+[browser_net_security-error.js]
+[browser_net_security-icon-click.js]
+[browser_net_security-redirect.js]
+[browser_net_security-state.js]
+[browser_net_security-tab-deselect.js]
+[browser_net_security-tab-visibility.js]
+[browser_net_security-warnings.js]
+[browser_net_send-beacon.js]
+[browser_net_send-beacon-other-tab.js]
+[browser_net_simple-init.js]
+[browser_net_simple-request-data.js]
+skip-if = true # Bug 1258809
+[browser_net_simple-request-details.js]
+skip-if = true # Bug 1258809
+[browser_net_simple-request.js]
+[browser_net_sort-01.js]
+skip-if = (e10s && debug && os == 'mac') # Bug 1253037
+[browser_net_sort-02.js]
+[browser_net_sort-03.js]
+[browser_net_statistics-01.js]
+[browser_net_statistics-02.js]
+[browser_net_statistics-03.js]
+[browser_net_status-codes.js]
+[browser_net_streaming-response.js]
+[browser_net_throttle.js]
+[browser_net_timeline_ticks.js]
+[browser_net_timing-division.js]
+[browser_net_truncate.js]
+[browser_net_persistent_logs.js]
diff --git a/devtools/client/netmonitor/test/browser_net_aaa_leaktest.js b/devtools/client/netmonitor/test/browser_net_aaa_leaktest.js
new file mode 100644
index 000000000..31c1e03ad
--- /dev/null
+++ b/devtools/client/netmonitor/test/browser_net_aaa_leaktest.js
@@ -0,0 +1,28 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+/**
+ * Tests if the network monitor leaks on initialization and sudden destruction.
+ * You can also use this initialization format as a template for other tests.
+ */
+
+add_task(function* () {
+ let { tab, monitor } = yield initNetMonitor(SIMPLE_URL);
+ info("Starting test... ");
+
+ let { document, NetMonitorView, NetMonitorController } = monitor.panelWin;
+ let { RequestsMenu, NetworkDetails } = NetMonitorView;
+
+ ok(tab, "Should have a tab available.");
+ ok(monitor, "Should have a network monitor pane available.");
+
+ ok(document, "Should have a document available.");
+ ok(NetMonitorView, "Should have a NetMonitorView object available.");
+ ok(NetMonitorController, "Should have a NetMonitorController object available.");
+ ok(RequestsMenu, "Should have a RequestsMenu object available.");
+ ok(NetworkDetails, "Should have a NetworkDetails object available.");
+
+ yield teardown(monitor);
+});
diff --git a/devtools/client/netmonitor/test/browser_net_accessibility-01.js b/devtools/client/netmonitor/test/browser_net_accessibility-01.js
new file mode 100644
index 000000000..c0832064f
--- /dev/null
+++ b/devtools/client/netmonitor/test/browser_net_accessibility-01.js
@@ -0,0 +1,87 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+/**
+ * Tests if focus modifiers work for the SideMenuWidget.
+ */
+
+add_task(function* () {
+ let { tab, monitor } = yield initNetMonitor(CUSTOM_GET_URL);
+ info("Starting test... ");
+
+ // It seems that this test may be slow on Ubuntu builds running on ec2.
+ requestLongerTimeout(2);
+
+ let { NetMonitorView } = monitor.panelWin;
+ let { RequestsMenu } = NetMonitorView;
+
+ RequestsMenu.lazyUpdate = false;
+
+ let count = 0;
+ function check(selectedIndex, paneVisibility) {
+ info("Performing check " + (count++) + ".");
+
+ is(RequestsMenu.selectedIndex, selectedIndex,
+ "The selected item in the requests menu was incorrect.");
+ is(NetMonitorView.detailsPaneHidden, !paneVisibility,
+ "The network requests details pane visibility state was incorrect.");
+ }
+
+ let wait = waitForNetworkEvents(monitor, 2);
+ yield ContentTask.spawn(tab.linkedBrowser, {}, function* () {
+ content.wrappedJSObject.performRequests(2);
+ });
+ yield wait;
+
+ check(-1, false);
+
+ RequestsMenu.focusLastVisibleItem();
+ check(1, true);
+ RequestsMenu.focusFirstVisibleItem();
+ check(0, true);
+
+ RequestsMenu.focusNextItem();
+ check(1, true);
+ RequestsMenu.focusPrevItem();
+ check(0, true);
+
+ RequestsMenu.focusItemAtDelta(+1);
+ check(1, true);
+ RequestsMenu.focusItemAtDelta(-1);
+ check(0, true);
+
+ RequestsMenu.focusItemAtDelta(+10);
+ check(1, true);
+ RequestsMenu.focusItemAtDelta(-10);
+ check(0, true);
+
+ wait = waitForNetworkEvents(monitor, 18);
+ yield ContentTask.spawn(tab.linkedBrowser, {}, function* () {
+ content.wrappedJSObject.performRequests(18);
+ });
+ yield wait;
+
+ RequestsMenu.focusLastVisibleItem();
+ check(19, true);
+ RequestsMenu.focusFirstVisibleItem();
+ check(0, true);
+
+ RequestsMenu.focusNextItem();
+ check(1, true);
+ RequestsMenu.focusPrevItem();
+ check(0, true);
+
+ RequestsMenu.focusItemAtDelta(+10);
+ check(10, true);
+ RequestsMenu.focusItemAtDelta(-10);
+ check(0, true);
+
+ RequestsMenu.focusItemAtDelta(+100);
+ check(19, true);
+ RequestsMenu.focusItemAtDelta(-100);
+ check(0, true);
+
+ yield teardown(monitor);
+});
diff --git a/devtools/client/netmonitor/test/browser_net_accessibility-02.js b/devtools/client/netmonitor/test/browser_net_accessibility-02.js
new file mode 100644
index 000000000..33420a440
--- /dev/null
+++ b/devtools/client/netmonitor/test/browser_net_accessibility-02.js
@@ -0,0 +1,130 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+/**
+ * Tests if keyboard and mouse navigation works in the network requests menu.
+ */
+
+add_task(function* () {
+ let { tab, monitor } = yield initNetMonitor(CUSTOM_GET_URL);
+ info("Starting test... ");
+
+ // It seems that this test may be slow on Ubuntu builds running on ec2.
+ requestLongerTimeout(2);
+
+ let { window, $, NetMonitorView } = monitor.panelWin;
+ let { RequestsMenu } = NetMonitorView;
+
+ RequestsMenu.lazyUpdate = false;
+
+ let count = 0;
+ function check(selectedIndex, paneVisibility) {
+ info("Performing check " + (count++) + ".");
+
+ is(RequestsMenu.selectedIndex, selectedIndex,
+ "The selected item in the requests menu was incorrect.");
+ is(NetMonitorView.detailsPaneHidden, !paneVisibility,
+ "The network requests details pane visibility state was incorrect.");
+ }
+
+ let wait = waitForNetworkEvents(monitor, 2);
+ yield ContentTask.spawn(tab.linkedBrowser, {}, function* () {
+ content.wrappedJSObject.performRequests(2);
+ });
+ yield wait;
+
+ check(-1, false);
+
+ EventUtils.sendKey("DOWN", window);
+ check(0, true);
+ EventUtils.sendKey("UP", window);
+ check(0, true);
+
+ EventUtils.sendKey("PAGE_DOWN", window);
+ check(1, true);
+ EventUtils.sendKey("PAGE_UP", window);
+ check(0, true);
+
+ EventUtils.sendKey("END", window);
+ check(1, true);
+ EventUtils.sendKey("HOME", window);
+ check(0, true);
+
+ wait = waitForNetworkEvents(monitor, 18);
+ yield ContentTask.spawn(tab.linkedBrowser, {}, function* () {
+ content.wrappedJSObject.performRequests(18);
+ });
+ yield wait;
+
+ EventUtils.sendKey("DOWN", window);
+ check(1, true);
+ EventUtils.sendKey("DOWN", window);
+ check(2, true);
+ EventUtils.sendKey("UP", window);
+ check(1, true);
+ EventUtils.sendKey("UP", window);
+ check(0, true);
+
+ EventUtils.sendKey("PAGE_DOWN", window);
+ check(4, true);
+ EventUtils.sendKey("PAGE_DOWN", window);
+ check(8, true);
+ EventUtils.sendKey("PAGE_UP", window);
+ check(4, true);
+ EventUtils.sendKey("PAGE_UP", window);
+ check(0, true);
+
+ EventUtils.sendKey("HOME", window);
+ check(0, true);
+ EventUtils.sendKey("HOME", window);
+ check(0, true);
+ EventUtils.sendKey("PAGE_UP", window);
+ check(0, true);
+ EventUtils.sendKey("HOME", window);
+ check(0, true);
+
+ EventUtils.sendKey("END", window);
+ check(19, true);
+ EventUtils.sendKey("END", window);
+ check(19, true);
+ EventUtils.sendKey("PAGE_DOWN", window);
+ check(19, true);
+ EventUtils.sendKey("END", window);
+ check(19, true);
+
+ EventUtils.sendKey("PAGE_UP", window);
+ check(15, true);
+ EventUtils.sendKey("PAGE_UP", window);
+ check(11, true);
+ EventUtils.sendKey("UP", window);
+ check(10, true);
+ EventUtils.sendKey("UP", window);
+ check(9, true);
+ EventUtils.sendKey("PAGE_DOWN", window);
+ check(13, true);
+ EventUtils.sendKey("PAGE_DOWN", window);
+ check(17, true);
+ EventUtils.sendKey("PAGE_DOWN", window);
+ check(19, true);
+ EventUtils.sendKey("PAGE_DOWN", window);
+ check(19, true);
+
+ EventUtils.sendKey("HOME", window);
+ check(0, true);
+ EventUtils.sendKey("DOWN", window);
+ check(1, true);
+ EventUtils.sendKey("END", window);
+ check(19, true);
+ EventUtils.sendKey("DOWN", window);
+ check(19, true);
+
+ EventUtils.sendMouseEvent({ type: "mousedown" }, $("#details-pane-toggle"));
+ check(-1, false);
+
+ EventUtils.sendMouseEvent({ type: "mousedown" }, $(".side-menu-widget-item"));
+ check(0, true);
+
+ yield teardown(monitor);
+});
diff --git a/devtools/client/netmonitor/test/browser_net_api-calls.js b/devtools/client/netmonitor/test/browser_net_api-calls.js
new file mode 100644
index 000000000..994dc0354
--- /dev/null
+++ b/devtools/client/netmonitor/test/browser_net_api-calls.js
@@ -0,0 +1,39 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+/**
+ * Tests whether API call URLs (without a filename) are correctly displayed
+ * (including Unicode)
+ */
+
+add_task(function* () {
+ let { tab, monitor } = yield initNetMonitor(API_CALLS_URL);
+ info("Starting test... ");
+
+ let { NetMonitorView } = monitor.panelWin;
+ let { RequestsMenu } = NetMonitorView;
+
+ RequestsMenu.lazyUpdate = false;
+
+ const REQUEST_URIS = [
+ "http://example.com/api/fileName.xml",
+ "http://example.com/api/file%E2%98%A2.xml",
+ "http://example.com/api/ascii/get/",
+ "http://example.com/api/unicode/%E2%98%A2/",
+ "http://example.com/api/search/?q=search%E2%98%A2"
+ ];
+
+ let wait = waitForNetworkEvents(monitor, 5);
+ yield ContentTask.spawn(tab.linkedBrowser, {}, function* () {
+ content.wrappedJSObject.performRequests();
+ });
+ yield wait;
+
+ REQUEST_URIS.forEach(function (uri, index) {
+ verifyRequestItemTarget(RequestsMenu.getItemAtIndex(index), "GET", uri);
+ });
+
+ yield teardown(monitor);
+});
diff --git a/devtools/client/netmonitor/test/browser_net_autoscroll.js b/devtools/client/netmonitor/test/browser_net_autoscroll.js
new file mode 100644
index 000000000..9abb3fd17
--- /dev/null
+++ b/devtools/client/netmonitor/test/browser_net_autoscroll.js
@@ -0,0 +1,75 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+/**
+ * Bug 863102 - Automatically scroll down upon new network requests.
+ */
+add_task(function* () {
+ requestLongerTimeout(2);
+
+ let { monitor } = yield initNetMonitor(INFINITE_GET_URL);
+ let win = monitor.panelWin;
+ let topNode = win.document.getElementById("requests-menu-contents");
+ let requestsContainer = topNode.getElementsByTagName("scrollbox")[0];
+ ok(!!requestsContainer, "Container element exists as expected.");
+
+ // (1) Check that the scroll position is maintained at the bottom
+ // when the requests overflow the vertical size of the container.
+ yield waitForRequestsToOverflowContainer();
+ yield waitForScroll();
+ ok(scrolledToBottom(requestsContainer), "Scrolled to bottom on overflow.");
+
+ // (2) Now set the scroll position somewhere in the middle and check
+ // that additional requests do not change the scroll position.
+ let children = requestsContainer.childNodes;
+ let middleNode = children.item(children.length / 2);
+ middleNode.scrollIntoView();
+ ok(!scrolledToBottom(requestsContainer), "Not scrolled to bottom.");
+ // save for comparison later
+ let scrollTop = requestsContainer.scrollTop;
+ yield waitForNetworkEvents(monitor, 8);
+ yield waitSomeTime();
+ is(requestsContainer.scrollTop, scrollTop, "Did not scroll.");
+
+ // (3) Now set the scroll position back at the bottom and check that
+ // additional requests *do* cause the container to scroll down.
+ requestsContainer.scrollTop = requestsContainer.scrollHeight;
+ ok(scrolledToBottom(requestsContainer), "Set scroll position to bottom.");
+ yield waitForNetworkEvents(monitor, 8);
+ yield waitForScroll();
+ ok(scrolledToBottom(requestsContainer), "Still scrolled to bottom.");
+
+ // (4) Now select an item in the list and check that additional requests
+ // do not change the scroll position.
+ monitor.panelWin.NetMonitorView.RequestsMenu.selectedIndex = 0;
+ yield waitForNetworkEvents(monitor, 8);
+ yield waitSomeTime();
+ is(requestsContainer.scrollTop, 0, "Did not scroll.");
+
+ // Done: clean up.
+ yield teardown(monitor);
+
+ function* waitForRequestsToOverflowContainer() {
+ while (true) {
+ yield waitForNetworkEvents(monitor, 1);
+ if (requestsContainer.scrollHeight > requestsContainer.clientHeight) {
+ return;
+ }
+ }
+ }
+
+ function scrolledToBottom(element) {
+ return element.scrollTop + element.clientHeight >= element.scrollHeight;
+ }
+
+ function waitSomeTime() {
+ // Wait to make sure no scrolls happen
+ return wait(50);
+ }
+
+ function waitForScroll() {
+ return monitor._view.RequestsMenu.widget.once("scroll-to-bottom");
+ }
+});
diff --git a/devtools/client/netmonitor/test/browser_net_brotli.js b/devtools/client/netmonitor/test/browser_net_brotli.js
new file mode 100644
index 000000000..cc6908d68
--- /dev/null
+++ b/devtools/client/netmonitor/test/browser_net_brotli.js
@@ -0,0 +1,91 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+const BROTLI_URL = HTTPS_EXAMPLE_URL + "html_brotli-test-page.html";
+const BROTLI_REQUESTS = 1;
+
+/**
+ * Test brotli encoded response is handled correctly on HTTPS.
+ */
+
+add_task(function* () {
+ let { L10N } = require("devtools/client/netmonitor/l10n");
+
+ let { tab, monitor } = yield initNetMonitor(BROTLI_URL);
+ info("Starting test... ");
+
+ let { document, Editor, NetMonitorView } = monitor.panelWin;
+ let { RequestsMenu } = NetMonitorView;
+
+ RequestsMenu.lazyUpdate = false;
+
+ let wait = waitForNetworkEvents(monitor, BROTLI_REQUESTS);
+ yield ContentTask.spawn(tab.linkedBrowser, {}, function* () {
+ content.wrappedJSObject.performRequests();
+ });
+ yield wait;
+
+ verifyRequestItemTarget(RequestsMenu.getItemAtIndex(0),
+ "GET", HTTPS_CONTENT_TYPE_SJS + "?fmt=br", {
+ status: 200,
+ statusText: "Connected",
+ type: "plain",
+ fullMimeType: "text/plain",
+ transferred: L10N.getFormatStrWithNumbers("networkMenu.sizeB", 10),
+ size: L10N.getFormatStrWithNumbers("networkMenu.sizeB", 64),
+ time: true
+ });
+
+ let onEvent = waitForResponseBodyDisplayed();
+ EventUtils.sendMouseEvent({ type: "mousedown" },
+ document.getElementById("details-pane-toggle"));
+ EventUtils.sendMouseEvent({ type: "mousedown" },
+ document.querySelectorAll("#details-pane tab")[3]);
+ yield onEvent;
+ yield testResponseTab("br");
+
+ yield teardown(monitor);
+
+ function* testResponseTab(type) {
+ let tabEl = document.querySelectorAll("#details-pane tab")[3];
+ let tabpanel = document.querySelectorAll("#details-pane tabpanel")[3];
+
+ is(tabEl.getAttribute("selected"), "true",
+ "The response tab in the network details pane should be selected.");
+
+ function checkVisibility(box) {
+ is(tabpanel.querySelector("#response-content-info-header")
+ .hasAttribute("hidden"), true,
+ "The response info header doesn't have the intended visibility.");
+ is(tabpanel.querySelector("#response-content-json-box")
+ .hasAttribute("hidden"), box != "json",
+ "The response content json box doesn't have the intended visibility.");
+ is(tabpanel.querySelector("#response-content-textarea-box")
+ .hasAttribute("hidden"), box != "textarea",
+ "The response content textarea box doesn't have the intended visibility.");
+ is(tabpanel.querySelector("#response-content-image-box")
+ .hasAttribute("hidden"), box != "image",
+ "The response content image box doesn't have the intended visibility.");
+ }
+
+ switch (type) {
+ case "br": {
+ checkVisibility("textarea");
+
+ let expected = "X".repeat(64);
+ let editor = yield NetMonitorView.editor("#response-content-textarea");
+ is(editor.getText(), expected,
+ "The text shown in the source editor is incorrect for the brotli request.");
+ is(editor.getMode(), Editor.modes.text,
+ "The mode active in the source editor is incorrect for the brotli request.");
+ break;
+ }
+ }
+ }
+
+ function waitForResponseBodyDisplayed() {
+ return monitor.panelWin.once(monitor.panelWin.EVENTS.RESPONSE_BODY_DISPLAYED);
+ }
+});
diff --git a/devtools/client/netmonitor/test/browser_net_cached-status.js b/devtools/client/netmonitor/test/browser_net_cached-status.js
new file mode 100644
index 000000000..66b926bea
--- /dev/null
+++ b/devtools/client/netmonitor/test/browser_net_cached-status.js
@@ -0,0 +1,111 @@
+/* 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 cached requests have the correct status code
+ */
+
+add_task(function* () {
+ let { tab, monitor } = yield initNetMonitor(STATUS_CODES_URL, null, true);
+ info("Starting test... ");
+
+ let { NetMonitorView } = monitor.panelWin;
+ let { RequestsMenu, NetworkDetails } = NetMonitorView;
+
+ RequestsMenu.lazyUpdate = false;
+ NetworkDetails._params.lazyEmpty = false;
+
+ const REQUEST_DATA = [
+ {
+ method: "GET",
+ uri: STATUS_CODES_SJS + "?sts=ok&cached",
+ details: {
+ status: 200,
+ statusText: "OK",
+ type: "plain",
+ fullMimeType: "text/plain; charset=utf-8"
+ }
+ },
+ {
+ method: "GET",
+ uri: STATUS_CODES_SJS + "?sts=redirect&cached",
+ details: {
+ status: 301,
+ statusText: "Moved Permanently",
+ type: "html",
+ fullMimeType: "text/html; charset=utf-8"
+ }
+ },
+ {
+ method: "GET",
+ uri: "http://example.com/redirected",
+ details: {
+ status: 404,
+ statusText: "Not Found",
+ type: "html",
+ fullMimeType: "text/html; charset=utf-8"
+ }
+ },
+ {
+ method: "GET",
+ uri: STATUS_CODES_SJS + "?sts=ok&cached",
+ details: {
+ status: 200,
+ statusText: "OK (cached)",
+ displayedStatus: "cached",
+ type: "plain",
+ fullMimeType: "text/plain; charset=utf-8"
+ }
+ },
+ {
+ method: "GET",
+ uri: STATUS_CODES_SJS + "?sts=redirect&cached",
+ details: {
+ status: 301,
+ statusText: "Moved Permanently (cached)",
+ displayedStatus: "cached",
+ type: "html",
+ fullMimeType: "text/html; charset=utf-8"
+ }
+ },
+ {
+ method: "GET",
+ uri: "http://example.com/redirected",
+ details: {
+ status: 404,
+ statusText: "Not Found",
+ type: "html",
+ fullMimeType: "text/html; charset=utf-8"
+ }
+ }
+ ];
+
+ info("Performing requests #1...");
+ yield performRequestsAndWait();
+
+ info("Performing requests #2...");
+ yield performRequestsAndWait();
+
+ let index = 0;
+ for (let request of REQUEST_DATA) {
+ let item = RequestsMenu.getItemAtIndex(index);
+
+ info("Verifying request #" + index);
+ yield verifyRequestItemTarget(item, request.method, request.uri, request.details);
+
+ index++;
+ }
+
+ yield teardown(monitor);
+
+ function* performRequestsAndWait() {
+ let wait = waitForNetworkEvents(monitor, 3);
+ yield ContentTask.spawn(tab.linkedBrowser, {}, function* () {
+ content.wrappedJSObject.performCachedRequests();
+ });
+ yield wait;
+ }
+});
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 000000000..2e73965d0
--- /dev/null
+++ b/devtools/client/netmonitor/test/browser_net_cause.js
@@ -0,0 +1,147 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+/**
+ * Tests if request cause is reported correctly.
+ */
+
+const CAUSE_FILE_NAME = "html_cause-test-page.html";
+const CAUSE_URL = EXAMPLE_URL + CAUSE_FILE_NAME;
+
+const EXPECTED_REQUESTS = [
+ {
+ method: "GET",
+ url: CAUSE_URL,
+ causeType: "document",
+ causeUri: "",
+ // The document load has internal privileged JS code on the stack
+ stack: true
+ },
+ {
+ method: "GET",
+ url: EXAMPLE_URL + "stylesheet_request",
+ causeType: "stylesheet",
+ causeUri: CAUSE_URL,
+ stack: false
+ },
+ {
+ method: "GET",
+ url: EXAMPLE_URL + "img_request",
+ causeType: "img",
+ causeUri: CAUSE_URL,
+ stack: false
+ },
+ {
+ method: "GET",
+ url: EXAMPLE_URL + "xhr_request",
+ causeType: "xhr",
+ causeUri: CAUSE_URL,
+ stack: [{ fn: "performXhrRequest", file: CAUSE_FILE_NAME, line: 22 }]
+ },
+ {
+ method: "GET",
+ url: EXAMPLE_URL + "fetch_request",
+ causeType: "fetch",
+ causeUri: CAUSE_URL,
+ stack: [{ fn: "performFetchRequest", file: CAUSE_FILE_NAME, line: 26 }]
+ },
+ {
+ method: "GET",
+ url: EXAMPLE_URL + "promise_fetch_request",
+ causeType: "fetch",
+ causeUri: CAUSE_URL,
+ stack: [
+ { fn: "performPromiseFetchRequest", file: CAUSE_FILE_NAME, line: 38 },
+ { fn: null, file: CAUSE_FILE_NAME, line: 37, asyncCause: "promise callback" },
+ ]
+ },
+ {
+ method: "GET",
+ url: EXAMPLE_URL + "timeout_fetch_request",
+ causeType: "fetch",
+ causeUri: CAUSE_URL,
+ stack: [
+ { fn: "performTimeoutFetchRequest", file: CAUSE_FILE_NAME, line: 40 },
+ { fn: "performPromiseFetchRequest", file: CAUSE_FILE_NAME, line: 39,
+ asyncCause: "setTimeout handler" },
+ ]
+ },
+ {
+ method: "POST",
+ url: EXAMPLE_URL + "beacon_request",
+ causeType: "beacon",
+ causeUri: CAUSE_URL,
+ stack: [{ fn: "performBeaconRequest", file: CAUSE_FILE_NAME, line: 30 }]
+ },
+];
+
+add_task(function* () {
+ // Async stacks aren't on by default in all builds
+ yield SpecialPowers.pushPrefEnv({ set: [["javascript.options.asyncstack", 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 { tab, monitor } = yield initNetMonitor(SIMPLE_URL);
+ let { $, NetMonitorView } = monitor.panelWin;
+ let { RequestsMenu } = NetMonitorView;
+ RequestsMenu.lazyUpdate = false;
+
+ let wait = waitForNetworkEvents(monitor, EXPECTED_REQUESTS.length);
+ tab.linkedBrowser.loadURI(CAUSE_URL);
+ yield wait;
+
+ is(RequestsMenu.itemCount, EXPECTED_REQUESTS.length,
+ "All the page events should be recorded.");
+
+ EXPECTED_REQUESTS.forEach((spec, i) => {
+ let { method, url, causeType, causeUri, stack } = spec;
+
+ let requestItem = RequestsMenu.getItemAtIndex(i);
+ verifyRequestItemTarget(requestItem,
+ method, url, { cause: { type: causeType, loadingDocumentUri: causeUri } }
+ );
+
+ let { stacktrace } = requestItem.attachment.cause;
+ let stackLen = stacktrace ? stacktrace.length : 0;
+
+ if (stack) {
+ ok(stacktrace, `Request #${i} has a stacktrace`);
+ ok(stackLen > 0,
+ `Request #${i} (${causeType}) has a stacktrace with ${stackLen} items`);
+
+ // if "stack" is array, check the details about the top stack frames
+ if (Array.isArray(stack)) {
+ stack.forEach((frame, j) => {
+ is(stacktrace[j].functionName, frame.fn,
+ `Request #${i} has the correct function on JS stack frame #${j}`);
+ is(stacktrace[j].filename.split("/").pop(), frame.file,
+ `Request #${i} has the correct file on JS stack frame #${j}`);
+ is(stacktrace[j].lineNumber, frame.line,
+ `Request #${i} has the correct line number on JS stack frame #${j}`);
+ is(stacktrace[j].asyncCause, frame.asyncCause,
+ `Request #${i} has the correct async cause on JS stack frame #${j}`);
+ });
+ }
+ } else {
+ is(stackLen, 0, `Request #${i} (${causeType}) has an empty stacktrace`);
+ }
+ });
+
+ // Sort the requests by cause and check the order
+ EventUtils.sendMouseEvent({ type: "click" }, $("#requests-menu-cause-button"));
+ let expectedOrder = EXPECTED_REQUESTS.map(r => r.causeType).sort();
+ expectedOrder.forEach((expectedCause, i) => {
+ let { target } = RequestsMenu.getItemAtIndex(i);
+ let causeLabel = target.querySelector(".requests-menu-cause-label");
+ let cause = causeLabel.getAttribute("value");
+ is(cause, expectedCause, `The request #${i} has the expected cause after sorting`);
+ });
+
+ yield teardown(monitor);
+});
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 000000000..ace6390ab
--- /dev/null
+++ b/devtools/client/netmonitor/test/browser_net_cause_redirect.js
@@ -0,0 +1,57 @@
+/* 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)
+ */
+
+add_task(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 { tab, monitor } = yield initNetMonitor(CUSTOM_GET_URL);
+ let { RequestsMenu } = monitor.panelWin.NetMonitorView;
+ RequestsMenu.lazyUpdate = false;
+
+ let wait = waitForNetworkEvents(monitor, EXPECTED_REQUESTS.length);
+ yield performRequests(2, HSTS_SJS);
+ yield wait;
+
+ 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;
+ let stackLen = stacktrace ? stacktrace.length : 0;
+
+ if (hasStack) {
+ ok(stacktrace, `Request #${i} has a stacktrace`);
+ ok(stackLen > 0, `Request #${i} has a stacktrace with ${stackLen} items`);
+ } else {
+ is(stackLen, 0, `Request #${i} has an empty stacktrace`);
+ }
+ });
+
+ // Send a request to reset the HSTS policy to state before the test
+ wait = waitForNetworkEvents(monitor, 1);
+ yield performRequests(1, HSTS_SJS + "?reset");
+ yield wait;
+
+ yield teardown(monitor);
+
+ function performRequests(count, url) {
+ return ContentTask.spawn(tab.linkedBrowser, { count, url }, function* (args) {
+ content.wrappedJSObject.performRequests(args.count, args.url);
+ });
+ }
+});
diff --git a/devtools/client/netmonitor/test/browser_net_charts-01.js b/devtools/client/netmonitor/test/browser_net_charts-01.js
new file mode 100644
index 000000000..987881836
--- /dev/null
+++ b/devtools/client/netmonitor/test/browser_net_charts-01.js
@@ -0,0 +1,73 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+/**
+ * Makes sure Pie Charts have the right internal structure.
+ */
+
+add_task(function* () {
+ let { monitor } = yield initNetMonitor(SIMPLE_URL);
+ info("Starting test... ");
+
+ let { document, Chart } = monitor.panelWin;
+
+ let pie = Chart.Pie(document, {
+ width: 100,
+ height: 100,
+ data: [{
+ size: 1,
+ label: "foo"
+ }, {
+ size: 2,
+ label: "bar"
+ }, {
+ size: 3,
+ label: "baz"
+ }]
+ });
+
+ let node = pie.node;
+ let slices = node.querySelectorAll(".pie-chart-slice.chart-colored-blob");
+ let labels = node.querySelectorAll(".pie-chart-label");
+
+ ok(node.classList.contains("pie-chart-container") &&
+ node.classList.contains("generic-chart-container"),
+ "A pie chart container was created successfully.");
+
+ is(slices.length, 3,
+ "There should be 3 pie chart slices created.");
+ ok(slices[0].getAttribute("d").match(
+ /\s*M 50,50 L 49\.\d+,97\.\d+ A 47\.5,47\.5 0 0 1 49\.\d+,2\.5\d* Z/),
+ "The first slice has the correct data.");
+ ok(slices[1].getAttribute("d").match(
+ /\s*M 50,50 L 91\.\d+,26\.\d+ A 47\.5,47\.5 0 0 1 49\.\d+,97\.\d+ Z/),
+ "The second slice has the correct data.");
+ ok(slices[2].getAttribute("d").match(
+ /\s*M 50,50 L 50\.\d+,2\.5\d* A 47\.5,47\.5 0 0 1 91\.\d+,26\.\d+ Z/),
+ "The third slice has the correct data.");
+
+ ok(slices[0].hasAttribute("largest"),
+ "The first slice should be the largest one.");
+ ok(slices[2].hasAttribute("smallest"),
+ "The third slice should be the smallest one.");
+
+ ok(slices[0].getAttribute("name"), "baz",
+ "The first slice's name is correct.");
+ ok(slices[1].getAttribute("name"), "bar",
+ "The first slice's name is correct.");
+ ok(slices[2].getAttribute("name"), "foo",
+ "The first slice's name is correct.");
+
+ is(labels.length, 3,
+ "There should be 3 pie chart labels created.");
+ is(labels[0].textContent, "baz",
+ "The first label's text is correct.");
+ is(labels[1].textContent, "bar",
+ "The first label's text is correct.");
+ is(labels[2].textContent, "foo",
+ "The first label's text is correct.");
+
+ yield teardown(monitor);
+});
diff --git a/devtools/client/netmonitor/test/browser_net_charts-02.js b/devtools/client/netmonitor/test/browser_net_charts-02.js
new file mode 100644
index 000000000..ae53147f0
--- /dev/null
+++ b/devtools/client/netmonitor/test/browser_net_charts-02.js
@@ -0,0 +1,49 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+/**
+ * Makes sure Pie Charts have the right internal structure when
+ * initialized with empty data.
+ */
+
+add_task(function* () {
+ let { L10N } = require("devtools/client/netmonitor/l10n");
+
+ let { monitor } = yield initNetMonitor(SIMPLE_URL);
+ info("Starting test... ");
+
+ let { document, Chart } = monitor.panelWin;
+
+ let pie = Chart.Pie(document, {
+ data: null,
+ width: 100,
+ height: 100
+ });
+
+ let node = pie.node;
+ let slices = node.querySelectorAll(".pie-chart-slice.chart-colored-blob");
+ let labels = node.querySelectorAll(".pie-chart-label");
+
+ ok(node.classList.contains("pie-chart-container") &&
+ node.classList.contains("generic-chart-container"),
+ "A pie chart container was created successfully.");
+
+ is(slices.length, 1, "There should be 1 pie chart slice created.");
+ ok(slices[0].getAttribute("d").match(
+ /\s*M 50,50 L 50\.\d+,2\.5\d* A 47\.5,47\.5 0 1 1 49\.\d+,2\.5\d* Z/),
+ "The first slice has the correct data.");
+
+ ok(slices[0].hasAttribute("largest"),
+ "The first slice should be the largest one.");
+ ok(slices[0].hasAttribute("smallest"),
+ "The first slice should also be the smallest one.");
+ ok(slices[0].getAttribute("name"), L10N.getStr("pieChart.loading"),
+ "The first slice's name is correct.");
+
+ is(labels.length, 1, "There should be 1 pie chart label created.");
+ is(labels[0].textContent, "Loading", "The first label's text is correct.");
+
+ yield teardown(monitor);
+});
diff --git a/devtools/client/netmonitor/test/browser_net_charts-03.js b/devtools/client/netmonitor/test/browser_net_charts-03.js
new file mode 100644
index 000000000..c7d9b0c1a
--- /dev/null
+++ b/devtools/client/netmonitor/test/browser_net_charts-03.js
@@ -0,0 +1,106 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+/**
+ * Makes sure Table Charts have the right internal structure.
+ */
+
+add_task(function* () {
+ let { L10N } = require("devtools/client/netmonitor/l10n");
+
+ let { monitor } = yield initNetMonitor(SIMPLE_URL);
+ info("Starting test... ");
+
+ let { document, Chart } = monitor.panelWin;
+
+ let table = Chart.Table(document, {
+ title: "Table title",
+ data: [{
+ label1: 1,
+ label2: 11.1
+ }, {
+ label1: 2,
+ label2: 12.2
+ }, {
+ label1: 3,
+ label2: 13.3
+ }],
+ strings: {
+ label2: (value, index) => value + ["foo", "bar", "baz"][index]
+ },
+ totals: {
+ label1: value => "Hello " + L10N.numberWithDecimals(value, 2),
+ label2: value => "World " + L10N.numberWithDecimals(value, 2)
+ }
+ });
+
+ let node = table.node;
+ let title = node.querySelector(".table-chart-title");
+ let grid = node.querySelector(".table-chart-grid");
+ let totals = node.querySelector(".table-chart-totals");
+ let rows = grid.querySelectorAll(".table-chart-row");
+ let sums = node.querySelectorAll(".table-chart-summary-label");
+
+ ok(node.classList.contains("table-chart-container") &&
+ node.classList.contains("generic-chart-container"),
+ "A table chart container was created successfully.");
+
+ ok(title, "A title node was created successfully.");
+ is(title.getAttribute("value"), "Table title",
+ "The title node displays the correct text.");
+
+ is(rows.length, 3, "There should be 3 table chart rows created.");
+
+ ok(rows[0].querySelector(".table-chart-row-box.chart-colored-blob"),
+ "A colored blob exists for the firt row.");
+ is(rows[0].querySelectorAll("label")[0].getAttribute("name"), "label1",
+ "The first column of the first row exists.");
+ is(rows[0].querySelectorAll("label")[1].getAttribute("name"), "label2",
+ "The second column of the first row exists.");
+ is(rows[0].querySelectorAll("label")[0].getAttribute("value"), "1",
+ "The first column of the first row displays the correct text.");
+ is(rows[0].querySelectorAll("label")[1].getAttribute("value"), "11.1foo",
+ "The second column of the first row displays the correct text.");
+
+ ok(rows[1].querySelector(".table-chart-row-box.chart-colored-blob"),
+ "A colored blob exists for the second row.");
+ is(rows[1].querySelectorAll("label")[0].getAttribute("name"), "label1",
+ "The first column of the second row exists.");
+ is(rows[1].querySelectorAll("label")[1].getAttribute("name"), "label2",
+ "The second column of the second row exists.");
+ is(rows[1].querySelectorAll("label")[0].getAttribute("value"), "2",
+ "The first column of the second row displays the correct text.");
+ is(rows[1].querySelectorAll("label")[1].getAttribute("value"), "12.2bar",
+ "The second column of the first row displays the correct text.");
+
+ ok(rows[2].querySelector(".table-chart-row-box.chart-colored-blob"),
+ "A colored blob exists for the third row.");
+ is(rows[2].querySelectorAll("label")[0].getAttribute("name"), "label1",
+ "The first column of the third row exists.");
+ is(rows[2].querySelectorAll("label")[1].getAttribute("name"), "label2",
+ "The second column of the third row exists.");
+ is(rows[2].querySelectorAll("label")[0].getAttribute("value"), "3",
+ "The first column of the third row displays the correct text.");
+ is(rows[2].querySelectorAll("label")[1].getAttribute("value"), "13.3baz",
+ "The second column of the third row displays the correct text.");
+
+ is(sums.length, 2, "There should be 2 total summaries created.");
+
+ is(totals.querySelectorAll(".table-chart-summary-label")[0].getAttribute("name"),
+ "label1",
+ "The first sum's type is correct.");
+ is(totals.querySelectorAll(".table-chart-summary-label")[0].getAttribute("value"),
+ "Hello 6",
+ "The first sum's value is correct.");
+
+ is(totals.querySelectorAll(".table-chart-summary-label")[1].getAttribute("name"),
+ "label2",
+ "The second sum's type is correct.");
+ is(totals.querySelectorAll(".table-chart-summary-label")[1].getAttribute("value"),
+ "World 36.60",
+ "The second sum's value is correct.");
+
+ yield teardown(monitor);
+});
diff --git a/devtools/client/netmonitor/test/browser_net_charts-04.js b/devtools/client/netmonitor/test/browser_net_charts-04.js
new file mode 100644
index 000000000..0d150c409
--- /dev/null
+++ b/devtools/client/netmonitor/test/browser_net_charts-04.js
@@ -0,0 +1,75 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+/**
+ * Makes sure Pie Charts have the right internal structure when
+ * initialized with empty data.
+ */
+
+add_task(function* () {
+ let { L10N } = require("devtools/client/netmonitor/l10n");
+
+ let { monitor } = yield initNetMonitor(SIMPLE_URL);
+ info("Starting test... ");
+
+ let { document, Chart } = monitor.panelWin;
+
+ let table = Chart.Table(document, {
+ title: "Table title",
+ data: null,
+ totals: {
+ label1: value => "Hello " + L10N.numberWithDecimals(value, 2),
+ label2: value => "World " + L10N.numberWithDecimals(value, 2)
+ }
+ });
+
+ let node = table.node;
+ let title = node.querySelector(".table-chart-title");
+ let grid = node.querySelector(".table-chart-grid");
+ let totals = node.querySelector(".table-chart-totals");
+ let rows = grid.querySelectorAll(".table-chart-row");
+ let sums = node.querySelectorAll(".table-chart-summary-label");
+
+ ok(node.classList.contains("table-chart-container") &&
+ node.classList.contains("generic-chart-container"),
+ "A table chart container was created successfully.");
+
+ ok(title, "A title node was created successfully.");
+ is(title.getAttribute("value"), "Table title",
+ "The title node displays the correct text.");
+
+ is(rows.length, 1, "There should be 1 table chart row created.");
+
+ ok(rows[0].querySelector(".table-chart-row-box.chart-colored-blob"),
+ "A colored blob exists for the firt row.");
+ is(rows[0].querySelectorAll("label")[0].getAttribute("name"), "size",
+ "The first column of the first row exists.");
+ is(rows[0].querySelectorAll("label")[1].getAttribute("name"), "label",
+ "The second column of the first row exists.");
+ is(rows[0].querySelectorAll("label")[0].getAttribute("value"), "",
+ "The first column of the first row displays the correct text.");
+ is(rows[0].querySelectorAll("label")[1].getAttribute("value"),
+ L10N.getStr("tableChart.loading"),
+ "The second column of the first row displays the correct text.");
+
+ is(sums.length, 2,
+ "There should be 2 total summaries created.");
+
+ is(totals.querySelectorAll(".table-chart-summary-label")[0].getAttribute("name"),
+ "label1",
+ "The first sum's type is correct.");
+ is(totals.querySelectorAll(".table-chart-summary-label")[0].getAttribute("value"),
+ "Hello 0",
+ "The first sum's value is correct.");
+
+ is(totals.querySelectorAll(".table-chart-summary-label")[1].getAttribute("name"),
+ "label2",
+ "The second sum's type is correct.");
+ is(totals.querySelectorAll(".table-chart-summary-label")[1].getAttribute("value"),
+ "World 0",
+ "The second sum's value is correct.");
+
+ yield teardown(monitor);
+});
diff --git a/devtools/client/netmonitor/test/browser_net_charts-05.js b/devtools/client/netmonitor/test/browser_net_charts-05.js
new file mode 100644
index 000000000..00445b132
--- /dev/null
+++ b/devtools/client/netmonitor/test/browser_net_charts-05.js
@@ -0,0 +1,61 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+/**
+ * Makes sure Pie+Table Charts have the right internal structure.
+ */
+
+add_task(function* () {
+ let { L10N } = require("devtools/client/netmonitor/l10n");
+
+ let { monitor } = yield initNetMonitor(SIMPLE_URL);
+ info("Starting test... ");
+
+ let { document, Chart } = monitor.panelWin;
+
+ let chart = Chart.PieTable(document, {
+ title: "Table title",
+ data: [{
+ size: 1,
+ label: 11.1
+ }, {
+ size: 2,
+ label: 12.2
+ }, {
+ size: 3,
+ label: 13.3
+ }],
+ strings: {
+ label2: (value, index) => value + ["foo", "bar", "baz"][index]
+ },
+ totals: {
+ size: value => "Hello " + L10N.numberWithDecimals(value, 2),
+ label: value => "World " + L10N.numberWithDecimals(value, 2)
+ }
+ });
+
+ ok(chart.pie, "The pie chart proxy is accessible.");
+ ok(chart.table, "The table chart proxy is accessible.");
+
+ let node = chart.node;
+ let rows = node.querySelectorAll(".table-chart-row");
+ let sums = node.querySelectorAll(".table-chart-summary-label");
+
+ ok(node.classList.contains("pie-table-chart-container"),
+ "A pie+table chart container was created successfully.");
+
+ ok(node.querySelector(".table-chart-title"),
+ "A title node was created successfully.");
+ ok(node.querySelector(".pie-chart-container"),
+ "A pie chart was created successfully.");
+ ok(node.querySelector(".table-chart-container"),
+ "A table chart was created successfully.");
+
+ is(rows.length, 3, "There should be 3 pie chart slices created.");
+ is(rows.length, 3, "There should be 3 table chart rows created.");
+ is(sums.length, 2, "There should be 2 total summaries created.");
+
+ yield teardown(monitor);
+});
diff --git a/devtools/client/netmonitor/test/browser_net_charts-06.js b/devtools/client/netmonitor/test/browser_net_charts-06.js
new file mode 100644
index 000000000..4bb70e53e
--- /dev/null
+++ b/devtools/client/netmonitor/test/browser_net_charts-06.js
@@ -0,0 +1,47 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+/**
+ * Makes sure Pie Charts correctly handle empty source data.
+ */
+
+add_task(function* () {
+ let { L10N } = require("devtools/client/netmonitor/l10n");
+
+ let { monitor } = yield initNetMonitor(SIMPLE_URL);
+ info("Starting test... ");
+
+ let { document, Chart } = monitor.panelWin;
+
+ let pie = Chart.Pie(document, {
+ data: [],
+ width: 100,
+ height: 100
+ });
+
+ let node = pie.node;
+ let slices = node.querySelectorAll(".pie-chart-slice.chart-colored-blob");
+ let labels = node.querySelectorAll(".pie-chart-label");
+
+ is(slices.length, 1,
+ "There should be 1 pie chart slice created.");
+ ok(slices[0].getAttribute("d").match(
+ /\s*M 50,50 L 50\.\d+,2\.5\d* A 47\.5,47\.5 0 1 1 49\.\d+,2\.5\d* Z/),
+ "The slice has the correct data.");
+
+ ok(slices[0].hasAttribute("largest"),
+ "The slice should be the largest one.");
+ ok(slices[0].hasAttribute("smallest"),
+ "The slice should also be the smallest one.");
+ ok(slices[0].getAttribute("name"), L10N.getStr("pieChart.unavailable"),
+ "The slice's name is correct.");
+
+ is(labels.length, 1,
+ "There should be 1 pie chart label created.");
+ is(labels[0].textContent, "Empty",
+ "The label's text is correct.");
+
+ yield teardown(monitor);
+});
diff --git a/devtools/client/netmonitor/test/browser_net_charts-07.js b/devtools/client/netmonitor/test/browser_net_charts-07.js
new file mode 100644
index 000000000..bb992e4eb
--- /dev/null
+++ b/devtools/client/netmonitor/test/browser_net_charts-07.js
@@ -0,0 +1,63 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+/**
+ * Makes sure Table Charts correctly handle empty source data.
+ */
+
+add_task(function* () {
+ let { L10N } = require("devtools/client/netmonitor/l10n");
+
+ let { monitor } = yield initNetMonitor(SIMPLE_URL);
+ info("Starting test... ");
+
+ let { document, Chart } = monitor.panelWin;
+
+ let table = Chart.Table(document, {
+ data: [],
+ totals: {
+ label1: value => "Hello " + L10N.numberWithDecimals(value, 2),
+ label2: value => "World " + L10N.numberWithDecimals(value, 2)
+ }
+ });
+
+ let node = table.node;
+ let grid = node.querySelector(".table-chart-grid");
+ let totals = node.querySelector(".table-chart-totals");
+ let rows = grid.querySelectorAll(".table-chart-row");
+ let sums = node.querySelectorAll(".table-chart-summary-label");
+
+ is(rows.length, 1, "There should be 1 table chart row created.");
+
+ ok(rows[0].querySelector(".table-chart-row-box.chart-colored-blob"),
+ "A colored blob exists for the firt row.");
+ is(rows[0].querySelectorAll("label")[0].getAttribute("name"), "size",
+ "The first column of the first row exists.");
+ is(rows[0].querySelectorAll("label")[1].getAttribute("name"), "label",
+ "The second column of the first row exists.");
+ is(rows[0].querySelectorAll("label")[0].getAttribute("value"), "",
+ "The first column of the first row displays the correct text.");
+ is(rows[0].querySelectorAll("label")[1].getAttribute("value"),
+ L10N.getStr("tableChart.unavailable"),
+ "The second column of the first row displays the correct text.");
+
+ is(sums.length, 2, "There should be 2 total summaries created.");
+
+ is(totals.querySelectorAll(".table-chart-summary-label")[0].getAttribute("name"),
+ "label1",
+ "The first sum's type is correct.");
+ is(totals.querySelectorAll(".table-chart-summary-label")[0].getAttribute("value"),
+ "Hello 0",
+ "The first sum's value is correct.");
+
+ is(totals.querySelectorAll(".table-chart-summary-label")[1].getAttribute("name"),
+ "label2",
+ "The second sum's type is correct.");
+ is(totals.querySelectorAll(".table-chart-summary-label")[1].getAttribute("value"),
+ "World 0",
+ "The second sum's value is correct.");
+
+ yield teardown(monitor);
+});
diff --git a/devtools/client/netmonitor/test/browser_net_clear.js b/devtools/client/netmonitor/test/browser_net_clear.js
new file mode 100644
index 000000000..94a60cd39
--- /dev/null
+++ b/devtools/client/netmonitor/test/browser_net_clear.js
@@ -0,0 +1,77 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+/**
+ * Tests if the clear button empties the request menu.
+ */
+
+add_task(function* () {
+ let { tab, monitor } = yield initNetMonitor(SIMPLE_URL);
+ info("Starting test... ");
+
+ let { $, NetMonitorView } = monitor.panelWin;
+ let { RequestsMenu } = NetMonitorView;
+ let detailsPane = $("#details-pane");
+ let detailsPaneToggleButton = $("#details-pane-toggle");
+ let clearButton = $("#requests-menu-clear-button");
+
+ RequestsMenu.lazyUpdate = false;
+
+ // Make sure we start in a sane state
+ assertNoRequestState(RequestsMenu, detailsPaneToggleButton);
+
+ // Load one request and assert it shows up in the list
+ let networkEvent = monitor.panelWin.once(monitor.panelWin.EVENTS.NETWORK_EVENT);
+ tab.linkedBrowser.reload();
+ yield networkEvent;
+
+ assertSingleRequestState();
+
+ // Click clear and make sure the requests are gone
+ EventUtils.sendMouseEvent({ type: "click" }, clearButton);
+ assertNoRequestState();
+
+ // Load a second request and make sure they still show up
+ networkEvent = monitor.panelWin.once(monitor.panelWin.EVENTS.NETWORK_EVENT);
+ tab.linkedBrowser.reload();
+ yield networkEvent;
+
+ assertSingleRequestState();
+
+ // Make sure we can now open the details pane
+ NetMonitorView.toggleDetailsPane({ visible: true, animated: false });
+ ok(!detailsPane.classList.contains("pane-collapsed") &&
+ !detailsPaneToggleButton.classList.contains("pane-collapsed"),
+ "The details pane should be visible after clicking the toggle button.");
+
+ // Click clear and make sure the details pane closes
+ EventUtils.sendMouseEvent({ type: "click" }, clearButton);
+ assertNoRequestState();
+ ok(detailsPane.classList.contains("pane-collapsed") &&
+ detailsPaneToggleButton.classList.contains("pane-collapsed"),
+ "The details pane should not be visible clicking 'clear'.");
+
+ return teardown(monitor);
+
+ /**
+ * Asserts the state of the network monitor when one request has loaded
+ */
+ function assertSingleRequestState() {
+ is(RequestsMenu.itemCount, 1,
+ "The request menu should have one item at this point.");
+ is(detailsPaneToggleButton.hasAttribute("disabled"), false,
+ "The pane toggle button should be enabled after a request is made.");
+ }
+
+ /**
+ * Asserts the state of the network monitor when no requests have loaded
+ */
+ function assertNoRequestState() {
+ is(RequestsMenu.itemCount, 0,
+ "The request menu should be empty at this point.");
+ is(detailsPaneToggleButton.hasAttribute("disabled"), true,
+ "The pane toggle button should be disabled when the request menu is cleared.");
+ }
+});
diff --git a/devtools/client/netmonitor/test/browser_net_complex-params.js b/devtools/client/netmonitor/test/browser_net_complex-params.js
new file mode 100644
index 000000000..103c644bb
--- /dev/null
+++ b/devtools/client/netmonitor/test/browser_net_complex-params.js
@@ -0,0 +1,195 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+/**
+ * Tests whether complex request params and payload sent via POST are
+ * displayed correctly.
+ */
+
+add_task(function* () {
+ let { L10N } = require("devtools/client/netmonitor/l10n");
+
+ let { tab, monitor } = yield initNetMonitor(PARAMS_URL);
+ info("Starting test... ");
+
+ let { document, EVENTS, Editor, NetMonitorView } = monitor.panelWin;
+ let { RequestsMenu, NetworkDetails } = NetMonitorView;
+
+ RequestsMenu.lazyUpdate = false;
+ NetworkDetails._params.lazyEmpty = false;
+
+ let wait = waitForNetworkEvents(monitor, 1, 6);
+ yield ContentTask.spawn(tab.linkedBrowser, {}, function* () {
+ content.wrappedJSObject.performRequests();
+ });
+ yield wait;
+
+ let onEvent = monitor.panelWin.once(EVENTS.REQUEST_POST_PARAMS_DISPLAYED);
+ EventUtils.sendMouseEvent({ type: "mousedown" },
+ document.getElementById("details-pane-toggle"));
+ EventUtils.sendMouseEvent({ type: "mousedown" },
+ document.querySelectorAll("#details-pane tab")[2]);
+ yield onEvent;
+ yield testParamsTab1("a", '""', '{ "foo": "bar" }', '""');
+
+ onEvent = monitor.panelWin.once(EVENTS.REQUEST_POST_PARAMS_DISPLAYED);
+ RequestsMenu.selectedIndex = 1;
+ yield onEvent;
+ yield testParamsTab1("a", '"b"', '{ "foo": "bar" }', '""');
+
+ onEvent = monitor.panelWin.once(EVENTS.REQUEST_POST_PARAMS_DISPLAYED);
+ RequestsMenu.selectedIndex = 2;
+ yield onEvent;
+ yield testParamsTab1("a", '"b"', "foo", '"bar"');
+
+ onEvent = monitor.panelWin.once(EVENTS.REQUEST_POST_PARAMS_DISPLAYED);
+ RequestsMenu.selectedIndex = 3;
+ yield onEvent;
+ yield testParamsTab2("a", '""', '{ "foo": "bar" }', "js");
+
+ onEvent = monitor.panelWin.once(EVENTS.REQUEST_POST_PARAMS_DISPLAYED);
+ RequestsMenu.selectedIndex = 4;
+ yield onEvent;
+ yield testParamsTab2("a", '"b"', '{ "foo": "bar" }', "js");
+
+ onEvent = monitor.panelWin.once(EVENTS.REQUEST_POST_PARAMS_DISPLAYED);
+ RequestsMenu.selectedIndex = 5;
+ yield onEvent;
+ yield testParamsTab2("a", '"b"', "?foo=bar", "text");
+
+ onEvent = monitor.panelWin.once(EVENTS.SIDEBAR_POPULATED);
+ RequestsMenu.selectedIndex = 6;
+ yield onEvent;
+ yield testParamsTab3("a", '"b"');
+
+ yield teardown(monitor);
+
+ function testParamsTab1(queryStringParamName, queryStringParamValue,
+ formDataParamName, formDataParamValue) {
+ let tabpanel = document.querySelectorAll("#details-pane tabpanel")[2];
+
+ is(tabpanel.querySelectorAll(".variables-view-scope").length, 2,
+ "The number of param scopes displayed in this tabpanel is incorrect.");
+ is(tabpanel.querySelectorAll(".variable-or-property").length, 2,
+ "The number of param values displayed in this tabpanel is incorrect.");
+ is(tabpanel.querySelectorAll(".variables-view-empty-notice").length, 0,
+ "The empty notice should not be displayed in this tabpanel.");
+
+ is(tabpanel.querySelector("#request-params-box")
+ .hasAttribute("hidden"), false,
+ "The request params box should not be hidden.");
+ is(tabpanel.querySelector("#request-post-data-textarea-box")
+ .hasAttribute("hidden"), true,
+ "The request post data textarea box should be hidden.");
+
+ let paramsScope = tabpanel.querySelectorAll(".variables-view-scope")[0];
+ let formDataScope = tabpanel.querySelectorAll(".variables-view-scope")[1];
+
+ is(paramsScope.querySelector(".name").getAttribute("value"),
+ L10N.getStr("paramsQueryString"),
+ "The params scope doesn't have the correct title.");
+ is(formDataScope.querySelector(".name").getAttribute("value"),
+ L10N.getStr("paramsFormData"),
+ "The form data scope doesn't have the correct title.");
+
+ is(paramsScope.querySelectorAll(".variables-view-variable .name")[0]
+ .getAttribute("value"),
+ queryStringParamName,
+ "The first query string param name was incorrect.");
+ is(paramsScope.querySelectorAll(".variables-view-variable .value")[0]
+ .getAttribute("value"),
+ queryStringParamValue,
+ "The first query string param value was incorrect.");
+
+ is(formDataScope.querySelectorAll(".variables-view-variable .name")[0]
+ .getAttribute("value"),
+ formDataParamName,
+ "The first form data param name was incorrect.");
+ is(formDataScope.querySelectorAll(".variables-view-variable .value")[0]
+ .getAttribute("value"),
+ formDataParamValue,
+ "The first form data param value was incorrect.");
+ }
+
+ function* testParamsTab2(queryStringParamName, queryStringParamValue,
+ requestPayload, editorMode) {
+ let isJSON = editorMode == "js";
+ let tabpanel = document.querySelectorAll("#details-pane tabpanel")[2];
+
+ is(tabpanel.querySelectorAll(".variables-view-scope").length, 2,
+ "The number of param scopes displayed in this tabpanel is incorrect.");
+ is(tabpanel.querySelectorAll(".variable-or-property").length, isJSON ? 4 : 1,
+ "The number of param values displayed in this tabpanel is incorrect.");
+ is(tabpanel.querySelectorAll(".variables-view-empty-notice").length, 0,
+ "The empty notice should not be displayed in this tabpanel.");
+
+ is(tabpanel.querySelector("#request-params-box")
+ .hasAttribute("hidden"), false,
+ "The request params box should not be hidden.");
+ is(tabpanel.querySelector("#request-post-data-textarea-box")
+ .hasAttribute("hidden"), isJSON,
+ "The request post data textarea box should be hidden.");
+
+ let paramsScope = tabpanel.querySelectorAll(".variables-view-scope")[0];
+ let payloadScope = tabpanel.querySelectorAll(".variables-view-scope")[1];
+
+ is(paramsScope.querySelector(".name").getAttribute("value"),
+ L10N.getStr("paramsQueryString"),
+ "The params scope doesn't have the correct title.");
+ is(payloadScope.querySelector(".name").getAttribute("value"),
+ isJSON ? L10N.getStr("jsonScopeName") : L10N.getStr("paramsPostPayload"),
+ "The request payload scope doesn't have the correct title.");
+
+ is(paramsScope.querySelectorAll(".variables-view-variable .name")[0]
+ .getAttribute("value"),
+ queryStringParamName,
+ "The first query string param name was incorrect.");
+ is(paramsScope.querySelectorAll(".variables-view-variable .value")[0]
+ .getAttribute("value"),
+ queryStringParamValue,
+ "The first query string param value was incorrect.");
+
+ if (isJSON) {
+ let requestPayloadObject = JSON.parse(requestPayload);
+ let requestPairs = Object.keys(requestPayloadObject)
+ .map(k => [k, requestPayloadObject[k]]);
+ let displayedNames = payloadScope.querySelectorAll(
+ ".variables-view-property.variable-or-property .name");
+ let displayedValues = payloadScope.querySelectorAll(
+ ".variables-view-property.variable-or-property .value");
+ for (let i = 0; i < requestPairs.length; i++) {
+ let [requestPayloadName, requestPayloadValue] = requestPairs[i];
+ is(requestPayloadName, displayedNames[i].getAttribute("value"),
+ "JSON property name " + i + " should be displayed correctly");
+ is('"' + requestPayloadValue + '"', displayedValues[i].getAttribute("value"),
+ "JSON property value " + i + " should be displayed correctly");
+ }
+ } else {
+ let editor = yield NetMonitorView.editor("#request-post-data-textarea");
+ is(editor.getText(), requestPayload,
+ "The text shown in the source editor is incorrect.");
+ is(editor.getMode(), Editor.modes[editorMode],
+ "The mode active in the source editor is incorrect.");
+ }
+ }
+
+ function testParamsTab3(queryStringParamName, queryStringParamValue) {
+ let tabpanel = document.querySelectorAll("#details-pane tabpanel")[2];
+
+ is(tabpanel.querySelectorAll(".variables-view-scope").length, 0,
+ "The number of param scopes displayed in this tabpanel is incorrect.");
+ is(tabpanel.querySelectorAll(".variable-or-property").length, 0,
+ "The number of param values displayed in this tabpanel is incorrect.");
+ is(tabpanel.querySelectorAll(".variables-view-empty-notice").length, 1,
+ "The empty notice should be displayed in this tabpanel.");
+
+ is(tabpanel.querySelector("#request-params-box")
+ .hasAttribute("hidden"), false,
+ "The request params box should not be hidden.");
+ is(tabpanel.querySelector("#request-post-data-textarea-box")
+ .hasAttribute("hidden"), true,
+ "The request post data textarea box should be hidden.");
+ }
+});
diff --git a/devtools/client/netmonitor/test/browser_net_content-type.js b/devtools/client/netmonitor/test/browser_net_content-type.js
new file mode 100644
index 000000000..1951bc69d
--- /dev/null
+++ b/devtools/client/netmonitor/test/browser_net_content-type.js
@@ -0,0 +1,255 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+/**
+ * Tests if different response content types are handled correctly.
+ */
+
+add_task(function* () {
+ let { L10N } = require("devtools/client/netmonitor/l10n");
+
+ let { tab, monitor } = yield initNetMonitor(CONTENT_TYPE_WITHOUT_CACHE_URL);
+ info("Starting test... ");
+
+ let { document, Editor, NetMonitorView } = monitor.panelWin;
+ let { RequestsMenu } = NetMonitorView;
+
+ RequestsMenu.lazyUpdate = false;
+
+ let wait = waitForNetworkEvents(monitor, CONTENT_TYPE_WITHOUT_CACHE_REQUESTS);
+ yield ContentTask.spawn(tab.linkedBrowser, {}, function* () {
+ content.wrappedJSObject.performRequests();
+ });
+ yield wait;
+
+ verifyRequestItemTarget(RequestsMenu.getItemAtIndex(0),
+ "GET", CONTENT_TYPE_SJS + "?fmt=xml", {
+ status: 200,
+ statusText: "OK",
+ type: "xml",
+ fullMimeType: "text/xml; charset=utf-8",
+ size: L10N.getFormatStrWithNumbers("networkMenu.sizeB", 42),
+ time: true
+ });
+ verifyRequestItemTarget(RequestsMenu.getItemAtIndex(1),
+ "GET", CONTENT_TYPE_SJS + "?fmt=css", {
+ status: 200,
+ statusText: "OK",
+ type: "css",
+ fullMimeType: "text/css; charset=utf-8",
+ size: L10N.getFormatStrWithNumbers("networkMenu.sizeB", 34),
+ time: true
+ });
+ verifyRequestItemTarget(RequestsMenu.getItemAtIndex(2),
+ "GET", CONTENT_TYPE_SJS + "?fmt=js", {
+ status: 200,
+ statusText: "OK",
+ type: "js",
+ fullMimeType: "application/javascript; charset=utf-8",
+ size: L10N.getFormatStrWithNumbers("networkMenu.sizeB", 34),
+ time: true
+ });
+ verifyRequestItemTarget(RequestsMenu.getItemAtIndex(3),
+ "GET", CONTENT_TYPE_SJS + "?fmt=json", {
+ status: 200,
+ statusText: "OK",
+ type: "json",
+ fullMimeType: "application/json; charset=utf-8",
+ size: L10N.getFormatStrWithNumbers("networkMenu.sizeB", 29),
+ time: true
+ });
+ verifyRequestItemTarget(RequestsMenu.getItemAtIndex(4),
+ "GET", CONTENT_TYPE_SJS + "?fmt=bogus", {
+ status: 404,
+ statusText: "Not Found",
+ type: "html",
+ fullMimeType: "text/html; charset=utf-8",
+ size: L10N.getFormatStrWithNumbers("networkMenu.sizeB", 24),
+ time: true
+ });
+ verifyRequestItemTarget(RequestsMenu.getItemAtIndex(5),
+ "GET", TEST_IMAGE, {
+ fuzzyUrl: true,
+ status: 200,
+ statusText: "OK",
+ type: "png",
+ fullMimeType: "image/png",
+ size: L10N.getFormatStrWithNumbers("networkMenu.sizeB", 580),
+ time: true
+ });
+ verifyRequestItemTarget(RequestsMenu.getItemAtIndex(6),
+ "GET", CONTENT_TYPE_SJS + "?fmt=gzip", {
+ status: 200,
+ statusText: "OK",
+ type: "plain",
+ fullMimeType: "text/plain",
+ transferred: L10N.getFormatStrWithNumbers("networkMenu.sizeB", 73),
+ size: L10N.getFormatStrWithNumbers("networkMenu.sizeKB", 10.73),
+ time: true
+ });
+
+ let onEvent = waitForResponseBodyDisplayed();
+ EventUtils.sendMouseEvent({ type: "mousedown" },
+ document.getElementById("details-pane-toggle"));
+ EventUtils.sendMouseEvent({ type: "mousedown" },
+ document.querySelectorAll("#details-pane tab")[3]);
+ yield onEvent;
+ yield testResponseTab("xml");
+
+ yield selectIndexAndWaitForTabUpdated(1);
+ yield testResponseTab("css");
+
+ yield selectIndexAndWaitForTabUpdated(2);
+ yield testResponseTab("js");
+
+ yield selectIndexAndWaitForTabUpdated(3);
+ yield testResponseTab("json");
+
+ yield selectIndexAndWaitForTabUpdated(4);
+ yield testResponseTab("html");
+
+ yield selectIndexAndWaitForTabUpdated(5);
+ yield testResponseTab("png");
+
+ yield selectIndexAndWaitForTabUpdated(6);
+ yield testResponseTab("gzip");
+
+ yield teardown(monitor);
+
+ function* testResponseTab(type) {
+ let tabEl = document.querySelectorAll("#details-pane tab")[3];
+ let tabpanel = document.querySelectorAll("#details-pane tabpanel")[3];
+
+ is(tabEl.getAttribute("selected"), "true",
+ "The response tab in the network details pane should be selected.");
+
+ function checkVisibility(box) {
+ is(tabpanel.querySelector("#response-content-info-header")
+ .hasAttribute("hidden"), true,
+ "The response info header doesn't have the intended visibility.");
+ is(tabpanel.querySelector("#response-content-json-box")
+ .hasAttribute("hidden"), box != "json",
+ "The response content json box doesn't have the intended visibility.");
+ is(tabpanel.querySelector("#response-content-textarea-box")
+ .hasAttribute("hidden"), box != "textarea",
+ "The response content textarea box doesn't have the intended visibility.");
+ is(tabpanel.querySelector("#response-content-image-box")
+ .hasAttribute("hidden"), box != "image",
+ "The response content image box doesn't have the intended visibility.");
+ }
+
+ switch (type) {
+ case "xml": {
+ checkVisibility("textarea");
+
+ let editor = yield NetMonitorView.editor("#response-content-textarea");
+ is(editor.getText(), "<label value='greeting'>Hello XML!</label>",
+ "The text shown in the source editor is incorrect for the xml request.");
+ is(editor.getMode(), Editor.modes.html,
+ "The mode active in the source editor is incorrect for the xml request.");
+ break;
+ }
+ case "css": {
+ checkVisibility("textarea");
+
+ let editor = yield NetMonitorView.editor("#response-content-textarea");
+ is(editor.getText(), "body:pre { content: 'Hello CSS!' }",
+ "The text shown in the source editor is incorrect for the xml request.");
+ is(editor.getMode(), Editor.modes.css,
+ "The mode active in the source editor is incorrect for the xml request.");
+ break;
+ }
+ case "js": {
+ checkVisibility("textarea");
+
+ let editor = yield NetMonitorView.editor("#response-content-textarea");
+ is(editor.getText(), "function() { return 'Hello JS!'; }",
+ "The text shown in the source editor is incorrect for the xml request.");
+ is(editor.getMode(), Editor.modes.js,
+ "The mode active in the source editor is incorrect for the xml request.");
+ break;
+ }
+ case "json": {
+ checkVisibility("json");
+
+ is(tabpanel.querySelectorAll(".variables-view-scope").length, 1,
+ "There should be 1 json scope displayed in this tabpanel.");
+ is(tabpanel.querySelectorAll(".variables-view-property").length, 2,
+ "There should be 2 json properties displayed in this tabpanel.");
+ is(tabpanel.querySelectorAll(".variables-view-empty-notice").length, 0,
+ "The empty notice should not be displayed in this tabpanel.");
+
+ let jsonScope = tabpanel.querySelectorAll(".variables-view-scope")[0];
+
+ is(jsonScope.querySelector(".name").getAttribute("value"),
+ L10N.getStr("jsonScopeName"),
+ "The json scope doesn't have the correct title.");
+
+ is(jsonScope.querySelectorAll(".variables-view-property .name")[0]
+ .getAttribute("value"),
+ "greeting", "The first json property name was incorrect.");
+ is(jsonScope.querySelectorAll(".variables-view-property .value")[0]
+ .getAttribute("value"),
+ "\"Hello JSON!\"", "The first json property value was incorrect.");
+
+ is(jsonScope.querySelectorAll(".variables-view-property .name")[1]
+ .getAttribute("value"),
+ "__proto__", "The second json property name was incorrect.");
+ is(jsonScope.querySelectorAll(".variables-view-property .value")[1]
+ .getAttribute("value"),
+ "Object", "The second json property value was incorrect.");
+ break;
+ }
+ case "html": {
+ checkVisibility("textarea");
+
+ let editor = yield NetMonitorView.editor("#response-content-textarea");
+ is(editor.getText(), "<blink>Not Found</blink>",
+ "The text shown in the source editor is incorrect for the xml request.");
+ is(editor.getMode(), Editor.modes.html,
+ "The mode active in the source editor is incorrect for the xml request.");
+ break;
+ }
+ case "png": {
+ checkVisibility("image");
+
+ let imageNode = tabpanel.querySelector("#response-content-image");
+ yield once(imageNode, "load");
+
+ is(tabpanel.querySelector("#response-content-image-name-value")
+ .getAttribute("value"), "test-image.png",
+ "The image name info isn't correct.");
+ is(tabpanel.querySelector("#response-content-image-mime-value")
+ .getAttribute("value"), "image/png",
+ "The image mime info isn't correct.");
+ is(tabpanel.querySelector("#response-content-image-dimensions-value")
+ .getAttribute("value"), "16" + " \u00D7 " + "16",
+ "The image dimensions info isn't correct.");
+ break;
+ }
+ case "gzip": {
+ checkVisibility("textarea");
+
+ let expected = new Array(1000).join("Hello gzip!");
+ let editor = yield NetMonitorView.editor("#response-content-textarea");
+ is(editor.getText(), expected,
+ "The text shown in the source editor is incorrect for the gzip request.");
+ is(editor.getMode(), Editor.modes.text,
+ "The mode active in the source editor is incorrect for the gzip request.");
+ break;
+ }
+ }
+ }
+
+ function selectIndexAndWaitForTabUpdated(index) {
+ let onTabUpdated = monitor.panelWin.once(monitor.panelWin.EVENTS.TAB_UPDATED);
+ RequestsMenu.selectedIndex = index;
+ return onTabUpdated;
+ }
+
+ function waitForResponseBodyDisplayed() {
+ return monitor.panelWin.once(monitor.panelWin.EVENTS.RESPONSE_BODY_DISPLAYED);
+ }
+});
diff --git a/devtools/client/netmonitor/test/browser_net_copy_as_curl.js b/devtools/client/netmonitor/test/browser_net_copy_as_curl.js
new file mode 100644
index 000000000..9cf66aa4f
--- /dev/null
+++ b/devtools/client/netmonitor/test/browser_net_copy_as_curl.js
@@ -0,0 +1,88 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+/**
+ * Tests if Copy as cURL works.
+ */
+
+add_task(function* () {
+ let { tab, monitor } = yield initNetMonitor(CURL_URL);
+ info("Starting test... ");
+
+ // Different quote chars are used for Windows and POSIX
+ const QUOTE = Services.appinfo.OS == "WINNT" ? "\"" : "'";
+
+ // Quote a string, escape the quotes inside the string
+ function quote(str) {
+ return QUOTE + str.replace(new RegExp(QUOTE, "g"), `\\${QUOTE}`) + QUOTE;
+ }
+
+ // Header param is formatted as -H "Header: value" or -H 'Header: value'
+ function header(h) {
+ return "-H " + quote(h);
+ }
+
+ // Construct the expected command
+ const EXPECTED_RESULT = [
+ "curl " + quote(SIMPLE_SJS),
+ "--compressed",
+ header("Host: example.com"),
+ header("User-Agent: " + navigator.userAgent),
+ header("Accept: */*"),
+ header("Accept-Language: " + navigator.language),
+ header("X-Custom-Header-1: Custom value"),
+ header("X-Custom-Header-2: 8.8.8.8"),
+ header("X-Custom-Header-3: Mon, 3 Mar 2014 11:11:11 GMT"),
+ header("Referer: " + CURL_URL),
+ header("Connection: keep-alive"),
+ header("Pragma: no-cache"),
+ header("Cache-Control: no-cache")
+ ];
+
+ let { NetMonitorView } = monitor.panelWin;
+ let { RequestsMenu } = NetMonitorView;
+ RequestsMenu.lazyUpdate = false;
+
+ let wait = waitForNetworkEvents(monitor, 1);
+ yield ContentTask.spawn(tab.linkedBrowser, SIMPLE_SJS, function* (url) {
+ content.wrappedJSObject.performRequest(url);
+ });
+ yield wait;
+
+ let requestItem = RequestsMenu.getItemAtIndex(0);
+ RequestsMenu.selectedItem = requestItem;
+
+ yield waitForClipboardPromise(function setup() {
+ RequestsMenu.contextMenu.copyAsCurl();
+ }, function validate(result) {
+ if (typeof result !== "string") {
+ return false;
+ }
+
+ // Different setups may produce the same command, but with the
+ // parameters in a different order in the commandline (which is fine).
+ // Here we confirm that the commands are the same even in that case.
+
+ // This monster regexp parses the command line into an array of arguments,
+ // recognizing quoted args with matching quotes and escaped quotes inside:
+ // [ "curl 'url'", "--standalone-arg", "-arg-with-quoted-string 'value\'s'" ]
+ let matchRe = /[-A-Za-z1-9]+(?: ([\"'])(?:\\\1|.)*?\1)?/g;
+
+ let actual = result.match(matchRe);
+
+ // Must begin with the same "curl 'URL'" segment
+ if (!actual || EXPECTED_RESULT[0] != actual[0]) {
+ return false;
+ }
+
+ // Must match each of the params in the middle (headers and --compressed)
+ return EXPECTED_RESULT.length === actual.length &&
+ EXPECTED_RESULT.every(param => actual.includes(param));
+ });
+
+ info("Clipboard contains a cURL command for the currently selected item's url.");
+
+ yield teardown(monitor);
+});
diff --git a/devtools/client/netmonitor/test/browser_net_copy_headers.js b/devtools/client/netmonitor/test/browser_net_copy_headers.js
new file mode 100644
index 000000000..36ce2fb34
--- /dev/null
+++ b/devtools/client/netmonitor/test/browser_net_copy_headers.js
@@ -0,0 +1,72 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+/**
+ * Tests if copying a request's request/response headers works.
+ */
+
+add_task(function* () {
+ let { tab, monitor } = yield initNetMonitor(SIMPLE_URL);
+ info("Starting test... ");
+
+ let { NetMonitorView } = monitor.panelWin;
+ let { RequestsMenu } = NetMonitorView;
+
+ RequestsMenu.lazyUpdate = false;
+
+ let wait = waitForNetworkEvents(monitor, 1);
+ tab.linkedBrowser.reload();
+ yield wait;
+
+ let requestItem = RequestsMenu.getItemAtIndex(0);
+ RequestsMenu.selectedItem = requestItem;
+
+ let { method, httpVersion, status, statusText } = requestItem.attachment;
+
+ const EXPECTED_REQUEST_HEADERS = [
+ `${method} ${SIMPLE_URL} ${httpVersion}`,
+ "Host: example.com",
+ "User-Agent: " + navigator.userAgent + "",
+ "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8",
+ "Accept-Language: " + navigator.languages.join(",") + ";q=0.5",
+ "Accept-Encoding: gzip, deflate",
+ "Connection: keep-alive",
+ "Upgrade-Insecure-Requests: 1",
+ "Pragma: no-cache",
+ "Cache-Control: no-cache"
+ ].join("\n");
+
+ yield waitForClipboardPromise(function setup() {
+ RequestsMenu.contextMenu.copyRequestHeaders();
+ }, function validate(result) {
+ // Sometimes, a "Cookie" header is left over from other tests. Remove it:
+ result = String(result).replace(/Cookie: [^\n]+\n/, "");
+ return result === EXPECTED_REQUEST_HEADERS;
+ });
+ info("Clipboard contains the currently selected item's request headers.");
+
+ const EXPECTED_RESPONSE_HEADERS = [
+ `${httpVersion} ${status} ${statusText}`,
+ "Last-Modified: Sun, 3 May 2015 11:11:11 GMT",
+ "Content-Type: text/html",
+ "Content-Length: 465",
+ "Connection: close",
+ "Server: httpd.js",
+ "Date: Sun, 3 May 2015 11:11:11 GMT"
+ ].join("\n");
+
+ yield waitForClipboardPromise(function setup() {
+ RequestsMenu.contextMenu.copyResponseHeaders();
+ }, function validate(result) {
+ // Fake the "Last-Modified" and "Date" headers because they will vary:
+ result = String(result)
+ .replace(/Last-Modified: [^\n]+ GMT/, "Last-Modified: Sun, 3 May 2015 11:11:11 GMT")
+ .replace(/Date: [^\n]+ GMT/, "Date: Sun, 3 May 2015 11:11:11 GMT");
+ return result === EXPECTED_RESPONSE_HEADERS;
+ });
+ info("Clipboard contains the currently selected item's response headers.");
+
+ yield teardown(monitor);
+});
diff --git a/devtools/client/netmonitor/test/browser_net_copy_image_as_data_uri.js b/devtools/client/netmonitor/test/browser_net_copy_image_as_data_uri.js
new file mode 100644
index 000000000..144ced80d
--- /dev/null
+++ b/devtools/client/netmonitor/test/browser_net_copy_image_as_data_uri.js
@@ -0,0 +1,35 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+/**
+ * Tests if copying an image as data uri works.
+ */
+
+add_task(function* () {
+ let { tab, monitor } = yield initNetMonitor(CONTENT_TYPE_WITHOUT_CACHE_URL);
+ info("Starting test... ");
+
+ let { NetMonitorView } = monitor.panelWin;
+ let { RequestsMenu } = NetMonitorView;
+
+ RequestsMenu.lazyUpdate = false;
+
+ let wait = waitForNetworkEvents(monitor, CONTENT_TYPE_WITHOUT_CACHE_REQUESTS);
+ yield ContentTask.spawn(tab.linkedBrowser, {}, function* () {
+ content.wrappedJSObject.performRequests();
+ });
+ yield wait;
+
+ let requestItem = RequestsMenu.getItemAtIndex(5);
+ RequestsMenu.selectedItem = requestItem;
+
+ yield waitForClipboardPromise(function setup() {
+ RequestsMenu.contextMenu.copyImageAsDataUri();
+ }, TEST_IMAGE_DATA_URI);
+
+ ok(true, "Clipboard contains the currently selected image as data uri.");
+
+ yield teardown(monitor);
+});
diff --git a/devtools/client/netmonitor/test/browser_net_copy_params.js b/devtools/client/netmonitor/test/browser_net_copy_params.js
new file mode 100644
index 000000000..1cb6f6620
--- /dev/null
+++ b/devtools/client/netmonitor/test/browser_net_copy_params.js
@@ -0,0 +1,98 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+/**
+ * Tests whether copying a request item's parameters works.
+ */
+
+add_task(function* () {
+ let { tab, monitor } = yield initNetMonitor(PARAMS_URL);
+ info("Starting test... ");
+
+ let { NetMonitorView } = monitor.panelWin;
+ let { RequestsMenu } = NetMonitorView;
+
+ RequestsMenu.lazyUpdate = false;
+
+ let wait = waitForNetworkEvents(monitor, 1, 6);
+ yield ContentTask.spawn(tab.linkedBrowser, {}, function* () {
+ content.wrappedJSObject.performRequests();
+ });
+ yield wait;
+
+ RequestsMenu.selectedItem = RequestsMenu.getItemAtIndex(0);
+ yield testCopyUrlParamsHidden(false);
+ yield testCopyUrlParams("a");
+ yield testCopyPostDataHidden(false);
+ yield testCopyPostData("{ \"foo\": \"bar\" }");
+
+ RequestsMenu.selectedItem = RequestsMenu.getItemAtIndex(1);
+ yield testCopyUrlParamsHidden(false);
+ yield testCopyUrlParams("a=b");
+ yield testCopyPostDataHidden(false);
+ yield testCopyPostData("{ \"foo\": \"bar\" }");
+
+ RequestsMenu.selectedItem = RequestsMenu.getItemAtIndex(2);
+ yield testCopyUrlParamsHidden(false);
+ yield testCopyUrlParams("a=b");
+ yield testCopyPostDataHidden(false);
+ yield testCopyPostData("foo=bar");
+
+ RequestsMenu.selectedItem = RequestsMenu.getItemAtIndex(3);
+ yield testCopyUrlParamsHidden(false);
+ yield testCopyUrlParams("a");
+ yield testCopyPostDataHidden(false);
+ yield testCopyPostData("{ \"foo\": \"bar\" }");
+
+ RequestsMenu.selectedItem = RequestsMenu.getItemAtIndex(4);
+ yield testCopyUrlParamsHidden(false);
+ yield testCopyUrlParams("a=b");
+ yield testCopyPostDataHidden(false);
+ yield testCopyPostData("{ \"foo\": \"bar\" }");
+
+ RequestsMenu.selectedItem = RequestsMenu.getItemAtIndex(5);
+ yield testCopyUrlParamsHidden(false);
+ yield testCopyUrlParams("a=b");
+ yield testCopyPostDataHidden(false);
+ yield testCopyPostData("?foo=bar");
+
+ RequestsMenu.selectedItem = RequestsMenu.getItemAtIndex(6);
+ yield testCopyUrlParamsHidden(true);
+ yield testCopyPostDataHidden(true);
+
+ return teardown(monitor);
+
+ function testCopyUrlParamsHidden(hidden) {
+ let allMenuItems = openContextMenuAndGetAllItems(NetMonitorView);
+ let copyUrlParamsNode = allMenuItems.find(item =>
+ item.id === "request-menu-context-copy-url-params");
+ is(copyUrlParamsNode.visible, !hidden,
+ "The \"Copy URL Parameters\" context menu item should" + (hidden ? " " : " not ") +
+ "be hidden.");
+ }
+
+ function* testCopyUrlParams(queryString) {
+ yield waitForClipboardPromise(function setup() {
+ RequestsMenu.contextMenu.copyUrlParams();
+ }, queryString);
+ ok(true, "The url query string copied from the selected item is correct.");
+ }
+
+ function testCopyPostDataHidden(hidden) {
+ let allMenuItems = openContextMenuAndGetAllItems(NetMonitorView);
+ let copyPostDataNode = allMenuItems.find(item =>
+ item.id === "request-menu-context-copy-post-data");
+ is(copyPostDataNode.visible, !hidden,
+ "The \"Copy POST Data\" context menu item should" + (hidden ? " " : " not ") +
+ "be hidden.");
+ }
+
+ function* testCopyPostData(postData) {
+ yield waitForClipboardPromise(function setup() {
+ RequestsMenu.contextMenu.copyPostData();
+ }, postData);
+ ok(true, "The post data string copied from the selected item is correct.");
+ }
+});
diff --git a/devtools/client/netmonitor/test/browser_net_copy_response.js b/devtools/client/netmonitor/test/browser_net_copy_response.js
new file mode 100644
index 000000000..411fe5cf0
--- /dev/null
+++ b/devtools/client/netmonitor/test/browser_net_copy_response.js
@@ -0,0 +1,35 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+/**
+ * Tests if copying a request's response works.
+ */
+
+add_task(function* () {
+ let { tab, monitor } = yield initNetMonitor(CONTENT_TYPE_WITHOUT_CACHE_URL);
+ info("Starting test... ");
+
+ const EXPECTED_RESULT = '{ "greeting": "Hello JSON!" }';
+
+ let { NetMonitorView } = monitor.panelWin;
+ let { RequestsMenu } = NetMonitorView;
+
+ RequestsMenu.lazyUpdate = false;
+
+ let wait = waitForNetworkEvents(monitor, CONTENT_TYPE_WITHOUT_CACHE_REQUESTS);
+ yield ContentTask.spawn(tab.linkedBrowser, {}, function* () {
+ content.wrappedJSObject.performRequests();
+ });
+ yield wait;
+
+ let requestItem = RequestsMenu.getItemAtIndex(3);
+ RequestsMenu.selectedItem = requestItem;
+
+ yield waitForClipboardPromise(function setup() {
+ RequestsMenu.contextMenu.copyResponse();
+ }, EXPECTED_RESULT);
+
+ yield teardown(monitor);
+});
diff --git a/devtools/client/netmonitor/test/browser_net_copy_svg_image_as_data_uri.js b/devtools/client/netmonitor/test/browser_net_copy_svg_image_as_data_uri.js
new file mode 100644
index 000000000..252ce92bd
--- /dev/null
+++ b/devtools/client/netmonitor/test/browser_net_copy_svg_image_as_data_uri.js
@@ -0,0 +1,37 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+/**
+ * Tests if copying an image as data uri works.
+ */
+
+const SVG_URL = EXAMPLE_URL + "dropmarker.svg";
+
+add_task(function* () {
+ let { tab, monitor } = yield initNetMonitor(CURL_URL);
+ info("Starting test... ");
+
+ let { NetMonitorView } = monitor.panelWin;
+ let { RequestsMenu } = NetMonitorView;
+
+ RequestsMenu.lazyUpdate = false;
+
+ let wait = waitForNetworkEvents(monitor, 1);
+ yield ContentTask.spawn(tab.linkedBrowser, SVG_URL, function* (url) {
+ content.wrappedJSObject.performRequest(url);
+ });
+ yield wait;
+
+ let requestItem = RequestsMenu.getItemAtIndex(0);
+ RequestsMenu.selectedItem = requestItem;
+
+ yield waitForClipboardPromise(function setup() {
+ RequestsMenu.contextMenu.copyImageAsDataUri();
+ }, function check(text) {
+ return text.startsWith("data:") && !/undefined/.test(text);
+ });
+
+ yield teardown(monitor);
+});
diff --git a/devtools/client/netmonitor/test/browser_net_copy_url.js b/devtools/client/netmonitor/test/browser_net_copy_url.js
new file mode 100644
index 000000000..660f5fe79
--- /dev/null
+++ b/devtools/client/netmonitor/test/browser_net_copy_url.js
@@ -0,0 +1,31 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+/**
+ * Tests if copying a request's url works.
+ */
+
+add_task(function* () {
+ let { tab, monitor } = yield initNetMonitor(CUSTOM_GET_URL);
+ info("Starting test... ");
+
+ let { NetMonitorView } = monitor.panelWin;
+ let { RequestsMenu } = NetMonitorView;
+
+ let wait = waitForNetworkEvents(monitor, 1);
+ yield ContentTask.spawn(tab.linkedBrowser, {}, function* () {
+ content.wrappedJSObject.performRequests(1);
+ });
+ yield wait;
+
+ let requestItem = RequestsMenu.getItemAtIndex(0);
+ RequestsMenu.selectedItem = requestItem;
+
+ yield waitForClipboardPromise(function setup() {
+ RequestsMenu.contextMenu.copyUrl();
+ }, requestItem.attachment.url);
+
+ yield teardown(monitor);
+});
diff --git a/devtools/client/netmonitor/test/browser_net_cors_requests.js b/devtools/client/netmonitor/test/browser_net_cors_requests.js
new file mode 100644
index 000000000..d61b8e2f0
--- /dev/null
+++ b/devtools/client/netmonitor/test/browser_net_cors_requests.js
@@ -0,0 +1,33 @@
+/* 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 CORS preflight requests are displayed by network monitor
+ */
+
+add_task(function* () {
+ let { tab, monitor } = yield initNetMonitor(CORS_URL);
+ let { RequestsMenu } = monitor.panelWin.NetMonitorView;
+ RequestsMenu.lazyUpdate = false;
+
+ let wait = waitForNetworkEvents(monitor, 1, 1);
+
+ info("Performing a CORS request");
+ let requestUrl = "http://test1.example.com" + CORS_SJS_PATH;
+ yield ContentTask.spawn(tab.linkedBrowser, requestUrl, function* (url) {
+ content.wrappedJSObject.performRequests(url, "triggering/preflight", "post-data");
+ });
+
+ info("Waiting until the requests appear in netmonitor");
+ yield wait;
+
+ info("Checking the preflight and flight methods");
+ ["OPTIONS", "POST"].forEach((method, i) => {
+ verifyRequestItemTarget(RequestsMenu.getItemAtIndex(i), method, requestUrl);
+ });
+
+ yield teardown(monitor);
+});
diff --git a/devtools/client/netmonitor/test/browser_net_curl-utils.js b/devtools/client/netmonitor/test/browser_net_curl-utils.js
new file mode 100644
index 000000000..7a5fc7926
--- /dev/null
+++ b/devtools/client/netmonitor/test/browser_net_curl-utils.js
@@ -0,0 +1,228 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+/**
+ * Tests Curl Utils functionality.
+ */
+
+const { CurlUtils } = require("devtools/client/shared/curl");
+
+add_task(function* () {
+ let { tab, monitor } = yield initNetMonitor(CURL_UTILS_URL);
+ info("Starting test... ");
+
+ let { NetMonitorView, gNetwork } = monitor.panelWin;
+ let { RequestsMenu } = NetMonitorView;
+
+ RequestsMenu.lazyUpdate = false;
+
+ let wait = waitForNetworkEvents(monitor, 1, 3);
+ yield ContentTask.spawn(tab.linkedBrowser, SIMPLE_SJS, function* (url) {
+ content.wrappedJSObject.performRequests(url);
+ });
+ yield wait;
+
+ let requests = {
+ get: RequestsMenu.getItemAtIndex(0),
+ post: RequestsMenu.getItemAtIndex(1),
+ multipart: RequestsMenu.getItemAtIndex(2),
+ multipartForm: RequestsMenu.getItemAtIndex(3)
+ };
+
+ let data = yield createCurlData(requests.get.attachment, gNetwork);
+ testFindHeader(data);
+
+ data = yield createCurlData(requests.post.attachment, gNetwork);
+ testIsUrlEncodedRequest(data);
+ testWritePostDataTextParams(data);
+
+ data = yield createCurlData(requests.multipart.attachment, gNetwork);
+ testIsMultipartRequest(data);
+ testGetMultipartBoundary(data);
+ testRemoveBinaryDataFromMultipartText(data);
+
+ data = yield createCurlData(requests.multipartForm.attachment, gNetwork);
+ testGetHeadersFromMultipartText(data);
+
+ if (Services.appinfo.OS != "WINNT") {
+ testEscapeStringPosix();
+ } else {
+ testEscapeStringWin();
+ }
+
+ yield teardown(monitor);
+});
+
+function testIsUrlEncodedRequest(data) {
+ let isUrlEncoded = CurlUtils.isUrlEncodedRequest(data);
+ ok(isUrlEncoded, "Should return true for url encoded requests.");
+}
+
+function testIsMultipartRequest(data) {
+ let isMultipart = CurlUtils.isMultipartRequest(data);
+ ok(isMultipart, "Should return true for multipart/form-data requests.");
+}
+
+function testFindHeader(data) {
+ let headers = data.headers;
+ let hostName = CurlUtils.findHeader(headers, "Host");
+ let requestedWithLowerCased = CurlUtils.findHeader(headers, "x-requested-with");
+ let doesNotExist = CurlUtils.findHeader(headers, "X-Does-Not-Exist");
+
+ is(hostName, "example.com",
+ "Header with name 'Host' should be found in the request array.");
+ is(requestedWithLowerCased, "XMLHttpRequest",
+ "The search should be case insensitive.");
+ is(doesNotExist, null,
+ "Should return null when a header is not found.");
+}
+
+function testWritePostDataTextParams(data) {
+ let params = CurlUtils.writePostDataTextParams(data.postDataText);
+ is(params, "param1=value1&param2=value2&param3=value3",
+ "Should return a serialized representation of the request parameters");
+}
+
+function testGetMultipartBoundary(data) {
+ let boundary = CurlUtils.getMultipartBoundary(data);
+ ok(/-{3,}\w+/.test(boundary),
+ "A boundary string should be found in a multipart request.");
+}
+
+function testRemoveBinaryDataFromMultipartText(data) {
+ let generatedBoundary = CurlUtils.getMultipartBoundary(data);
+ let text = data.postDataText;
+ let binaryRemoved =
+ CurlUtils.removeBinaryDataFromMultipartText(text, generatedBoundary);
+ let boundary = "--" + generatedBoundary;
+
+ const EXPECTED_POSIX_RESULT = [
+ "$'",
+ boundary,
+ "\\r\\n\\r\\n",
+ "Content-Disposition: form-data; name=\"param1\"",
+ "\\r\\n\\r\\n",
+ "value1",
+ "\\r\\n",
+ boundary,
+ "\\r\\n\\r\\n",
+ "Content-Disposition: form-data; name=\"file\"; filename=\"filename.png\"",
+ "\\r\\n",
+ "Content-Type: image/png",
+ "\\r\\n\\r\\n",
+ boundary + "--",
+ "\\r\\n",
+ "'"
+ ].join("");
+
+ const EXPECTED_WIN_RESULT = [
+ '"' + boundary + '"^',
+ "\u000d\u000A\u000d\u000A",
+ '"Content-Disposition: form-data; name=""param1"""^',
+ "\u000d\u000A\u000d\u000A",
+ '"value1"^',
+ "\u000d\u000A",
+ '"' + boundary + '"^',
+ "\u000d\u000A\u000d\u000A",
+ '"Content-Disposition: form-data; name=""file""; filename=""filename.png"""^',
+ "\u000d\u000A",
+ '"Content-Type: image/png"^',
+ "\u000d\u000A\u000d\u000A",
+ '"' + boundary + '--"^',
+ "\u000d\u000A",
+ '""'
+ ].join("");
+
+ if (Services.appinfo.OS != "WINNT") {
+ is(CurlUtils.escapeStringPosix(binaryRemoved), EXPECTED_POSIX_RESULT,
+ "The mulitpart request payload should not contain binary data.");
+ } else {
+ is(CurlUtils.escapeStringWin(binaryRemoved), EXPECTED_WIN_RESULT,
+ "WinNT: The mulitpart request payload should not contain binary data.");
+ }
+}
+
+function testGetHeadersFromMultipartText(data) {
+ let headers = CurlUtils.getHeadersFromMultipartText(data.postDataText);
+
+ ok(Array.isArray(headers), "Should return an array.");
+ ok(headers.length > 0, "There should exist at least one request header.");
+ is(headers[0].name, "Content-Type", "The first header name should be 'Content-Type'.");
+}
+
+function testEscapeStringPosix() {
+ let surroundedWithQuotes = "A simple string";
+ is(CurlUtils.escapeStringPosix(surroundedWithQuotes), "'A simple string'",
+ "The string should be surrounded with single quotes.");
+
+ let singleQuotes = "It's unusual to put crickets in your coffee.";
+ is(CurlUtils.escapeStringPosix(singleQuotes),
+ "$'It\\'s unusual to put crickets in your coffee.'",
+ "Single quotes should be escaped.");
+
+ let newLines = "Line 1\r\nLine 2\u000d\u000ALine3";
+ is(CurlUtils.escapeStringPosix(newLines), "$'Line 1\\r\\nLine 2\\r\\nLine3'",
+ "Newlines should be escaped.");
+
+ let controlChars = "\u0007 \u0009 \u000C \u001B";
+ is(CurlUtils.escapeStringPosix(controlChars), "$'\\x07 \\x09 \\x0c \\x1b'",
+ "Control characters should be escaped.");
+
+ let extendedAsciiChars = "æ ø ü ß ö é";
+ is(CurlUtils.escapeStringPosix(extendedAsciiChars),
+ "$'\\xc3\\xa6 \\xc3\\xb8 \\xc3\\xbc \\xc3\\x9f \\xc3\\xb6 \\xc3\\xa9'",
+ "Character codes outside of the decimal range 32 - 126 should be escaped.");
+}
+
+function testEscapeStringWin() {
+ let surroundedWithDoubleQuotes = "A simple string";
+ is(CurlUtils.escapeStringWin(surroundedWithDoubleQuotes), '"A simple string"',
+ "The string should be surrounded with double quotes.");
+
+ let doubleQuotes = "Quote: \"Time is an illusion. Lunchtime doubly so.\"";
+ is(CurlUtils.escapeStringWin(doubleQuotes),
+ '"Quote: ""Time is an illusion. Lunchtime doubly so."""',
+ "Double quotes should be escaped.");
+
+ let percentSigns = "%AppData%";
+ is(CurlUtils.escapeStringWin(percentSigns), '""%"AppData"%""',
+ "Percent signs should be escaped.");
+
+ let backslashes = "\\A simple string\\";
+ is(CurlUtils.escapeStringWin(backslashes), '"\\\\A simple string\\\\"',
+ "Backslashes should be escaped.");
+
+ let newLines = "line1\r\nline2\r\nline3";
+ is(CurlUtils.escapeStringWin(newLines),
+ '"line1"^\u000d\u000A"line2"^\u000d\u000A"line3"',
+ "Newlines should be escaped.");
+}
+
+function* createCurlData(selected, network, controller) {
+ let { url, method, httpVersion } = selected;
+
+ // Create a sanitized object for the Curl command generator.
+ let data = {
+ url,
+ method,
+ headers: [],
+ httpVersion,
+ postDataText: null
+ };
+
+ // Fetch header values.
+ for (let { name, value } of selected.requestHeaders.headers) {
+ let text = yield network.getString(value);
+ data.headers.push({ name: name, value: text });
+ }
+
+ // Fetch the request payload.
+ if (selected.requestPostData) {
+ let postData = selected.requestPostData.postData.text;
+ data.postDataText = yield network.getString(postData);
+ }
+
+ return data;
+}
diff --git a/devtools/client/netmonitor/test/browser_net_cyrillic-01.js b/devtools/client/netmonitor/test/browser_net_cyrillic-01.js
new file mode 100644
index 000000000..43d6f522e
--- /dev/null
+++ b/devtools/client/netmonitor/test/browser_net_cyrillic-01.js
@@ -0,0 +1,45 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+/**
+ * Tests if cyrillic text is rendered correctly in the source editor.
+ */
+
+add_task(function* () {
+ let { tab, monitor } = yield initNetMonitor(CYRILLIC_URL);
+ info("Starting test... ");
+
+ let { document, EVENTS, Editor, NetMonitorView } = monitor.panelWin;
+ let { RequestsMenu } = NetMonitorView;
+
+ RequestsMenu.lazyUpdate = false;
+
+ let wait = waitForNetworkEvents(monitor, 1);
+ yield ContentTask.spawn(tab.linkedBrowser, {}, function* () {
+ content.wrappedJSObject.performRequests();
+ });
+ yield wait;
+
+ verifyRequestItemTarget(RequestsMenu.getItemAtIndex(0),
+ "GET", CONTENT_TYPE_SJS + "?fmt=txt", {
+ status: 200,
+ statusText: "DA DA DA"
+ });
+
+ EventUtils.sendMouseEvent({ type: "mousedown" },
+ document.getElementById("details-pane-toggle"));
+ EventUtils.sendMouseEvent({ type: "mousedown" },
+ document.querySelectorAll("#details-pane tab")[3]);
+
+ yield monitor.panelWin.once(EVENTS.RESPONSE_BODY_DISPLAYED);
+ let editor = yield NetMonitorView.editor("#response-content-textarea");
+ // u044F = я
+ is(editor.getText().indexOf("\u044F"), 26,
+ "The text shown in the source editor is correct.");
+ is(editor.getMode(), Editor.modes.text,
+ "The mode active in the source editor is correct.");
+
+ yield teardown(monitor);
+});
diff --git a/devtools/client/netmonitor/test/browser_net_cyrillic-02.js b/devtools/client/netmonitor/test/browser_net_cyrillic-02.js
new file mode 100644
index 000000000..cd6b2000e
--- /dev/null
+++ b/devtools/client/netmonitor/test/browser_net_cyrillic-02.js
@@ -0,0 +1,44 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+/**
+ * Tests if cyrillic text is rendered correctly in the source editor
+ * when loaded directly from an HTML page.
+ */
+
+add_task(function* () {
+ let { tab, monitor } = yield initNetMonitor(CYRILLIC_URL);
+ info("Starting test... ");
+
+ let { document, EVENTS, Editor, NetMonitorView } = monitor.panelWin;
+ let { RequestsMenu } = NetMonitorView;
+
+ RequestsMenu.lazyUpdate = false;
+
+ let wait = waitForNetworkEvents(monitor, 1);
+ tab.linkedBrowser.reload();
+ yield wait;
+
+ verifyRequestItemTarget(RequestsMenu.getItemAtIndex(0),
+ "GET", CYRILLIC_URL, {
+ status: 200,
+ statusText: "OK"
+ });
+
+ EventUtils.sendMouseEvent({ type: "mousedown" },
+ document.getElementById("details-pane-toggle"));
+ EventUtils.sendMouseEvent({ type: "mousedown" },
+ document.querySelectorAll("#details-pane tab")[3]);
+
+ yield monitor.panelWin.once(EVENTS.RESPONSE_BODY_DISPLAYED);
+ let editor = yield NetMonitorView.editor("#response-content-textarea");
+ // u044F = я
+ is(editor.getText().indexOf("\u044F"), 486,
+ "The text shown in the source editor is correct.");
+ is(editor.getMode(), Editor.modes.html,
+ "The mode active in the source editor is correct.");
+
+ return teardown(monitor);
+});
diff --git a/devtools/client/netmonitor/test/browser_net_details-no-duplicated-content.js b/devtools/client/netmonitor/test/browser_net_details-no-duplicated-content.js
new file mode 100644
index 000000000..c3df51ced
--- /dev/null
+++ b/devtools/client/netmonitor/test/browser_net_details-no-duplicated-content.js
@@ -0,0 +1,172 @@
+/* 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";
+
+// A test to ensure that the content in details pane is not duplicated.
+
+add_task(function* () {
+ let { tab, monitor } = yield initNetMonitor(CUSTOM_GET_URL);
+ let panel = monitor.panelWin;
+ let { NetMonitorView, EVENTS } = panel;
+ let { RequestsMenu, NetworkDetails } = NetMonitorView;
+
+ const COOKIE_UNIQUE_PATH = "/do-not-use-in-other-tests-using-cookies";
+
+ let TEST_CASES = [
+ {
+ desc: "Test headers tab",
+ pageURI: CUSTOM_GET_URL,
+ requestURI: null,
+ isPost: false,
+ tabIndex: 0,
+ variablesView: NetworkDetails._headers,
+ expectedScopeLength: 2,
+ },
+ {
+ desc: "Test cookies tab",
+ pageURI: CUSTOM_GET_URL,
+ requestURI: COOKIE_UNIQUE_PATH,
+ isPost: false,
+ tabIndex: 1,
+ variablesView: NetworkDetails._cookies,
+ expectedScopeLength: 1,
+ },
+ {
+ desc: "Test params tab",
+ pageURI: POST_RAW_URL,
+ requestURI: null,
+ isPost: true,
+ tabIndex: 2,
+ variablesView: NetworkDetails._params,
+ expectedScopeLength: 1,
+ },
+ ];
+
+ info("Adding a cookie for the \"Cookie\" tab test");
+ yield setDocCookie("a=b; path=" + COOKIE_UNIQUE_PATH);
+
+ info("Running tests");
+ for (let spec of TEST_CASES) {
+ yield runTestCase(spec);
+ }
+
+ // Remove the cookie. If an error occurs the path of the cookie ensures it
+ // doesn't mess with the other tests.
+ info("Removing the added cookie.");
+ yield setDocCookie(
+ "a=; expires=Thu, 01 Jan 1970 00:00:00 GMT; path=" + COOKIE_UNIQUE_PATH);
+
+ yield teardown(monitor);
+
+ /**
+ * Set a content document cookie
+ */
+ function setDocCookie(cookie) {
+ return ContentTask.spawn(tab.linkedBrowser, cookie, function* (cookieArg) {
+ content.document.cookie = cookieArg;
+ });
+ }
+
+ /**
+ * A helper that handles the execution of each case.
+ */
+ function* runTestCase(spec) {
+ info("Running case: " + spec.desc);
+ let wait = waitForNetworkEvents(monitor, 1);
+ tab.linkedBrowser.loadURI(spec.pageURI);
+ yield wait;
+
+ RequestsMenu.clear();
+ yield waitForFinalDetailTabUpdate(spec.tabIndex, spec.isPost, spec.requestURI);
+
+ is(spec.variablesView._store.length, spec.expectedScopeLength,
+ "View contains " + spec.expectedScopeLength + " scope headers");
+ }
+
+ /**
+ * A helper that prepares the variables view for the actual testing. It
+ * - selects the correct tab
+ * - performs the specified request to specified URI
+ * - opens the details view
+ * - waits for the final update to happen
+ */
+ function* waitForFinalDetailTabUpdate(tabIndex, isPost, uri) {
+ let onNetworkEvent = panel.once(EVENTS.NETWORK_EVENT);
+ let onDetailsPopulated = panel.once(EVENTS.NETWORKDETAILSVIEW_POPULATED);
+ let onRequestFinished = isPost ?
+ waitForNetworkEvents(monitor, 0, 1) :
+ waitForNetworkEvents(monitor, 1);
+
+ info("Performing a request");
+ yield ContentTask.spawn(tab.linkedBrowser, uri, function* (url) {
+ content.wrappedJSObject.performRequests(1, url);
+ });
+
+ info("Waiting for NETWORK_EVENT");
+ yield onNetworkEvent;
+
+ if (!RequestsMenu.getItemAtIndex(0)) {
+ info("Waiting for the request to be added to the view");
+ yield monitor.panelWin.once(EVENTS.REQUEST_ADDED);
+ }
+
+ ok(true, "Received NETWORK_EVENT. Selecting the item.");
+ let item = RequestsMenu.getItemAtIndex(0);
+ RequestsMenu.selectedItem = item;
+
+ info("Item selected. Waiting for NETWORKDETAILSVIEW_POPULATED");
+ yield onDetailsPopulated;
+
+ info("Received populated event. Selecting tab at index " + tabIndex);
+ NetworkDetails.widget.selectedIndex = tabIndex;
+
+ info("Waiting for request to finish.");
+ yield onRequestFinished;
+
+ ok(true, "Request finished.");
+
+ /**
+ * Because this test uses lazy updates there's four scenarios to consider:
+ * #1: Everything is updated and test is ready to continue.
+ * #2: There's updates that are waiting to be flushed.
+ * #3: Updates are flushed but the tab update is still running.
+ * #4: There's pending updates and a tab update is still running.
+ *
+ * For case #1 there's not going to be a TAB_UPDATED event so don't wait for
+ * it (bug 1106181).
+ *
+ * For cases #2 and #3 it's enough to wait for one TAB_UPDATED event as for
+ * - case #2 the next flush will perform the final update and single
+ * TAB_UPDATED event is emitted.
+ * - case #3 the running update is the final update that'll emit one
+ * TAB_UPDATED event.
+ *
+ * For case #4 we must wait for the updates to be flushed before we can
+ * start waiting for TAB_UPDATED event or we'll continue the test right
+ * after the pending update finishes.
+ */
+ let hasQueuedUpdates = RequestsMenu._updateQueue.length !== 0;
+ let hasRunningTabUpdate = NetworkDetails._viewState.updating[tabIndex];
+
+ if (hasQueuedUpdates || hasRunningTabUpdate) {
+ info("There's pending updates - waiting for them to finish.");
+ info(" hasQueuedUpdates: " + hasQueuedUpdates);
+ info(" hasRunningTabUpdate: " + hasRunningTabUpdate);
+
+ if (hasQueuedUpdates && hasRunningTabUpdate) {
+ info("Waiting for updates to be flushed.");
+ // _flushRequests calls .populate which emits the following event
+ yield panel.once(EVENTS.NETWORKDETAILSVIEW_POPULATED);
+
+ info("Requests flushed.");
+ }
+
+ info("Waiting for final tab update.");
+ yield waitFor(panel, EVENTS.TAB_UPDATED);
+ }
+
+ info("All updates completed.");
+ }
+});
diff --git a/devtools/client/netmonitor/test/browser_net_filter-01.js b/devtools/client/netmonitor/test/browser_net_filter-01.js
new file mode 100644
index 000000000..b0d76c629
--- /dev/null
+++ b/devtools/client/netmonitor/test/browser_net_filter-01.js
@@ -0,0 +1,264 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+/**
+ * Test if filtering items in the network table works correctly.
+ */
+const BASIC_REQUESTS = [
+ { url: "sjs_content-type-test-server.sjs?fmt=html&res=undefined&text=Sample" },
+ { url: "sjs_content-type-test-server.sjs?fmt=css&text=sample" },
+ { url: "sjs_content-type-test-server.sjs?fmt=js&text=sample" },
+];
+
+const REQUESTS_WITH_MEDIA = BASIC_REQUESTS.concat([
+ { url: "sjs_content-type-test-server.sjs?fmt=font" },
+ { url: "sjs_content-type-test-server.sjs?fmt=image" },
+ { url: "sjs_content-type-test-server.sjs?fmt=audio" },
+ { url: "sjs_content-type-test-server.sjs?fmt=video" },
+]);
+
+const REQUESTS_WITH_MEDIA_AND_FLASH = REQUESTS_WITH_MEDIA.concat([
+ { url: "sjs_content-type-test-server.sjs?fmt=flash" },
+]);
+
+const REQUESTS_WITH_MEDIA_AND_FLASH_AND_WS = REQUESTS_WITH_MEDIA_AND_FLASH.concat([
+ /* "Upgrade" is a reserved header and can not be set on XMLHttpRequest */
+ { url: "sjs_content-type-test-server.sjs?fmt=ws" },
+]);
+
+add_task(function* () {
+ let Actions = require("devtools/client/netmonitor/actions/index");
+ let { monitor } = yield initNetMonitor(FILTERING_URL);
+ let { gStore } = monitor.panelWin;
+
+ function setFreetextFilter(value) {
+ gStore.dispatch(Actions.setFilterText(value));
+ }
+
+ info("Starting test... ");
+
+ let { $, NetMonitorView } = monitor.panelWin;
+ let { RequestsMenu } = NetMonitorView;
+
+ RequestsMenu.lazyUpdate = false;
+
+ let wait = waitForNetworkEvents(monitor, 9);
+ loadCommonFrameScript();
+ yield performRequestsInContent(REQUESTS_WITH_MEDIA_AND_FLASH_AND_WS);
+ yield wait;
+
+ EventUtils.sendMouseEvent({ type: "mousedown" }, $("#details-pane-toggle"));
+
+ isnot(RequestsMenu.selectedItem, null,
+ "There should be a selected item in the requests menu.");
+ is(RequestsMenu.selectedIndex, 0,
+ "The first item should be selected in the requests menu.");
+ is(NetMonitorView.detailsPaneHidden, false,
+ "The details pane should not be hidden after toggle button was pressed.");
+
+ // First test with single filters...
+ testFilterButtons(monitor, "all");
+ testContents([1, 1, 1, 1, 1, 1, 1, 1, 1]);
+
+ EventUtils.sendMouseEvent({ type: "click" }, $("#requests-menu-filter-html-button"));
+ testFilterButtons(monitor, "html");
+ testContents([1, 0, 0, 0, 0, 0, 0, 0, 0]);
+
+ // Reset filters
+ EventUtils.sendMouseEvent({ type: "click" }, $("#requests-menu-filter-all-button"));
+ EventUtils.sendMouseEvent({ type: "click" }, $("#requests-menu-filter-css-button"));
+ testFilterButtons(monitor, "css");
+ testContents([0, 1, 0, 0, 0, 0, 0, 0, 0]);
+
+ EventUtils.sendMouseEvent({ type: "click" }, $("#requests-menu-filter-all-button"));
+ EventUtils.sendMouseEvent({ type: "click" }, $("#requests-menu-filter-js-button"));
+ testFilterButtons(monitor, "js");
+ testContents([0, 0, 1, 0, 0, 0, 0, 0, 0]);
+
+ EventUtils.sendMouseEvent({ type: "click" }, $("#requests-menu-filter-all-button"));
+ EventUtils.sendMouseEvent({ type: "click" }, $("#requests-menu-filter-xhr-button"));
+ testFilterButtons(monitor, "xhr");
+ testContents([1, 1, 1, 1, 1, 1, 1, 1, 0]);
+
+ EventUtils.sendMouseEvent({ type: "click" }, $("#requests-menu-filter-all-button"));
+ EventUtils.sendMouseEvent({ type: "click" }, $("#requests-menu-filter-fonts-button"));
+ testFilterButtons(monitor, "fonts");
+ testContents([0, 0, 0, 1, 0, 0, 0, 0, 0]);
+
+ EventUtils.sendMouseEvent({ type: "click" }, $("#requests-menu-filter-all-button"));
+ EventUtils.sendMouseEvent({ type: "click" }, $("#requests-menu-filter-images-button"));
+ testFilterButtons(monitor, "images");
+ testContents([0, 0, 0, 0, 1, 0, 0, 0, 0]);
+
+ EventUtils.sendMouseEvent({ type: "click" }, $("#requests-menu-filter-all-button"));
+ EventUtils.sendMouseEvent({ type: "click" }, $("#requests-menu-filter-media-button"));
+ testFilterButtons(monitor, "media");
+ testContents([0, 0, 0, 0, 0, 1, 1, 0, 0]);
+
+ EventUtils.sendMouseEvent({ type: "click" }, $("#requests-menu-filter-all-button"));
+ EventUtils.sendMouseEvent({ type: "click" }, $("#requests-menu-filter-flash-button"));
+ testFilterButtons(monitor, "flash");
+ testContents([0, 0, 0, 0, 0, 0, 0, 1, 0]);
+
+ EventUtils.sendMouseEvent({ type: "click" }, $("#requests-menu-filter-all-button"));
+ EventUtils.sendMouseEvent({ type: "click" }, $("#requests-menu-filter-ws-button"));
+ testFilterButtons(monitor, "ws");
+ testContents([0, 0, 0, 0, 0, 0, 0, 0, 1]);
+
+ EventUtils.sendMouseEvent({ type: "click" }, $("#requests-menu-filter-all-button"));
+ testFilterButtons(monitor, "all");
+ testContents([1, 1, 1, 1, 1, 1, 1, 1, 1]);
+
+ // Text in filter box that matches nothing should hide all.
+ EventUtils.sendMouseEvent({ type: "click" }, $("#requests-menu-filter-all-button"));
+ setFreetextFilter("foobar");
+ testContents([0, 0, 0, 0, 0, 0, 0, 0, 0]);
+
+ // Text in filter box that matches should filter out everything else.
+ EventUtils.sendMouseEvent({ type: "click" }, $("#requests-menu-filter-all-button"));
+ setFreetextFilter("sample");
+ testContents([1, 1, 1, 0, 0, 0, 0, 0, 0]);
+
+ // Text in filter box that matches should filter out everything else.
+ EventUtils.sendMouseEvent({ type: "click" }, $("#requests-menu-filter-all-button"));
+ setFreetextFilter("SAMPLE");
+ testContents([1, 1, 1, 0, 0, 0, 0, 0, 0]);
+
+ // Test negative filtering (only show unmatched items)
+ EventUtils.sendMouseEvent({ type: "click" }, $("#requests-menu-filter-all-button"));
+ setFreetextFilter("-sample");
+ testContents([0, 0, 0, 1, 1, 1, 1, 1, 1]);
+
+ // ...then combine multiple filters together.
+
+ // Enable filtering for html and css; should show request of both type.
+ setFreetextFilter("");
+ EventUtils.sendMouseEvent({ type: "click" }, $("#requests-menu-filter-html-button"));
+ EventUtils.sendMouseEvent({ type: "click" }, $("#requests-menu-filter-css-button"));
+ testFilterButtonsCustom(monitor, [0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0]);
+ testContents([1, 1, 0, 0, 0, 0, 0, 0, 0]);
+
+ // Html and css filter enabled and text filter should show just the html and css match.
+ // Should not show both the items matching the button plus the items matching the text.
+ setFreetextFilter("sample");
+ testContents([1, 1, 0, 0, 0, 0, 0, 0, 0]);
+
+ EventUtils.sendMouseEvent({ type: "click" }, $("#requests-menu-filter-flash-button"));
+ setFreetextFilter("");
+ testFilterButtonsCustom(monitor, [0, 1, 1, 0, 0, 0, 0, 0, 1, 0, 0]);
+ testContents([1, 1, 0, 0, 0, 0, 0, 1, 0]);
+
+ // Disable some filters. Only one left active.
+ EventUtils.sendMouseEvent({ type: "click" }, $("#requests-menu-filter-css-button"));
+ EventUtils.sendMouseEvent({ type: "click" }, $("#requests-menu-filter-flash-button"));
+ testFilterButtons(monitor, "html");
+ testContents([1, 0, 0, 0, 0, 0, 0, 0, 0]);
+
+ // Disable last active filter. Should toggle to all.
+ EventUtils.sendMouseEvent({ type: "click" }, $("#requests-menu-filter-html-button"));
+ testFilterButtons(monitor, "all");
+ testContents([1, 1, 1, 1, 1, 1, 1, 1, 1]);
+
+ // Enable few filters and click on all. Only "all" should be checked.
+ EventUtils.sendMouseEvent({ type: "click" }, $("#requests-menu-filter-html-button"));
+ EventUtils.sendMouseEvent({ type: "click" }, $("#requests-menu-filter-css-button"));
+ EventUtils.sendMouseEvent({ type: "click" }, $("#requests-menu-filter-ws-button"));
+ testFilterButtonsCustom(monitor, [0, 1, 1, 0, 0, 0, 0, 0, 0, 1]);
+ EventUtils.sendMouseEvent({ type: "click" }, $("#requests-menu-filter-all-button"));
+ testFilterButtons(monitor, "all");
+ testContents([1, 1, 1, 1, 1, 1, 1, 1, 1]);
+
+ yield teardown(monitor);
+
+ function testContents(visibility) {
+ isnot(RequestsMenu.selectedItem, null,
+ "There should still be a selected item after filtering.");
+ is(RequestsMenu.selectedIndex, 0,
+ "The first item should be still selected after filtering.");
+ is(NetMonitorView.detailsPaneHidden, false,
+ "The details pane should still be visible after filtering.");
+
+ is(RequestsMenu.items.length, visibility.length,
+ "There should be a specific amount of items in the requests menu.");
+ is(RequestsMenu.visibleItems.length, visibility.filter(e => e).length,
+ "There should be a specific amount of visbile items in the requests menu.");
+
+ for (let i = 0; i < visibility.length; i++) {
+ is(RequestsMenu.getItemAtIndex(i).target.hidden, !visibility[i],
+ "The item at index " + i + " doesn't have the correct hidden state.");
+ }
+
+ verifyRequestItemTarget(RequestsMenu.getItemAtIndex(0),
+ "GET", CONTENT_TYPE_SJS + "?fmt=html", {
+ fuzzyUrl: true,
+ status: 200,
+ statusText: "OK",
+ type: "html",
+ fullMimeType: "text/html; charset=utf-8"
+ });
+ verifyRequestItemTarget(RequestsMenu.getItemAtIndex(1),
+ "GET", CONTENT_TYPE_SJS + "?fmt=css", {
+ fuzzyUrl: true,
+ status: 200,
+ statusText: "OK",
+ type: "css",
+ fullMimeType: "text/css; charset=utf-8"
+ });
+ verifyRequestItemTarget(RequestsMenu.getItemAtIndex(2),
+ "GET", CONTENT_TYPE_SJS + "?fmt=js", {
+ fuzzyUrl: true,
+ status: 200,
+ statusText: "OK",
+ type: "js",
+ fullMimeType: "application/javascript; charset=utf-8"
+ });
+ verifyRequestItemTarget(RequestsMenu.getItemAtIndex(3),
+ "GET", CONTENT_TYPE_SJS + "?fmt=font", {
+ fuzzyUrl: true,
+ status: 200,
+ statusText: "OK",
+ type: "woff",
+ fullMimeType: "font/woff"
+ });
+ verifyRequestItemTarget(RequestsMenu.getItemAtIndex(4),
+ "GET", CONTENT_TYPE_SJS + "?fmt=image", {
+ fuzzyUrl: true,
+ status: 200,
+ statusText: "OK",
+ type: "png",
+ fullMimeType: "image/png"
+ });
+ verifyRequestItemTarget(RequestsMenu.getItemAtIndex(5),
+ "GET", CONTENT_TYPE_SJS + "?fmt=audio", {
+ fuzzyUrl: true,
+ status: 200,
+ statusText: "OK",
+ type: "ogg",
+ fullMimeType: "audio/ogg"
+ });
+ verifyRequestItemTarget(RequestsMenu.getItemAtIndex(6),
+ "GET", CONTENT_TYPE_SJS + "?fmt=video", {
+ fuzzyUrl: true,
+ status: 200,
+ statusText: "OK",
+ type: "webm",
+ fullMimeType: "video/webm"
+ });
+ verifyRequestItemTarget(RequestsMenu.getItemAtIndex(7),
+ "GET", CONTENT_TYPE_SJS + "?fmt=flash", {
+ fuzzyUrl: true,
+ status: 200,
+ statusText: "OK",
+ type: "x-shockwave-flash",
+ fullMimeType: "application/x-shockwave-flash"
+ });
+ verifyRequestItemTarget(RequestsMenu.getItemAtIndex(8),
+ "GET", CONTENT_TYPE_SJS + "?fmt=ws", {
+ fuzzyUrl: true,
+ status: 101,
+ statusText: "Switching Protocols",
+ });
+ }
+});
diff --git a/devtools/client/netmonitor/test/browser_net_filter-02.js b/devtools/client/netmonitor/test/browser_net_filter-02.js
new file mode 100644
index 000000000..70a051b6d
--- /dev/null
+++ b/devtools/client/netmonitor/test/browser_net_filter-02.js
@@ -0,0 +1,200 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+/**
+ * Test if filtering items in the network table works correctly with new requests.
+ */
+
+const BASIC_REQUESTS = [
+ { url: "sjs_content-type-test-server.sjs?fmt=html&res=undefined" },
+ { url: "sjs_content-type-test-server.sjs?fmt=css" },
+ { url: "sjs_content-type-test-server.sjs?fmt=js" },
+];
+
+const REQUESTS_WITH_MEDIA = BASIC_REQUESTS.concat([
+ { url: "sjs_content-type-test-server.sjs?fmt=font" },
+ { url: "sjs_content-type-test-server.sjs?fmt=image" },
+ { url: "sjs_content-type-test-server.sjs?fmt=audio" },
+ { url: "sjs_content-type-test-server.sjs?fmt=video" },
+]);
+
+const REQUESTS_WITH_MEDIA_AND_FLASH = REQUESTS_WITH_MEDIA.concat([
+ { url: "sjs_content-type-test-server.sjs?fmt=flash" },
+]);
+
+const REQUESTS_WITH_MEDIA_AND_FLASH_AND_WS = REQUESTS_WITH_MEDIA_AND_FLASH.concat([
+ /* "Upgrade" is a reserved header and can not be set on XMLHttpRequest */
+ { url: "sjs_content-type-test-server.sjs?fmt=ws" },
+]);
+
+add_task(function* () {
+ let { monitor } = yield initNetMonitor(FILTERING_URL);
+ info("Starting test... ");
+
+ // It seems that this test may be slow on Ubuntu builds running on ec2.
+ requestLongerTimeout(2);
+
+ let { $, NetMonitorView } = monitor.panelWin;
+ let { RequestsMenu } = NetMonitorView;
+
+ RequestsMenu.lazyUpdate = false;
+
+ let wait = waitForNetworkEvents(monitor, 9);
+ loadCommonFrameScript();
+ yield performRequestsInContent(REQUESTS_WITH_MEDIA_AND_FLASH_AND_WS);
+ yield wait;
+
+ EventUtils.sendMouseEvent({ type: "mousedown" }, $("#details-pane-toggle"));
+
+ isnot(RequestsMenu.selectedItem, null,
+ "There should be a selected item in the requests menu.");
+ is(RequestsMenu.selectedIndex, 0,
+ "The first item should be selected in the requests menu.");
+ is(NetMonitorView.detailsPaneHidden, false,
+ "The details pane should not be hidden after toggle button was pressed.");
+
+ testFilterButtons(monitor, "all");
+ testContents([1, 1, 1, 1, 1, 1, 1, 1, 1]);
+
+ info("Testing html filtering.");
+ EventUtils.sendMouseEvent({ type: "click" }, $("#requests-menu-filter-html-button"));
+ testFilterButtons(monitor, "html");
+ testContents([1, 0, 0, 0, 0, 0, 0, 0, 0]);
+
+ info("Performing more requests.");
+ wait = waitForNetworkEvents(monitor, 9);
+ yield performRequestsInContent(REQUESTS_WITH_MEDIA_AND_FLASH_AND_WS);
+ yield wait;
+
+ info("Testing html filtering again.");
+ testFilterButtons(monitor, "html");
+ testContents([1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0]);
+
+ info("Performing more requests.");
+ wait = waitForNetworkEvents(monitor, 9);
+ yield performRequestsInContent(REQUESTS_WITH_MEDIA_AND_FLASH_AND_WS);
+ yield wait;
+
+ info("Testing html filtering again.");
+ testFilterButtons(monitor, "html");
+ testContents([1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0,
+ 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0]);
+
+ info("Resetting filters.");
+ EventUtils.sendMouseEvent({ type: "click" }, $("#requests-menu-filter-all-button"));
+ testFilterButtons(monitor, "all");
+ testContents([1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]);
+
+ yield teardown(monitor);
+
+ function testContents(visibility) {
+ isnot(RequestsMenu.selectedItem, null,
+ "There should still be a selected item after filtering.");
+ is(RequestsMenu.selectedIndex, 0,
+ "The first item should be still selected after filtering.");
+ is(NetMonitorView.detailsPaneHidden, false,
+ "The details pane should still be visible after filtering.");
+
+ is(RequestsMenu.items.length, visibility.length,
+ "There should be a specific amount of items in the requests menu.");
+ is(RequestsMenu.visibleItems.length, visibility.filter(e => e).length,
+ "There should be a specific amount of visbile items in the requests menu.");
+
+ for (let i = 0; i < visibility.length; i++) {
+ is(RequestsMenu.getItemAtIndex(i).target.hidden, !visibility[i],
+ "The item at index " + i + " doesn't have the correct hidden state.");
+ }
+
+ for (let i = 0; i < visibility.length; i += 9) {
+ verifyRequestItemTarget(RequestsMenu.getItemAtIndex(i),
+ "GET", CONTENT_TYPE_SJS + "?fmt=html", {
+ fuzzyUrl: true,
+ status: 200,
+ statusText: "OK",
+ type: "html",
+ fullMimeType: "text/html; charset=utf-8"
+ });
+ }
+ for (let i = 1; i < visibility.length; i += 9) {
+ verifyRequestItemTarget(RequestsMenu.getItemAtIndex(i),
+ "GET", CONTENT_TYPE_SJS + "?fmt=css", {
+ fuzzyUrl: true,
+ status: 200,
+ statusText: "OK",
+ type: "css",
+ fullMimeType: "text/css; charset=utf-8"
+ });
+ }
+ for (let i = 2; i < visibility.length; i += 9) {
+ verifyRequestItemTarget(RequestsMenu.getItemAtIndex(i),
+ "GET", CONTENT_TYPE_SJS + "?fmt=js", {
+ fuzzyUrl: true,
+ status: 200,
+ statusText: "OK",
+ type: "js",
+ fullMimeType: "application/javascript; charset=utf-8"
+ });
+ }
+ for (let i = 3; i < visibility.length; i += 9) {
+ verifyRequestItemTarget(RequestsMenu.getItemAtIndex(i),
+ "GET", CONTENT_TYPE_SJS + "?fmt=font", {
+ fuzzyUrl: true,
+ status: 200,
+ statusText: "OK",
+ type: "woff",
+ fullMimeType: "font/woff"
+ });
+ }
+ for (let i = 4; i < visibility.length; i += 9) {
+ verifyRequestItemTarget(RequestsMenu.getItemAtIndex(i),
+ "GET", CONTENT_TYPE_SJS + "?fmt=image", {
+ fuzzyUrl: true,
+ status: 200,
+ statusText: "OK",
+ type: "png",
+ fullMimeType: "image/png"
+ });
+ }
+ for (let i = 5; i < visibility.length; i += 9) {
+ verifyRequestItemTarget(RequestsMenu.getItemAtIndex(i),
+ "GET", CONTENT_TYPE_SJS + "?fmt=audio", {
+ fuzzyUrl: true,
+ status: 200,
+ statusText: "OK",
+ type: "ogg",
+ fullMimeType: "audio/ogg"
+ });
+ }
+ for (let i = 6; i < visibility.length; i += 9) {
+ verifyRequestItemTarget(RequestsMenu.getItemAtIndex(i),
+ "GET", CONTENT_TYPE_SJS + "?fmt=video", {
+ fuzzyUrl: true,
+ status: 200,
+ statusText: "OK",
+ type: "webm",
+ fullMimeType: "video/webm"
+ });
+ }
+ for (let i = 7; i < visibility.length; i += 9) {
+ verifyRequestItemTarget(RequestsMenu.getItemAtIndex(i),
+ "GET", CONTENT_TYPE_SJS + "?fmt=flash", {
+ fuzzyUrl: true,
+ status: 200,
+ statusText: "OK",
+ type: "x-shockwave-flash",
+ fullMimeType: "application/x-shockwave-flash"
+ });
+ }
+ for (let i = 8; i < visibility.length; i += 9) {
+ verifyRequestItemTarget(RequestsMenu.getItemAtIndex(i),
+ "GET", CONTENT_TYPE_SJS + "?fmt=ws", {
+ fuzzyUrl: true,
+ status: 101,
+ statusText: "Switching Protocols"
+ });
+ }
+ }
+});
diff --git a/devtools/client/netmonitor/test/browser_net_filter-03.js b/devtools/client/netmonitor/test/browser_net_filter-03.js
new file mode 100644
index 000000000..2babdaab3
--- /dev/null
+++ b/devtools/client/netmonitor/test/browser_net_filter-03.js
@@ -0,0 +1,185 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+/**
+ * Test if filtering items in the network table works correctly with new requests
+ * and while sorting is enabled.
+ */
+const BASIC_REQUESTS = [
+ { url: "sjs_content-type-test-server.sjs?fmt=html&res=undefined" },
+ { url: "sjs_content-type-test-server.sjs?fmt=css" },
+ { url: "sjs_content-type-test-server.sjs?fmt=js" },
+];
+
+const REQUESTS_WITH_MEDIA = BASIC_REQUESTS.concat([
+ { url: "sjs_content-type-test-server.sjs?fmt=font" },
+ { url: "sjs_content-type-test-server.sjs?fmt=image" },
+ { url: "sjs_content-type-test-server.sjs?fmt=audio" },
+ { url: "sjs_content-type-test-server.sjs?fmt=video" },
+]);
+
+add_task(function* () {
+ let { monitor } = yield initNetMonitor(FILTERING_URL);
+ info("Starting test... ");
+
+ // It seems that this test may be slow on Ubuntu builds running on ec2.
+ requestLongerTimeout(2);
+
+ let { $, NetMonitorView } = monitor.panelWin;
+ let { RequestsMenu } = NetMonitorView;
+
+ RequestsMenu.lazyUpdate = false;
+
+ // The test assumes that the first HTML request here has a longer response
+ // body than the other HTML requests performed later during the test.
+ let requests = Cu.cloneInto(REQUESTS_WITH_MEDIA, {});
+ let newres = "res=<p>" + new Array(10).join(Math.random(10)) + "</p>";
+ requests[0].url = requests[0].url.replace("res=undefined", newres);
+
+ loadCommonFrameScript();
+
+ let wait = waitForNetworkEvents(monitor, 7);
+ yield performRequestsInContent(requests);
+ yield wait;
+
+ EventUtils.sendMouseEvent({ type: "mousedown" }, $("#details-pane-toggle"));
+
+ isnot(RequestsMenu.selectedItem, null,
+ "There should be a selected item in the requests menu.");
+ is(RequestsMenu.selectedIndex, 0,
+ "The first item should be selected in the requests menu.");
+ is(NetMonitorView.detailsPaneHidden, false,
+ "The details pane should not be hidden after toggle button was pressed.");
+
+ testFilterButtons(monitor, "all");
+ testContents([0, 1, 2, 3, 4, 5, 6], 7, 0);
+
+ info("Sorting by size, ascending.");
+ EventUtils.sendMouseEvent({ type: "click" }, $("#requests-menu-size-button"));
+ testFilterButtons(monitor, "all");
+ testContents([6, 4, 5, 0, 1, 2, 3], 7, 6);
+
+ info("Testing html filtering.");
+ EventUtils.sendMouseEvent({ type: "click" }, $("#requests-menu-filter-html-button"));
+ testFilterButtons(monitor, "html");
+ testContents([6, 4, 5, 0, 1, 2, 3], 1, 6);
+
+ info("Performing more requests.");
+ wait = waitForNetworkEvents(monitor, 7);
+ performRequestsInContent(REQUESTS_WITH_MEDIA);
+ yield wait;
+
+ info("Testing html filtering again.");
+ resetSorting();
+ testFilterButtons(monitor, "html");
+ testContents([8, 13, 9, 11, 10, 12, 0, 4, 1, 5, 2, 6, 3, 7], 2, 13);
+
+ info("Performing more requests.");
+ performRequestsInContent(REQUESTS_WITH_MEDIA);
+ yield waitForNetworkEvents(monitor, 7);
+
+ info("Testing html filtering again.");
+ resetSorting();
+ testFilterButtons(monitor, "html");
+ testContents([12, 13, 20, 14, 16, 18, 15, 17, 19, 0, 4, 8, 1, 5, 9, 2, 6, 10, 3, 7, 11],
+ 3, 20);
+
+ yield teardown(monitor);
+
+ function resetSorting() {
+ EventUtils.sendMouseEvent({ type: "click" }, $("#requests-menu-waterfall-button"));
+ EventUtils.sendMouseEvent({ type: "click" }, $("#requests-menu-size-button"));
+ }
+
+ function testContents(order, visible, selection) {
+ isnot(RequestsMenu.selectedItem, null,
+ "There should still be a selected item after filtering.");
+ is(RequestsMenu.selectedIndex, selection,
+ "The first item should be still selected after filtering.");
+ is(NetMonitorView.detailsPaneHidden, false,
+ "The details pane should still be visible after filtering.");
+
+ is(RequestsMenu.items.length, order.length,
+ "There should be a specific amount of items in the requests menu.");
+ is(RequestsMenu.visibleItems.length, visible,
+ "There should be a specific amount of visbile items in the requests menu.");
+
+ for (let i = 0; i < order.length; i++) {
+ is(RequestsMenu.getItemAtIndex(i), RequestsMenu.items[i],
+ "The requests menu items aren't ordered correctly. Misplaced item " + i + ".");
+ }
+
+ for (let i = 0, len = order.length / 7; i < len; i++) {
+ verifyRequestItemTarget(RequestsMenu.getItemAtIndex(order[i]),
+ "GET", CONTENT_TYPE_SJS + "?fmt=html", {
+ fuzzyUrl: true,
+ status: 200,
+ statusText: "OK",
+ type: "html",
+ fullMimeType: "text/html; charset=utf-8"
+ });
+ }
+ for (let i = 0, len = order.length / 7; i < len; i++) {
+ verifyRequestItemTarget(RequestsMenu.getItemAtIndex(order[i + len]),
+ "GET", CONTENT_TYPE_SJS + "?fmt=css", {
+ fuzzyUrl: true,
+ status: 200,
+ statusText: "OK",
+ type: "css",
+ fullMimeType: "text/css; charset=utf-8"
+ });
+ }
+ for (let i = 0, len = order.length / 7; i < len; i++) {
+ verifyRequestItemTarget(RequestsMenu.getItemAtIndex(order[i + len * 2]),
+ "GET", CONTENT_TYPE_SJS + "?fmt=js", {
+ fuzzyUrl: true,
+ status: 200,
+ statusText: "OK",
+ type: "js",
+ fullMimeType: "application/javascript; charset=utf-8"
+ });
+ }
+ for (let i = 0, len = order.length / 7; i < len; i++) {
+ verifyRequestItemTarget(RequestsMenu.getItemAtIndex(order[i + len * 3]),
+ "GET", CONTENT_TYPE_SJS + "?fmt=font", {
+ fuzzyUrl: true,
+ status: 200,
+ statusText: "OK",
+ type: "woff",
+ fullMimeType: "font/woff"
+ });
+ }
+ for (let i = 0, len = order.length / 7; i < len; i++) {
+ verifyRequestItemTarget(RequestsMenu.getItemAtIndex(order[i + len * 4]),
+ "GET", CONTENT_TYPE_SJS + "?fmt=image", {
+ fuzzyUrl: true,
+ status: 200,
+ statusText: "OK",
+ type: "png",
+ fullMimeType: "image/png"
+ });
+ }
+ for (let i = 0, len = order.length / 7; i < len; i++) {
+ verifyRequestItemTarget(RequestsMenu.getItemAtIndex(order[i + len * 5]),
+ "GET", CONTENT_TYPE_SJS + "?fmt=audio", {
+ fuzzyUrl: true,
+ status: 200,
+ statusText: "OK",
+ type: "ogg",
+ fullMimeType: "audio/ogg"
+ });
+ }
+ for (let i = 0, len = order.length / 7; i < len; i++) {
+ verifyRequestItemTarget(RequestsMenu.getItemAtIndex(order[i + len * 6]),
+ "GET", CONTENT_TYPE_SJS + "?fmt=video", {
+ fuzzyUrl: true,
+ status: 200,
+ statusText: "OK",
+ type: "webm",
+ fullMimeType: "video/webm"
+ });
+ }
+ }
+});
diff --git a/devtools/client/netmonitor/test/browser_net_filter-04.js b/devtools/client/netmonitor/test/browser_net_filter-04.js
new file mode 100644
index 000000000..e617dbaa9
--- /dev/null
+++ b/devtools/client/netmonitor/test/browser_net_filter-04.js
@@ -0,0 +1,63 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+/**
+ * Tests if invalid filter types are sanitized when loaded from the preferences.
+ */
+
+const BASIC_REQUESTS = [
+ { url: "sjs_content-type-test-server.sjs?fmt=html&res=undefined" },
+ { url: "sjs_content-type-test-server.sjs?fmt=css" },
+ { url: "sjs_content-type-test-server.sjs?fmt=js" },
+];
+
+const REQUESTS_WITH_MEDIA = BASIC_REQUESTS.concat([
+ { url: "sjs_content-type-test-server.sjs?fmt=font" },
+ { url: "sjs_content-type-test-server.sjs?fmt=image" },
+ { url: "sjs_content-type-test-server.sjs?fmt=audio" },
+ { url: "sjs_content-type-test-server.sjs?fmt=video" },
+]);
+
+const REQUESTS_WITH_MEDIA_AND_FLASH = REQUESTS_WITH_MEDIA.concat([
+ { url: "sjs_content-type-test-server.sjs?fmt=flash" },
+]);
+
+const REQUESTS_WITH_MEDIA_AND_FLASH_AND_WS = REQUESTS_WITH_MEDIA_AND_FLASH.concat([
+ /* "Upgrade" is a reserved header and can not be set on XMLHttpRequest */
+ { url: "sjs_content-type-test-server.sjs?fmt=ws" },
+]);
+
+add_task(function* () {
+ Services.prefs.setCharPref("devtools.netmonitor.filters", '["js", "bogus"]');
+
+ let { monitor } = yield initNetMonitor(FILTERING_URL);
+ info("Starting test... ");
+
+ let { Prefs, NetMonitorView } = monitor.panelWin;
+ let { RequestsMenu } = NetMonitorView;
+
+ RequestsMenu.lazyUpdate = false;
+
+ is(Prefs.filters.length, 2,
+ "All filter types were loaded as an array from the preferences.");
+ is(Prefs.filters[0], "js",
+ "The first filter type is correct.");
+ is(Prefs.filters[1], "bogus",
+ "The second filter type is invalid, but loaded anyway.");
+
+ let wait = waitForNetworkEvents(monitor, 9);
+ loadCommonFrameScript();
+ yield performRequestsInContent(REQUESTS_WITH_MEDIA_AND_FLASH_AND_WS);
+ yield wait;
+
+ testFilterButtons(monitor, "js");
+ ok(true, "Only the correct filter type was taken into consideration.");
+
+ yield teardown(monitor);
+
+ let filters = Services.prefs.getCharPref("devtools.netmonitor.filters");
+ is(filters, '["js"]',
+ "The bogus filter type was ignored and removed from the preferences.");
+});
diff --git a/devtools/client/netmonitor/test/browser_net_footer-summary.js b/devtools/client/netmonitor/test/browser_net_footer-summary.js
new file mode 100644
index 000000000..e484b2097
--- /dev/null
+++ b/devtools/client/netmonitor/test/browser_net_footer-summary.js
@@ -0,0 +1,75 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+/**
+ * Test if the summary text displayed in the network requests menu footer
+ * is correct.
+ */
+
+add_task(function* () {
+ requestLongerTimeout(2);
+ let { L10N } = require("devtools/client/netmonitor/l10n");
+ let { PluralForm } = require("devtools/shared/plural-form");
+
+ let { tab, monitor } = yield initNetMonitor(FILTERING_URL);
+ info("Starting test... ");
+
+ let { $, NetMonitorView } = monitor.panelWin;
+ let { RequestsMenu } = NetMonitorView;
+
+ RequestsMenu.lazyUpdate = false;
+ testStatus();
+
+ for (let i = 0; i < 2; i++) {
+ info(`Performing requests in batch #${i}`);
+ let wait = waitForNetworkEvents(monitor, 8);
+ yield ContentTask.spawn(tab.linkedBrowser, {}, function* () {
+ content.wrappedJSObject.performRequests('{ "getMedia": true, "getFlash": true }');
+ });
+ yield wait;
+
+ testStatus();
+
+ let buttons = ["html", "css", "js", "xhr", "fonts", "images", "media", "flash"];
+ for (let button of buttons) {
+ let buttonEl = $(`#requests-menu-filter-${button}-button`);
+ EventUtils.sendMouseEvent({ type: "click" }, buttonEl);
+ testStatus();
+ }
+ }
+
+ yield teardown(monitor);
+
+ function testStatus() {
+ let summary = $("#requests-menu-network-summary-button");
+ let value = summary.getAttribute("label");
+ info("Current summary: " + value);
+
+ let visibleItems = RequestsMenu.visibleItems;
+ let visibleRequestsCount = visibleItems.length;
+ let totalRequestsCount = RequestsMenu.itemCount;
+ info("Current requests: " + visibleRequestsCount + " of " + totalRequestsCount + ".");
+
+ if (!totalRequestsCount || !visibleRequestsCount) {
+ is(value, L10N.getStr("networkMenu.empty"),
+ "The current summary text is incorrect, expected an 'empty' label.");
+ return;
+ }
+
+ let totalBytes = RequestsMenu._getTotalBytesOfRequests(visibleItems);
+ let totalMillis =
+ RequestsMenu._getNewestRequest(visibleItems).attachment.endedMillis -
+ RequestsMenu._getOldestRequest(visibleItems).attachment.startedMillis;
+
+ info("Computed total bytes: " + totalBytes);
+ info("Computed total millis: " + totalMillis);
+
+ is(value, PluralForm.get(visibleRequestsCount, L10N.getStr("networkMenu.summary"))
+ .replace("#1", visibleRequestsCount)
+ .replace("#2", L10N.numberWithDecimals((totalBytes || 0) / 1024, 2))
+ .replace("#3", L10N.numberWithDecimals((totalMillis || 0) / 1000, 2))
+ , "The current summary text is incorrect.");
+ }
+});
diff --git a/devtools/client/netmonitor/test/browser_net_frame.js b/devtools/client/netmonitor/test/browser_net_frame.js
new file mode 100644
index 000000000..eeded652b
--- /dev/null
+++ b/devtools/client/netmonitor/test/browser_net_frame.js
@@ -0,0 +1,221 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+/**
+ * Tests for all expected requests when an iframe is loading a subdocument.
+ */
+
+const TOP_FILE_NAME = "html_frame-test-page.html";
+const SUB_FILE_NAME = "html_frame-subdocument.html";
+const TOP_URL = EXAMPLE_URL + TOP_FILE_NAME;
+const SUB_URL = EXAMPLE_URL + SUB_FILE_NAME;
+
+const EXPECTED_REQUESTS_TOP = [
+ {
+ method: "GET",
+ url: TOP_URL,
+ causeType: "document",
+ causeUri: "",
+ stack: true
+ },
+ {
+ method: "GET",
+ url: EXAMPLE_URL + "stylesheet_request",
+ causeType: "stylesheet",
+ causeUri: TOP_URL,
+ stack: false
+ },
+ {
+ method: "GET",
+ url: EXAMPLE_URL + "img_request",
+ causeType: "img",
+ causeUri: TOP_URL,
+ stack: false
+ },
+ {
+ method: "GET",
+ url: EXAMPLE_URL + "xhr_request",
+ causeType: "xhr",
+ causeUri: TOP_URL,
+ stack: [{ fn: "performXhrRequest", file: TOP_FILE_NAME, line: 23 }]
+ },
+ {
+ method: "GET",
+ url: EXAMPLE_URL + "fetch_request",
+ causeType: "fetch",
+ causeUri: TOP_URL,
+ stack: [{ fn: "performFetchRequest", file: TOP_FILE_NAME, line: 27 }]
+ },
+ {
+ method: "GET",
+ url: EXAMPLE_URL + "promise_fetch_request",
+ causeType: "fetch",
+ causeUri: TOP_URL,
+ stack: [
+ { fn: "performPromiseFetchRequest", file: TOP_FILE_NAME, line: 39 },
+ { fn: null, file: TOP_FILE_NAME, line: 38, asyncCause: "promise callback" },
+ ]
+ },
+ {
+ method: "GET",
+ url: EXAMPLE_URL + "timeout_fetch_request",
+ causeType: "fetch",
+ causeUri: TOP_URL,
+ stack: [
+ { fn: "performTimeoutFetchRequest", file: TOP_FILE_NAME, line: 41 },
+ { fn: "performPromiseFetchRequest", file: TOP_FILE_NAME, line: 40,
+ asyncCause: "setTimeout handler" },
+ ]
+ },
+ {
+ method: "POST",
+ url: EXAMPLE_URL + "beacon_request",
+ causeType: "beacon",
+ causeUri: TOP_URL,
+ stack: [{ fn: "performBeaconRequest", file: TOP_FILE_NAME, line: 31 }]
+ },
+];
+
+const EXPECTED_REQUESTS_SUB = [
+ {
+ method: "GET",
+ url: SUB_URL,
+ causeType: "subdocument",
+ causeUri: TOP_URL,
+ stack: false
+ },
+ {
+ method: "GET",
+ url: EXAMPLE_URL + "stylesheet_request",
+ causeType: "stylesheet",
+ causeUri: SUB_URL,
+ stack: false
+ },
+ {
+ method: "GET",
+ url: EXAMPLE_URL + "img_request",
+ causeType: "img",
+ causeUri: SUB_URL,
+ stack: false
+ },
+ {
+ method: "GET",
+ url: EXAMPLE_URL + "xhr_request",
+ causeType: "xhr",
+ causeUri: SUB_URL,
+ stack: [{ fn: "performXhrRequest", file: SUB_FILE_NAME, line: 22 }]
+ },
+ {
+ method: "GET",
+ url: EXAMPLE_URL + "fetch_request",
+ causeType: "fetch",
+ causeUri: SUB_URL,
+ stack: [{ fn: "performFetchRequest", file: SUB_FILE_NAME, line: 26 }]
+ },
+ {
+ method: "GET",
+ url: EXAMPLE_URL + "promise_fetch_request",
+ causeType: "fetch",
+ causeUri: SUB_URL,
+ stack: [
+ { fn: "performPromiseFetchRequest", file: SUB_FILE_NAME, line: 38 },
+ { fn: null, file: SUB_FILE_NAME, line: 37, asyncCause: "promise callback" },
+ ]
+ },
+ {
+ method: "GET",
+ url: EXAMPLE_URL + "timeout_fetch_request",
+ causeType: "fetch",
+ causeUri: SUB_URL,
+ stack: [
+ { fn: "performTimeoutFetchRequest", file: SUB_FILE_NAME, line: 40 },
+ { fn: "performPromiseFetchRequest", file: SUB_FILE_NAME, line: 39,
+ asyncCause: "setTimeout handler" },
+ ]
+ },
+ {
+ method: "POST",
+ url: EXAMPLE_URL + "beacon_request",
+ causeType: "beacon",
+ causeUri: SUB_URL,
+ stack: [{ fn: "performBeaconRequest", file: SUB_FILE_NAME, line: 30 }]
+ },
+];
+
+const REQUEST_COUNT = EXPECTED_REQUESTS_TOP.length + EXPECTED_REQUESTS_SUB.length;
+
+add_task(function* () {
+ // Async stacks aren't on by default in all builds
+ yield SpecialPowers.pushPrefEnv({ set: [["javascript.options.asyncstack", 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 TOP_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 { tab, monitor } = yield initNetMonitor(SIMPLE_URL);
+ let { NetMonitorView } = monitor.panelWin;
+ let { RequestsMenu } = NetMonitorView;
+ RequestsMenu.lazyUpdate = false;
+
+ tab.linkedBrowser.loadURI(TOP_URL, null, null);
+
+ yield waitForNetworkEvents(monitor, REQUEST_COUNT);
+
+ is(RequestsMenu.itemCount, REQUEST_COUNT,
+ "All the page events should be recorded.");
+
+ // While there is a defined order for requests in each document separately, the requests
+ // from different documents may interleave in various ways that change per test run, so
+ // there is not a single order when considering all the requests together.
+ let currentTop = 0;
+ let currentSub = 0;
+ for (let i = 0; i < REQUEST_COUNT; i++) {
+ let requestItem = RequestsMenu.getItemAtIndex(i);
+
+ let itemUrl = requestItem.attachment.url;
+ let itemCauseUri = requestItem.target.querySelector(".requests-menu-cause-label")
+ .getAttribute("tooltiptext");
+ let spec;
+ if (itemUrl == SUB_URL || itemCauseUri == SUB_URL) {
+ spec = EXPECTED_REQUESTS_SUB[currentSub++];
+ } else {
+ spec = EXPECTED_REQUESTS_TOP[currentTop++];
+ }
+ let { method, url, causeType, causeUri, stack } = spec;
+
+ verifyRequestItemTarget(requestItem,
+ method, url, { cause: { type: causeType, loadingDocumentUri: causeUri } }
+ );
+
+ let { stacktrace } = requestItem.attachment.cause;
+ let stackLen = stacktrace ? stacktrace.length : 0;
+
+ if (stack) {
+ ok(stacktrace, `Request #${i} has a stacktrace`);
+ ok(stackLen > 0,
+ `Request #${i} (${causeType}) has a stacktrace with ${stackLen} items`);
+
+ // if "stack" is array, check the details about the top stack frames
+ if (Array.isArray(stack)) {
+ stack.forEach((frame, j) => {
+ is(stacktrace[j].functionName, frame.fn,
+ `Request #${i} has the correct function on JS stack frame #${j}`);
+ is(stacktrace[j].filename.split("/").pop(), frame.file,
+ `Request #${i} has the correct file on JS stack frame #${j}`);
+ is(stacktrace[j].lineNumber, frame.line,
+ `Request #${i} has the correct line number on JS stack frame #${j}`);
+ is(stacktrace[j].asyncCause, frame.asyncCause,
+ `Request #${i} has the correct async cause on JS stack frame #${j}`);
+ });
+ }
+ } else {
+ is(stackLen, 0, `Request #${i} (${causeType}) has an empty stacktrace`);
+ }
+ }
+
+ yield teardown(monitor);
+});
diff --git a/devtools/client/netmonitor/test/browser_net_html-preview.js b/devtools/client/netmonitor/test/browser_net_html-preview.js
new file mode 100644
index 000000000..351009de5
--- /dev/null
+++ b/devtools/client/netmonitor/test/browser_net_html-preview.js
@@ -0,0 +1,62 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+/**
+ * Tests if html responses show and properly populate a "Preview" tab.
+ */
+
+add_task(function* () {
+ let { tab, monitor } = yield initNetMonitor(CONTENT_TYPE_URL);
+ info("Starting test... ");
+
+ let { $, document, EVENTS, NetMonitorView } = monitor.panelWin;
+ let { RequestsMenu } = NetMonitorView;
+
+ RequestsMenu.lazyUpdate = false;
+
+ let wait = waitForNetworkEvents(monitor, 6);
+ yield ContentTask.spawn(tab.linkedBrowser, {}, function* () {
+ content.wrappedJSObject.performRequests();
+ });
+ yield wait;
+
+ EventUtils.sendMouseEvent({ type: "mousedown" },
+ document.getElementById("details-pane-toggle"));
+
+ is($("#event-details-pane").selectedIndex, 0,
+ "The first tab in the details pane should be selected.");
+ is($("#preview-tab").hidden, true,
+ "The preview tab should be hidden for non html responses.");
+ is($("#preview-tabpanel").hidden, false,
+ "The preview tabpanel is not hidden for non html responses.");
+
+ RequestsMenu.selectedIndex = 4;
+ NetMonitorView.toggleDetailsPane({ visible: true, animated: false }, 6);
+
+ is($("#event-details-pane").selectedIndex, 6,
+ "The sixth tab in the details pane should be selected.");
+ is($("#preview-tab").hidden, false,
+ "The preview tab should be visible now.");
+
+ yield monitor.panelWin.once(EVENTS.RESPONSE_HTML_PREVIEW_DISPLAYED);
+ let iframe = $("#response-preview");
+ ok(iframe,
+ "There should be a response preview iframe available.");
+ ok(iframe.contentDocument,
+ "The iframe's content document should be available.");
+ is(iframe.contentDocument.querySelector("blink").textContent, "Not Found",
+ "The iframe's content document should be loaded and correct.");
+
+ RequestsMenu.selectedIndex = 5;
+
+ is($("#event-details-pane").selectedIndex, 0,
+ "The first tab in the details pane should be selected again.");
+ is($("#preview-tab").hidden, true,
+ "The preview tab should be hidden again for non html responses.");
+ is($("#preview-tabpanel").hidden, false,
+ "The preview tabpanel is not hidden again for non html responses.");
+
+ yield teardown(monitor);
+});
diff --git a/devtools/client/netmonitor/test/browser_net_icon-preview.js b/devtools/client/netmonitor/test/browser_net_icon-preview.js
new file mode 100644
index 000000000..e3c5bde4e
--- /dev/null
+++ b/devtools/client/netmonitor/test/browser_net_icon-preview.js
@@ -0,0 +1,71 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+/**
+ * Tests if image responses show a thumbnail in the requests menu.
+ */
+
+add_task(function* () {
+ let Actions = require("devtools/client/netmonitor/actions/index");
+
+ let { tab, monitor } = yield initNetMonitor(CONTENT_TYPE_WITHOUT_CACHE_URL);
+ info("Starting test... ");
+
+ let { $, $all, EVENTS, ACTIVITY_TYPE, NetMonitorView, NetMonitorController,
+ gStore } = monitor.panelWin;
+ let { RequestsMenu } = NetMonitorView;
+
+ let wait = waitForEvents();
+ yield performRequests();
+ yield wait;
+
+ info("Checking the image thumbnail when all items are shown.");
+ checkImageThumbnail();
+
+ RequestsMenu.sortBy("size");
+ info("Checking the image thumbnail when all items are sorted.");
+ checkImageThumbnail();
+
+ gStore.dispatch(Actions.toggleFilterType("images"));
+ info("Checking the image thumbnail when only images are shown.");
+ checkImageThumbnail();
+
+ info("Reloading the debuggee and performing all requests again...");
+ wait = waitForEvents();
+ yield reloadAndPerformRequests();
+ yield wait;
+
+ info("Checking the image thumbnail after a reload.");
+ checkImageThumbnail();
+
+ yield teardown(monitor);
+
+ function waitForEvents() {
+ return promise.all([
+ waitForNetworkEvents(monitor, CONTENT_TYPE_WITHOUT_CACHE_REQUESTS),
+ monitor.panelWin.once(EVENTS.RESPONSE_IMAGE_THUMBNAIL_DISPLAYED)
+ ]);
+ }
+
+ function performRequests() {
+ return ContentTask.spawn(tab.linkedBrowser, {}, function* () {
+ content.wrappedJSObject.performRequests();
+ });
+ }
+
+ function* reloadAndPerformRequests() {
+ yield NetMonitorController.triggerActivity(ACTIVITY_TYPE.RELOAD.WITH_CACHE_ENABLED);
+ yield performRequests();
+ }
+
+ function checkImageThumbnail() {
+ is($all(".requests-menu-icon[type=thumbnail]").length, 1,
+ "There should be only one image request with a thumbnail displayed.");
+ is($(".requests-menu-icon[type=thumbnail]").src, TEST_IMAGE_DATA_URI,
+ "The image requests-menu-icon thumbnail is displayed correctly.");
+ is($(".requests-menu-icon[type=thumbnail]").hidden, false,
+ "The image requests-menu-icon thumbnail should not be hidden.");
+ }
+});
diff --git a/devtools/client/netmonitor/test/browser_net_image-tooltip.js b/devtools/client/netmonitor/test/browser_net_image-tooltip.js
new file mode 100644
index 000000000..04cd26959
--- /dev/null
+++ b/devtools/client/netmonitor/test/browser_net_image-tooltip.js
@@ -0,0 +1,101 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+const IMAGE_TOOLTIP_URL = EXAMPLE_URL + "html_image-tooltip-test-page.html";
+const IMAGE_TOOLTIP_REQUESTS = 1;
+
+/**
+ * Tests if image responses show a popup in the requests menu when hovered.
+ */
+add_task(function* test() {
+ let { tab, monitor } = yield initNetMonitor(IMAGE_TOOLTIP_URL);
+ info("Starting test... ");
+
+ let { $, EVENTS, ACTIVITY_TYPE, NetMonitorView, NetMonitorController } =
+ monitor.panelWin;
+ let { RequestsMenu } = NetMonitorView;
+ RequestsMenu.lazyUpdate = true;
+
+ let onEvents = waitForNetworkEvents(monitor, IMAGE_TOOLTIP_REQUESTS);
+ let onThumbnail = monitor.panelWin.once(EVENTS.RESPONSE_IMAGE_THUMBNAIL_DISPLAYED);
+
+ yield performRequests();
+ yield onEvents;
+ yield onThumbnail;
+
+ info("Checking the image thumbnail after a few requests were made...");
+ yield showTooltipAndVerify(RequestsMenu.tooltip, RequestsMenu.items[0]);
+
+ // Hide tooltip before next test, to avoid the situation that tooltip covers
+ // the icon for the request of the next test.
+ info("Checking the image thumbnail gets hidden...");
+ yield hideTooltipAndVerify(RequestsMenu.tooltip, RequestsMenu.items[0]);
+
+ // +1 extra document reload
+ onEvents = waitForNetworkEvents(monitor, IMAGE_TOOLTIP_REQUESTS + 1);
+ onThumbnail = monitor.panelWin.once(EVENTS.RESPONSE_IMAGE_THUMBNAIL_DISPLAYED);
+
+ info("Reloading the debuggee and performing all requests again...");
+ yield NetMonitorController.triggerActivity(ACTIVITY_TYPE.RELOAD.WITH_CACHE_ENABLED);
+ yield performRequests();
+ yield onEvents;
+ yield onThumbnail;
+
+ info("Checking the image thumbnail after a reload.");
+ yield showTooltipAndVerify(RequestsMenu.tooltip, RequestsMenu.items[1]);
+
+ info("Checking if the image thumbnail is hidden when mouse leaves the menu widget");
+ let requestsMenuEl = $("#requests-menu-contents");
+ let onHidden = RequestsMenu.tooltip.once("hidden");
+ EventUtils.synthesizeMouse(requestsMenuEl, 0, 0, {type: "mouseout"}, monitor.panelWin);
+ yield onHidden;
+
+ yield teardown(monitor);
+
+ function performRequests() {
+ return ContentTask.spawn(tab.linkedBrowser, {}, function* () {
+ content.wrappedJSObject.performRequests();
+ });
+ }
+
+ /**
+ * Show a tooltip on the {requestItem} and verify that it was displayed
+ * with the expected content.
+ */
+ 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.panel.querySelector("img").src, TEST_IMAGE_DATA_URI,
+ "The tooltip's image content is displayed correctly.");
+ }
+
+ /**
+ * Trigger a tooltip over an element by sending mousemove event.
+ * @return a promise that resolves when the tooltip is shown
+ */
+ function showTooltipOn(tooltip, element) {
+ let onShown = tooltip.once("shown");
+ let win = element.ownerDocument.defaultView;
+ EventUtils.synthesizeMouseAtCenter(element, {type: "mousemove"}, win);
+ return onShown;
+ }
+
+ /**
+ * Hide a tooltip on the {requestItem} and verify that it was closed.
+ */
+ function* hideTooltipAndVerify(tooltip, requestItem) {
+ // Hovering method hides tooltip.
+ let anchor = $(".requests-menu-method", requestItem.target);
+
+ let onHidden = tooltip.once("hidden");
+ let win = anchor.ownerDocument.defaultView;
+ EventUtils.synthesizeMouseAtCenter(anchor, {type: "mousemove"}, win);
+ yield onHidden;
+
+ info("Tooltip was successfully closed.");
+ }
+});
diff --git a/devtools/client/netmonitor/test/browser_net_json-long.js b/devtools/client/netmonitor/test/browser_net_json-long.js
new file mode 100644
index 000000000..2347d26c4
--- /dev/null
+++ b/devtools/client/netmonitor/test/browser_net_json-long.js
@@ -0,0 +1,98 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+/**
+ * Tests if very long JSON responses are handled correctly.
+ */
+
+add_task(function* () {
+ let { L10N } = require("devtools/client/netmonitor/l10n");
+
+ let { tab, monitor } = yield initNetMonitor(JSON_LONG_URL);
+ info("Starting test... ");
+
+ // This is receiving over 80 KB of json and will populate over 6000 items
+ // in a variables view instance. Debug builds are slow.
+ requestLongerTimeout(4);
+
+ let { document, EVENTS, NetMonitorView } = monitor.panelWin;
+ let { RequestsMenu } = NetMonitorView;
+
+ RequestsMenu.lazyUpdate = false;
+
+ let wait = waitForNetworkEvents(monitor, 1);
+ yield ContentTask.spawn(tab.linkedBrowser, {}, function* () {
+ content.wrappedJSObject.performRequests();
+ });
+ yield wait;
+
+ verifyRequestItemTarget(RequestsMenu.getItemAtIndex(0),
+ "GET", CONTENT_TYPE_SJS + "?fmt=json-long", {
+ status: 200,
+ statusText: "OK",
+ type: "json",
+ fullMimeType: "text/json; charset=utf-8",
+ size: L10N.getFormatStr("networkMenu.sizeKB",
+ L10N.numberWithDecimals(85975 / 1024, 2)),
+ time: true
+ });
+
+ let onEvent = monitor.panelWin.once(EVENTS.RESPONSE_BODY_DISPLAYED);
+ EventUtils.sendMouseEvent({ type: "mousedown" },
+ document.getElementById("details-pane-toggle"));
+ EventUtils.sendMouseEvent({ type: "mousedown" },
+ document.querySelectorAll("#details-pane tab")[3]);
+ yield onEvent;
+
+ testResponseTab();
+
+ yield teardown(monitor);
+
+ function testResponseTab() {
+ let tabEl = document.querySelectorAll("#details-pane tab")[3];
+ let tabpanel = document.querySelectorAll("#details-pane tabpanel")[3];
+
+ is(tabEl.getAttribute("selected"), "true",
+ "The response tab in the network details pane should be selected.");
+
+ is(tabpanel.querySelector("#response-content-info-header")
+ .hasAttribute("hidden"), true,
+ "The response info header doesn't have the intended visibility.");
+ is(tabpanel.querySelector("#response-content-json-box")
+ .hasAttribute("hidden"), false,
+ "The response content json box doesn't have the intended visibility.");
+ is(tabpanel.querySelector("#response-content-textarea-box")
+ .hasAttribute("hidden"), true,
+ "The response content textarea box doesn't have the intended visibility.");
+ is(tabpanel.querySelector("#response-content-image-box")
+ .hasAttribute("hidden"), true,
+ "The response content image box doesn't have the intended visibility.");
+
+ is(tabpanel.querySelectorAll(".variables-view-scope").length, 1,
+ "There should be 1 json scope displayed in this tabpanel.");
+ is(tabpanel.querySelectorAll(".variables-view-property").length, 6143,
+ "There should be 6143 json properties displayed in this tabpanel.");
+ is(tabpanel.querySelectorAll(".variables-view-empty-notice").length, 0,
+ "The empty notice should not be displayed in this tabpanel.");
+
+ let jsonScope = tabpanel.querySelectorAll(".variables-view-scope")[0];
+ let names = ".variables-view-property > .title > .name";
+ let values = ".variables-view-property > .title > .value";
+
+ is(jsonScope.querySelector(".name").getAttribute("value"),
+ L10N.getStr("jsonScopeName"),
+ "The json scope doesn't have the correct title.");
+
+ is(jsonScope.querySelectorAll(names)[0].getAttribute("value"),
+ "0", "The first json property name was incorrect.");
+ is(jsonScope.querySelectorAll(values)[0].getAttribute("value"),
+ "Object", "The first json property value was incorrect.");
+
+ is(jsonScope.querySelectorAll(names)[1].getAttribute("value"),
+ "greeting", "The second json property name was incorrect.");
+ is(jsonScope.querySelectorAll(values)[1].getAttribute("value"),
+ "\"Hello long string JSON!\"", "The second json property value was incorrect.");
+ }
+});
diff --git a/devtools/client/netmonitor/test/browser_net_json-malformed.js b/devtools/client/netmonitor/test/browser_net_json-malformed.js
new file mode 100644
index 000000000..6bed60480
--- /dev/null
+++ b/devtools/client/netmonitor/test/browser_net_json-malformed.js
@@ -0,0 +1,77 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+/**
+ * Tests if malformed JSON responses are handled correctly.
+ */
+
+add_task(function* () {
+ let { tab, monitor } = yield initNetMonitor(JSON_MALFORMED_URL);
+ info("Starting test... ");
+
+ let { document, EVENTS, Editor, NetMonitorView } = monitor.panelWin;
+ let { RequestsMenu } = NetMonitorView;
+
+ RequestsMenu.lazyUpdate = false;
+
+ let wait = waitForNetworkEvents(monitor, 1);
+ yield ContentTask.spawn(tab.linkedBrowser, {}, function* () {
+ content.wrappedJSObject.performRequests();
+ });
+ yield wait;
+
+ verifyRequestItemTarget(RequestsMenu.getItemAtIndex(0),
+ "GET", CONTENT_TYPE_SJS + "?fmt=json-malformed", {
+ status: 200,
+ statusText: "OK",
+ type: "json",
+ fullMimeType: "text/json; charset=utf-8"
+ });
+
+ let onEvent = monitor.panelWin.once(EVENTS.RESPONSE_BODY_DISPLAYED);
+ EventUtils.sendMouseEvent({ type: "mousedown" },
+ document.getElementById("details-pane-toggle"));
+ EventUtils.sendMouseEvent({ type: "mousedown" },
+ document.querySelectorAll("#details-pane tab")[3]);
+ yield onEvent;
+
+ let tabEl = document.querySelectorAll("#details-pane tab")[3];
+ let tabpanel = document.querySelectorAll("#details-pane tabpanel")[3];
+
+ is(tabEl.getAttribute("selected"), "true",
+ "The response tab in the network details pane should be selected.");
+
+ is(tabpanel.querySelector("#response-content-info-header")
+ .hasAttribute("hidden"), false,
+ "The response info header doesn't have the intended visibility.");
+ is(tabpanel.querySelector("#response-content-info-header")
+ .getAttribute("value"),
+ "SyntaxError: JSON.parse: unexpected non-whitespace character after JSON data" +
+ " at line 1 column 40 of the JSON data",
+ "The response info header doesn't have the intended value attribute.");
+ is(tabpanel.querySelector("#response-content-info-header")
+ .getAttribute("tooltiptext"),
+ "SyntaxError: JSON.parse: unexpected non-whitespace character after JSON data" +
+ " at line 1 column 40 of the JSON data",
+ "The response info header doesn't have the intended tooltiptext attribute.");
+
+ is(tabpanel.querySelector("#response-content-json-box")
+ .hasAttribute("hidden"), true,
+ "The response content json box doesn't have the intended visibility.");
+ is(tabpanel.querySelector("#response-content-textarea-box")
+ .hasAttribute("hidden"), false,
+ "The response content textarea box doesn't have the intended visibility.");
+ is(tabpanel.querySelector("#response-content-image-box")
+ .hasAttribute("hidden"), true,
+ "The response content image box doesn't have the intended visibility.");
+
+ let editor = yield NetMonitorView.editor("#response-content-textarea");
+ is(editor.getText(), "{ \"greeting\": \"Hello malformed JSON!\" },",
+ "The text shown in the source editor is incorrect.");
+ is(editor.getMode(), Editor.modes.js,
+ "The mode active in the source editor is incorrect.");
+
+ yield teardown(monitor);
+});
diff --git a/devtools/client/netmonitor/test/browser_net_json_custom_mime.js b/devtools/client/netmonitor/test/browser_net_json_custom_mime.js
new file mode 100644
index 000000000..210ffbbe8
--- /dev/null
+++ b/devtools/client/netmonitor/test/browser_net_json_custom_mime.js
@@ -0,0 +1,90 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+/**
+ * Tests if JSON responses with unusal/custom MIME types are handled correctly.
+ */
+
+add_task(function* () {
+ let { L10N } = require("devtools/client/netmonitor/l10n");
+
+ let { tab, monitor } = yield initNetMonitor(JSON_CUSTOM_MIME_URL);
+ info("Starting test... ");
+
+ let { document, EVENTS, NetMonitorView } = monitor.panelWin;
+ let { RequestsMenu } = NetMonitorView;
+
+ RequestsMenu.lazyUpdate = false;
+
+ let wait = waitForNetworkEvents(monitor, 1);
+ yield ContentTask.spawn(tab.linkedBrowser, {}, function* () {
+ content.wrappedJSObject.performRequests();
+ });
+ yield wait;
+
+ verifyRequestItemTarget(RequestsMenu.getItemAtIndex(0),
+ "GET", CONTENT_TYPE_SJS + "?fmt=json-custom-mime", {
+ status: 200,
+ statusText: "OK",
+ type: "x-bigcorp-json",
+ fullMimeType: "text/x-bigcorp-json; charset=utf-8",
+ size: L10N.getFormatStrWithNumbers("networkMenu.sizeB", 41),
+ time: true
+ });
+
+ let onEvent = monitor.panelWin.once(EVENTS.RESPONSE_BODY_DISPLAYED);
+ EventUtils.sendMouseEvent({ type: "mousedown" },
+ document.getElementById("details-pane-toggle"));
+ EventUtils.sendMouseEvent({ type: "mousedown" },
+ document.querySelectorAll("#details-pane tab")[3]);
+ yield onEvent;
+
+ testResponseTab();
+
+ yield teardown(monitor);
+
+ function testResponseTab() {
+ let tabEl = document.querySelectorAll("#details-pane tab")[3];
+ let tabpanel = document.querySelectorAll("#details-pane tabpanel")[3];
+
+ is(tabEl.getAttribute("selected"), "true",
+ "The response tab in the network details pane should be selected.");
+
+ is(tabpanel.querySelector("#response-content-info-header")
+ .hasAttribute("hidden"), true,
+ "The response info header doesn't have the intended visibility.");
+ is(tabpanel.querySelector("#response-content-json-box")
+ .hasAttribute("hidden"), false,
+ "The response content json box doesn't have the intended visibility.");
+ is(tabpanel.querySelector("#response-content-textarea-box")
+ .hasAttribute("hidden"), true,
+ "The response content textarea box doesn't have the intended visibility.");
+ is(tabpanel.querySelector("#response-content-image-box")
+ .hasAttribute("hidden"), true,
+ "The response content image box doesn't have the intended visibility.");
+
+ is(tabpanel.querySelectorAll(".variables-view-scope").length, 1,
+ "There should be 1 json scope displayed in this tabpanel.");
+ is(tabpanel.querySelectorAll(".variables-view-property").length, 2,
+ "There should be 2 json properties displayed in this tabpanel.");
+ is(tabpanel.querySelectorAll(".variables-view-empty-notice").length, 0,
+ "The empty notice should not be displayed in this tabpanel.");
+
+ let jsonScope = tabpanel.querySelectorAll(".variables-view-scope")[0];
+ is(jsonScope.querySelectorAll(".variables-view-property .name")[0]
+ .getAttribute("value"),
+ "greeting", "The first json property name was incorrect.");
+ is(jsonScope.querySelectorAll(".variables-view-property .value")[0]
+ .getAttribute("value"),
+ "\"Hello oddly-named JSON!\"", "The first json property value was incorrect.");
+
+ is(jsonScope.querySelectorAll(".variables-view-property .name")[1]
+ .getAttribute("value"),
+ "__proto__", "The second json property name was incorrect.");
+ is(jsonScope.querySelectorAll(".variables-view-property .value")[1]
+ .getAttribute("value"),
+ "Object", "The second json property value was incorrect.");
+ }
+});
diff --git a/devtools/client/netmonitor/test/browser_net_json_text_mime.js b/devtools/client/netmonitor/test/browser_net_json_text_mime.js
new file mode 100644
index 000000000..edc98a5c9
--- /dev/null
+++ b/devtools/client/netmonitor/test/browser_net_json_text_mime.js
@@ -0,0 +1,90 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+/**
+ * Tests if JSON responses with unusal/custom MIME types are handled correctly.
+ */
+
+add_task(function* () {
+ let { L10N } = require("devtools/client/netmonitor/l10n");
+
+ let { tab, monitor } = yield initNetMonitor(JSON_TEXT_MIME_URL);
+ info("Starting test... ");
+
+ let { document, EVENTS, NetMonitorView } = monitor.panelWin;
+ let { RequestsMenu } = NetMonitorView;
+
+ RequestsMenu.lazyUpdate = false;
+
+ let wait = waitForNetworkEvents(monitor, 1);
+ yield ContentTask.spawn(tab.linkedBrowser, {}, function* () {
+ content.wrappedJSObject.performRequests();
+ });
+ yield wait;
+
+ verifyRequestItemTarget(RequestsMenu.getItemAtIndex(0),
+ "GET", CONTENT_TYPE_SJS + "?fmt=json-text-mime", {
+ status: 200,
+ statusText: "OK",
+ type: "plain",
+ fullMimeType: "text/plain; charset=utf-8",
+ size: L10N.getFormatStrWithNumbers("networkMenu.sizeB", 41),
+ time: true
+ });
+
+ let onEvent = monitor.panelWin.once(EVENTS.RESPONSE_BODY_DISPLAYED);
+ EventUtils.sendMouseEvent({ type: "mousedown" },
+ document.getElementById("details-pane-toggle"));
+ EventUtils.sendMouseEvent({ type: "mousedown" },
+ document.querySelectorAll("#details-pane tab")[3]);
+ yield onEvent;
+
+ testResponseTab();
+
+ yield teardown(monitor);
+
+ function testResponseTab() {
+ let tabEl = document.querySelectorAll("#details-pane tab")[3];
+ let tabpanel = document.querySelectorAll("#details-pane tabpanel")[3];
+
+ is(tabEl.getAttribute("selected"), "true",
+ "The response tab in the network details pane should be selected.");
+
+ is(tabpanel.querySelector("#response-content-info-header")
+ .hasAttribute("hidden"), true,
+ "The response info header doesn't have the intended visibility.");
+ is(tabpanel.querySelector("#response-content-json-box")
+ .hasAttribute("hidden"), false,
+ "The response content json box doesn't have the intended visibility.");
+ is(tabpanel.querySelector("#response-content-textarea-box")
+ .hasAttribute("hidden"), true,
+ "The response content textarea box doesn't have the intended visibility.");
+ is(tabpanel.querySelector("#response-content-image-box")
+ .hasAttribute("hidden"), true,
+ "The response content image box doesn't have the intended visibility.");
+
+ is(tabpanel.querySelectorAll(".variables-view-scope").length, 1,
+ "There should be 1 json scope displayed in this tabpanel.");
+ is(tabpanel.querySelectorAll(".variables-view-property").length, 2,
+ "There should be 2 json properties displayed in this tabpanel.");
+ is(tabpanel.querySelectorAll(".variables-view-empty-notice").length, 0,
+ "The empty notice should not be displayed in this tabpanel.");
+
+ let jsonScope = tabpanel.querySelectorAll(".variables-view-scope")[0];
+ is(jsonScope.querySelectorAll(".variables-view-property .name")[0]
+ .getAttribute("value"),
+ "greeting", "The first json property name was incorrect.");
+ is(jsonScope.querySelectorAll(".variables-view-property .value")[0]
+ .getAttribute("value"),
+ "\"Hello third-party JSON!\"", "The first json property value was incorrect.");
+
+ is(jsonScope.querySelectorAll(".variables-view-property .name")[1]
+ .getAttribute("value"),
+ "__proto__", "The second json property name was incorrect.");
+ is(jsonScope.querySelectorAll(".variables-view-property .value")[1]
+ .getAttribute("value"),
+ "Object", "The second json property value was incorrect.");
+ }
+});
diff --git a/devtools/client/netmonitor/test/browser_net_jsonp.js b/devtools/client/netmonitor/test/browser_net_jsonp.js
new file mode 100644
index 000000000..3007d8c4d
--- /dev/null
+++ b/devtools/client/netmonitor/test/browser_net_jsonp.js
@@ -0,0 +1,111 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+/**
+ * Tests if JSONP responses are handled correctly.
+ */
+
+add_task(function* () {
+ let { L10N } = require("devtools/client/netmonitor/l10n");
+
+ let { tab, monitor } = yield initNetMonitor(JSONP_URL);
+ info("Starting test... ");
+
+ let { document, EVENTS, NetMonitorView } = monitor.panelWin;
+ let { RequestsMenu, NetworkDetails } = NetMonitorView;
+
+ RequestsMenu.lazyUpdate = false;
+ NetworkDetails._json.lazyEmpty = false;
+
+ let wait = waitForNetworkEvents(monitor, 2);
+ yield ContentTask.spawn(tab.linkedBrowser, {}, function* () {
+ content.wrappedJSObject.performRequests();
+ });
+ yield wait;
+
+ verifyRequestItemTarget(RequestsMenu.getItemAtIndex(0),
+ "GET", CONTENT_TYPE_SJS + "?fmt=jsonp&jsonp=$_0123Fun", {
+ status: 200,
+ statusText: "OK",
+ type: "json",
+ fullMimeType: "text/json; charset=utf-8",
+ size: L10N.getFormatStrWithNumbers("networkMenu.sizeB", 41),
+ time: true
+ });
+ verifyRequestItemTarget(RequestsMenu.getItemAtIndex(1),
+ "GET", CONTENT_TYPE_SJS + "?fmt=jsonp2&jsonp=$_4567Sad", {
+ status: 200,
+ statusText: "OK",
+ type: "json",
+ fullMimeType: "text/json; charset=utf-8",
+ size: L10N.getFormatStrWithNumbers("networkMenu.sizeB", 54),
+ time: true
+ });
+
+ let onEvent = monitor.panelWin.once(EVENTS.RESPONSE_BODY_DISPLAYED);
+ EventUtils.sendMouseEvent({ type: "mousedown" },
+ document.getElementById("details-pane-toggle"));
+ EventUtils.sendMouseEvent({ type: "mousedown" },
+ document.querySelectorAll("#details-pane tab")[3]);
+ yield onEvent;
+
+ testResponseTab("$_0123Fun", "\"Hello JSONP!\"");
+
+ onEvent = monitor.panelWin.once(EVENTS.RESPONSE_BODY_DISPLAYED);
+ RequestsMenu.selectedIndex = 1;
+ yield onEvent;
+
+ testResponseTab("$_4567Sad", "\"Hello weird JSONP!\"");
+
+ yield teardown(monitor);
+
+ function testResponseTab(func, greeting) {
+ let tabEl = document.querySelectorAll("#details-pane tab")[3];
+ let tabpanel = document.querySelectorAll("#details-pane tabpanel")[3];
+
+ is(tabEl.getAttribute("selected"), "true",
+ "The response tab in the network details pane should be selected.");
+
+ is(tabpanel.querySelector("#response-content-info-header")
+ .hasAttribute("hidden"), true,
+ "The response info header doesn't have the intended visibility.");
+ is(tabpanel.querySelector("#response-content-json-box")
+ .hasAttribute("hidden"), false,
+ "The response content json box doesn't have the intended visibility.");
+ is(tabpanel.querySelector("#response-content-textarea-box")
+ .hasAttribute("hidden"), true,
+ "The response content textarea box doesn't have the intended visibility.");
+ is(tabpanel.querySelector("#response-content-image-box")
+ .hasAttribute("hidden"), true,
+ "The response content image box doesn't have the intended visibility.");
+
+ is(tabpanel.querySelectorAll(".variables-view-scope").length, 1,
+ "There should be 1 json scope displayed in this tabpanel.");
+ is(tabpanel.querySelectorAll(".variables-view-property").length, 2,
+ "There should be 2 json properties displayed in this tabpanel.");
+ is(tabpanel.querySelectorAll(".variables-view-empty-notice").length, 0,
+ "The empty notice should not be displayed in this tabpanel.");
+
+ let jsonScope = tabpanel.querySelectorAll(".variables-view-scope")[0];
+
+ is(jsonScope.querySelector(".name").getAttribute("value"),
+ L10N.getFormatStr("jsonpScopeName", func),
+ "The json scope doesn't have the correct title.");
+
+ is(jsonScope.querySelectorAll(".variables-view-property .name")[0]
+ .getAttribute("value"),
+ "greeting", "The first json property name was incorrect.");
+ is(jsonScope.querySelectorAll(".variables-view-property .value")[0]
+ .getAttribute("value"),
+ greeting, "The first json property value was incorrect.");
+
+ is(jsonScope.querySelectorAll(".variables-view-property .name")[1]
+ .getAttribute("value"),
+ "__proto__", "The second json property name was incorrect.");
+ is(jsonScope.querySelectorAll(".variables-view-property .value")[1]
+ .getAttribute("value"),
+ "Object", "The second json property value was incorrect.");
+ }
+});
diff --git a/devtools/client/netmonitor/test/browser_net_large-response.js b/devtools/client/netmonitor/test/browser_net_large-response.js
new file mode 100644
index 000000000..98d67b46d
--- /dev/null
+++ b/devtools/client/netmonitor/test/browser_net_large-response.js
@@ -0,0 +1,55 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+/**
+ * Tests if very large response contents are just displayed as plain text.
+ */
+
+const HTML_LONG_URL = CONTENT_TYPE_SJS + "?fmt=html-long";
+
+add_task(function* () {
+ let { tab, monitor } = yield initNetMonitor(CUSTOM_GET_URL);
+ info("Starting test... ");
+
+ // This test could potentially be slow because over 100 KB of stuff
+ // is going to be requested and displayed in the source editor.
+ requestLongerTimeout(2);
+
+ let { document, EVENTS, Editor, NetMonitorView } = monitor.panelWin;
+ let { RequestsMenu } = NetMonitorView;
+
+ RequestsMenu.lazyUpdate = false;
+
+ let wait = waitForNetworkEvents(monitor, 1);
+ yield ContentTask.spawn(tab.linkedBrowser, HTML_LONG_URL, function* (url) {
+ content.wrappedJSObject.performRequests(1, url);
+ });
+ yield wait;
+
+ verifyRequestItemTarget(RequestsMenu.getItemAtIndex(0),
+ "GET", CONTENT_TYPE_SJS + "?fmt=html-long", {
+ status: 200,
+ statusText: "OK"
+ });
+
+ let onEvent = monitor.panelWin.once(EVENTS.RESPONSE_BODY_DISPLAYED);
+ EventUtils.sendMouseEvent({ type: "mousedown" },
+ document.getElementById("details-pane-toggle"));
+ EventUtils.sendMouseEvent({ type: "mousedown" },
+ document.querySelectorAll("#details-pane tab")[3]);
+ yield onEvent;
+
+ let editor = yield NetMonitorView.editor("#response-content-textarea");
+ ok(editor.getText().match(/^<p>/),
+ "The text shown in the source editor is incorrect.");
+ is(editor.getMode(), Editor.modes.text,
+ "The mode active in the source editor is incorrect.");
+
+ yield teardown(monitor);
+
+ // This test uses a lot of memory, so force a GC to help fragmentation.
+ info("Forcing GC after netmonitor test.");
+ Cu.forceGC();
+});
diff --git a/devtools/client/netmonitor/test/browser_net_leak_on_tab_close.js b/devtools/client/netmonitor/test/browser_net_leak_on_tab_close.js
new file mode 100644
index 000000000..9e788f36c
--- /dev/null
+++ b/devtools/client/netmonitor/test/browser_net_leak_on_tab_close.js
@@ -0,0 +1,17 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+/**
+ * Tests that netmonitor doesn't leak windows on parent-side pages (bug 1285638)
+ */
+
+add_task(function* () {
+ // Tell initNetMonitor to enable cache. Otherwise it will assert that there were more
+ // than zero network requests during the page load. But when loading about:config,
+ // there are none.
+ let { monitor } = yield initNetMonitor("about:config", null, true);
+ ok(monitor, "The network monitor was opened");
+ yield teardown(monitor);
+});
diff --git a/devtools/client/netmonitor/test/browser_net_open_request_in_tab.js b/devtools/client/netmonitor/test/browser_net_open_request_in_tab.js
new file mode 100644
index 000000000..8e7ffcc84
--- /dev/null
+++ b/devtools/client/netmonitor/test/browser_net_open_request_in_tab.js
@@ -0,0 +1,37 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+/**
+ * Tests if Open in new tab works.
+ */
+
+add_task(function* () {
+ let { tab, monitor } = yield initNetMonitor(CUSTOM_GET_URL);
+ info("Starting test...");
+
+ let { NetMonitorView } = monitor.panelWin;
+ let { RequestsMenu } = NetMonitorView;
+
+ RequestsMenu.lazyUpdate = false;
+
+ let wait = waitForNetworkEvents(monitor, 1);
+ yield ContentTask.spawn(tab.linkedBrowser, {}, function* () {
+ content.wrappedJSObject.performRequests(1);
+ });
+ yield wait;
+
+ let requestItem = RequestsMenu.getItemAtIndex(0);
+ RequestsMenu.selectedItem = requestItem;
+
+ let onTabOpen = once(gBrowser.tabContainer, "TabOpen", false);
+ RequestsMenu.contextMenu.openRequestInTab();
+ yield onTabOpen;
+
+ ok(true, "A new tab has been opened");
+
+ yield teardown(monitor);
+
+ gBrowser.removeCurrentTab();
+});
diff --git a/devtools/client/netmonitor/test/browser_net_page-nav.js b/devtools/client/netmonitor/test/browser_net_page-nav.js
new file mode 100644
index 000000000..6ac18297c
--- /dev/null
+++ b/devtools/client/netmonitor/test/browser_net_page-nav.js
@@ -0,0 +1,69 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+/**
+ * Tests if page navigation ("close", "navigate", etc.) triggers an appropriate
+ * action in the network monitor.
+ */
+
+add_task(function* () {
+ let { tab, monitor } = yield initNetMonitor(SIMPLE_URL);
+ info("Starting test... ");
+
+ let { EVENTS } = monitor.panelWin;
+
+ yield testNavigate();
+ yield testNavigateBack();
+ yield testClose();
+
+ function* testNavigate() {
+ info("Navigating forward...");
+
+ let onWillNav = monitor.panelWin.once(EVENTS.TARGET_WILL_NAVIGATE);
+ let onDidNav = monitor.panelWin.once(EVENTS.TARGET_DID_NAVIGATE);
+
+ tab.linkedBrowser.loadURI(NAVIGATE_URL);
+ yield onWillNav;
+
+ is(tab.linkedBrowser.currentURI.spec, SIMPLE_URL,
+ "Target started navigating to the correct location.");
+
+ yield onDidNav;
+ is(tab.linkedBrowser.currentURI.spec, NAVIGATE_URL,
+ "Target finished navigating to the correct location.");
+ }
+
+ function* testNavigateBack() {
+ info("Navigating backward...");
+
+ let onWillNav = monitor.panelWin.once(EVENTS.TARGET_WILL_NAVIGATE);
+ let onDidNav = monitor.panelWin.once(EVENTS.TARGET_DID_NAVIGATE);
+
+ tab.linkedBrowser.loadURI(SIMPLE_URL);
+ yield onWillNav;
+
+ is(tab.linkedBrowser.currentURI.spec, NAVIGATE_URL,
+ "Target started navigating back to the previous location.");
+
+ yield onDidNav;
+ is(tab.linkedBrowser.currentURI.spec, SIMPLE_URL,
+ "Target finished navigating back to the previous location.");
+ }
+
+ function* testClose() {
+ info("Closing...");
+
+ let onDestroyed = monitor.once("destroyed");
+ removeTab(tab);
+ yield onDestroyed;
+
+ ok(!monitor._controller.client,
+ "There shouldn't be a client available after destruction.");
+ ok(!monitor._controller.tabClient,
+ "There shouldn't be a tabClient available after destruction.");
+ ok(!monitor._controller.webConsoleClient,
+ "There shouldn't be a webConsoleClient available after destruction.");
+ }
+});
diff --git a/devtools/client/netmonitor/test/browser_net_pane-collapse.js b/devtools/client/netmonitor/test/browser_net_pane-collapse.js
new file mode 100644
index 000000000..2760ec000
--- /dev/null
+++ b/devtools/client/netmonitor/test/browser_net_pane-collapse.js
@@ -0,0 +1,72 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+/**
+ * Tests if the network monitor panes collapse properly.
+ */
+
+add_task(function* () {
+ let { monitor } = yield initNetMonitor(SIMPLE_URL);
+ info("Starting test... ");
+
+ let { document, Prefs, NetMonitorView } = monitor.panelWin;
+ let detailsPane = document.getElementById("details-pane");
+ let detailsPaneToggleButton = document.getElementById("details-pane-toggle");
+
+ ok(detailsPane.classList.contains("pane-collapsed") &&
+ detailsPaneToggleButton.classList.contains("pane-collapsed"),
+ "The details pane should initially be hidden.");
+
+ NetMonitorView.toggleDetailsPane({ visible: true, animated: false });
+
+ let width = ~~(detailsPane.getAttribute("width"));
+ is(width, Prefs.networkDetailsWidth,
+ "The details pane has an incorrect width.");
+ is(detailsPane.style.marginLeft, "0px",
+ "The details pane has an incorrect left margin.");
+ is(detailsPane.style.marginRight, "0px",
+ "The details pane has an incorrect right margin.");
+ ok(!detailsPane.hasAttribute("animated"),
+ "The details pane has an incorrect animated attribute.");
+ ok(!detailsPane.classList.contains("pane-collapsed") &&
+ !detailsPaneToggleButton.classList.contains("pane-collapsed"),
+ "The details pane should at this point be visible.");
+
+ // Trigger reflow to make sure the UI is in required state.
+ document.documentElement.getBoundingClientRect();
+
+ NetMonitorView.toggleDetailsPane({ visible: false, animated: true });
+
+ yield once(detailsPane, "transitionend");
+
+ let margin = -(width + 1) + "px";
+ is(width, Prefs.networkDetailsWidth,
+ "The details pane has an incorrect width after collapsing.");
+ is(detailsPane.style.marginLeft, margin,
+ "The details pane has an incorrect left margin after collapsing.");
+ is(detailsPane.style.marginRight, margin,
+ "The details pane has an incorrect right margin after collapsing.");
+ ok(!detailsPane.hasAttribute("animated"),
+ "The details pane has an incorrect attribute after an animated collapsing.");
+ ok(detailsPane.classList.contains("pane-collapsed") &&
+ detailsPaneToggleButton.classList.contains("pane-collapsed"),
+ "The details pane should not be visible after collapsing.");
+
+ NetMonitorView.toggleDetailsPane({ visible: true, animated: false });
+
+ is(width, Prefs.networkDetailsWidth,
+ "The details pane has an incorrect width after uncollapsing.");
+ is(detailsPane.style.marginLeft, "0px",
+ "The details pane has an incorrect left margin after uncollapsing.");
+ is(detailsPane.style.marginRight, "0px",
+ "The details pane has an incorrect right margin after uncollapsing.");
+ ok(!detailsPane.hasAttribute("animated"),
+ "The details pane has an incorrect attribute after an unanimated uncollapsing.");
+ ok(!detailsPane.classList.contains("pane-collapsed") &&
+ !detailsPaneToggleButton.classList.contains("pane-collapsed"),
+ "The details pane should be visible again after uncollapsing.");
+
+ yield teardown(monitor);
+});
diff --git a/devtools/client/netmonitor/test/browser_net_pane-toggle.js b/devtools/client/netmonitor/test/browser_net_pane-toggle.js
new file mode 100644
index 000000000..87b71019c
--- /dev/null
+++ b/devtools/client/netmonitor/test/browser_net_pane-toggle.js
@@ -0,0 +1,74 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+/**
+ * Tests if toggling the details pane works as expected.
+ */
+
+add_task(function* () {
+ let { tab, monitor } = yield initNetMonitor(SIMPLE_URL);
+ info("Starting test... ");
+
+ let { $, NetMonitorView } = monitor.panelWin;
+ let { RequestsMenu } = NetMonitorView;
+ let { NETWORK_EVENT, TAB_UPDATED } = monitor.panelWin.EVENTS;
+ RequestsMenu.lazyUpdate = false;
+
+ let toggleButton = $("#details-pane-toggle");
+
+ is(toggleButton.hasAttribute("disabled"), true,
+ "The pane toggle button should be disabled when the frontend is opened.");
+ is(toggleButton.classList.contains("pane-collapsed"), true,
+ "The pane toggle button should indicate that the details pane is " +
+ "collapsed when the frontend is opened.");
+ is(NetMonitorView.detailsPaneHidden, true,
+ "The details pane should be hidden when the frontend is opened.");
+ is(RequestsMenu.selectedItem, null,
+ "There should be no selected item in the requests menu.");
+
+ let networkEvent = monitor.panelWin.once(NETWORK_EVENT);
+ tab.linkedBrowser.reload();
+ yield networkEvent;
+
+ is(toggleButton.hasAttribute("disabled"), false,
+ "The pane toggle button should be enabled after the first request.");
+ is(toggleButton.classList.contains("pane-collapsed"), true,
+ "The pane toggle button should still indicate that the details pane is " +
+ "collapsed after the first request.");
+ is(NetMonitorView.detailsPaneHidden, true,
+ "The details pane should still be hidden after the first request.");
+ is(RequestsMenu.selectedItem, null,
+ "There should still be no selected item in the requests menu.");
+
+ EventUtils.sendMouseEvent({ type: "mousedown" }, toggleButton);
+
+ yield monitor.panelWin.once(TAB_UPDATED);
+
+ is(toggleButton.hasAttribute("disabled"), false,
+ "The pane toggle button should still be enabled after being pressed.");
+ is(toggleButton.classList.contains("pane-collapsed"), false,
+ "The pane toggle button should now indicate that the details pane is " +
+ "not collapsed anymore after being pressed.");
+ is(NetMonitorView.detailsPaneHidden, false,
+ "The details pane should not be hidden after toggle button was pressed.");
+ isnot(RequestsMenu.selectedItem, null,
+ "There should be a selected item in the requests menu.");
+ is(RequestsMenu.selectedIndex, 0,
+ "The first item should be selected in the requests menu.");
+
+ EventUtils.sendMouseEvent({ type: "mousedown" }, toggleButton);
+
+ is(toggleButton.hasAttribute("disabled"), false,
+ "The pane toggle button should still be enabled after being pressed again.");
+ is(toggleButton.classList.contains("pane-collapsed"), true,
+ "The pane toggle button should now indicate that the details pane is " +
+ "collapsed after being pressed again.");
+ is(NetMonitorView.detailsPaneHidden, true,
+ "The details pane should now be hidden after the toggle button was pressed again.");
+ is(RequestsMenu.selectedItem, null,
+ "There should now be no selected item in the requests menu.");
+
+ yield teardown(monitor);
+});
diff --git a/devtools/client/netmonitor/test/browser_net_persistent_logs.js b/devtools/client/netmonitor/test/browser_net_persistent_logs.js
new file mode 100644
index 000000000..ac2371e1f
--- /dev/null
+++ b/devtools/client/netmonitor/test/browser_net_persistent_logs.js
@@ -0,0 +1,51 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+/**
+ * Tests if the network monitor leaks on initialization and sudden destruction.
+ * You can also use this initialization format as a template for other tests.
+ */
+
+add_task(function* () {
+ let { tab, monitor } = yield initNetMonitor(SINGLE_GET_URL);
+ info("Starting test... ");
+
+ let { NetMonitorView } = monitor.panelWin;
+ let { RequestsMenu } = NetMonitorView;
+
+ Services.prefs.setBoolPref("devtools.webconsole.persistlog", false);
+
+ yield reloadAndWait();
+
+ is(RequestsMenu.itemCount, 2,
+ "The request menu should have two items at this point.");
+
+ yield reloadAndWait();
+
+ // Since the reload clears the log, we still expect two requests in the log
+ is(RequestsMenu.itemCount, 2,
+ "The request menu should still have two items at this point.");
+
+ // Now we toggle the persistence logs on
+ Services.prefs.setBoolPref("devtools.webconsole.persistlog", true);
+
+ yield reloadAndWait();
+
+ // Since we togged the persistence logs, we expect four items after the reload
+ is(RequestsMenu.itemCount, 4,
+ "The request menu should now have four items at this point.");
+
+ Services.prefs.setBoolPref("devtools.webconsole.persistlog", false);
+ return teardown(monitor);
+
+ /**
+ * Reload the page and wait for 2 GET requests. Race-free.
+ */
+ function reloadAndWait() {
+ let wait = waitForNetworkEvents(monitor, 2);
+ tab.linkedBrowser.reload();
+ return wait;
+ }
+});
diff --git a/devtools/client/netmonitor/test/browser_net_post-data-01.js b/devtools/client/netmonitor/test/browser_net_post-data-01.js
new file mode 100644
index 000000000..6d5f8dc1b
--- /dev/null
+++ b/devtools/client/netmonitor/test/browser_net_post-data-01.js
@@ -0,0 +1,166 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+/**
+ * Tests if the POST requests display the correct information in the UI.
+ */
+
+add_task(function* () {
+ let { L10N } = require("devtools/client/netmonitor/l10n");
+
+ let { tab, monitor } = yield initNetMonitor(POST_DATA_URL);
+ info("Starting test... ");
+
+ let { document, EVENTS, Editor, NetMonitorView } = monitor.panelWin;
+ let { RequestsMenu, NetworkDetails } = NetMonitorView;
+
+ RequestsMenu.lazyUpdate = false;
+ NetworkDetails._params.lazyEmpty = false;
+
+ let wait = waitForNetworkEvents(monitor, 0, 2);
+ yield ContentTask.spawn(tab.linkedBrowser, {}, function* () {
+ content.wrappedJSObject.performRequests();
+ });
+ yield wait;
+
+ verifyRequestItemTarget(RequestsMenu.getItemAtIndex(0),
+ "POST", SIMPLE_SJS + "?foo=bar&baz=42&type=urlencoded", {
+ status: 200,
+ statusText: "Och Aye",
+ type: "plain",
+ fullMimeType: "text/plain; charset=utf-8",
+ size: L10N.getFormatStrWithNumbers("networkMenu.sizeB", 12),
+ time: true
+ });
+ verifyRequestItemTarget(RequestsMenu.getItemAtIndex(1),
+ "POST", SIMPLE_SJS + "?foo=bar&baz=42&type=multipart", {
+ status: 200,
+ statusText: "Och Aye",
+ type: "plain",
+ fullMimeType: "text/plain; charset=utf-8",
+ size: L10N.getFormatStrWithNumbers("networkMenu.sizeB", 12),
+ time: true
+ });
+
+ let onEvent = monitor.panelWin.once(EVENTS.TAB_UPDATED);
+ EventUtils.sendMouseEvent({ type: "mousedown" },
+ document.getElementById("details-pane-toggle"));
+ EventUtils.sendMouseEvent({ type: "mousedown" },
+ document.querySelectorAll("#details-pane tab")[2]);
+ yield onEvent;
+ yield testParamsTab("urlencoded");
+
+ onEvent = monitor.panelWin.once(EVENTS.TAB_UPDATED);
+ RequestsMenu.selectedIndex = 1;
+ yield onEvent;
+ yield testParamsTab("multipart");
+
+ return teardown(monitor);
+
+ function* testParamsTab(type) {
+ let tabEl = document.querySelectorAll("#details-pane tab")[2];
+ let tabpanel = document.querySelectorAll("#details-pane tabpanel")[2];
+
+ is(tabEl.getAttribute("selected"), "true",
+ "The params tab in the network details pane should be selected.");
+
+ function checkVisibility(box) {
+ is(tabpanel.querySelector("#request-params-box")
+ .hasAttribute("hidden"), !box.includes("params"),
+ "The request params box doesn't have the indended visibility.");
+ is(tabpanel.querySelector("#request-post-data-textarea-box")
+ .hasAttribute("hidden"), !box.includes("textarea"),
+ "The request post data textarea box doesn't have the indended visibility.");
+ }
+
+ is(tabpanel.querySelectorAll(".variables-view-scope").length, 2,
+ "There should be 2 param scopes displayed in this tabpanel.");
+ is(tabpanel.querySelectorAll(".variables-view-empty-notice").length, 0,
+ "The empty notice should not be displayed in this tabpanel.");
+
+ let queryScope = tabpanel.querySelectorAll(".variables-view-scope")[0];
+ let postScope = tabpanel.querySelectorAll(".variables-view-scope")[1];
+
+ is(queryScope.querySelector(".name").getAttribute("value"),
+ L10N.getStr("paramsQueryString"),
+ "The query scope doesn't have the correct title.");
+
+ is(postScope.querySelector(".name").getAttribute("value"),
+ L10N.getStr(type == "urlencoded" ? "paramsFormData" : "paramsPostPayload"),
+ "The post scope doesn't have the correct title.");
+
+ is(queryScope.querySelectorAll(".variables-view-variable .name")[0]
+ .getAttribute("value"),
+ "foo", "The first query param name was incorrect.");
+ is(queryScope.querySelectorAll(".variables-view-variable .value")[0]
+ .getAttribute("value"),
+ "\"bar\"", "The first query param value was incorrect.");
+ is(queryScope.querySelectorAll(".variables-view-variable .name")[1]
+ .getAttribute("value"),
+ "baz", "The second query param name was incorrect.");
+ is(queryScope.querySelectorAll(".variables-view-variable .value")[1]
+ .getAttribute("value"),
+ "\"42\"", "The second query param value was incorrect.");
+ is(queryScope.querySelectorAll(".variables-view-variable .name")[2]
+ .getAttribute("value"),
+ "type", "The third query param name was incorrect.");
+ is(queryScope.querySelectorAll(".variables-view-variable .value")[2]
+ .getAttribute("value"),
+ "\"" + type + "\"", "The third query param value was incorrect.");
+
+ if (type == "urlencoded") {
+ checkVisibility("params");
+
+ is(tabpanel.querySelectorAll(".variables-view-variable").length, 5,
+ "There should be 5 param values displayed in this tabpanel.");
+ is(queryScope.querySelectorAll(".variables-view-variable").length, 3,
+ "There should be 3 param values displayed in the query scope.");
+ is(postScope.querySelectorAll(".variables-view-variable").length, 2,
+ "There should be 2 param values displayed in the post scope.");
+
+ is(postScope.querySelectorAll(".variables-view-variable .name")[0]
+ .getAttribute("value"),
+ "foo", "The first post param name was incorrect.");
+ is(postScope.querySelectorAll(".variables-view-variable .value")[0]
+ .getAttribute("value"),
+ "\"bar\"", "The first post param value was incorrect.");
+ is(postScope.querySelectorAll(".variables-view-variable .name")[1]
+ .getAttribute("value"),
+ "baz", "The second post param name was incorrect.");
+ is(postScope.querySelectorAll(".variables-view-variable .value")[1]
+ .getAttribute("value"),
+ "\"123\"", "The second post param value was incorrect.");
+ } else {
+ checkVisibility("params textarea");
+
+ is(tabpanel.querySelectorAll(".variables-view-variable").length, 3,
+ "There should be 3 param values displayed in this tabpanel.");
+ is(queryScope.querySelectorAll(".variables-view-variable").length, 3,
+ "There should be 3 param values displayed in the query scope.");
+ is(postScope.querySelectorAll(".variables-view-variable").length, 0,
+ "There should be 0 param values displayed in the post scope.");
+
+ let editor = yield NetMonitorView.editor("#request-post-data-textarea");
+ let text = editor.getText();
+
+ ok(text.includes("Content-Disposition: form-data; name=\"text\""),
+ "The text shown in the source editor is incorrect (1.1).");
+ ok(text.includes("Content-Disposition: form-data; name=\"email\""),
+ "The text shown in the source editor is incorrect (2.1).");
+ ok(text.includes("Content-Disposition: form-data; name=\"range\""),
+ "The text shown in the source editor is incorrect (3.1).");
+ ok(text.includes("Content-Disposition: form-data; name=\"Custom field\""),
+ "The text shown in the source editor is incorrect (4.1).");
+ ok(text.includes("Some text..."),
+ "The text shown in the source editor is incorrect (2.2).");
+ ok(text.includes("42"),
+ "The text shown in the source editor is incorrect (3.2).");
+ ok(text.includes("Extra data"),
+ "The text shown in the source editor is incorrect (4.2).");
+ is(editor.getMode(), Editor.modes.text,
+ "The mode active in the source editor is incorrect.");
+ }
+ }
+});
diff --git a/devtools/client/netmonitor/test/browser_net_post-data-02.js b/devtools/client/netmonitor/test/browser_net_post-data-02.js
new file mode 100644
index 000000000..3cdd2f14a
--- /dev/null
+++ b/devtools/client/netmonitor/test/browser_net_post-data-02.js
@@ -0,0 +1,73 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+/**
+ * Tests if the POST requests display the correct information in the UI,
+ * for raw payloads with attached content-type headers.
+ */
+
+add_task(function* () {
+ let { L10N } = require("devtools/client/netmonitor/l10n");
+
+ let { tab, monitor } = yield initNetMonitor(POST_RAW_URL);
+ info("Starting test... ");
+
+ let { document, EVENTS, NetMonitorView } = monitor.panelWin;
+ let { RequestsMenu, NetworkDetails } = NetMonitorView;
+
+ RequestsMenu.lazyUpdate = false;
+ NetworkDetails._params.lazyEmpty = false;
+
+ let wait = waitForNetworkEvents(monitor, 0, 1);
+ yield ContentTask.spawn(tab.linkedBrowser, {}, function* () {
+ content.wrappedJSObject.performRequests();
+ });
+ yield wait;
+
+ let onEvent = monitor.panelWin.once(EVENTS.TAB_UPDATED);
+ NetMonitorView.toggleDetailsPane({ visible: true }, 2);
+ RequestsMenu.selectedIndex = 0;
+ yield onEvent;
+
+ let tabEl = document.querySelectorAll("#event-details-pane tab")[2];
+ let tabpanel = document.querySelectorAll("#event-details-pane tabpanel")[2];
+
+ is(tabEl.getAttribute("selected"), "true",
+ "The params tab in the network details pane should be selected.");
+
+ is(tabpanel.querySelector("#request-params-box")
+ .hasAttribute("hidden"), false,
+ "The request params box doesn't have the indended visibility.");
+ is(tabpanel.querySelector("#request-post-data-textarea-box")
+ .hasAttribute("hidden"), true,
+ "The request post data textarea box doesn't have the indended visibility.");
+
+ is(tabpanel.querySelectorAll(".variables-view-scope").length, 1,
+ "There should be 1 param scopes displayed in this tabpanel.");
+ is(tabpanel.querySelectorAll(".variables-view-empty-notice").length, 0,
+ "The empty notice should not be displayed in this tabpanel.");
+
+ let postScope = tabpanel.querySelectorAll(".variables-view-scope")[0];
+ is(postScope.querySelector(".name").getAttribute("value"),
+ L10N.getStr("paramsFormData"),
+ "The post scope doesn't have the correct title.");
+
+ is(postScope.querySelectorAll(".variables-view-variable").length, 2,
+ "There should be 2 param values displayed in the post scope.");
+ is(postScope.querySelectorAll(".variables-view-variable .name")[0]
+ .getAttribute("value"),
+ "foo", "The first query param name was incorrect.");
+ is(postScope.querySelectorAll(".variables-view-variable .value")[0]
+ .getAttribute("value"),
+ "\"bar\"", "The first query param value was incorrect.");
+ is(postScope.querySelectorAll(".variables-view-variable .name")[1]
+ .getAttribute("value"),
+ "baz", "The second query param name was incorrect.");
+ is(postScope.querySelectorAll(".variables-view-variable .value")[1]
+ .getAttribute("value"),
+ "\"123\"", "The second query param value was incorrect.");
+
+ return teardown(monitor);
+});
diff --git a/devtools/client/netmonitor/test/browser_net_post-data-03.js b/devtools/client/netmonitor/test/browser_net_post-data-03.js
new file mode 100644
index 000000000..3433f89ce
--- /dev/null
+++ b/devtools/client/netmonitor/test/browser_net_post-data-03.js
@@ -0,0 +1,98 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+/**
+ * Tests if the POST requests display the correct information in the UI,
+ * for raw payloads with content-type headers attached to the upload stream.
+ */
+
+add_task(function* () {
+ let { L10N } = require("devtools/client/netmonitor/l10n");
+
+ let { tab, monitor } = yield initNetMonitor(POST_RAW_WITH_HEADERS_URL);
+ info("Starting test... ");
+
+ let { document, EVENTS, NetMonitorView } = monitor.panelWin;
+ let { RequestsMenu } = NetMonitorView;
+
+ RequestsMenu.lazyUpdate = false;
+
+ let wait = waitForNetworkEvents(monitor, 0, 1);
+ yield ContentTask.spawn(tab.linkedBrowser, {}, function* () {
+ content.wrappedJSObject.performRequests();
+ });
+ yield wait;
+
+ let onEvent = monitor.panelWin.once(EVENTS.TAB_UPDATED);
+ NetMonitorView.toggleDetailsPane({ visible: true });
+ RequestsMenu.selectedIndex = 0;
+ yield onEvent;
+
+ let tabEl = document.querySelectorAll("#details-pane tab")[0];
+ let tabpanel = document.querySelectorAll("#details-pane tabpanel")[0];
+ let requestFromUploadScope = tabpanel.querySelectorAll(".variables-view-scope")[2];
+
+ is(tabEl.getAttribute("selected"), "true",
+ "The headers tab in the network details pane should be selected.");
+ is(tabpanel.querySelectorAll(".variables-view-scope").length, 3,
+ "There should be 3 header scopes displayed in this tabpanel.");
+
+ is(requestFromUploadScope.querySelector(".name").getAttribute("value"),
+ L10N.getStr("requestHeadersFromUpload") + " (" +
+ L10N.getFormatStr("networkMenu.sizeKB", L10N.numberWithDecimals(74 / 1024, 3)) + ")",
+ "The request headers from upload scope doesn't have the correct title.");
+
+ is(requestFromUploadScope.querySelectorAll(".variables-view-variable").length, 2,
+ "There should be 2 headers displayed in the request headers from upload scope.");
+
+ is(requestFromUploadScope.querySelectorAll(".variables-view-variable .name")[0]
+ .getAttribute("value"),
+ "content-type", "The first request header name was incorrect.");
+ is(requestFromUploadScope.querySelectorAll(".variables-view-variable .value")[0]
+ .getAttribute("value"), "\"application/x-www-form-urlencoded\"",
+ "The first request header value was incorrect.");
+ is(requestFromUploadScope.querySelectorAll(".variables-view-variable .name")[1]
+ .getAttribute("value"),
+ "custom-header", "The second request header name was incorrect.");
+ is(requestFromUploadScope.querySelectorAll(".variables-view-variable .value")[1]
+ .getAttribute("value"),
+ "\"hello world!\"", "The second request header value was incorrect.");
+
+ onEvent = monitor.panelWin.once(EVENTS.TAB_UPDATED);
+ EventUtils.sendMouseEvent({ type: "mousedown" },
+ document.querySelectorAll("#details-pane tab")[2]);
+ yield onEvent;
+
+ tabEl = document.querySelectorAll("#details-pane tab")[2];
+ tabpanel = document.querySelectorAll("#details-pane tabpanel")[2];
+ let formDataScope = tabpanel.querySelectorAll(".variables-view-scope")[0];
+
+ is(tab.getAttribute("selected"), "true",
+ "The response tab in the network details pane should be selected.");
+ is(tabpanel.querySelectorAll(".variables-view-scope").length, 1,
+ "There should be 1 header scope displayed in this tabpanel.");
+
+ is(formDataScope.querySelector(".name").getAttribute("value"),
+ L10N.getStr("paramsFormData"),
+ "The form data scope doesn't have the correct title.");
+
+ is(formDataScope.querySelectorAll(".variables-view-variable").length, 2,
+ "There should be 2 payload values displayed in the form data scope.");
+
+ is(formDataScope.querySelectorAll(".variables-view-variable .name")[0]
+ .getAttribute("value"),
+ "foo", "The first payload param name was incorrect.");
+ is(formDataScope.querySelectorAll(".variables-view-variable .value")[0]
+ .getAttribute("value"),
+ "\"bar\"", "The first payload param value was incorrect.");
+ is(formDataScope.querySelectorAll(".variables-view-variable .name")[1]
+ .getAttribute("value"),
+ "baz", "The second payload param name was incorrect.");
+ is(formDataScope.querySelectorAll(".variables-view-variable .value")[1]
+ .getAttribute("value"),
+ "\"123\"", "The second payload param value was incorrect.");
+
+ return teardown(monitor);
+});
diff --git a/devtools/client/netmonitor/test/browser_net_post-data-04.js b/devtools/client/netmonitor/test/browser_net_post-data-04.js
new file mode 100644
index 000000000..565792287
--- /dev/null
+++ b/devtools/client/netmonitor/test/browser_net_post-data-04.js
@@ -0,0 +1,74 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+/**
+ * Tests if the POST requests display the correct information in the UI,
+ * for JSON payloads.
+ */
+
+add_task(function* () {
+ let { L10N } = require("devtools/client/netmonitor/l10n");
+
+ let { tab, monitor } = yield initNetMonitor(POST_JSON_URL);
+ info("Starting test... ");
+
+ let { document, EVENTS, NetMonitorView } = monitor.panelWin;
+ let { RequestsMenu, NetworkDetails } = NetMonitorView;
+
+ RequestsMenu.lazyUpdate = false;
+ NetworkDetails._params.lazyEmpty = false;
+
+ let wait = waitForNetworkEvents(monitor, 0, 1);
+ yield ContentTask.spawn(tab.linkedBrowser, {}, function* () {
+ content.wrappedJSObject.performRequests();
+ });
+ yield wait;
+
+ let onEvent = monitor.panelWin.once(EVENTS.TAB_UPDATED);
+ NetMonitorView.toggleDetailsPane({ visible: true }, 2);
+ RequestsMenu.selectedIndex = 0;
+ yield onEvent;
+
+ let tabEl = document.querySelectorAll("#event-details-pane tab")[2];
+ let tabpanel = document.querySelectorAll("#event-details-pane tabpanel")[2];
+
+ is(tabEl.getAttribute("selected"), "true",
+ "The params tab in the network details pane should be selected.");
+
+ is(tabpanel.querySelector("#request-params-box")
+ .hasAttribute("hidden"), false,
+ "The request params box doesn't have the intended visibility.");
+ is(tabpanel.querySelector("#request-post-data-textarea-box")
+ .hasAttribute("hidden"), true,
+ "The request post data textarea box doesn't have the intended visibility.");
+
+ is(tabpanel.querySelectorAll(".variables-view-scope").length, 1,
+ "There should be 1 param scopes displayed in this tabpanel.");
+ is(tabpanel.querySelectorAll(".variables-view-empty-notice").length, 0,
+ "The empty notice should not be displayed in this tabpanel.");
+
+ let jsonScope = tabpanel.querySelectorAll(".variables-view-scope")[0];
+ is(jsonScope.querySelector(".name").getAttribute("value"),
+ L10N.getStr("jsonScopeName"),
+ "The JSON scope doesn't have the correct title.");
+
+ let valueScope = tabpanel.querySelector(
+ ".variables-view-scope > .variables-view-element-details");
+
+ is(valueScope.querySelectorAll(".variables-view-variable").length, 1,
+ "There should be 1 value displayed in the JSON scope.");
+ is(valueScope.querySelector(".variables-view-property .name")
+ .getAttribute("value"),
+ "a", "The JSON var name was incorrect.");
+ is(valueScope.querySelector(".variables-view-property .value")
+ .getAttribute("value"),
+ "1", "The JSON var value was incorrect.");
+
+ let detailsParent = valueScope.querySelector(".variables-view-property .name")
+ .closest(".variables-view-element-details");
+ is(detailsParent.hasAttribute("open"), true, "The JSON value must be visible");
+
+ return teardown(monitor);
+});
diff --git a/devtools/client/netmonitor/test/browser_net_prefs-and-l10n.js b/devtools/client/netmonitor/test/browser_net_prefs-and-l10n.js
new file mode 100644
index 000000000..e73f94d6d
--- /dev/null
+++ b/devtools/client/netmonitor/test/browser_net_prefs-and-l10n.js
@@ -0,0 +1,54 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+/**
+ * Tests if the preferences and localization objects work correctly.
+ */
+
+add_task(function* () {
+ let { L10N } = require("devtools/client/netmonitor/l10n");
+
+ let { monitor } = yield initNetMonitor(SIMPLE_URL);
+ info("Starting test... ");
+
+ ok(monitor.panelWin.Prefs,
+ "Should have a preferences object available on the panel window.");
+
+ testL10N();
+ testPrefs();
+
+ return teardown(monitor);
+
+ function testL10N() {
+ is(typeof L10N.getStr("netmonitor.security.enabled"), "string",
+ "The getStr() method didn't return a valid string.");
+ is(typeof L10N.getFormatStr("networkMenu.totalMS", "foo"), "string",
+ "The getFormatStr() method didn't return a valid string.");
+ }
+
+ function testPrefs() {
+ let { Prefs } = monitor.panelWin;
+
+ is(Prefs.networkDetailsWidth,
+ Services.prefs.getIntPref("devtools.netmonitor.panes-network-details-width"),
+ "Getting a pref should work correctly.");
+
+ let previousValue = Prefs.networkDetailsWidth;
+ let bogusValue = ~~(Math.random() * 100);
+ Prefs.networkDetailsWidth = bogusValue;
+ is(Prefs.networkDetailsWidth,
+ Services.prefs.getIntPref("devtools.netmonitor.panes-network-details-width"),
+ "Getting a pref after it has been modified should work correctly.");
+ is(Prefs.networkDetailsWidth, bogusValue,
+ "The pref wasn't updated correctly in the preferences object.");
+
+ Prefs.networkDetailsWidth = previousValue;
+ is(Prefs.networkDetailsWidth,
+ Services.prefs.getIntPref("devtools.netmonitor.panes-network-details-width"),
+ "Getting a pref after it has been modified again should work correctly.");
+ is(Prefs.networkDetailsWidth, previousValue,
+ "The pref wasn't updated correctly again in the preferences object.");
+ }
+});
diff --git a/devtools/client/netmonitor/test/browser_net_prefs-reload.js b/devtools/client/netmonitor/test/browser_net_prefs-reload.js
new file mode 100644
index 000000000..ee56ee446
--- /dev/null
+++ b/devtools/client/netmonitor/test/browser_net_prefs-reload.js
@@ -0,0 +1,215 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+/**
+ * Tests if the prefs that should survive across tool reloads work.
+ */
+
+add_task(function* () {
+ let Actions = require("devtools/client/netmonitor/actions/index");
+ let { monitor } = yield initNetMonitor(SIMPLE_URL);
+ info("Starting test... ");
+
+ // This test reopens the network monitor a bunch of times, for different
+ // hosts (bottom, side, window). This seems to be slow on debug builds.
+ requestLongerTimeout(3);
+
+ // Use these getters instead of caching instances inside the panel win,
+ // since the tool is reopened a bunch of times during this test
+ // and the instances will differ.
+ let getView = () => monitor.panelWin.NetMonitorView;
+ let getStore = () => monitor.panelWin.gStore;
+
+ let prefsToCheck = {
+ filters: {
+ // A custom new value to be used for the verified preference.
+ newValue: ["html", "css"],
+ // Getter used to retrieve the current value from the frontend, in order
+ // to verify that the pref was applied properly.
+ validateValue: ($) => getView().RequestsMenu._activeFilters,
+ // Predicate used to modify the frontend when setting the new pref value,
+ // before trying to validate the changes.
+ modifyFrontend: ($, value) => value.forEach(e =>
+ getStore().dispatch(Actions.toggleFilterType(e)))
+ },
+ networkDetailsWidth: {
+ newValue: ~~(Math.random() * 200 + 100),
+ validateValue: ($) => ~~$("#details-pane").getAttribute("width"),
+ modifyFrontend: ($, value) => $("#details-pane").setAttribute("width", value)
+ },
+ networkDetailsHeight: {
+ newValue: ~~(Math.random() * 300 + 100),
+ validateValue: ($) => ~~$("#details-pane").getAttribute("height"),
+ modifyFrontend: ($, value) => $("#details-pane").setAttribute("height", value)
+ }
+ /* add more prefs here... */
+ };
+
+ yield testBottom();
+ yield testSide();
+ yield testWindow();
+
+ info("Moving toolbox back to the bottom...");
+ yield monitor._toolbox.switchHost(Toolbox.HostType.BOTTOM);
+ return teardown(monitor);
+
+ function storeFirstPrefValues() {
+ info("Caching initial pref values.");
+
+ for (let name in prefsToCheck) {
+ let currentValue = monitor.panelWin.Prefs[name];
+ prefsToCheck[name].firstValue = currentValue;
+ }
+ }
+
+ function validateFirstPrefValues() {
+ info("Validating current pref values to the UI elements.");
+
+ for (let name in prefsToCheck) {
+ let currentValue = monitor.panelWin.Prefs[name];
+ let firstValue = prefsToCheck[name].firstValue;
+ let validateValue = prefsToCheck[name].validateValue;
+
+ is(currentValue.toSource(), firstValue.toSource(),
+ "Pref " + name + " should be equal to first value: " + firstValue);
+ is(currentValue.toSource(), validateValue(monitor.panelWin.$).toSource(),
+ "Pref " + name + " should validate: " + currentValue);
+ }
+ }
+
+ function modifyFrontend() {
+ info("Modifying UI elements to the specified new values.");
+
+ for (let name in prefsToCheck) {
+ let currentValue = monitor.panelWin.Prefs[name];
+ let firstValue = prefsToCheck[name].firstValue;
+ let newValue = prefsToCheck[name].newValue;
+ let validateValue = prefsToCheck[name].validateValue;
+ let modFrontend = prefsToCheck[name].modifyFrontend;
+
+ modFrontend(monitor.panelWin.$, newValue);
+ info("Modified UI element affecting " + name + " to: " + newValue);
+
+ is(currentValue.toSource(), firstValue.toSource(),
+ "Pref " + name + " should still be equal to first value: " + firstValue);
+ isnot(currentValue.toSource(), newValue.toSource(),
+ "Pref " + name + " should't yet be equal to second value: " + newValue);
+ is(newValue.toSource(), validateValue(monitor.panelWin.$).toSource(),
+ "The UI element affecting " + name + " should validate: " + newValue);
+ }
+ }
+
+ function validateNewPrefValues() {
+ info("Invalidating old pref values to the modified UI elements.");
+
+ for (let name in prefsToCheck) {
+ let currentValue = monitor.panelWin.Prefs[name];
+ let firstValue = prefsToCheck[name].firstValue;
+ let newValue = prefsToCheck[name].newValue;
+ let validateValue = prefsToCheck[name].validateValue;
+
+ isnot(currentValue.toSource(), firstValue.toSource(),
+ "Pref " + name + " should't be equal to first value: " + firstValue);
+ is(currentValue.toSource(), newValue.toSource(),
+ "Pref " + name + " should now be equal to second value: " + newValue);
+ is(newValue.toSource(), validateValue(monitor.panelWin.$).toSource(),
+ "The UI element affecting " + name + " should validate: " + newValue);
+ }
+ }
+
+ function resetFrontend() {
+ info("Resetting UI elements to the cached initial pref values.");
+
+ for (let name in prefsToCheck) {
+ let currentValue = monitor.panelWin.Prefs[name];
+ let firstValue = prefsToCheck[name].firstValue;
+ let newValue = prefsToCheck[name].newValue;
+ let validateValue = prefsToCheck[name].validateValue;
+ let modFrontend = prefsToCheck[name].modifyFrontend;
+
+ modFrontend(monitor.panelWin.$, firstValue);
+ info("Modified UI element affecting " + name + " to: " + firstValue);
+
+ isnot(currentValue.toSource(), firstValue.toSource(),
+ "Pref " + name + " should't yet be equal to first value: " + firstValue);
+ is(currentValue.toSource(), newValue.toSource(),
+ "Pref " + name + " should still be equal to second value: " + newValue);
+ is(firstValue.toSource(), validateValue(monitor.panelWin.$).toSource(),
+ "The UI element affecting " + name + " should validate: " + firstValue);
+ }
+ }
+
+ function* testBottom() {
+ info("Testing prefs reload for a bottom host.");
+ storeFirstPrefValues();
+
+ // Validate and modify while toolbox is on the bottom.
+ validateFirstPrefValues();
+ modifyFrontend();
+
+ let newMonitor = yield restartNetMonitor(monitor);
+ monitor = newMonitor.monitor;
+
+ // Revalidate and reset frontend while toolbox is on the bottom.
+ validateNewPrefValues();
+ resetFrontend();
+
+ newMonitor = yield restartNetMonitor(monitor);
+ monitor = newMonitor.monitor;
+
+ // Revalidate.
+ validateFirstPrefValues();
+ }
+
+ function* testSide() {
+ info("Moving toolbox to the side...");
+
+ yield monitor._toolbox.switchHost(Toolbox.HostType.SIDE);
+ info("Testing prefs reload for a side host.");
+ storeFirstPrefValues();
+
+ // Validate and modify frontend while toolbox is on the side.
+ validateFirstPrefValues();
+ modifyFrontend();
+
+ let newMonitor = yield restartNetMonitor(monitor);
+ monitor = newMonitor.monitor;
+
+ // Revalidate and reset frontend while toolbox is on the side.
+ validateNewPrefValues();
+ resetFrontend();
+
+ newMonitor = yield restartNetMonitor(monitor);
+ monitor = newMonitor.monitor;
+
+ // Revalidate.
+ validateFirstPrefValues();
+ }
+
+ function* testWindow() {
+ info("Moving toolbox into a window...");
+
+ yield monitor._toolbox.switchHost(Toolbox.HostType.WINDOW);
+ info("Testing prefs reload for a window host.");
+ storeFirstPrefValues();
+
+ // Validate and modify frontend while toolbox is in a window.
+ validateFirstPrefValues();
+ modifyFrontend();
+
+ let newMonitor = yield restartNetMonitor(monitor);
+ monitor = newMonitor.monitor;
+
+ // Revalidate and reset frontend while toolbox is in a window.
+ validateNewPrefValues();
+ resetFrontend();
+
+ newMonitor = yield restartNetMonitor(monitor);
+ monitor = newMonitor.monitor;
+
+ // Revalidate.
+ validateFirstPrefValues();
+ }
+});
diff --git a/devtools/client/netmonitor/test/browser_net_raw_headers.js b/devtools/client/netmonitor/test/browser_net_raw_headers.js
new file mode 100644
index 000000000..2cb734745
--- /dev/null
+++ b/devtools/client/netmonitor/test/browser_net_raw_headers.js
@@ -0,0 +1,70 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+/**
+ * Tests if showing raw headers works.
+ */
+
+add_task(function* () {
+ let { tab, monitor } = yield initNetMonitor(POST_DATA_URL);
+ info("Starting test... ");
+
+ let { document, EVENTS, NetMonitorView } = monitor.panelWin;
+ let { RequestsMenu } = NetMonitorView;
+
+ RequestsMenu.lazyUpdate = false;
+
+ let wait = waitForNetworkEvents(monitor, 0, 2);
+ yield ContentTask.spawn(tab.linkedBrowser, {}, function* () {
+ content.wrappedJSObject.performRequests();
+ });
+ yield wait;
+
+ let origItem = RequestsMenu.getItemAtIndex(0);
+
+ let onTabEvent = monitor.panelWin.once(EVENTS.TAB_UPDATED);
+ RequestsMenu.selectedItem = origItem;
+ yield onTabEvent;
+
+ EventUtils.sendMouseEvent({ type: "click" },
+ document.getElementById("toggle-raw-headers"));
+
+ testShowRawHeaders(origItem.attachment);
+
+ EventUtils.sendMouseEvent({ type: "click" },
+ document.getElementById("toggle-raw-headers"));
+
+ testHideRawHeaders(document);
+
+ return teardown(monitor);
+
+ /*
+ * Tests that raw headers were displayed correctly
+ */
+ function testShowRawHeaders(data) {
+ let requestHeaders = document.getElementById("raw-request-headers-textarea").value;
+ for (let header of data.requestHeaders.headers) {
+ ok(requestHeaders.indexOf(header.name + ": " + header.value) >= 0,
+ "textarea contains request headers");
+ }
+ let responseHeaders = document.getElementById("raw-response-headers-textarea").value;
+ for (let header of data.responseHeaders.headers) {
+ ok(responseHeaders.indexOf(header.name + ": " + header.value) >= 0,
+ "textarea contains response headers");
+ }
+ }
+
+ /*
+ * Tests that raw headers textareas are hidden and empty
+ */
+ function testHideRawHeaders() {
+ let rawHeadersHidden = document.getElementById("raw-headers").getAttribute("hidden");
+ let requestTextarea = document.getElementById("raw-request-headers-textarea");
+ let responseTextarea = document.getElementById("raw-response-headers-textarea");
+ ok(rawHeadersHidden, "raw headers textareas are hidden");
+ ok(requestTextarea.value == "", "raw request headers textarea is empty");
+ ok(responseTextarea.value == "", "raw response headers textarea is empty");
+ }
+});
diff --git a/devtools/client/netmonitor/test/browser_net_reload-button.js b/devtools/client/netmonitor/test/browser_net_reload-button.js
new file mode 100644
index 000000000..e91de8302
--- /dev/null
+++ b/devtools/client/netmonitor/test/browser_net_reload-button.js
@@ -0,0 +1,25 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+/**
+ * Tests if the empty-requests reload button works.
+ */
+
+add_task(function* () {
+ let { monitor } = yield initNetMonitor(SINGLE_GET_URL);
+ info("Starting test... ");
+
+ let { document, NetMonitorView } = monitor.panelWin;
+ let { RequestsMenu } = NetMonitorView;
+
+ let wait = waitForNetworkEvents(monitor, 2);
+ let button = document.querySelector("#requests-menu-reload-notice-button");
+ button.click();
+ yield wait;
+
+ is(RequestsMenu.itemCount, 2, "The request menu should have two items after reloading");
+
+ return teardown(monitor);
+});
diff --git a/devtools/client/netmonitor/test/browser_net_reload-markers.js b/devtools/client/netmonitor/test/browser_net_reload-markers.js
new file mode 100644
index 000000000..26866830f
--- /dev/null
+++ b/devtools/client/netmonitor/test/browser_net_reload-markers.js
@@ -0,0 +1,35 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+/**
+ * Tests if the empty-requests reload button works.
+ */
+
+add_task(function* () {
+ let { monitor } = yield initNetMonitor(SINGLE_GET_URL);
+ info("Starting test... ");
+
+ let { document, EVENTS } = monitor.panelWin;
+ let button = document.querySelector("#requests-menu-reload-notice-button");
+ button.click();
+
+ let markers = [];
+
+ monitor.panelWin.on(EVENTS.TIMELINE_EVENT, (_, marker) => {
+ markers.push(marker);
+ });
+
+ yield waitForNetworkEvents(monitor, 2);
+ yield waitUntil(() => markers.length == 2);
+
+ ok(true, "Reloading finished");
+
+ is(markers[0].name, "document::DOMContentLoaded",
+ "The first received marker is correct.");
+ is(markers[1].name, "document::Load",
+ "The second received marker is correct.");
+
+ return teardown(monitor);
+});
diff --git a/devtools/client/netmonitor/test/browser_net_req-resp-bodies.js b/devtools/client/netmonitor/test/browser_net_req-resp-bodies.js
new file mode 100644
index 000000000..71a913501
--- /dev/null
+++ b/devtools/client/netmonitor/test/browser_net_req-resp-bodies.js
@@ -0,0 +1,68 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+/**
+ * Test if request and response body logging stays on after opening the console.
+ */
+
+add_task(function* () {
+ let { L10N } = require("devtools/client/netmonitor/l10n");
+
+ let { tab, monitor } = yield initNetMonitor(JSON_LONG_URL);
+ info("Starting test... ");
+
+ let { NetMonitorView } = monitor.panelWin;
+ let { RequestsMenu } = NetMonitorView;
+
+ RequestsMenu.lazyUpdate = false;
+
+ // Perform first batch of requests.
+ let wait = waitForNetworkEvents(monitor, 1);
+ yield ContentTask.spawn(tab.linkedBrowser, {}, function* () {
+ content.wrappedJSObject.performRequests();
+ });
+ yield wait;
+
+ verifyRequest(0);
+
+ // Switch to the webconsole.
+ let onWebConsole = monitor._toolbox.once("webconsole-selected");
+ monitor._toolbox.selectTool("webconsole");
+ yield onWebConsole;
+
+ // Switch back to the netmonitor.
+ let onNetMonitor = monitor._toolbox.once("netmonitor-selected");
+ monitor._toolbox.selectTool("netmonitor");
+ yield onNetMonitor;
+
+ // Reload debugee.
+ wait = waitForNetworkEvents(monitor, 1);
+ tab.linkedBrowser.reload();
+ yield wait;
+
+ // Perform another batch of requests.
+ wait = waitForNetworkEvents(monitor, 1);
+ yield ContentTask.spawn(tab.linkedBrowser, {}, function* () {
+ content.wrappedJSObject.performRequests();
+ });
+ yield wait;
+
+ verifyRequest(1);
+
+ return teardown(monitor);
+
+ function verifyRequest(offset) {
+ verifyRequestItemTarget(RequestsMenu.getItemAtIndex(offset),
+ "GET", CONTENT_TYPE_SJS + "?fmt=json-long", {
+ status: 200,
+ statusText: "OK",
+ type: "json",
+ fullMimeType: "text/json; charset=utf-8",
+ size: L10N.getFormatStr("networkMenu.sizeKB",
+ L10N.numberWithDecimals(85975 / 1024, 2)),
+ time: true
+ });
+ }
+});
diff --git a/devtools/client/netmonitor/test/browser_net_resend.js b/devtools/client/netmonitor/test/browser_net_resend.js
new file mode 100644
index 000000000..7b540ec50
--- /dev/null
+++ b/devtools/client/netmonitor/test/browser_net_resend.js
@@ -0,0 +1,174 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+/**
+ * Tests if resending a request works.
+ */
+
+const ADD_QUERY = "t1=t2";
+const ADD_HEADER = "Test-header: true";
+const ADD_UA_HEADER = "User-Agent: Custom-Agent";
+const ADD_POSTDATA = "&t3=t4";
+
+add_task(function* () {
+ let { tab, monitor } = yield initNetMonitor(POST_DATA_URL);
+ info("Starting test... ");
+
+ let { panelWin } = monitor;
+ let { document, EVENTS, NetMonitorView } = panelWin;
+ let { RequestsMenu } = NetMonitorView;
+
+ RequestsMenu.lazyUpdate = false;
+
+ let wait = waitForNetworkEvents(monitor, 0, 2);
+ yield ContentTask.spawn(tab.linkedBrowser, {}, function* () {
+ content.wrappedJSObject.performRequests();
+ });
+ yield wait;
+
+ let origItem = RequestsMenu.getItemAtIndex(0);
+
+ let onTabUpdated = panelWin.once(EVENTS.TAB_UPDATED);
+ RequestsMenu.selectedItem = origItem;
+ yield onTabUpdated;
+
+ // add a new custom request cloned from selected request
+ let onPopulated = panelWin.once(EVENTS.CUSTOMREQUESTVIEW_POPULATED);
+ RequestsMenu.cloneSelectedRequest();
+ yield onPopulated;
+
+ testCustomForm(origItem.attachment);
+
+ let customItem = RequestsMenu.selectedItem;
+ testCustomItem(customItem, origItem);
+
+ // edit the custom request
+ yield editCustomForm();
+ testCustomItemChanged(customItem, origItem);
+
+ // send the new request
+ wait = waitForNetworkEvents(monitor, 0, 1);
+ RequestsMenu.sendCustomRequest();
+ yield wait;
+
+ let sentItem = RequestsMenu.selectedItem;
+ testSentRequest(sentItem.attachment, origItem.attachment);
+
+ return teardown(monitor);
+
+ function testCustomItem(item, orig) {
+ let method = item.target.querySelector(".requests-menu-method").value;
+ let origMethod = orig.target.querySelector(".requests-menu-method").value;
+ is(method, origMethod, "menu item is showing the same method as original request");
+
+ let file = item.target.querySelector(".requests-menu-file").value;
+ let origFile = orig.target.querySelector(".requests-menu-file").value;
+ is(file, origFile, "menu item is showing the same file name as original request");
+
+ let domain = item.target.querySelector(".requests-menu-domain").value;
+ let origDomain = orig.target.querySelector(".requests-menu-domain").value;
+ is(domain, origDomain, "menu item is showing the same domain as original request");
+ }
+
+ function testCustomItemChanged(item, orig) {
+ let file = item.target.querySelector(".requests-menu-file").value;
+ let expectedFile = orig.target.querySelector(".requests-menu-file").value +
+ "&" + ADD_QUERY;
+
+ is(file, expectedFile, "menu item is updated to reflect url entered in form");
+ }
+
+ /*
+ * Test that the New Request form was populated correctly
+ */
+ function testCustomForm(data) {
+ is(document.getElementById("custom-method-value").value, data.method,
+ "new request form showing correct method");
+
+ is(document.getElementById("custom-url-value").value, data.url,
+ "new request form showing correct url");
+
+ let query = document.getElementById("custom-query-value");
+ is(query.value, "foo=bar\nbaz=42\ntype=urlencoded",
+ "new request form showing correct query string");
+
+ let headers = document.getElementById("custom-headers-value").value.split("\n");
+ for (let {name, value} of data.requestHeaders.headers) {
+ ok(headers.indexOf(name + ": " + value) >= 0, "form contains header from request");
+ }
+
+ let postData = document.getElementById("custom-postdata-value");
+ is(postData.value, data.requestPostData.postData.text,
+ "new request form showing correct post data");
+ }
+
+ /*
+ * Add some params and headers to the request form
+ */
+ function* editCustomForm() {
+ panelWin.focus();
+
+ let query = document.getElementById("custom-query-value");
+ let queryFocus = once(query, "focus", false);
+ // Bug 1195825: Due to some unexplained dark-matter with promise,
+ // focus only works if delayed by one tick.
+ query.setSelectionRange(query.value.length, query.value.length);
+ executeSoon(() => query.focus());
+ yield queryFocus;
+
+ // add params to url query string field
+ type(["VK_RETURN"]);
+ type(ADD_QUERY);
+
+ let headers = document.getElementById("custom-headers-value");
+ let headersFocus = once(headers, "focus", false);
+ headers.setSelectionRange(headers.value.length, headers.value.length);
+ headers.focus();
+ yield headersFocus;
+
+ // add a header
+ type(["VK_RETURN"]);
+ type(ADD_HEADER);
+
+ // add a User-Agent header, to check if default headers can be modified
+ // (there will be two of them, first gets overwritten by the second)
+ type(["VK_RETURN"]);
+ type(ADD_UA_HEADER);
+
+ let postData = document.getElementById("custom-postdata-value");
+ let postFocus = once(postData, "focus", false);
+ postData.setSelectionRange(postData.value.length, postData.value.length);
+ postData.focus();
+ yield postFocus;
+
+ // add to POST data
+ type(ADD_POSTDATA);
+ }
+
+ /*
+ * Make sure newly created event matches expected request
+ */
+ function testSentRequest(data, origData) {
+ is(data.method, origData.method, "correct method in sent request");
+ is(data.url, origData.url + "&" + ADD_QUERY, "correct url in sent request");
+
+ let { headers } = data.requestHeaders;
+ let hasHeader = headers.some(h => `${h.name}: ${h.value}` == ADD_HEADER);
+ ok(hasHeader, "new header added to sent request");
+
+ let hasUAHeader = headers.some(h => `${h.name}: ${h.value}` == ADD_UA_HEADER);
+ ok(hasUAHeader, "User-Agent header added to sent request");
+
+ is(data.requestPostData.postData.text,
+ origData.requestPostData.postData.text + ADD_POSTDATA,
+ "post data added to sent request");
+ }
+
+ function type(string) {
+ for (let ch of string) {
+ EventUtils.synthesizeKey(ch, {}, panelWin);
+ }
+ }
+});
diff --git a/devtools/client/netmonitor/test/browser_net_resend_cors.js b/devtools/client/netmonitor/test/browser_net_resend_cors.js
new file mode 100644
index 000000000..d63c3b54e
--- /dev/null
+++ b/devtools/client/netmonitor/test/browser_net_resend_cors.js
@@ -0,0 +1,80 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+/**
+ * Tests if resending a CORS request avoids the security checks and doesn't send
+ * a preflight OPTIONS request (bug 1270096 and friends)
+ */
+
+add_task(function* () {
+ let { tab, monitor } = yield initNetMonitor(CORS_URL);
+ info("Starting test... ");
+
+ let { EVENTS, NetMonitorView } = monitor.panelWin;
+ let { RequestsMenu } = NetMonitorView;
+
+ RequestsMenu.lazyUpdate = false;
+
+ let requestUrl = "http://test1.example.com" + CORS_SJS_PATH;
+
+ info("Waiting for OPTIONS, then POST");
+ let wait = waitForNetworkEvents(monitor, 1, 1);
+ yield ContentTask.spawn(tab.linkedBrowser, requestUrl, function* (url) {
+ content.wrappedJSObject.performRequests(url, "triggering/preflight", "post-data");
+ });
+ yield wait;
+
+ const METHODS = ["OPTIONS", "POST"];
+
+ // Check the requests that were sent
+ for (let [i, method] of METHODS.entries()) {
+ let { attachment } = RequestsMenu.getItemAtIndex(i);
+ is(attachment.method, method, `The ${method} request has the right method`);
+ is(attachment.url, requestUrl, `The ${method} request has the right URL`);
+ }
+
+ // Resend both requests without modification. Wait for resent OPTIONS, then POST.
+ // POST is supposed to have no preflight OPTIONS request this time (CORS is disabled)
+ let onRequests = waitForNetworkEvents(monitor, 1, 1);
+ for (let [i, method] of METHODS.entries()) {
+ let item = RequestsMenu.getItemAtIndex(i);
+
+ info(`Selecting the ${method} request (at index ${i})`);
+ let onUpdate = monitor.panelWin.once(EVENTS.TAB_UPDATED);
+ RequestsMenu.selectedItem = item;
+ yield onUpdate;
+
+ info("Cloning the selected request into a custom clone");
+ let onPopulate = monitor.panelWin.once(EVENTS.CUSTOMREQUESTVIEW_POPULATED);
+ RequestsMenu.cloneSelectedRequest();
+ yield onPopulate;
+
+ info("Sending the cloned request (without change)");
+ RequestsMenu.sendCustomRequest();
+ }
+
+ info("Waiting for both resent requests");
+ yield onRequests;
+
+ // Check the resent requests
+ for (let [i, method] of METHODS.entries()) {
+ let index = i + 2;
+ let item = RequestsMenu.getItemAtIndex(index).attachment;
+ is(item.method, method, `The ${method} request has the right method`);
+ is(item.url, requestUrl, `The ${method} request has the right URL`);
+ is(item.status, 200, `The ${method} response has the right status`);
+
+ if (method === "POST") {
+ is(item.requestPostData.postData.text, "post-data",
+ "The POST request has the right POST data");
+ // eslint-disable-next-line mozilla/no-cpows-in-tests
+ is(item.responseContent.content.text, "Access-Control-Allow-Origin: *",
+ "The POST response has the right content");
+ }
+ }
+
+ info("Finishing the test");
+ return teardown(monitor);
+});
diff --git a/devtools/client/netmonitor/test/browser_net_resend_headers.js b/devtools/client/netmonitor/test/browser_net_resend_headers.js
new file mode 100644
index 000000000..0503817e3
--- /dev/null
+++ b/devtools/client/netmonitor/test/browser_net_resend_headers.js
@@ -0,0 +1,67 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+/**
+ * Test if custom request headers are not ignored (bug 1270096 and friends)
+ */
+
+add_task(function* () {
+ let { monitor } = yield initNetMonitor(SIMPLE_SJS);
+ info("Starting test... ");
+
+ let { NetMonitorView, NetMonitorController } = monitor.panelWin;
+ let { RequestsMenu } = NetMonitorView;
+
+ RequestsMenu.lazyUpdate = false;
+
+ let requestUrl = SIMPLE_SJS;
+ let requestHeaders = [
+ { name: "Host", value: "fakehost.example.com" },
+ { name: "User-Agent", value: "Testzilla" },
+ { name: "Referer", value: "http://example.com/referrer" },
+ { name: "Accept", value: "application/jarda"},
+ { name: "Accept-Encoding", value: "compress, identity, funcoding" },
+ { name: "Accept-Language", value: "cs-CZ" }
+ ];
+
+ let wait = waitForNetworkEvents(monitor, 0, 1);
+ NetMonitorController.webConsoleClient.sendHTTPRequest({
+ url: requestUrl,
+ method: "POST",
+ headers: requestHeaders,
+ body: "Hello"
+ });
+ yield wait;
+
+ let { attachment } = RequestsMenu.getItemAtIndex(0);
+ is(attachment.method, "POST", "The request has the right method");
+ is(attachment.url, requestUrl, "The request has the right URL");
+
+ for (let { name, value } of attachment.requestHeaders.headers) {
+ info(`Request header: ${name}: ${value}`);
+ }
+
+ function hasRequestHeader(name, value) {
+ let { headers } = attachment.requestHeaders;
+ return headers.some(h => h.name === name && h.value === value);
+ }
+
+ function hasNotRequestHeader(name) {
+ let { headers } = attachment.requestHeaders;
+ return headers.every(h => h.name !== name);
+ }
+
+ for (let { name, value } of requestHeaders) {
+ ok(hasRequestHeader(name, value), `The ${name} header has the right value`);
+ }
+
+ // Check that the Cookie header was not added silently (i.e., that the request is
+ // anonymous.
+ for (let name of ["Cookie"]) {
+ ok(hasNotRequestHeader(name), `The ${name} header is not present`);
+ }
+
+ return teardown(monitor);
+});
diff --git a/devtools/client/netmonitor/test/browser_net_security-details.js b/devtools/client/netmonitor/test/browser_net_security-details.js
new file mode 100644
index 000000000..0a83b3ed9
--- /dev/null
+++ b/devtools/client/netmonitor/test/browser_net_security-details.js
@@ -0,0 +1,102 @@
+/* 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 Security details tab contains the expected data.
+ */
+
+add_task(function* () {
+ let { tab, monitor } = yield initNetMonitor(CUSTOM_GET_URL);
+ let { $, EVENTS, NetMonitorView } = monitor.panelWin;
+ let { RequestsMenu, NetworkDetails } = NetMonitorView;
+ RequestsMenu.lazyUpdate = false;
+
+ info("Performing a secure request.");
+ const REQUESTS_URL = "https://example.com" + CORS_SJS_PATH;
+ let wait = waitForNetworkEvents(monitor, 1);
+ yield ContentTask.spawn(tab.linkedBrowser, REQUESTS_URL, function* (url) {
+ content.wrappedJSObject.performRequests(1, url);
+ });
+ yield wait;
+
+ info("Selecting the request.");
+ RequestsMenu.selectedIndex = 0;
+
+ info("Waiting for details pane to be updated.");
+ yield monitor.panelWin.once(EVENTS.TAB_UPDATED);
+
+ info("Selecting security tab.");
+ NetworkDetails.widget.selectedIndex = 5;
+
+ info("Waiting for security tab to be updated.");
+ yield monitor.panelWin.once(EVENTS.TAB_UPDATED);
+
+ let errorbox = $("#security-error");
+ let infobox = $("#security-information");
+
+ is(errorbox.hidden, true, "Error box is hidden.");
+ is(infobox.hidden, false, "Information box visible.");
+
+ // Connection
+
+ // The protocol will be TLS but the exact version depends on which protocol
+ // the test server example.com supports.
+ let protocol = $("#security-protocol-version-value").value;
+ ok(protocol.startsWith("TLS"), "The protocol " + protocol + " seems valid.");
+
+ // The cipher suite used by the test server example.com might change at any
+ // moment but all of them should start with "TLS_".
+ // http://www.iana.org/assignments/tls-parameters/tls-parameters.xhtml
+ let suite = $("#security-ciphersuite-value").value;
+ ok(suite.startsWith("TLS_"), "The suite " + suite + " seems valid.");
+
+ // Host
+ checkLabel("#security-info-host-header", "Host example.com:");
+ checkLabel("#security-http-strict-transport-security-value", "Disabled");
+ checkLabel("#security-public-key-pinning-value", "Disabled");
+
+ // Cert
+ checkLabel("#security-cert-subject-cn", "example.com");
+ checkLabel("#security-cert-subject-o", "<Not Available>");
+ checkLabel("#security-cert-subject-ou", "<Not Available>");
+
+ checkLabel("#security-cert-issuer-cn", "Temporary Certificate Authority");
+ checkLabel("#security-cert-issuer-o", "Mozilla Testing");
+ checkLabel("#security-cert-issuer-ou", "<Not Available>");
+
+ // Locale sensitive and varies between timezones. Cant't compare equality or
+ // the test fails depending on which part of the world the test is executed.
+ checkLabelNotEmpty("#security-cert-validity-begins");
+ checkLabelNotEmpty("#security-cert-validity-expires");
+
+ checkLabelNotEmpty("#security-cert-sha1-fingerprint");
+ checkLabelNotEmpty("#security-cert-sha256-fingerprint");
+ yield teardown(monitor);
+
+ /**
+ * A helper that compares value attribute of a label with given selector to the
+ * expected value.
+ */
+ function checkLabel(selector, expected) {
+ info("Checking label " + selector);
+
+ let element = $(selector);
+
+ ok(element, "Selector matched an element.");
+ is(element.value, expected, "Label has the expected value.");
+ }
+
+ /**
+ * A helper that checks the label with given selector is not an empty string.
+ */
+ function checkLabelNotEmpty(selector) {
+ info("Checking that label " + selector + " is non-empty.");
+
+ let element = $(selector);
+
+ ok(element, "Selector matched an element.");
+ isnot(element.value, "", "Label was not empty.");
+ }
+});
diff --git a/devtools/client/netmonitor/test/browser_net_security-error.js b/devtools/client/netmonitor/test/browser_net_security-error.js
new file mode 100644
index 000000000..f6b8b34f3
--- /dev/null
+++ b/devtools/client/netmonitor/test/browser_net_security-error.js
@@ -0,0 +1,70 @@
+/* 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 Security details tab shows an error message with broken connections.
+ */
+
+add_task(function* () {
+ let { tab, monitor } = yield initNetMonitor(CUSTOM_GET_URL);
+ let { $, EVENTS, NetMonitorView } = monitor.panelWin;
+ let { RequestsMenu, NetworkDetails } = NetMonitorView;
+ RequestsMenu.lazyUpdate = false;
+
+ info("Requesting a resource that has a certificate problem.");
+
+ let wait = waitForSecurityBrokenNetworkEvent();
+ yield ContentTask.spawn(tab.linkedBrowser, {}, function* () {
+ content.wrappedJSObject.performRequests(1, "https://nocert.example.com");
+ });
+ yield wait;
+
+ info("Selecting the request.");
+ RequestsMenu.selectedIndex = 0;
+
+ info("Waiting for details pane to be updated.");
+ yield monitor.panelWin.once(EVENTS.TAB_UPDATED);
+
+ info("Selecting security tab.");
+ NetworkDetails.widget.selectedIndex = 5;
+
+ info("Waiting for security tab to be updated.");
+ yield monitor.panelWin.once(EVENTS.TAB_UPDATED);
+
+ let errorbox = $("#security-error");
+ let errormsg = $("#security-error-message");
+ let infobox = $("#security-information");
+
+ is(errorbox.hidden, false, "Error box is visble.");
+ is(infobox.hidden, true, "Information box is hidden.");
+
+ isnot(errormsg.value, "", "Error message is not empty.");
+
+ return teardown(monitor);
+
+ /**
+ * Returns a promise that's resolved once a request with security issues is
+ * completed.
+ */
+ function waitForSecurityBrokenNetworkEvent() {
+ let awaitedEvents = [
+ "UPDATING_REQUEST_HEADERS",
+ "RECEIVED_REQUEST_HEADERS",
+ "UPDATING_REQUEST_COOKIES",
+ "RECEIVED_REQUEST_COOKIES",
+ "STARTED_RECEIVING_RESPONSE",
+ "UPDATING_RESPONSE_CONTENT",
+ "RECEIVED_RESPONSE_CONTENT",
+ "UPDATING_EVENT_TIMINGS",
+ "RECEIVED_EVENT_TIMINGS",
+ ];
+
+ let promises = awaitedEvents.map((event) => {
+ return monitor.panelWin.once(EVENTS[event]);
+ });
+
+ return Promise.all(promises);
+ }
+});
diff --git a/devtools/client/netmonitor/test/browser_net_security-icon-click.js b/devtools/client/netmonitor/test/browser_net_security-icon-click.js
new file mode 100644
index 000000000..2385b11aa
--- /dev/null
+++ b/devtools/client/netmonitor/test/browser_net_security-icon-click.js
@@ -0,0 +1,57 @@
+/* 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 clicking on the security indicator opens the security details tab.
+ */
+
+add_task(function* () {
+ let { tab, monitor } = yield initNetMonitor(CUSTOM_GET_URL);
+ let { $, EVENTS, NetMonitorView } = monitor.panelWin;
+ let { RequestsMenu, NetworkDetails } = NetMonitorView;
+ RequestsMenu.lazyUpdate = false;
+
+ info("Requesting a resource over HTTPS.");
+ yield performRequestAndWait("https://example.com" + CORS_SJS_PATH + "?request_2");
+ yield performRequestAndWait("https://example.com" + CORS_SJS_PATH + "?request_1");
+
+ is(RequestsMenu.itemCount, 2, "Two events event logged.");
+
+ yield clickAndTestSecurityIcon();
+
+ info("Selecting headers panel again.");
+ NetworkDetails.widget.selectedIndex = 0;
+ yield monitor.panelWin.once(EVENTS.TAB_UPDATED);
+
+ info("Sorting the items by filename.");
+ EventUtils.sendMouseEvent({ type: "click" }, $("#requests-menu-file-button"));
+
+ info("Testing that security icon can be clicked after the items were sorted.");
+ yield clickAndTestSecurityIcon();
+
+ return teardown(monitor);
+
+ function* performRequestAndWait(url) {
+ let wait = waitForNetworkEvents(monitor, 1);
+ yield ContentTask.spawn(tab.linkedBrowser, { url }, function* (args) {
+ content.wrappedJSObject.performRequests(1, args.url);
+ });
+ return wait;
+ }
+
+ function* clickAndTestSecurityIcon() {
+ let item = RequestsMenu.items[0];
+ let icon = $(".requests-security-state-icon", item.target);
+
+ info("Clicking security icon of the first request and waiting for the " +
+ "panel to update.");
+
+ icon.click();
+ yield monitor.panelWin.once(EVENTS.TAB_UPDATED);
+
+ is(NetworkDetails.widget.selectedPanel, $("#security-tabpanel"),
+ "Security tab is selected.");
+ }
+});
diff --git a/devtools/client/netmonitor/test/browser_net_security-redirect.js b/devtools/client/netmonitor/test/browser_net_security-redirect.js
new file mode 100644
index 000000000..5f2956dbb
--- /dev/null
+++ b/devtools/client/netmonitor/test/browser_net_security-redirect.js
@@ -0,0 +1,38 @@
+/* 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 a http -> https redirect shows secure icon only for redirected https
+ * request.
+ */
+
+add_task(function* () {
+ let { tab, monitor } = yield initNetMonitor(CUSTOM_GET_URL);
+ let { $, NetMonitorView } = monitor.panelWin;
+ let { RequestsMenu } = NetMonitorView;
+ RequestsMenu.lazyUpdate = false;
+
+ let wait = waitForNetworkEvents(monitor, 2);
+ yield ContentTask.spawn(tab.linkedBrowser, HTTPS_REDIRECT_SJS, function* (url) {
+ content.wrappedJSObject.performRequests(1, url);
+ });
+ yield wait;
+
+ is(RequestsMenu.itemCount, 2, "There were two requests due to redirect.");
+
+ let initial = RequestsMenu.items[0];
+ let redirect = RequestsMenu.items[1];
+
+ let initialSecurityIcon = $(".requests-security-state-icon", initial.target);
+ let redirectSecurityIcon = $(".requests-security-state-icon", redirect.target);
+
+ ok(initialSecurityIcon.classList.contains("security-state-insecure"),
+ "Initial request was marked insecure.");
+
+ ok(redirectSecurityIcon.classList.contains("security-state-secure"),
+ "Redirected request was marked secure.");
+
+ yield teardown(monitor);
+});
diff --git a/devtools/client/netmonitor/test/browser_net_security-state.js b/devtools/client/netmonitor/test/browser_net_security-state.js
new file mode 100644
index 000000000..054e7c969
--- /dev/null
+++ b/devtools/client/netmonitor/test/browser_net_security-state.js
@@ -0,0 +1,119 @@
+/* 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 correct security state indicator appears depending on the security
+ * state.
+ */
+
+add_task(function* () {
+ const EXPECTED_SECURITY_STATES = {
+ "test1.example.com": "security-state-insecure",
+ "example.com": "security-state-secure",
+ "nocert.example.com": "security-state-broken",
+ "localhost": "security-state-local",
+ };
+
+ let { tab, monitor } = yield initNetMonitor(CUSTOM_GET_URL);
+ let { $, EVENTS, NetMonitorView } = monitor.panelWin;
+ let { RequestsMenu } = NetMonitorView;
+ RequestsMenu.lazyUpdate = false;
+
+ yield performRequests();
+
+ for (let item of RequestsMenu.items) {
+ let domain = $(".requests-menu-domain", item.target).value;
+
+ info("Found a request to " + domain);
+ ok(domain in EXPECTED_SECURITY_STATES, "Domain " + domain + " was expected.");
+
+ let classes = $(".requests-security-state-icon", item.target).classList;
+ let expectedClass = EXPECTED_SECURITY_STATES[domain];
+
+ info("Classes of security state icon are: " + classes);
+ info("Security state icon is expected to contain class: " + expectedClass);
+ ok(classes.contains(expectedClass), "Icon contained the correct class name.");
+ }
+
+ return teardown(monitor);
+
+ /**
+ * A helper that performs requests to
+ * - https://nocert.example.com (broken)
+ * - https://example.com (secure)
+ * - http://test1.example.com (insecure)
+ * - http://localhost (local)
+ * and waits until NetworkMonitor has handled all packets sent by the server.
+ */
+ function* performRequests() {
+ function executeRequests(count, url) {
+ return ContentTask.spawn(tab.linkedBrowser, {count, url}, function* (args) {
+ content.wrappedJSObject.performRequests(args.count, args.url);
+ });
+ }
+
+ // waitForNetworkEvents does not work for requests with security errors as
+ // those only emit 9/13 events of a successful request.
+ let done = waitForSecurityBrokenNetworkEvent();
+
+ info("Requesting a resource that has a certificate problem.");
+ yield executeRequests(1, "https://nocert.example.com");
+
+ // Wait for the request to complete before firing another request. Otherwise
+ // the request with security issues interfere with waitForNetworkEvents.
+ info("Waiting for request to complete.");
+ yield done;
+
+ // Next perform a request over HTTP. If done the other way around the latter
+ // occasionally hangs waiting for event timings that don't seem to appear...
+ done = waitForNetworkEvents(monitor, 1);
+ info("Requesting a resource over HTTP.");
+ yield executeRequests(1, "http://test1.example.com" + CORS_SJS_PATH);
+ yield done;
+
+ done = waitForNetworkEvents(monitor, 1);
+ info("Requesting a resource over HTTPS.");
+ yield executeRequests(1, "https://example.com" + CORS_SJS_PATH);
+ yield done;
+
+ done = waitForSecurityBrokenNetworkEvent(true);
+ info("Requesting a resource over HTTP to localhost.");
+ yield executeRequests(1, "http://localhost" + CORS_SJS_PATH);
+ yield done;
+
+ const expectedCount = Object.keys(EXPECTED_SECURITY_STATES).length;
+ is(RequestsMenu.itemCount, expectedCount, expectedCount + " events logged.");
+ }
+
+ /**
+ * Returns a promise that's resolved once a request with security issues is
+ * completed.
+ */
+ function waitForSecurityBrokenNetworkEvent(networkError) {
+ let awaitedEvents = [
+ "UPDATING_REQUEST_HEADERS",
+ "RECEIVED_REQUEST_HEADERS",
+ "UPDATING_REQUEST_COOKIES",
+ "RECEIVED_REQUEST_COOKIES",
+ "STARTED_RECEIVING_RESPONSE",
+ "UPDATING_RESPONSE_CONTENT",
+ "RECEIVED_RESPONSE_CONTENT",
+ "UPDATING_EVENT_TIMINGS",
+ "RECEIVED_EVENT_TIMINGS",
+ ];
+
+ // If the reason for breakage is a network error, then the
+ // STARTED_RECEIVING_RESPONSE event does not fire.
+ if (networkError) {
+ awaitedEvents = awaitedEvents.filter(e => e !== "STARTED_RECEIVING_RESPONSE");
+ }
+
+ let promises = awaitedEvents.map((event) => {
+ return monitor.panelWin.once(EVENTS[event]);
+ });
+
+ return Promise.all(promises);
+ }
+});
diff --git a/devtools/client/netmonitor/test/browser_net_security-tab-deselect.js b/devtools/client/netmonitor/test/browser_net_security-tab-deselect.js
new file mode 100644
index 000000000..4a2dd0885
--- /dev/null
+++ b/devtools/client/netmonitor/test/browser_net_security-tab-deselect.js
@@ -0,0 +1,46 @@
+/* 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 security details tab is no longer selected if an insecure request
+ * is selected.
+ */
+
+add_task(function* () {
+ let { tab, monitor } = yield initNetMonitor(CUSTOM_GET_URL);
+ let { EVENTS, NetMonitorView } = monitor.panelWin;
+ let { RequestsMenu, NetworkDetails } = NetMonitorView;
+ RequestsMenu.lazyUpdate = false;
+
+ info("Performing requests.");
+ let wait = waitForNetworkEvents(monitor, 2);
+ const REQUEST_URLS = [
+ "https://example.com" + CORS_SJS_PATH,
+ "http://example.com" + CORS_SJS_PATH,
+ ];
+ yield ContentTask.spawn(tab.linkedBrowser, REQUEST_URLS, function* (urls) {
+ for (let url of urls) {
+ content.wrappedJSObject.performRequests(1, url);
+ }
+ });
+ yield wait;
+
+ info("Selecting secure request.");
+ RequestsMenu.selectedIndex = 0;
+
+ info("Selecting security tab.");
+ NetworkDetails.widget.selectedIndex = 5;
+
+ info("Selecting insecure request.");
+ RequestsMenu.selectedIndex = 1;
+
+ info("Waiting for security tab to be updated.");
+ yield monitor.panelWin.once(EVENTS.NETWORKDETAILSVIEW_POPULATED);
+
+ is(NetworkDetails.widget.selectedIndex, 0,
+ "Selected tab was reset when selected security tab was hidden.");
+
+ return teardown(monitor);
+});
diff --git a/devtools/client/netmonitor/test/browser_net_security-tab-visibility.js b/devtools/client/netmonitor/test/browser_net_security-tab-visibility.js
new file mode 100644
index 000000000..b6685d7fe
--- /dev/null
+++ b/devtools/client/netmonitor/test/browser_net_security-tab-visibility.js
@@ -0,0 +1,121 @@
+/* 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 security details tab is visible only when it should.
+ */
+
+add_task(function* () {
+ const TEST_DATA = [
+ {
+ desc: "http request",
+ uri: "http://example.com" + CORS_SJS_PATH,
+ visibleOnNewEvent: false,
+ visibleOnSecurityInfo: false,
+ visibleOnceComplete: false,
+ }, {
+ desc: "working https request",
+ uri: "https://example.com" + CORS_SJS_PATH,
+ visibleOnNewEvent: false,
+ visibleOnSecurityInfo: true,
+ visibleOnceComplete: true,
+ }, {
+ desc: "broken https request",
+ uri: "https://nocert.example.com",
+ isBroken: true,
+ visibleOnNewEvent: false,
+ visibleOnSecurityInfo: true,
+ visibleOnceComplete: true,
+ }
+ ];
+
+ let { tab, monitor } = yield initNetMonitor(CUSTOM_GET_URL);
+ let { $, EVENTS, NetMonitorView } = monitor.panelWin;
+ let { RequestsMenu } = NetMonitorView;
+ RequestsMenu.lazyUpdate = false;
+
+ for (let testcase of TEST_DATA) {
+ info("Testing Security tab visibility for " + testcase.desc);
+ let onNewItem = monitor.panelWin.once(EVENTS.NETWORK_EVENT);
+ let onSecurityInfo = monitor.panelWin.once(EVENTS.RECEIVED_SECURITY_INFO);
+ let onComplete = testcase.isBroken ?
+ waitForSecurityBrokenNetworkEvent() :
+ waitForNetworkEvents(monitor, 1);
+
+ let tabEl = $("#security-tab");
+ let tabpanel = $("#security-tabpanel");
+
+ info("Performing a request to " + testcase.uri);
+ yield ContentTask.spawn(tab.linkedBrowser, testcase.uri, function* (url) {
+ content.wrappedJSObject.performRequests(1, url);
+ });
+
+ info("Waiting for new network event.");
+ yield onNewItem;
+
+ info("Selecting the request.");
+ RequestsMenu.selectedIndex = 0;
+
+ is(RequestsMenu.selectedItem.attachment.securityState, undefined,
+ "Security state has not yet arrived.");
+ is(tabEl.hidden, !testcase.visibleOnNewEvent,
+ "Security tab is " +
+ (testcase.visibleOnNewEvent ? "visible" : "hidden") +
+ " after new request was added to the menu.");
+ is(tabpanel.hidden, false,
+ "#security-tabpanel is visible after new request was added to the menu.");
+
+ info("Waiting for security information to arrive.");
+ yield onSecurityInfo;
+
+ ok(RequestsMenu.selectedItem.attachment.securityState,
+ "Security state arrived.");
+ is(tabEl.hidden, !testcase.visibleOnSecurityInfo,
+ "Security tab is " +
+ (testcase.visibleOnSecurityInfo ? "visible" : "hidden") +
+ " after security information arrived.");
+ is(tabpanel.hidden, false,
+ "#security-tabpanel is visible after security information arrived.");
+
+ info("Waiting for request to complete.");
+ yield onComplete;
+
+ is(tabEl.hidden, !testcase.visibleOnceComplete,
+ "Security tab is " +
+ (testcase.visibleOnceComplete ? "visible" : "hidden") +
+ " after request has been completed.");
+ is(tabpanel.hidden, false,
+ "#security-tabpanel is visible after request is complete.");
+
+ info("Clearing requests.");
+ RequestsMenu.clear();
+ }
+
+ return teardown(monitor);
+
+ /**
+ * Returns a promise that's resolved once a request with security issues is
+ * completed.
+ */
+ function waitForSecurityBrokenNetworkEvent() {
+ let awaitedEvents = [
+ "UPDATING_REQUEST_HEADERS",
+ "RECEIVED_REQUEST_HEADERS",
+ "UPDATING_REQUEST_COOKIES",
+ "RECEIVED_REQUEST_COOKIES",
+ "STARTED_RECEIVING_RESPONSE",
+ "UPDATING_RESPONSE_CONTENT",
+ "RECEIVED_RESPONSE_CONTENT",
+ "UPDATING_EVENT_TIMINGS",
+ "RECEIVED_EVENT_TIMINGS",
+ ];
+
+ let promises = awaitedEvents.map((event) => {
+ return monitor.panelWin.once(EVENTS[event]);
+ });
+
+ return Promise.all(promises);
+ }
+});
diff --git a/devtools/client/netmonitor/test/browser_net_security-warnings.js b/devtools/client/netmonitor/test/browser_net_security-warnings.js
new file mode 100644
index 000000000..cdfee70a1
--- /dev/null
+++ b/devtools/client/netmonitor/test/browser_net_security-warnings.js
@@ -0,0 +1,56 @@
+/* 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 warning indicators are shown when appropriate.
+ */
+
+const TEST_CASES = [
+ {
+ desc: "no warnings",
+ uri: "https://example.com" + CORS_SJS_PATH,
+ warnCipher: false,
+ },
+];
+
+add_task(function* () {
+ let { tab, monitor } = yield initNetMonitor(CUSTOM_GET_URL);
+ let { $, EVENTS, NetMonitorView } = monitor.panelWin;
+ let { RequestsMenu, NetworkDetails } = NetMonitorView;
+ RequestsMenu.lazyUpdate = false;
+
+ let cipher = $("#security-warning-cipher");
+
+ for (let test of TEST_CASES) {
+ info("Testing site with " + test.desc);
+
+ info("Performing request to " + test.uri);
+ let wait = waitForNetworkEvents(monitor, 1);
+ yield ContentTask.spawn(tab.linkedBrowser, test.uri, function* (url) {
+ content.wrappedJSObject.performRequests(1, url);
+ });
+ yield wait;
+
+ info("Selecting the request.");
+ RequestsMenu.selectedIndex = 0;
+
+ info("Waiting for details pane to be updated.");
+ yield monitor.panelWin.once(EVENTS.TAB_UPDATED);
+
+ if (NetworkDetails.widget.selectedIndex !== 5) {
+ info("Selecting security tab.");
+ NetworkDetails.widget.selectedIndex = 5;
+
+ info("Waiting for details pane to be updated.");
+ yield monitor.panelWin.once(EVENTS.TAB_UPDATED);
+ }
+
+ is(cipher.hidden, !test.warnCipher, "Cipher suite warning is hidden.");
+
+ RequestsMenu.clear();
+ }
+
+ return teardown(monitor);
+});
diff --git a/devtools/client/netmonitor/test/browser_net_send-beacon-other-tab.js b/devtools/client/netmonitor/test/browser_net_send-beacon-other-tab.js
new file mode 100644
index 000000000..b425ad5ca
--- /dev/null
+++ b/devtools/client/netmonitor/test/browser_net_send-beacon-other-tab.js
@@ -0,0 +1,34 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+/**
+ * Tests if beacons from other tabs are properly ignored.
+ */
+
+add_task(function* () {
+ let { tab, monitor } = yield initNetMonitor(SIMPLE_URL);
+ let { RequestsMenu } = monitor.panelWin.NetMonitorView;
+ RequestsMenu.lazyUpdate = false;
+
+ let beaconTab = yield addTab(SEND_BEACON_URL);
+ info("Beacon tab added successfully.");
+
+ is(RequestsMenu.itemCount, 0, "The requests menu should be empty.");
+
+ let wait = waitForNetworkEvents(monitor, 1);
+ yield ContentTask.spawn(beaconTab.linkedBrowser, {}, function* () {
+ content.wrappedJSObject.performRequest();
+ });
+ tab.linkedBrowser.reload();
+ yield wait;
+
+ is(RequestsMenu.itemCount, 1, "Only the reload should be recorded.");
+ let request = RequestsMenu.getItemAtIndex(0);
+ is(request.attachment.method, "GET", "The method is correct.");
+ is(request.attachment.status, "200", "The status is correct.");
+
+ yield removeTab(beaconTab);
+ return teardown(monitor);
+});
diff --git a/devtools/client/netmonitor/test/browser_net_send-beacon.js b/devtools/client/netmonitor/test/browser_net_send-beacon.js
new file mode 100644
index 000000000..bdc30a960
--- /dev/null
+++ b/devtools/client/netmonitor/test/browser_net_send-beacon.js
@@ -0,0 +1,31 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+/**
+ * Tests if beacons are handled correctly.
+ */
+
+add_task(function* () {
+ let { tab, monitor } = yield initNetMonitor(SEND_BEACON_URL);
+ let { RequestsMenu } = monitor.panelWin.NetMonitorView;
+
+ RequestsMenu.lazyUpdate = false;
+
+ is(RequestsMenu.itemCount, 0, "The requests menu should be empty.");
+
+ let wait = waitForNetworkEvents(monitor, 1);
+ yield ContentTask.spawn(tab.linkedBrowser, {}, function* () {
+ content.wrappedJSObject.performRequest();
+ });
+ yield wait;
+
+ is(RequestsMenu.itemCount, 1, "The beacon should be recorded.");
+ let request = RequestsMenu.getItemAtIndex(0);
+ is(request.attachment.method, "POST", "The method is correct.");
+ ok(request.attachment.url.endsWith("beacon_request"), "The URL is correct.");
+ is(request.attachment.status, "404", "The status is correct.");
+
+ return teardown(monitor);
+});
diff --git a/devtools/client/netmonitor/test/browser_net_service-worker-status.js b/devtools/client/netmonitor/test/browser_net_service-worker-status.js
new file mode 100644
index 000000000..d7ada1645
--- /dev/null
+++ b/devtools/client/netmonitor/test/browser_net_service-worker-status.js
@@ -0,0 +1,87 @@
+/* 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 status code
+ */
+
+// Service workers only work on https
+const URL = EXAMPLE_URL.replace("http:", "https:");
+
+const TEST_URL = URL + "service-workers/status-codes.html";
+
+add_task(function* () {
+ yield new Promise(done => {
+ let options = { "set": [
+ // Accept workers from mochitest's http.
+ ["dom.serviceWorkers.enabled", true],
+ ["dom.serviceWorkers.openWindow.enabled", true],
+ ["dom.serviceWorkers.testing.enabled", true],
+ ]};
+ SpecialPowers.pushPrefEnv(options, done);
+ });
+
+ let { tab, monitor } = yield initNetMonitor(TEST_URL, null, true);
+ info("Starting test... ");
+
+ let { NetMonitorView } = monitor.panelWin;
+ let { RequestsMenu } = NetMonitorView;
+
+ const REQUEST_DATA = [
+ {
+ method: "GET",
+ uri: URL + "service-workers/test/200",
+ details: {
+ status: 200,
+ statusText: "OK (service worker)",
+ displayedStatus: "service worker",
+ type: "plain",
+ fullMimeType: "text/plain; charset=UTF-8"
+ },
+ stackFunctions: ["doXHR", "performRequests"]
+ },
+ ];
+
+ info("Registering the service worker...");
+ yield ContentTask.spawn(tab.linkedBrowser, {}, function* () {
+ yield content.wrappedJSObject.registerServiceWorker();
+ });
+
+ info("Performing requests...");
+ let wait = waitForNetworkEvents(monitor, REQUEST_DATA.length);
+ yield ContentTask.spawn(tab.linkedBrowser, {}, function* () {
+ content.wrappedJSObject.performRequests();
+ });
+ yield wait;
+
+ let index = 0;
+ for (let request of REQUEST_DATA) {
+ let item = RequestsMenu.getItemAtIndex(index);
+
+ info(`Verifying request #${index}`);
+ yield verifyRequestItemTarget(item, request.method, request.uri, request.details);
+
+ let { stacktrace } = item.attachment.cause;
+ let stackLen = stacktrace ? stacktrace.length : 0;
+
+ ok(stacktrace, `Request #${index} has a stacktrace`);
+ ok(stackLen >= request.stackFunctions.length,
+ `Request #${index} has a stacktrace with enough (${stackLen}) items`);
+
+ request.stackFunctions.forEach((functionName, j) => {
+ is(stacktrace[j].functionName, functionName,
+ `Request #${index} has the correct function at position #${j} on the stack`);
+ });
+
+ index++;
+ }
+
+ info("Unregistering the service worker...");
+ yield ContentTask.spawn(tab.linkedBrowser, {}, function* () {
+ yield content.wrappedJSObject.unregisterServiceWorker();
+ });
+
+ yield teardown(monitor);
+});
diff --git a/devtools/client/netmonitor/test/browser_net_simple-init.js b/devtools/client/netmonitor/test/browser_net_simple-init.js
new file mode 100644
index 000000000..19d05811c
--- /dev/null
+++ b/devtools/client/netmonitor/test/browser_net_simple-init.js
@@ -0,0 +1,93 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+/**
+ * Simple check if the network monitor starts up and shuts down properly.
+ */
+
+function test() {
+ // These test suite functions are removed from the global scope inside a
+ // cleanup function. However, we still need them.
+ let gInfo = info;
+ let gOk = ok;
+
+ initNetMonitor(SIMPLE_URL).then(({ tab, monitor }) => {
+ info("Starting test... ");
+
+ is(tab.linkedBrowser.currentURI.spec, SIMPLE_URL,
+ "The current tab's location is the correct one.");
+
+ function checkIfInitialized(tag) {
+ info(`Checking if initialization is ok (${tag}).`);
+
+ ok(monitor._view,
+ `The network monitor view object exists (${tag}).`);
+ ok(monitor._controller,
+ `The network monitor controller object exists (${tag}).`);
+ ok(monitor._controller._startup,
+ `The network monitor controller object exists and is initialized (${tag}).`);
+
+ ok(monitor.isReady,
+ `The network monitor panel appears to be ready (${tag}).`);
+
+ ok(monitor._controller.tabClient,
+ `There should be a tabClient available at this point (${tag}).`);
+ ok(monitor._controller.webConsoleClient,
+ `There should be a webConsoleClient available at this point (${tag}).`);
+ ok(monitor._controller.timelineFront,
+ `There should be a timelineFront available at this point (${tag}).`);
+ }
+
+ function checkIfDestroyed(tag) {
+ gInfo("Checking if destruction is ok.");
+
+ gOk(monitor._view,
+ `The network monitor view object still exists (${tag}).`);
+ gOk(monitor._controller,
+ `The network monitor controller object still exists (${tag}).`);
+ gOk(monitor._controller._shutdown,
+ `The network monitor controller object still exists and is destroyed (${tag}).`);
+
+ gOk(!monitor._controller.tabClient,
+ `There shouldn't be a tabClient available after destruction (${tag}).`);
+ gOk(!monitor._controller.webConsoleClient,
+ `There shouldn't be a webConsoleClient available after destruction (${tag}).`);
+ gOk(!monitor._controller.timelineFront,
+ `There shouldn't be a timelineFront available after destruction (${tag}).`);
+ }
+
+ executeSoon(() => {
+ checkIfInitialized(1);
+
+ monitor._controller.startupNetMonitor()
+ .then(() => {
+ info("Starting up again shouldn't do anything special.");
+ checkIfInitialized(2);
+ return monitor._controller.connect();
+ })
+ .then(() => {
+ info("Connecting again shouldn't do anything special.");
+ checkIfInitialized(3);
+ return teardown(monitor);
+ })
+ .then(finish);
+ });
+
+ registerCleanupFunction(() => {
+ checkIfDestroyed(1);
+
+ monitor._controller.shutdownNetMonitor()
+ .then(() => {
+ gInfo("Shutting down again shouldn't do anything special.");
+ checkIfDestroyed(2);
+ return monitor._controller.disconnect();
+ })
+ .then(() => {
+ gInfo("Disconnecting again shouldn't do anything special.");
+ checkIfDestroyed(3);
+ });
+ });
+ });
+}
diff --git a/devtools/client/netmonitor/test/browser_net_simple-request-data.js b/devtools/client/netmonitor/test/browser_net_simple-request-data.js
new file mode 100644
index 000000000..1b952bd71
--- /dev/null
+++ b/devtools/client/netmonitor/test/browser_net_simple-request-data.js
@@ -0,0 +1,247 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+/**
+ * Tests if requests render correct information in the menu UI.
+ */
+
+function test() {
+ let { L10N } = require("devtools/client/netmonitor/l10n");
+
+ initNetMonitor(SIMPLE_SJS).then(({ tab, monitor }) => {
+ info("Starting test... ");
+
+ let { NetMonitorView } = monitor.panelWin;
+ let { RequestsMenu } = NetMonitorView;
+
+ RequestsMenu.lazyUpdate = false;
+
+ waitForNetworkEvents(monitor, 1)
+ .then(() => teardown(monitor))
+ .then(finish);
+
+ monitor.panelWin.once(monitor.panelWin.EVENTS.NETWORK_EVENT, () => {
+ is(RequestsMenu.selectedItem, null,
+ "There shouldn't be any selected item in the requests menu.");
+ is(RequestsMenu.itemCount, 1,
+ "The requests menu should not be empty after the first request.");
+ is(NetMonitorView.detailsPaneHidden, true,
+ "The details pane should still be hidden after the first request.");
+
+ let requestItem = RequestsMenu.getItemAtIndex(0);
+
+ is(typeof requestItem.value, "string",
+ "The attached request id is incorrect.");
+ isnot(requestItem.value, "",
+ "The attached request id should not be empty.");
+
+ is(typeof requestItem.attachment.startedDeltaMillis, "number",
+ "The attached startedDeltaMillis is incorrect.");
+ is(requestItem.attachment.startedDeltaMillis, 0,
+ "The attached startedDeltaMillis should be zero.");
+
+ is(typeof requestItem.attachment.startedMillis, "number",
+ "The attached startedMillis is incorrect.");
+ isnot(requestItem.attachment.startedMillis, 0,
+ "The attached startedMillis should not be zero.");
+
+ is(requestItem.attachment.requestHeaders, undefined,
+ "The requestHeaders should not yet be set.");
+ is(requestItem.attachment.requestCookies, undefined,
+ "The requestCookies should not yet be set.");
+ is(requestItem.attachment.requestPostData, undefined,
+ "The requestPostData should not yet be set.");
+
+ is(requestItem.attachment.responseHeaders, undefined,
+ "The responseHeaders should not yet be set.");
+ is(requestItem.attachment.responseCookies, undefined,
+ "The responseCookies should not yet be set.");
+
+ is(requestItem.attachment.httpVersion, undefined,
+ "The httpVersion should not yet be set.");
+ is(requestItem.attachment.status, undefined,
+ "The status should not yet be set.");
+ is(requestItem.attachment.statusText, undefined,
+ "The statusText should not yet be set.");
+
+ is(requestItem.attachment.headersSize, undefined,
+ "The headersSize should not yet be set.");
+ is(requestItem.attachment.transferredSize, undefined,
+ "The transferredSize should not yet be set.");
+ is(requestItem.attachment.contentSize, undefined,
+ "The contentSize should not yet be set.");
+
+ is(requestItem.attachment.mimeType, undefined,
+ "The mimeType should not yet be set.");
+ is(requestItem.attachment.responseContent, undefined,
+ "The responseContent should not yet be set.");
+
+ is(requestItem.attachment.totalTime, undefined,
+ "The totalTime should not yet be set.");
+ is(requestItem.attachment.eventTimings, undefined,
+ "The eventTimings should not yet be set.");
+
+ verifyRequestItemTarget(requestItem, "GET", SIMPLE_SJS);
+ });
+
+ monitor.panelWin.once(monitor.panelWin.EVENTS.RECEIVED_REQUEST_HEADERS, () => {
+ let requestItem = RequestsMenu.getItemAtIndex(0);
+
+ ok(requestItem.attachment.requestHeaders,
+ "There should be a requestHeaders attachment available.");
+ is(requestItem.attachment.requestHeaders.headers.length, 9,
+ "The requestHeaders attachment has an incorrect |headers| property.");
+ isnot(requestItem.attachment.requestHeaders.headersSize, 0,
+ "The requestHeaders attachment has an incorrect |headersSize| property.");
+ // Can't test for the exact request headers size because the value may
+ // vary across platforms ("User-Agent" header differs).
+
+ verifyRequestItemTarget(requestItem, "GET", SIMPLE_SJS);
+ });
+
+ monitor.panelWin.once(monitor.panelWin.EVENTS.RECEIVED_REQUEST_COOKIES, () => {
+ let requestItem = RequestsMenu.getItemAtIndex(0);
+
+ ok(requestItem.attachment.requestCookies,
+ "There should be a requestCookies attachment available.");
+ is(requestItem.attachment.requestCookies.cookies.length, 2,
+ "The requestCookies attachment has an incorrect |cookies| property.");
+
+ verifyRequestItemTarget(requestItem, "GET", SIMPLE_SJS);
+ });
+
+ monitor.panelWin.once(monitor.panelWin.EVENTS.RECEIVED_REQUEST_POST_DATA, () => {
+ ok(false, "Trap listener: this request doesn't have any post data.");
+ });
+
+ monitor.panelWin.once(monitor.panelWin.EVENTS.RECEIVED_RESPONSE_HEADERS, () => {
+ let requestItem = RequestsMenu.getItemAtIndex(0);
+
+ ok(requestItem.attachment.responseHeaders,
+ "There should be a responseHeaders attachment available.");
+ is(requestItem.attachment.responseHeaders.headers.length, 10,
+ "The responseHeaders attachment has an incorrect |headers| property.");
+ is(requestItem.attachment.responseHeaders.headersSize, 330,
+ "The responseHeaders attachment has an incorrect |headersSize| property.");
+
+ verifyRequestItemTarget(requestItem, "GET", SIMPLE_SJS);
+ });
+
+ monitor.panelWin.once(monitor.panelWin.EVENTS.RECEIVED_RESPONSE_COOKIES, () => {
+ let requestItem = RequestsMenu.getItemAtIndex(0);
+
+ ok(requestItem.attachment.responseCookies,
+ "There should be a responseCookies attachment available.");
+ is(requestItem.attachment.responseCookies.cookies.length, 2,
+ "The responseCookies attachment has an incorrect |cookies| property.");
+
+ verifyRequestItemTarget(requestItem, "GET", SIMPLE_SJS);
+ });
+
+ monitor.panelWin.once(monitor.panelWin.EVENTS.STARTED_RECEIVING_RESPONSE, () => {
+ let requestItem = RequestsMenu.getItemAtIndex(0);
+
+ is(requestItem.attachment.httpVersion, "HTTP/1.1",
+ "The httpVersion attachment has an incorrect value.");
+ is(requestItem.attachment.status, "200",
+ "The status attachment has an incorrect value.");
+ is(requestItem.attachment.statusText, "Och Aye",
+ "The statusText attachment has an incorrect value.");
+ is(requestItem.attachment.headersSize, 330,
+ "The headersSize attachment has an incorrect value.");
+
+ verifyRequestItemTarget(requestItem, "GET", SIMPLE_SJS, {
+ status: "200",
+ statusText: "Och Aye"
+ });
+ });
+
+ monitor.panelWin.once(monitor.panelWin.EVENTS.UPDATING_RESPONSE_CONTENT, () => {
+ let requestItem = RequestsMenu.getItemAtIndex(0);
+
+ is(requestItem.attachment.transferredSize, "12",
+ "The transferredSize attachment has an incorrect value.");
+ is(requestItem.attachment.contentSize, "12",
+ "The contentSize attachment has an incorrect value.");
+ is(requestItem.attachment.mimeType, "text/plain; charset=utf-8",
+ "The mimeType attachment has an incorrect value.");
+
+ verifyRequestItemTarget(requestItem, "GET", SIMPLE_SJS, {
+ type: "plain",
+ fullMimeType: "text/plain; charset=utf-8",
+ transferred: L10N.getFormatStrWithNumbers("networkMenu.sizeKB", 0.01),
+ size: L10N.getFormatStrWithNumbers("networkMenu.sizeKB", 0.01),
+ });
+ });
+
+ monitor.panelWin.once(monitor.panelWin.EVENTS.RECEIVED_RESPONSE_CONTENT, () => {
+ let requestItem = RequestsMenu.getItemAtIndex(0);
+
+ ok(requestItem.attachment.responseContent,
+ "There should be a responseContent attachment available.");
+ is(requestItem.attachment.responseContent.content.mimeType,
+ "text/plain; charset=utf-8",
+ "The responseContent attachment has an incorrect |content.mimeType| property.");
+ is(requestItem.attachment.responseContent.content.text,
+ "Hello world!",
+ "The responseContent attachment has an incorrect |content.text| property.");
+ is(requestItem.attachment.responseContent.content.size,
+ 12,
+ "The responseContent attachment has an incorrect |content.size| property.");
+
+ verifyRequestItemTarget(requestItem, "GET", SIMPLE_SJS, {
+ type: "plain",
+ fullMimeType: "text/plain; charset=utf-8",
+ transferred: L10N.getFormatStrWithNumbers("networkMenu.sizeKB", 0.01),
+ size: L10N.getFormatStrWithNumbers("networkMenu.sizeKB", 0.01),
+ });
+ });
+
+ monitor.panelWin.once(monitor.panelWin.EVENTS.UPDATING_EVENT_TIMINGS, () => {
+ let requestItem = RequestsMenu.getItemAtIndex(0);
+
+ is(typeof requestItem.attachment.totalTime, "number",
+ "The attached totalTime is incorrect.");
+ ok(requestItem.attachment.totalTime >= 0,
+ "The attached totalTime should be positive.");
+
+ is(typeof requestItem.attachment.endedMillis, "number",
+ "The attached endedMillis is incorrect.");
+ ok(requestItem.attachment.endedMillis >= 0,
+ "The attached endedMillis should be positive.");
+
+ verifyRequestItemTarget(requestItem, "GET", SIMPLE_SJS, {
+ time: true
+ });
+ });
+
+ monitor.panelWin.once(monitor.panelWin.EVENTS.RECEIVED_EVENT_TIMINGS, () => {
+ let requestItem = RequestsMenu.getItemAtIndex(0);
+
+ ok(requestItem.attachment.eventTimings,
+ "There should be a eventTimings attachment available.");
+ is(typeof requestItem.attachment.eventTimings.timings.blocked, "number",
+ "The eventTimings attachment has an incorrect |timings.blocked| property.");
+ is(typeof requestItem.attachment.eventTimings.timings.dns, "number",
+ "The eventTimings attachment has an incorrect |timings.dns| property.");
+ is(typeof requestItem.attachment.eventTimings.timings.connect, "number",
+ "The eventTimings attachment has an incorrect |timings.connect| property.");
+ is(typeof requestItem.attachment.eventTimings.timings.send, "number",
+ "The eventTimings attachment has an incorrect |timings.send| property.");
+ is(typeof requestItem.attachment.eventTimings.timings.wait, "number",
+ "The eventTimings attachment has an incorrect |timings.wait| property.");
+ is(typeof requestItem.attachment.eventTimings.timings.receive, "number",
+ "The eventTimings attachment has an incorrect |timings.receive| property.");
+ is(typeof requestItem.attachment.eventTimings.totalTime, "number",
+ "The eventTimings attachment has an incorrect |totalTime| property.");
+
+ verifyRequestItemTarget(requestItem, "GET", SIMPLE_SJS, {
+ time: true
+ });
+ });
+
+ tab.linkedBrowser.reload();
+ });
+}
diff --git a/devtools/client/netmonitor/test/browser_net_simple-request-details.js b/devtools/client/netmonitor/test/browser_net_simple-request-details.js
new file mode 100644
index 000000000..6be634e68
--- /dev/null
+++ b/devtools/client/netmonitor/test/browser_net_simple-request-details.js
@@ -0,0 +1,261 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+/**
+ * Tests if requests render correct information in the details UI.
+ */
+
+add_task(function* () {
+ let { L10N } = require("devtools/client/netmonitor/l10n");
+
+ let { tab, monitor } = yield initNetMonitor(SIMPLE_SJS);
+ info("Starting test... ");
+
+ let { document, EVENTS, Editor, NetMonitorView } = monitor.panelWin;
+ let { RequestsMenu } = NetMonitorView;
+ RequestsMenu.lazyUpdate = false;
+
+ let wait = waitForNetworkEvents(monitor, 1);
+ tab.linkedBrowser.reload();
+ yield wait;
+
+ is(RequestsMenu.selectedItem, null,
+ "There shouldn't be any selected item in the requests menu.");
+ is(RequestsMenu.itemCount, 1,
+ "The requests menu should not be empty after the first request.");
+ is(NetMonitorView.detailsPaneHidden, true,
+ "The details pane should still be hidden after the first request.");
+
+ let onTabUpdated = monitor.panelWin.once(EVENTS.TAB_UPDATED);
+ EventUtils.sendMouseEvent({ type: "mousedown" },
+ document.getElementById("details-pane-toggle"));
+ yield onTabUpdated;
+
+ isnot(RequestsMenu.selectedItem, null,
+ "There should be a selected item in the requests menu.");
+ is(RequestsMenu.selectedIndex, 0,
+ "The first item should be selected in the requests menu.");
+ is(NetMonitorView.detailsPaneHidden, false,
+ "The details pane should not be hidden after toggle button was pressed.");
+
+ testHeadersTab();
+ yield testCookiesTab();
+ testParamsTab();
+ yield testResponseTab();
+ testTimingsTab();
+ return teardown(monitor);
+
+ function testHeadersTab() {
+ let tabEl = document.querySelectorAll("#details-pane tab")[0];
+ let tabpanel = document.querySelectorAll("#details-pane tabpanel")[0];
+
+ is(tabEl.getAttribute("selected"), "true",
+ "The headers tab in the network details pane should be selected.");
+
+ is(tabpanel.querySelector("#headers-summary-url-value").getAttribute("value"),
+ SIMPLE_SJS, "The url summary value is incorrect.");
+ is(tabpanel.querySelector("#headers-summary-url-value").getAttribute("tooltiptext"),
+ SIMPLE_SJS, "The url summary tooltiptext is incorrect.");
+ is(tabpanel.querySelector("#headers-summary-method-value").getAttribute("value"),
+ "GET", "The method summary value is incorrect.");
+ is(tabpanel.querySelector("#headers-summary-address-value").getAttribute("value"),
+ "127.0.0.1:8888", "The remote address summary value is incorrect.");
+ is(tabpanel.querySelector("#headers-summary-status-circle").getAttribute("code"),
+ "200", "The status summary code is incorrect.");
+ is(tabpanel.querySelector("#headers-summary-status-value").getAttribute("value"),
+ "200 Och Aye", "The status summary value is incorrect.");
+
+ is(tabpanel.querySelectorAll(".variables-view-scope").length, 2,
+ "There should be 2 header scopes displayed in this tabpanel.");
+ is(tabpanel.querySelectorAll(".variable-or-property").length, 19,
+ "There should be 19 header values displayed in this tabpanel.");
+
+ is(tabpanel.querySelectorAll(".variables-view-empty-notice").length, 0,
+ "The empty notice should not be displayed in this tabpanel.");
+
+ let responseScope = tabpanel.querySelectorAll(".variables-view-scope")[0];
+ let requestScope = tabpanel.querySelectorAll(".variables-view-scope")[1];
+
+ is(responseScope.querySelector(".name").getAttribute("value"),
+ L10N.getStr("responseHeaders") + " (" +
+ L10N.getFormatStr("networkMenu.sizeKB",
+ L10N.numberWithDecimals(330 / 1024, 3)) + ")",
+ "The response headers scope doesn't have the correct title.");
+
+ ok(requestScope.querySelector(".name").getAttribute("value").includes(
+ L10N.getStr("requestHeaders") + " (0"),
+ "The request headers scope doesn't have the correct title.");
+ // Can't test for full request headers title because the size may
+ // vary across platforms ("User-Agent" header differs). We're pretty
+ // sure it's smaller than 1 MB though, so it starts with a 0.
+
+ is(responseScope.querySelectorAll(".variables-view-variable .name")[0]
+ .getAttribute("value"),
+ "Cache-Control", "The first response header name was incorrect.");
+ is(responseScope.querySelectorAll(".variables-view-variable .value")[0]
+ .getAttribute("value"),
+ "\"no-cache, no-store, must-revalidate\"",
+ "The first response header value was incorrect.");
+ is(responseScope.querySelectorAll(".variables-view-variable .name")[1]
+ .getAttribute("value"),
+ "Connection", "The second response header name was incorrect.");
+ is(responseScope.querySelectorAll(".variables-view-variable .value")[1]
+ .getAttribute("value"),
+ "\"close\"", "The second response header value was incorrect.");
+ is(responseScope.querySelectorAll(".variables-view-variable .name")[2]
+ .getAttribute("value"),
+ "Content-Length", "The third response header name was incorrect.");
+ is(responseScope.querySelectorAll(".variables-view-variable .value")[2]
+ .getAttribute("value"),
+ "\"12\"", "The third response header value was incorrect.");
+ is(responseScope.querySelectorAll(".variables-view-variable .name")[3]
+ .getAttribute("value"),
+ "Content-Type", "The fourth response header name was incorrect.");
+ is(responseScope.querySelectorAll(".variables-view-variable .value")[3]
+ .getAttribute("value"),
+ "\"text/plain; charset=utf-8\"", "The fourth response header value was incorrect.");
+ is(responseScope.querySelectorAll(".variables-view-variable .name")[9]
+ .getAttribute("value"),
+ "foo-bar", "The last response header name was incorrect.");
+ is(responseScope.querySelectorAll(".variables-view-variable .value")[9]
+ .getAttribute("value"),
+ "\"baz\"", "The last response header value was incorrect.");
+
+ is(requestScope.querySelectorAll(".variables-view-variable .name")[0]
+ .getAttribute("value"),
+ "Host", "The first request header name was incorrect.");
+ is(requestScope.querySelectorAll(".variables-view-variable .value")[0]
+ .getAttribute("value"),
+ "\"example.com\"", "The first request header value was incorrect.");
+ is(requestScope.querySelectorAll(".variables-view-variable .name")[6]
+ .getAttribute("value"),
+ "Connection", "The ante-penultimate request header name was incorrect.");
+ is(requestScope.querySelectorAll(".variables-view-variable .value")[6]
+ .getAttribute("value"),
+ "\"keep-alive\"", "The ante-penultimate request header value was incorrect.");
+ is(requestScope.querySelectorAll(".variables-view-variable .name")[7]
+ .getAttribute("value"),
+ "Pragma", "The penultimate request header name was incorrect.");
+ is(requestScope.querySelectorAll(".variables-view-variable .value")[7]
+ .getAttribute("value"),
+ "\"no-cache\"", "The penultimate request header value was incorrect.");
+ is(requestScope.querySelectorAll(".variables-view-variable .name")[8]
+ .getAttribute("value"),
+ "Cache-Control", "The last request header name was incorrect.");
+ is(requestScope.querySelectorAll(".variables-view-variable .value")[8]
+ .getAttribute("value"),
+ "\"no-cache\"", "The last request header value was incorrect.");
+ }
+
+ function* testCookiesTab() {
+ let onEvent = monitor.panelWin.once(EVENTS.TAB_UPDATED);
+ EventUtils.sendMouseEvent({ type: "mousedown" },
+ document.querySelectorAll("#details-pane tab")[1]);
+ yield onEvent;
+
+ let tabEl = document.querySelectorAll("#details-pane tab")[1];
+ let tabpanel = document.querySelectorAll("#details-pane tabpanel")[1];
+
+ is(tabEl.getAttribute("selected"), "true",
+ "The cookies tab in the network details pane should be selected.");
+
+ is(tabpanel.querySelectorAll(".variables-view-scope").length, 2,
+ "There should be 2 cookie scopes displayed in this tabpanel.");
+ is(tabpanel.querySelectorAll(".variable-or-property").length, 6,
+ "There should be 6 cookie values displayed in this tabpanel.");
+ }
+
+ function testParamsTab() {
+ EventUtils.sendMouseEvent({ type: "mousedown" },
+ document.querySelectorAll("#details-pane tab")[2]);
+
+ let tabEl = document.querySelectorAll("#details-pane tab")[2];
+ let tabpanel = document.querySelectorAll("#details-pane tabpanel")[2];
+
+ is(tabEl.getAttribute("selected"), "true",
+ "The params tab in the network details pane should be selected.");
+
+ is(tabpanel.querySelectorAll(".variables-view-scope").length, 0,
+ "There should be no param scopes displayed in this tabpanel.");
+ is(tabpanel.querySelectorAll(".variable-or-property").length, 0,
+ "There should be no param values displayed in this tabpanel.");
+ is(tabpanel.querySelectorAll(".variables-view-empty-notice").length, 1,
+ "The empty notice should be displayed in this tabpanel.");
+
+ is(tabpanel.querySelector("#request-params-box")
+ .hasAttribute("hidden"), false,
+ "The request params box should not be hidden.");
+ is(tabpanel.querySelector("#request-post-data-textarea-box")
+ .hasAttribute("hidden"), true,
+ "The request post data textarea box should be hidden.");
+ }
+
+ function* testResponseTab() {
+ let onEvent = monitor.panelWin.once(EVENTS.TAB_UPDATED);
+ EventUtils.sendMouseEvent({ type: "mousedown" },
+ document.querySelectorAll("#details-pane tab")[3]);
+ yield onEvent;
+
+ let tabEl = document.querySelectorAll("#details-pane tab")[3];
+ let tabpanel = document.querySelectorAll("#details-pane tabpanel")[3];
+
+ is(tabEl.getAttribute("selected"), "true",
+ "The response tab in the network details pane should be selected.");
+
+ is(tabpanel.querySelector("#response-content-info-header")
+ .hasAttribute("hidden"), true,
+ "The response info header should be hidden.");
+ is(tabpanel.querySelector("#response-content-json-box")
+ .hasAttribute("hidden"), true,
+ "The response content json box should be hidden.");
+ is(tabpanel.querySelector("#response-content-textarea-box")
+ .hasAttribute("hidden"), false,
+ "The response content textarea box should not be hidden.");
+ is(tabpanel.querySelector("#response-content-image-box")
+ .hasAttribute("hidden"), true,
+ "The response content image box should be hidden.");
+
+ let editor = yield NetMonitorView.editor("#response-content-textarea");
+ is(editor.getText(), "Hello world!",
+ "The text shown in the source editor is incorrect.");
+ is(editor.getMode(), Editor.modes.text,
+ "The mode active in the source editor is incorrect.");
+ }
+
+ function testTimingsTab() {
+ EventUtils.sendMouseEvent({ type: "mousedown" },
+ document.querySelectorAll("#details-pane tab")[4]);
+
+ let tabEl = document.querySelectorAll("#details-pane tab")[4];
+ let tabpanel = document.querySelectorAll("#details-pane tabpanel")[4];
+
+ is(tabEl.getAttribute("selected"), "true",
+ "The timings tab in the network details pane should be selected.");
+
+ ok(tabpanel.querySelector("#timings-summary-blocked .requests-menu-timings-total")
+ .getAttribute("value").match(/[0-9]+/),
+ "The blocked timing info does not appear to be correct.");
+
+ ok(tabpanel.querySelector("#timings-summary-dns .requests-menu-timings-total")
+ .getAttribute("value").match(/[0-9]+/),
+ "The dns timing info does not appear to be correct.");
+
+ ok(tabpanel.querySelector("#timings-summary-connect .requests-menu-timings-total")
+ .getAttribute("value").match(/[0-9]+/),
+ "The connect timing info does not appear to be correct.");
+
+ ok(tabpanel.querySelector("#timings-summary-send .requests-menu-timings-total")
+ .getAttribute("value").match(/[0-9]+/),
+ "The send timing info does not appear to be correct.");
+
+ ok(tabpanel.querySelector("#timings-summary-wait .requests-menu-timings-total")
+ .getAttribute("value").match(/[0-9]+/),
+ "The wait timing info does not appear to be correct.");
+
+ ok(tabpanel.querySelector("#timings-summary-receive .requests-menu-timings-total")
+ .getAttribute("value").match(/[0-9]+/),
+ "The receive timing info does not appear to be correct.");
+ }
+});
diff --git a/devtools/client/netmonitor/test/browser_net_simple-request.js b/devtools/client/netmonitor/test/browser_net_simple-request.js
new file mode 100644
index 000000000..898cb3710
--- /dev/null
+++ b/devtools/client/netmonitor/test/browser_net_simple-request.js
@@ -0,0 +1,72 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+"use strict";
+
+/**
+ * Test whether the UI state properly reflects existence of requests
+ * displayed in the Net panel. The following parts of the UI are
+ * tested:
+ * 1) Side panel visibility
+ * 2) Side panel toggle button
+ * 3) Empty user message visibility
+ * 4) Number of requests displayed
+ */
+add_task(function* () {
+ let { tab, monitor } = yield initNetMonitor(SIMPLE_URL);
+ info("Starting test... ");
+
+ let { document, NetMonitorView } = monitor.panelWin;
+ let { RequestsMenu } = NetMonitorView;
+
+ RequestsMenu.lazyUpdate = false;
+
+ is(document.querySelector("#details-pane-toggle").hasAttribute("disabled"), true,
+ "The pane toggle button should be disabled when the frontend is opened.");
+ is(document.querySelector("#requests-menu-empty-notice").hasAttribute("hidden"), false,
+ "An empty notice should be displayed when the frontend is opened.");
+ is(RequestsMenu.itemCount, 0,
+ "The requests menu should be empty when the frontend is opened.");
+ is(NetMonitorView.detailsPaneHidden, true,
+ "The details pane should be hidden when the frontend is opened.");
+
+ yield reloadAndWait();
+
+ is(document.querySelector("#details-pane-toggle").hasAttribute("disabled"), false,
+ "The pane toggle button should be enabled after the first request.");
+ is(document.querySelector("#requests-menu-empty-notice").hasAttribute("hidden"), true,
+ "The empty notice should be hidden after the first request.");
+ is(RequestsMenu.itemCount, 1,
+ "The requests menu should not be empty after the first request.");
+ is(NetMonitorView.detailsPaneHidden, true,
+ "The details pane should still be hidden after the first request.");
+
+ yield reloadAndWait();
+
+ is(document.querySelector("#details-pane-toggle").hasAttribute("disabled"), false,
+ "The pane toggle button should be still be enabled after a reload.");
+ is(document.querySelector("#requests-menu-empty-notice").hasAttribute("hidden"), true,
+ "The empty notice should be still hidden after a reload.");
+ is(RequestsMenu.itemCount, 1,
+ "The requests menu should not be empty after a reload.");
+ is(NetMonitorView.detailsPaneHidden, true,
+ "The details pane should still be hidden after a reload.");
+
+ RequestsMenu.clear();
+
+ is(document.querySelector("#details-pane-toggle").hasAttribute("disabled"), true,
+ "The pane toggle button should be disabled when after clear.");
+ is(document.querySelector("#requests-menu-empty-notice").hasAttribute("hidden"), false,
+ "An empty notice should be displayed again after clear.");
+ is(RequestsMenu.itemCount, 0,
+ "The requests menu should be empty after clear.");
+ is(NetMonitorView.detailsPaneHidden, true,
+ "The details pane should be hidden after clear.");
+
+ return teardown(monitor);
+
+ function* reloadAndWait() {
+ let wait = waitForNetworkEvents(monitor, 1);
+ tab.linkedBrowser.reload();
+ return wait;
+ }
+});
diff --git a/devtools/client/netmonitor/test/browser_net_sort-01.js b/devtools/client/netmonitor/test/browser_net_sort-01.js
new file mode 100644
index 000000000..2c4e718dc
--- /dev/null
+++ b/devtools/client/netmonitor/test/browser_net_sort-01.js
@@ -0,0 +1,230 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+/**
+ * Test if the sorting mechanism works correctly.
+ */
+
+add_task(function* () {
+ let { L10N } = require("devtools/client/netmonitor/l10n");
+
+ let { tab, monitor } = yield initNetMonitor(STATUS_CODES_URL);
+ info("Starting test... ");
+
+ let { $all, NetMonitorView } = monitor.panelWin;
+ let { RequestsMenu } = NetMonitorView;
+
+ RequestsMenu.lazyUpdate = false;
+
+ let wait = waitForNetworkEvents(monitor, 5);
+ yield ContentTask.spawn(tab.linkedBrowser, {}, function* () {
+ content.wrappedJSObject.performRequests();
+ });
+ yield wait;
+
+ testContents([0, 1, 2, 3, 4]);
+
+ info("Testing swap(0, 0)");
+ RequestsMenu.swapItemsAtIndices(0, 0);
+ RequestsMenu.refreshZebra();
+ testContents([0, 1, 2, 3, 4]);
+
+ info("Testing swap(0, 1)");
+ RequestsMenu.swapItemsAtIndices(0, 1);
+ RequestsMenu.refreshZebra();
+ testContents([1, 0, 2, 3, 4]);
+
+ info("Testing swap(0, 2)");
+ RequestsMenu.swapItemsAtIndices(0, 2);
+ RequestsMenu.refreshZebra();
+ testContents([1, 2, 0, 3, 4]);
+
+ info("Testing swap(0, 3)");
+ RequestsMenu.swapItemsAtIndices(0, 3);
+ RequestsMenu.refreshZebra();
+ testContents([1, 2, 3, 0, 4]);
+
+ info("Testing swap(0, 4)");
+ RequestsMenu.swapItemsAtIndices(0, 4);
+ RequestsMenu.refreshZebra();
+ testContents([1, 2, 3, 4, 0]);
+
+ info("Testing swap(1, 0)");
+ RequestsMenu.swapItemsAtIndices(1, 0);
+ RequestsMenu.refreshZebra();
+ testContents([0, 2, 3, 4, 1]);
+
+ info("Testing swap(1, 1)");
+ RequestsMenu.swapItemsAtIndices(1, 1);
+ RequestsMenu.refreshZebra();
+ testContents([0, 2, 3, 4, 1]);
+
+ info("Testing swap(1, 2)");
+ RequestsMenu.swapItemsAtIndices(1, 2);
+ RequestsMenu.refreshZebra();
+ testContents([0, 1, 3, 4, 2]);
+
+ info("Testing swap(1, 3)");
+ RequestsMenu.swapItemsAtIndices(1, 3);
+ RequestsMenu.refreshZebra();
+ testContents([0, 3, 1, 4, 2]);
+
+ info("Testing swap(1, 4)");
+ RequestsMenu.swapItemsAtIndices(1, 4);
+ RequestsMenu.refreshZebra();
+ testContents([0, 3, 4, 1, 2]);
+
+ info("Testing swap(2, 0)");
+ RequestsMenu.swapItemsAtIndices(2, 0);
+ RequestsMenu.refreshZebra();
+ testContents([2, 3, 4, 1, 0]);
+
+ info("Testing swap(2, 1)");
+ RequestsMenu.swapItemsAtIndices(2, 1);
+ RequestsMenu.refreshZebra();
+ testContents([1, 3, 4, 2, 0]);
+
+ info("Testing swap(2, 2)");
+ RequestsMenu.swapItemsAtIndices(2, 2);
+ RequestsMenu.refreshZebra();
+ testContents([1, 3, 4, 2, 0]);
+
+ info("Testing swap(2, 3)");
+ RequestsMenu.swapItemsAtIndices(2, 3);
+ RequestsMenu.refreshZebra();
+ testContents([1, 2, 4, 3, 0]);
+
+ info("Testing swap(2, 4)");
+ RequestsMenu.swapItemsAtIndices(2, 4);
+ RequestsMenu.refreshZebra();
+ testContents([1, 4, 2, 3, 0]);
+
+ info("Testing swap(3, 0)");
+ RequestsMenu.swapItemsAtIndices(3, 0);
+ RequestsMenu.refreshZebra();
+ testContents([1, 4, 2, 0, 3]);
+
+ info("Testing swap(3, 1)");
+ RequestsMenu.swapItemsAtIndices(3, 1);
+ RequestsMenu.refreshZebra();
+ testContents([3, 4, 2, 0, 1]);
+
+ info("Testing swap(3, 2)");
+ RequestsMenu.swapItemsAtIndices(3, 2);
+ RequestsMenu.refreshZebra();
+ testContents([2, 4, 3, 0, 1]);
+
+ info("Testing swap(3, 3)");
+ RequestsMenu.swapItemsAtIndices(3, 3);
+ RequestsMenu.refreshZebra();
+ testContents([2, 4, 3, 0, 1]);
+
+ info("Testing swap(3, 4)");
+ RequestsMenu.swapItemsAtIndices(3, 4);
+ RequestsMenu.refreshZebra();
+ testContents([2, 3, 4, 0, 1]);
+
+ info("Testing swap(4, 0)");
+ RequestsMenu.swapItemsAtIndices(4, 0);
+ RequestsMenu.refreshZebra();
+ testContents([2, 3, 0, 4, 1]);
+
+ info("Testing swap(4, 1)");
+ RequestsMenu.swapItemsAtIndices(4, 1);
+ RequestsMenu.refreshZebra();
+ testContents([2, 3, 0, 1, 4]);
+
+ info("Testing swap(4, 2)");
+ RequestsMenu.swapItemsAtIndices(4, 2);
+ RequestsMenu.refreshZebra();
+ testContents([4, 3, 0, 1, 2]);
+
+ info("Testing swap(4, 3)");
+ RequestsMenu.swapItemsAtIndices(4, 3);
+ RequestsMenu.refreshZebra();
+ testContents([3, 4, 0, 1, 2]);
+
+ info("Testing swap(4, 4)");
+ RequestsMenu.swapItemsAtIndices(4, 4);
+ RequestsMenu.refreshZebra();
+ testContents([3, 4, 0, 1, 2]);
+
+ info("Clearing sort.");
+ RequestsMenu.sortBy();
+ testContents([0, 1, 2, 3, 4]);
+
+ return teardown(monitor);
+
+ function testContents([a, b, c, d, e]) {
+ is(RequestsMenu.items.length, 5,
+ "There should be a total of 5 items in the requests menu.");
+ is(RequestsMenu.visibleItems.length, 5,
+ "There should be a total of 5 visbile items in the requests menu.");
+ is($all(".side-menu-widget-item").length, 5,
+ "The visible items in the requests menu are, in fact, visible!");
+
+ is(RequestsMenu.getItemAtIndex(0), RequestsMenu.items[0],
+ "The requests menu items aren't ordered correctly. First item is misplaced.");
+ is(RequestsMenu.getItemAtIndex(1), RequestsMenu.items[1],
+ "The requests menu items aren't ordered correctly. Second item is misplaced.");
+ is(RequestsMenu.getItemAtIndex(2), RequestsMenu.items[2],
+ "The requests menu items aren't ordered correctly. Third item is misplaced.");
+ is(RequestsMenu.getItemAtIndex(3), RequestsMenu.items[3],
+ "The requests menu items aren't ordered correctly. Fourth item is misplaced.");
+ is(RequestsMenu.getItemAtIndex(4), RequestsMenu.items[4],
+ "The requests menu items aren't ordered correctly. Fifth item is misplaced.");
+
+ verifyRequestItemTarget(RequestsMenu.getItemAtIndex(a),
+ "GET", STATUS_CODES_SJS + "?sts=100", {
+ status: 101,
+ statusText: "Switching Protocols",
+ type: "plain",
+ fullMimeType: "text/plain; charset=utf-8",
+ transferred: L10N.getStr("networkMenu.sizeUnavailable"),
+ size: L10N.getFormatStrWithNumbers("networkMenu.sizeB", 0),
+ time: true
+ });
+ verifyRequestItemTarget(RequestsMenu.getItemAtIndex(b),
+ "GET", STATUS_CODES_SJS + "?sts=200", {
+ status: 202,
+ statusText: "Created",
+ type: "plain",
+ fullMimeType: "text/plain; charset=utf-8",
+ transferred: L10N.getFormatStrWithNumbers("networkMenu.sizeB", 22),
+ size: L10N.getFormatStrWithNumbers("networkMenu.sizeB", 22),
+ time: true
+ });
+ verifyRequestItemTarget(RequestsMenu.getItemAtIndex(c),
+ "GET", STATUS_CODES_SJS + "?sts=300", {
+ status: 303,
+ statusText: "See Other",
+ type: "plain",
+ fullMimeType: "text/plain; charset=utf-8",
+ transferred: L10N.getFormatStrWithNumbers("networkMenu.sizeB", 22),
+ size: L10N.getFormatStrWithNumbers("networkMenu.sizeB", 22),
+ time: true
+ });
+ verifyRequestItemTarget(RequestsMenu.getItemAtIndex(d),
+ "GET", STATUS_CODES_SJS + "?sts=400", {
+ status: 404,
+ statusText: "Not Found",
+ type: "plain",
+ fullMimeType: "text/plain; charset=utf-8",
+ transferred: L10N.getFormatStrWithNumbers("networkMenu.sizeB", 22),
+ size: L10N.getFormatStrWithNumbers("networkMenu.sizeB", 22),
+ time: true
+ });
+ verifyRequestItemTarget(RequestsMenu.getItemAtIndex(e),
+ "GET", STATUS_CODES_SJS + "?sts=500", {
+ status: 501,
+ statusText: "Not Implemented",
+ type: "plain",
+ fullMimeType: "text/plain; charset=utf-8",
+ transferred: L10N.getFormatStrWithNumbers("networkMenu.sizeB", 22),
+ size: L10N.getFormatStrWithNumbers("networkMenu.sizeB", 22),
+ time: true
+ });
+ }
+});
diff --git a/devtools/client/netmonitor/test/browser_net_sort-02.js b/devtools/client/netmonitor/test/browser_net_sort-02.js
new file mode 100644
index 000000000..ce8c69e45
--- /dev/null
+++ b/devtools/client/netmonitor/test/browser_net_sort-02.js
@@ -0,0 +1,272 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+/**
+ * Test if sorting columns in the network table works correctly.
+ */
+
+add_task(function* () {
+ let { L10N } = require("devtools/client/netmonitor/l10n");
+
+ let { monitor } = yield initNetMonitor(SORTING_URL);
+ info("Starting test... ");
+
+ // It seems that this test may be slow on debug builds. This could be because
+ // of the heavy dom manipulation associated with sorting.
+ requestLongerTimeout(2);
+
+ let { $, $all, NetMonitorView } = monitor.panelWin;
+ let { RequestsMenu } = NetMonitorView;
+
+ // Loading the frame script and preparing the xhr request URLs so we can
+ // generate some requests later.
+ loadCommonFrameScript();
+ let requests = [{
+ url: "sjs_sorting-test-server.sjs?index=1&" + Math.random(),
+ method: "GET1"
+ }, {
+ url: "sjs_sorting-test-server.sjs?index=5&" + Math.random(),
+ method: "GET5"
+ }, {
+ url: "sjs_sorting-test-server.sjs?index=2&" + Math.random(),
+ method: "GET2"
+ }, {
+ url: "sjs_sorting-test-server.sjs?index=4&" + Math.random(),
+ method: "GET4"
+ }, {
+ url: "sjs_sorting-test-server.sjs?index=3&" + Math.random(),
+ method: "GET3"
+ }];
+
+ RequestsMenu.lazyUpdate = false;
+
+ let wait = waitForNetworkEvents(monitor, 5);
+ yield performRequestsInContent(requests);
+ yield wait;
+
+ EventUtils.sendMouseEvent({ type: "mousedown" }, $("#details-pane-toggle"));
+
+ isnot(RequestsMenu.selectedItem, null,
+ "There should be a selected item in the requests menu.");
+ is(RequestsMenu.selectedIndex, 0,
+ "The first item should be selected in the requests menu.");
+ is(NetMonitorView.detailsPaneHidden, false,
+ "The details pane should not be hidden after toggle button was pressed.");
+
+ testHeaders();
+ testContents([0, 2, 4, 3, 1]);
+
+ info("Testing status sort, ascending.");
+ EventUtils.sendMouseEvent({ type: "click" }, $("#requests-menu-status-button"));
+ testHeaders("status", "ascending");
+ testContents([0, 1, 2, 3, 4]);
+
+ info("Testing status sort, descending.");
+ EventUtils.sendMouseEvent({ type: "click" }, $("#requests-menu-status-button"));
+ testHeaders("status", "descending");
+ testContents([4, 3, 2, 1, 0]);
+
+ info("Testing status sort, ascending. Checking sort loops correctly.");
+ EventUtils.sendMouseEvent({ type: "click" }, $("#requests-menu-status-button"));
+ testHeaders("status", "ascending");
+ testContents([0, 1, 2, 3, 4]);
+
+ info("Testing method sort, ascending.");
+ EventUtils.sendMouseEvent({ type: "click" }, $("#requests-menu-method-button"));
+ testHeaders("method", "ascending");
+ testContents([0, 1, 2, 3, 4]);
+
+ info("Testing method sort, descending.");
+ EventUtils.sendMouseEvent({ type: "click" }, $("#requests-menu-method-button"));
+ testHeaders("method", "descending");
+ testContents([4, 3, 2, 1, 0]);
+
+ info("Testing method sort, ascending. Checking sort loops correctly.");
+ EventUtils.sendMouseEvent({ type: "click" }, $("#requests-menu-method-button"));
+ testHeaders("method", "ascending");
+ testContents([0, 1, 2, 3, 4]);
+
+ info("Testing file sort, ascending.");
+ EventUtils.sendMouseEvent({ type: "click" }, $("#requests-menu-file-button"));
+ testHeaders("file", "ascending");
+ testContents([0, 1, 2, 3, 4]);
+
+ info("Testing file sort, descending.");
+ EventUtils.sendMouseEvent({ type: "click" }, $("#requests-menu-file-button"));
+ testHeaders("file", "descending");
+ testContents([4, 3, 2, 1, 0]);
+
+ info("Testing file sort, ascending. Checking sort loops correctly.");
+ EventUtils.sendMouseEvent({ type: "click" }, $("#requests-menu-file-button"));
+ testHeaders("file", "ascending");
+ testContents([0, 1, 2, 3, 4]);
+
+ info("Testing type sort, ascending.");
+ EventUtils.sendMouseEvent({ type: "click" }, $("#requests-menu-type-button"));
+ testHeaders("type", "ascending");
+ testContents([0, 1, 2, 3, 4]);
+
+ info("Testing type sort, descending.");
+ EventUtils.sendMouseEvent({ type: "click" }, $("#requests-menu-type-button"));
+ testHeaders("type", "descending");
+ testContents([4, 3, 2, 1, 0]);
+
+ info("Testing type sort, ascending. Checking sort loops correctly.");
+ EventUtils.sendMouseEvent({ type: "click" }, $("#requests-menu-type-button"));
+ testHeaders("type", "ascending");
+ testContents([0, 1, 2, 3, 4]);
+
+ info("Testing transferred sort, ascending.");
+ EventUtils.sendMouseEvent({ type: "click" }, $("#requests-menu-transferred-button"));
+ testHeaders("transferred", "ascending");
+ testContents([0, 1, 2, 3, 4]);
+
+ info("Testing transferred sort, descending.");
+ EventUtils.sendMouseEvent({ type: "click" }, $("#requests-menu-transferred-button"));
+ testHeaders("transferred", "descending");
+ testContents([4, 3, 2, 1, 0]);
+
+ info("Testing transferred sort, ascending. Checking sort loops correctly.");
+ EventUtils.sendMouseEvent({ type: "click" }, $("#requests-menu-transferred-button"));
+ testHeaders("transferred", "ascending");
+ testContents([0, 1, 2, 3, 4]);
+
+ info("Testing size sort, ascending.");
+ EventUtils.sendMouseEvent({ type: "click" }, $("#requests-menu-size-button"));
+ testHeaders("size", "ascending");
+ testContents([0, 1, 2, 3, 4]);
+
+ info("Testing size sort, descending.");
+ EventUtils.sendMouseEvent({ type: "click" }, $("#requests-menu-size-button"));
+ testHeaders("size", "descending");
+ testContents([4, 3, 2, 1, 0]);
+
+ info("Testing size sort, ascending. Checking sort loops correctly.");
+ EventUtils.sendMouseEvent({ type: "click" }, $("#requests-menu-size-button"));
+ testHeaders("size", "ascending");
+ testContents([0, 1, 2, 3, 4]);
+
+ info("Testing waterfall sort, ascending.");
+ EventUtils.sendMouseEvent({ type: "click" }, $("#requests-menu-waterfall-button"));
+ testHeaders("waterfall", "ascending");
+ testContents([0, 2, 4, 3, 1]);
+
+ info("Testing waterfall sort, descending.");
+ EventUtils.sendMouseEvent({ type: "click" }, $("#requests-menu-waterfall-button"));
+ testHeaders("waterfall", "descending");
+ testContents([4, 2, 0, 1, 3]);
+
+ info("Testing waterfall sort, ascending. Checking sort loops correctly.");
+ EventUtils.sendMouseEvent({ type: "click" }, $("#requests-menu-waterfall-button"));
+ testHeaders("waterfall", "ascending");
+ testContents([0, 2, 4, 3, 1]);
+
+ return teardown(monitor);
+
+ function testHeaders(sortType, direction) {
+ let doc = monitor.panelWin.document;
+ let target = doc.querySelector("#requests-menu-" + sortType + "-button");
+ let headers = doc.querySelectorAll(".requests-menu-header-button");
+
+ for (let header of headers) {
+ if (header != target) {
+ is(header.hasAttribute("sorted"), false,
+ "The " + header.id + " header should not have a 'sorted' attribute.");
+ is(header.hasAttribute("tooltiptext"), false,
+ "The " + header.id + " header should not have a 'tooltiptext' attribute.");
+ } else {
+ is(header.getAttribute("sorted"), direction,
+ "The " + header.id + " header has an incorrect 'sorted' attribute.");
+ is(header.getAttribute("tooltiptext"), direction == "ascending"
+ ? L10N.getStr("networkMenu.sortedAsc")
+ : L10N.getStr("networkMenu.sortedDesc"),
+ "The " + header.id + " has an incorrect 'tooltiptext' attribute.");
+ }
+ }
+ }
+
+ function testContents([a, b, c, d, e]) {
+ isnot(RequestsMenu.selectedItem, null,
+ "There should still be a selected item after sorting.");
+ is(RequestsMenu.selectedIndex, a,
+ "The first item should be still selected after sorting.");
+ is(NetMonitorView.detailsPaneHidden, false,
+ "The details pane should still be visible after sorting.");
+
+ is(RequestsMenu.items.length, 5,
+ "There should be a total of 5 items in the requests menu.");
+ is(RequestsMenu.visibleItems.length, 5,
+ "There should be a total of 5 visbile items in the requests menu.");
+ is($all(".side-menu-widget-item").length, 5,
+ "The visible items in the requests menu are, in fact, visible!");
+
+ is(RequestsMenu.getItemAtIndex(0), RequestsMenu.items[0],
+ "The requests menu items aren't ordered correctly. First item is misplaced.");
+ is(RequestsMenu.getItemAtIndex(1), RequestsMenu.items[1],
+ "The requests menu items aren't ordered correctly. Second item is misplaced.");
+ is(RequestsMenu.getItemAtIndex(2), RequestsMenu.items[2],
+ "The requests menu items aren't ordered correctly. Third item is misplaced.");
+ is(RequestsMenu.getItemAtIndex(3), RequestsMenu.items[3],
+ "The requests menu items aren't ordered correctly. Fourth item is misplaced.");
+ is(RequestsMenu.getItemAtIndex(4), RequestsMenu.items[4],
+ "The requests menu items aren't ordered correctly. Fifth item is misplaced.");
+
+ verifyRequestItemTarget(RequestsMenu.getItemAtIndex(a),
+ "GET1", SORTING_SJS + "?index=1", {
+ fuzzyUrl: true,
+ status: 101,
+ statusText: "Meh",
+ type: "1",
+ fullMimeType: "text/1",
+ transferred: L10N.getStr("networkMenu.sizeUnavailable"),
+ size: L10N.getFormatStrWithNumbers("networkMenu.sizeB", 0),
+ time: true
+ });
+ verifyRequestItemTarget(RequestsMenu.getItemAtIndex(b),
+ "GET2", SORTING_SJS + "?index=2", {
+ fuzzyUrl: true,
+ status: 200,
+ statusText: "Meh",
+ type: "2",
+ fullMimeType: "text/2",
+ transferred: L10N.getFormatStrWithNumbers("networkMenu.sizeB", 19),
+ size: L10N.getFormatStrWithNumbers("networkMenu.sizeB", 19),
+ time: true
+ });
+ verifyRequestItemTarget(RequestsMenu.getItemAtIndex(c),
+ "GET3", SORTING_SJS + "?index=3", {
+ fuzzyUrl: true,
+ status: 300,
+ statusText: "Meh",
+ type: "3",
+ fullMimeType: "text/3",
+ transferred: L10N.getFormatStrWithNumbers("networkMenu.sizeB", 29),
+ size: L10N.getFormatStrWithNumbers("networkMenu.sizeB", 29),
+ time: true
+ });
+ verifyRequestItemTarget(RequestsMenu.getItemAtIndex(d),
+ "GET4", SORTING_SJS + "?index=4", {
+ fuzzyUrl: true,
+ status: 400,
+ statusText: "Meh",
+ type: "4",
+ fullMimeType: "text/4",
+ transferred: L10N.getFormatStrWithNumbers("networkMenu.sizeB", 39),
+ size: L10N.getFormatStrWithNumbers("networkMenu.sizeB", 39),
+ time: true
+ });
+ verifyRequestItemTarget(RequestsMenu.getItemAtIndex(e),
+ "GET5", SORTING_SJS + "?index=5", {
+ fuzzyUrl: true,
+ status: 500,
+ statusText: "Meh",
+ type: "5",
+ fullMimeType: "text/5",
+ transferred: L10N.getFormatStrWithNumbers("networkMenu.sizeB", 49),
+ size: L10N.getFormatStrWithNumbers("networkMenu.sizeB", 49),
+ time: true
+ });
+ }
+});
diff --git a/devtools/client/netmonitor/test/browser_net_sort-03.js b/devtools/client/netmonitor/test/browser_net_sort-03.js
new file mode 100644
index 000000000..ada0872a8
--- /dev/null
+++ b/devtools/client/netmonitor/test/browser_net_sort-03.js
@@ -0,0 +1,209 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+/**
+ * Test if sorting columns in the network table works correctly with new requests.
+ */
+
+add_task(function* () {
+ let { L10N } = require("devtools/client/netmonitor/l10n");
+
+ let { monitor } = yield initNetMonitor(SORTING_URL);
+ info("Starting test... ");
+
+ // It seems that this test may be slow on debug builds. This could be because
+ // of the heavy dom manipulation associated with sorting.
+ requestLongerTimeout(2);
+
+ let { $, $all, NetMonitorView } = monitor.panelWin;
+ let { RequestsMenu } = NetMonitorView;
+
+ // Loading the frame script and preparing the xhr request URLs so we can
+ // generate some requests later.
+ loadCommonFrameScript();
+ let requests = [{
+ url: "sjs_sorting-test-server.sjs?index=1&" + Math.random(),
+ method: "GET1"
+ }, {
+ url: "sjs_sorting-test-server.sjs?index=5&" + Math.random(),
+ method: "GET5"
+ }, {
+ url: "sjs_sorting-test-server.sjs?index=2&" + Math.random(),
+ method: "GET2"
+ }, {
+ url: "sjs_sorting-test-server.sjs?index=4&" + Math.random(),
+ method: "GET4"
+ }, {
+ url: "sjs_sorting-test-server.sjs?index=3&" + Math.random(),
+ method: "GET3"
+ }];
+
+ RequestsMenu.lazyUpdate = false;
+
+ let wait = waitForNetworkEvents(monitor, 5);
+ yield performRequestsInContent(requests);
+ yield wait;
+
+ EventUtils.sendMouseEvent({ type: "mousedown" }, $("#details-pane-toggle"));
+
+ isnot(RequestsMenu.selectedItem, null,
+ "There should be a selected item in the requests menu.");
+ is(RequestsMenu.selectedIndex, 0,
+ "The first item should be selected in the requests menu.");
+ is(NetMonitorView.detailsPaneHidden, false,
+ "The details pane should not be hidden after toggle button was pressed.");
+
+ testHeaders();
+ testContents([0, 2, 4, 3, 1], 0);
+
+ info("Testing status sort, ascending.");
+ EventUtils.sendMouseEvent({ type: "click" }, $("#requests-menu-status-button"));
+ testHeaders("status", "ascending");
+ testContents([0, 1, 2, 3, 4], 0);
+
+ info("Performing more requests.");
+ wait = waitForNetworkEvents(monitor, 5);
+ yield performRequestsInContent(requests);
+ yield wait;
+
+ info("Testing status sort again, ascending.");
+ testHeaders("status", "ascending");
+ testContents([0, 1, 2, 3, 4, 5, 6, 7, 8, 9], 0);
+
+ info("Testing status sort, descending.");
+ EventUtils.sendMouseEvent({ type: "click" }, $("#requests-menu-status-button"));
+ testHeaders("status", "descending");
+ testContents([9, 8, 7, 6, 5, 4, 3, 2, 1, 0], 9);
+
+ info("Performing more requests.");
+ wait = waitForNetworkEvents(monitor, 5);
+ yield performRequestsInContent(requests);
+ yield wait;
+
+ info("Testing status sort again, descending.");
+ testHeaders("status", "descending");
+ testContents([14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0], 14);
+
+ info("Testing status sort yet again, ascending.");
+ EventUtils.sendMouseEvent({ type: "click" }, $("#requests-menu-status-button"));
+ testHeaders("status", "ascending");
+ testContents([0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14], 0);
+
+ info("Testing status sort yet again, descending.");
+ EventUtils.sendMouseEvent({ type: "click" }, $("#requests-menu-status-button"));
+ testHeaders("status", "descending");
+ testContents([14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0], 14);
+
+ return teardown(monitor);
+
+ function testHeaders(sortType, direction) {
+ let doc = monitor.panelWin.document;
+ let target = doc.querySelector("#requests-menu-" + sortType + "-button");
+ let headers = doc.querySelectorAll(".requests-menu-header-button");
+
+ for (let header of headers) {
+ if (header != target) {
+ is(header.hasAttribute("sorted"), false,
+ "The " + header.id + " header should not have a 'sorted' attribute.");
+ is(header.hasAttribute("tooltiptext"), false,
+ "The " + header.id + " header should not have a 'tooltiptext' attribute.");
+ } else {
+ is(header.getAttribute("sorted"), direction,
+ "The " + header.id + " header has an incorrect 'sorted' attribute.");
+ is(header.getAttribute("tooltiptext"), direction == "ascending"
+ ? L10N.getStr("networkMenu.sortedAsc")
+ : L10N.getStr("networkMenu.sortedDesc"),
+ "The " + header.id + " has an incorrect 'tooltiptext' attribute.");
+ }
+ }
+ }
+
+ function testContents(order, selection) {
+ isnot(RequestsMenu.selectedItem, null,
+ "There should still be a selected item after sorting.");
+ is(RequestsMenu.selectedIndex, selection,
+ "The first item should be still selected after sorting.");
+ is(NetMonitorView.detailsPaneHidden, false,
+ "The details pane should still be visible after sorting.");
+
+ is(RequestsMenu.items.length, order.length,
+ "There should be a specific number of items in the requests menu.");
+ is(RequestsMenu.visibleItems.length, order.length,
+ "There should be a specific number of visbile items in the requests menu.");
+ is($all(".side-menu-widget-item").length, order.length,
+ "The visible items in the requests menu are, in fact, visible!");
+
+ for (let i = 0; i < order.length; i++) {
+ is(RequestsMenu.getItemAtIndex(i), RequestsMenu.items[i],
+ "The requests menu items aren't ordered correctly. Misplaced item " + i + ".");
+ }
+
+ for (let i = 0, len = order.length / 5; i < len; i++) {
+ verifyRequestItemTarget(RequestsMenu.getItemAtIndex(order[i]),
+ "GET1", SORTING_SJS + "?index=1", {
+ fuzzyUrl: true,
+ status: 101,
+ statusText: "Meh",
+ type: "1",
+ fullMimeType: "text/1",
+ transferred: L10N.getStr("networkMenu.sizeUnavailable"),
+ size: L10N.getFormatStrWithNumbers("networkMenu.sizeB", 0),
+ time: true
+ });
+ }
+ for (let i = 0, len = order.length / 5; i < len; i++) {
+ verifyRequestItemTarget(RequestsMenu.getItemAtIndex(order[i + len]),
+ "GET2", SORTING_SJS + "?index=2", {
+ fuzzyUrl: true,
+ status: 200,
+ statusText: "Meh",
+ type: "2",
+ fullMimeType: "text/2",
+ transferred: L10N.getFormatStrWithNumbers("networkMenu.sizeB", 19),
+ size: L10N.getFormatStrWithNumbers("networkMenu.sizeB", 19),
+ time: true
+ });
+ }
+ for (let i = 0, len = order.length / 5; i < len; i++) {
+ verifyRequestItemTarget(RequestsMenu.getItemAtIndex(order[i + len * 2]),
+ "GET3", SORTING_SJS + "?index=3", {
+ fuzzyUrl: true,
+ status: 300,
+ statusText: "Meh",
+ type: "3",
+ fullMimeType: "text/3",
+ transferred: L10N.getFormatStrWithNumbers("networkMenu.sizeB", 29),
+ size: L10N.getFormatStrWithNumbers("networkMenu.sizeB", 29),
+ time: true
+ });
+ }
+ for (let i = 0, len = order.length / 5; i < len; i++) {
+ verifyRequestItemTarget(RequestsMenu.getItemAtIndex(order[i + len * 3]),
+ "GET4", SORTING_SJS + "?index=4", {
+ fuzzyUrl: true,
+ status: 400,
+ statusText: "Meh",
+ type: "4",
+ fullMimeType: "text/4",
+ transferred: L10N.getFormatStrWithNumbers("networkMenu.sizeB", 39),
+ size: L10N.getFormatStrWithNumbers("networkMenu.sizeB", 39),
+ time: true
+ });
+ }
+ for (let i = 0, len = order.length / 5; i < len; i++) {
+ verifyRequestItemTarget(RequestsMenu.getItemAtIndex(order[i + len * 4]),
+ "GET5", SORTING_SJS + "?index=5", {
+ fuzzyUrl: true,
+ status: 500,
+ statusText: "Meh",
+ type: "5",
+ fullMimeType: "text/5",
+ transferred: L10N.getFormatStrWithNumbers("networkMenu.sizeB", 49),
+ size: L10N.getFormatStrWithNumbers("networkMenu.sizeB", 49),
+ time: true
+ });
+ }
+ }
+});
diff --git a/devtools/client/netmonitor/test/browser_net_statistics-01.js b/devtools/client/netmonitor/test/browser_net_statistics-01.js
new file mode 100644
index 000000000..d7e75b997
--- /dev/null
+++ b/devtools/client/netmonitor/test/browser_net_statistics-01.js
@@ -0,0 +1,63 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+/**
+ * Tests if the statistics view is populated correctly.
+ */
+
+add_task(function* () {
+ let { monitor } = yield initNetMonitor(STATISTICS_URL);
+ info("Starting test... ");
+
+ let panel = monitor.panelWin;
+ let { $, $all, EVENTS, NetMonitorView } = panel;
+ is(NetMonitorView.currentFrontendMode, "network-inspector-view",
+ "The initial frontend mode is correct.");
+
+ is($("#primed-cache-chart").childNodes.length, 0,
+ "There should be no primed cache chart created yet.");
+ is($("#empty-cache-chart").childNodes.length, 0,
+ "There should be no empty cache chart created yet.");
+
+ let onChartDisplayed = Promise.all([
+ panel.once(EVENTS.PRIMED_CACHE_CHART_DISPLAYED),
+ panel.once(EVENTS.EMPTY_CACHE_CHART_DISPLAYED)
+ ]);
+ let onPlaceholderDisplayed = panel.once(EVENTS.PLACEHOLDER_CHARTS_DISPLAYED);
+
+ info("Displaying statistics view");
+ NetMonitorView.toggleFrontendMode();
+ is(NetMonitorView.currentFrontendMode, "network-statistics-view",
+ "The current frontend mode is correct.");
+
+ info("Waiting for placeholder to display");
+ yield onPlaceholderDisplayed;
+ is($("#primed-cache-chart").childNodes.length, 1,
+ "There should be a placeholder primed cache chart created now.");
+ is($("#empty-cache-chart").childNodes.length, 1,
+ "There should be a placeholder empty cache chart created now.");
+
+ is($all(".pie-chart-container[placeholder=true]").length, 2,
+ "Two placeholder pie chart appear to be rendered correctly.");
+ is($all(".table-chart-container[placeholder=true]").length, 2,
+ "Two placeholder table chart appear to be rendered correctly.");
+
+ info("Waiting for chart to display");
+ yield onChartDisplayed;
+ is($("#primed-cache-chart").childNodes.length, 1,
+ "There should be a real primed cache chart created now.");
+ is($("#empty-cache-chart").childNodes.length, 1,
+ "There should be a real empty cache chart created now.");
+
+ yield waitUntil(
+ () => $all(".pie-chart-container:not([placeholder=true])").length == 2);
+ ok(true, "Two real pie charts appear to be rendered correctly.");
+
+ yield waitUntil(
+ () => $all(".table-chart-container:not([placeholder=true])").length == 2);
+ ok(true, "Two real table charts appear to be rendered correctly.");
+
+ yield teardown(monitor);
+});
diff --git a/devtools/client/netmonitor/test/browser_net_statistics-02.js b/devtools/client/netmonitor/test/browser_net_statistics-02.js
new file mode 100644
index 000000000..361247e16
--- /dev/null
+++ b/devtools/client/netmonitor/test/browser_net_statistics-02.js
@@ -0,0 +1,42 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+/**
+ * Tests if the network inspector view is shown when the target navigates
+ * away while in the statistics view.
+ */
+
+add_task(function* () {
+ let { tab, monitor } = yield initNetMonitor(STATISTICS_URL);
+ info("Starting test... ");
+
+ let panel = monitor.panelWin;
+ let { EVENTS, NetMonitorView } = panel;
+ is(NetMonitorView.currentFrontendMode, "network-inspector-view",
+ "The initial frontend mode is correct.");
+
+ let onChartDisplayed = Promise.all([
+ panel.once(EVENTS.PRIMED_CACHE_CHART_DISPLAYED),
+ panel.once(EVENTS.EMPTY_CACHE_CHART_DISPLAYED)
+ ]);
+
+ info("Displaying statistics view");
+ NetMonitorView.toggleFrontendMode();
+ yield onChartDisplayed;
+ is(NetMonitorView.currentFrontendMode, "network-statistics-view",
+ "The frontend mode is currently in the statistics view.");
+
+ info("Reloading page");
+ let onWillNavigate = panel.once(EVENTS.TARGET_WILL_NAVIGATE);
+ let onDidNavigate = panel.once(EVENTS.TARGET_DID_NAVIGATE);
+ tab.linkedBrowser.reload();
+ yield onWillNavigate;
+ is(NetMonitorView.currentFrontendMode, "network-inspector-view",
+ "The frontend mode switched back to the inspector view.");
+ yield onDidNavigate;
+ is(NetMonitorView.currentFrontendMode, "network-inspector-view",
+ "The frontend mode is still in the inspector view.");
+ yield teardown(monitor);
+});
diff --git a/devtools/client/netmonitor/test/browser_net_statistics-03.js b/devtools/client/netmonitor/test/browser_net_statistics-03.js
new file mode 100644
index 000000000..f3c6bf691
--- /dev/null
+++ b/devtools/client/netmonitor/test/browser_net_statistics-03.js
@@ -0,0 +1,45 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+/**
+ * Test if the correct filtering predicates are used when filtering from
+ * the performance analysis view.
+ */
+
+add_task(function* () {
+ let { monitor } = yield initNetMonitor(FILTERING_URL);
+ info("Starting test... ");
+
+ let panel = monitor.panelWin;
+ let { $, EVENTS, NetMonitorView } = panel;
+
+ EventUtils.sendMouseEvent({ type: "click" }, $("#requests-menu-filter-html-button"));
+ EventUtils.sendMouseEvent({ type: "click" }, $("#requests-menu-filter-css-button"));
+ EventUtils.sendMouseEvent({ type: "click" }, $("#requests-menu-filter-js-button"));
+ EventUtils.sendMouseEvent({ type: "click" }, $("#requests-menu-filter-ws-button"));
+ EventUtils.sendMouseEvent({ type: "click" }, $("#requests-menu-filter-other-button"));
+ testFilterButtonsCustom(monitor, [0, 1, 1, 1, 0, 0, 0, 0, 0, 1]);
+ info("The correct filtering predicates are used before entering perf. analysis mode.");
+
+ let onEvents = promise.all([
+ panel.once(EVENTS.PRIMED_CACHE_CHART_DISPLAYED),
+ panel.once(EVENTS.EMPTY_CACHE_CHART_DISPLAYED)
+ ]);
+ NetMonitorView.toggleFrontendMode();
+ yield onEvents;
+
+ is(NetMonitorView.currentFrontendMode, "network-statistics-view",
+ "The frontend mode is switched to the statistics view.");
+
+ EventUtils.sendMouseEvent({ type: "click" }, $(".pie-chart-slice"));
+
+ is(NetMonitorView.currentFrontendMode, "network-inspector-view",
+ "The frontend mode is switched back to the inspector view.");
+
+ testFilterButtons(monitor, "html");
+ info("The correct filtering predicate is used when exiting perf. analysis mode.");
+
+ yield teardown(monitor);
+});
diff --git a/devtools/client/netmonitor/test/browser_net_status-codes.js b/devtools/client/netmonitor/test/browser_net_status-codes.js
new file mode 100644
index 000000000..f38ee71e4
--- /dev/null
+++ b/devtools/client/netmonitor/test/browser_net_status-codes.js
@@ -0,0 +1,213 @@
+/* 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 display the correct status code and text in the UI.
+ */
+
+add_task(function* () {
+ let { L10N } = require("devtools/client/netmonitor/l10n");
+
+ let { tab, monitor } = yield initNetMonitor(STATUS_CODES_URL);
+
+ info("Starting test... ");
+
+ let { document, EVENTS, NetMonitorView } = monitor.panelWin;
+ let { RequestsMenu, NetworkDetails } = NetMonitorView;
+ let requestItems = [];
+
+ RequestsMenu.lazyUpdate = false;
+ NetworkDetails._params.lazyEmpty = false;
+
+ const REQUEST_DATA = [
+ {
+ // request #0
+ method: "GET",
+ uri: STATUS_CODES_SJS + "?sts=100",
+ details: {
+ status: 101,
+ statusText: "Switching Protocols",
+ type: "plain",
+ fullMimeType: "text/plain; charset=utf-8",
+ size: L10N.getFormatStrWithNumbers("networkMenu.sizeB", 0),
+ time: true
+ }
+ },
+ {
+ // request #1
+ method: "GET",
+ uri: STATUS_CODES_SJS + "?sts=200",
+ details: {
+ status: 202,
+ statusText: "Created",
+ type: "plain",
+ fullMimeType: "text/plain; charset=utf-8",
+ size: L10N.getFormatStrWithNumbers("networkMenu.sizeB", 22),
+ time: true
+ }
+ },
+ {
+ // request #2
+ method: "GET",
+ uri: STATUS_CODES_SJS + "?sts=300",
+ details: {
+ status: 303,
+ statusText: "See Other",
+ type: "plain",
+ fullMimeType: "text/plain; charset=utf-8",
+ size: L10N.getFormatStrWithNumbers("networkMenu.sizeB", 22),
+ time: true
+ }
+ },
+ {
+ // request #3
+ method: "GET",
+ uri: STATUS_CODES_SJS + "?sts=400",
+ details: {
+ status: 404,
+ statusText: "Not Found",
+ type: "plain",
+ fullMimeType: "text/plain; charset=utf-8",
+ size: L10N.getFormatStrWithNumbers("networkMenu.sizeB", 22),
+ time: true
+ }
+ },
+ {
+ // request #4
+ method: "GET",
+ uri: STATUS_CODES_SJS + "?sts=500",
+ details: {
+ status: 501,
+ statusText: "Not Implemented",
+ type: "plain",
+ fullMimeType: "text/plain; charset=utf-8",
+ size: L10N.getFormatStrWithNumbers("networkMenu.sizeB", 22),
+ time: true
+ }
+ }
+ ];
+
+ let wait = waitForNetworkEvents(monitor, 5);
+ yield ContentTask.spawn(tab.linkedBrowser, {}, function* () {
+ content.wrappedJSObject.performRequests();
+ });
+ yield wait;
+
+ info("Performing tests");
+ yield verifyRequests();
+ yield testTab(0, testSummary);
+ yield testTab(2, testParams);
+
+ return teardown(monitor);
+
+ /**
+ * A helper that verifies all requests show the correct information and caches
+ * RequestsMenu items to requestItems array.
+ */
+ function* verifyRequests() {
+ info("Verifying requests contain correct information.");
+ let index = 0;
+ for (let request of REQUEST_DATA) {
+ let item = RequestsMenu.getItemAtIndex(index);
+ requestItems[index] = item;
+
+ info("Verifying request #" + index);
+ yield verifyRequestItemTarget(item, request.method, request.uri, request.details);
+
+ index++;
+ }
+ }
+
+ /**
+ * A helper that opens a given tab of request details pane, selects and passes
+ * all requests to the given test function.
+ *
+ * @param Number tabIdx
+ * The index of NetworkDetails tab to activate.
+ * @param Function testFn(requestItem)
+ * A function that should perform all necessary tests. It's called once
+ * for every item of REQUEST_DATA with that item being selected in the
+ * NetworkMonitor.
+ */
+ function* testTab(tabIdx, testFn) {
+ info("Testing tab #" + tabIdx);
+ EventUtils.sendMouseEvent({ type: "mousedown" },
+ document.querySelectorAll("#details-pane tab")[tabIdx]);
+
+ let counter = 0;
+ for (let item of REQUEST_DATA) {
+ info("Waiting tab #" + tabIdx + " to update with request #" + counter);
+ yield chooseRequest(counter);
+
+ info("Tab updated. Performing checks");
+ yield testFn(item);
+
+ counter++;
+ }
+ }
+
+ /**
+ * A function that tests "Summary" contains correct information.
+ */
+ function* testSummary(data) {
+ let tabpanel = document.querySelectorAll("#details-pane tabpanel")[0];
+
+ let { method, uri, details: { status, statusText } } = data;
+ is(tabpanel.querySelector("#headers-summary-url-value").getAttribute("value"),
+ uri, "The url summary value is incorrect.");
+ is(tabpanel.querySelector("#headers-summary-method-value").getAttribute("value"),
+ method, "The method summary value is incorrect.");
+ is(tabpanel.querySelector("#headers-summary-status-circle").getAttribute("code"),
+ status, "The status summary code is incorrect.");
+ is(tabpanel.querySelector("#headers-summary-status-value").getAttribute("value"),
+ status + " " + statusText, "The status summary value is incorrect.");
+ }
+
+ /**
+ * A function that tests "Params" tab contains correct information.
+ */
+ function* testParams(data) {
+ let tabpanel = document.querySelectorAll("#details-pane tabpanel")[2];
+ let statusParamValue = data.uri.split("=").pop();
+ let statusParamShownValue = "\"" + statusParamValue + "\"";
+
+ is(tabpanel.querySelectorAll(".variables-view-scope").length, 1,
+ "There should be 1 param scope displayed in this tabpanel.");
+ is(tabpanel.querySelectorAll(".variable-or-property").length, 1,
+ "There should be 1 param value displayed in this tabpanel.");
+ is(tabpanel.querySelectorAll(".variables-view-empty-notice").length, 0,
+ "The empty notice should not be displayed in this tabpanel.");
+
+ let paramsScope = tabpanel.querySelectorAll(".variables-view-scope")[0];
+
+ is(paramsScope.querySelector(".name").getAttribute("value"),
+ L10N.getStr("paramsQueryString"),
+ "The params scope doesn't have the correct title.");
+
+ is(paramsScope.querySelectorAll(".variables-view-variable .name")[0]
+ .getAttribute("value"),
+ "sts", "The param name was incorrect.");
+ is(paramsScope.querySelectorAll(".variables-view-variable .value")[0]
+ .getAttribute("value"),
+ statusParamShownValue, "The param value was incorrect.");
+
+ is(tabpanel.querySelector("#request-params-box")
+ .hasAttribute("hidden"), false,
+ "The request params box should not be hidden.");
+ is(tabpanel.querySelector("#request-post-data-textarea-box")
+ .hasAttribute("hidden"), true,
+ "The request post data textarea box should be hidden.");
+ }
+
+ /**
+ * A helper that clicks on a specified request and returns a promise resolved
+ * when NetworkDetails has been populated with the data of the given request.
+ */
+ function chooseRequest(index) {
+ let onTabUpdated = monitor.panelWin.once(EVENTS.TAB_UPDATED);
+ EventUtils.sendMouseEvent({ type: "mousedown" }, requestItems[index].target);
+ return onTabUpdated;
+ }
+});
diff --git a/devtools/client/netmonitor/test/browser_net_streaming-response.js b/devtools/client/netmonitor/test/browser_net_streaming-response.js
new file mode 100644
index 000000000..49a75ec32
--- /dev/null
+++ b/devtools/client/netmonitor/test/browser_net_streaming-response.js
@@ -0,0 +1,69 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+/**
+ * Tests if reponses from streaming content types (MPEG-DASH, HLS) are
+ * displayed as XML or plain text
+ */
+
+add_task(function* () {
+ let { tab, monitor } = yield initNetMonitor(CUSTOM_GET_URL);
+
+ info("Starting test... ");
+ let { panelWin } = monitor;
+ let { document, Editor, NetMonitorView } = panelWin;
+ let { RequestsMenu } = NetMonitorView;
+
+ const REQUESTS = [
+ [ "hls-m3u8", /^#EXTM3U/, Editor.modes.text ],
+ [ "mpeg-dash", /^<\?xml/, Editor.modes.html ]
+ ];
+
+ RequestsMenu.lazyUpdate = false;
+
+ let wait = waitForNetworkEvents(monitor, REQUESTS.length);
+ for (let [fmt] of REQUESTS) {
+ let url = CONTENT_TYPE_SJS + "?fmt=" + fmt;
+ yield ContentTask.spawn(tab.linkedBrowser, { url }, function* (args) {
+ content.wrappedJSObject.performRequests(1, args.url);
+ });
+ }
+ yield wait;
+
+ REQUESTS.forEach(([ fmt ], i) => {
+ verifyRequestItemTarget(RequestsMenu.getItemAtIndex(i),
+ "GET", CONTENT_TYPE_SJS + "?fmt=" + fmt, {
+ status: 200,
+ statusText: "OK"
+ });
+ });
+
+ EventUtils.sendMouseEvent({ type: "mousedown" },
+ document.getElementById("details-pane-toggle"));
+ EventUtils.sendMouseEvent({ type: "mousedown" },
+ document.querySelectorAll("#details-pane tab")[3]);
+
+ yield panelWin.once(panelWin.EVENTS.RESPONSE_BODY_DISPLAYED);
+ let editor = yield NetMonitorView.editor("#response-content-textarea");
+
+ // the hls-m3u8 part
+ testEditorContent(editor, REQUESTS[0]);
+
+ RequestsMenu.selectedIndex = 1;
+ yield panelWin.once(panelWin.EVENTS.TAB_UPDATED);
+ yield panelWin.once(panelWin.EVENTS.RESPONSE_BODY_DISPLAYED);
+
+ // the mpeg-dash part
+ testEditorContent(editor, REQUESTS[1]);
+
+ return teardown(monitor);
+
+ function testEditorContent(e, [ fmt, textRe, mode ]) {
+ ok(e.getText().match(textRe),
+ "The text shown in the source editor for " + fmt + " is correct.");
+ is(e.getMode(), mode,
+ "The mode active in the source editor for " + fmt + " is correct.");
+ }
+});
diff --git a/devtools/client/netmonitor/test/browser_net_throttle.js b/devtools/client/netmonitor/test/browser_net_throttle.js
new file mode 100644
index 000000000..c1e7723b8
--- /dev/null
+++ b/devtools/client/netmonitor/test/browser_net_throttle.js
@@ -0,0 +1,57 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+// Network throttling integration test.
+
+"use strict";
+
+add_task(function* () {
+ yield throttleTest(true);
+ yield throttleTest(false);
+});
+
+function* throttleTest(actuallyThrottle) {
+ requestLongerTimeout(2);
+
+ let { monitor } = yield initNetMonitor(SIMPLE_URL);
+ const {ACTIVITY_TYPE, EVENTS, NetMonitorController, NetMonitorView} = monitor.panelWin;
+
+ info("Starting test... (actuallyThrottle = " + actuallyThrottle + ")");
+
+ // When throttling, must be smaller than the length of the content
+ // of SIMPLE_URL in bytes.
+ const size = actuallyThrottle ? 200 : 0;
+
+ const request = {
+ "NetworkMonitor.throttleData": {
+ roundTripTimeMean: 0,
+ roundTripTimeMax: 0,
+ downloadBPSMean: size,
+ downloadBPSMax: size,
+ uploadBPSMean: 10000,
+ uploadBPSMax: 10000,
+ },
+ };
+ let client = monitor._controller.webConsoleClient;
+
+ info("sending throttle request");
+ let deferred = promise.defer();
+ client.setPreferences(request, response => {
+ deferred.resolve(response);
+ });
+ yield deferred.promise;
+
+ let eventPromise = monitor.panelWin.once(EVENTS.RECEIVED_EVENT_TIMINGS);
+ yield NetMonitorController.triggerActivity(ACTIVITY_TYPE.RELOAD.WITH_CACHE_DISABLED);
+ yield eventPromise;
+
+ let requestItem = NetMonitorView.RequestsMenu.getItemAtIndex(0);
+ const reportedOneSecond = requestItem.attachment.eventTimings.timings.receive > 1000;
+ if (actuallyThrottle) {
+ ok(reportedOneSecond, "download reported as taking more than one second");
+ } else {
+ ok(!reportedOneSecond, "download reported as taking less than one second");
+ }
+
+ yield teardown(monitor);
+}
diff --git a/devtools/client/netmonitor/test/browser_net_timeline_ticks.js b/devtools/client/netmonitor/test/browser_net_timeline_ticks.js
new file mode 100644
index 000000000..2aafcb98d
--- /dev/null
+++ b/devtools/client/netmonitor/test/browser_net_timeline_ticks.js
@@ -0,0 +1,142 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+/**
+ * Tests if timeline correctly displays interval divisions.
+ */
+
+add_task(function* () {
+ let { L10N } = require("devtools/client/netmonitor/l10n");
+
+ let { tab, monitor } = yield initNetMonitor(SIMPLE_URL);
+ info("Starting test... ");
+
+ let { $, $all, NetMonitorView, NetMonitorController } = monitor.panelWin;
+ let { RequestsMenu } = NetMonitorView;
+
+ // Disable transferred size column support for this test.
+ // Without this, the waterfall only has enough room for one division, which
+ // would remove most of the value of this test.
+ $("#requests-menu-transferred-header-box").hidden = true;
+ $("#requests-menu-item-template .requests-menu-transferred").hidden = true;
+
+ RequestsMenu.lazyUpdate = false;
+
+ ok($("#requests-menu-waterfall-label"),
+ "An timeline label should be displayed when the frontend is opened.");
+ ok($all(".requests-menu-timings-division").length == 0,
+ "No tick labels should be displayed when the frontend is opened.");
+
+ ok(!RequestsMenu._canvas, "No canvas should be created when the frontend is opened.");
+ ok(!RequestsMenu._ctx, "No 2d context should be created when the frontend is opened.");
+
+ let wait = waitForNetworkEvents(monitor, 1);
+ tab.linkedBrowser.reload();
+ yield wait;
+
+ // Make sure the DOMContentLoaded and load markers don't interfere with
+ // this test by removing them and redrawing the waterfall (bug 1224088).
+ NetMonitorController.NetworkEventsHandler.clearMarkers();
+ RequestsMenu._flushWaterfallViews(true);
+
+ ok(!$("#requests-menu-waterfall-label"),
+ "The timeline label should be hidden after the first request.");
+ ok($all(".requests-menu-timings-division").length >= 3,
+ "There should be at least 3 tick labels in the network requests header.");
+
+ is($all(".requests-menu-timings-division")[0].getAttribute("value"),
+ L10N.getFormatStr("networkMenu.millisecond", 0),
+ "The first tick label has an incorrect value");
+ is($all(".requests-menu-timings-division")[1].getAttribute("value"),
+ L10N.getFormatStr("networkMenu.millisecond", 80),
+ "The second tick label has an incorrect value");
+ is($all(".requests-menu-timings-division")[2].getAttribute("value"),
+ L10N.getFormatStr("networkMenu.millisecond", 160),
+ "The third tick label has an incorrect value");
+
+ is($all(".requests-menu-timings-division")[0].style.transform, "translateX(0px)",
+ "The first tick label has an incorrect translation");
+ is($all(".requests-menu-timings-division")[1].style.transform, "translateX(80px)",
+ "The second tick label has an incorrect translation");
+ is($all(".requests-menu-timings-division")[2].style.transform, "translateX(160px)",
+ "The third tick label has an incorrect translation");
+
+ ok(RequestsMenu._canvas, "A canvas should be created after the first request.");
+ ok(RequestsMenu._ctx, "A 2d context should be created after the first request.");
+
+ let imageData = RequestsMenu._ctx.getImageData(0, 0, 161, 1);
+ ok(imageData, "The image data should have been created.");
+
+ let data = imageData.data;
+ ok(data, "The image data should contain a pixel array.");
+
+ ok(hasPixelAt(0), "The tick at 0 is should not be empty.");
+ ok(!hasPixelAt(1), "The tick at 1 is should be empty.");
+ ok(!hasPixelAt(19), "The tick at 19 is should be empty.");
+ ok(hasPixelAt(20), "The tick at 20 is should not be empty.");
+ ok(!hasPixelAt(21), "The tick at 21 is should be empty.");
+ ok(!hasPixelAt(39), "The tick at 39 is should be empty.");
+ ok(hasPixelAt(40), "The tick at 40 is should not be empty.");
+ ok(!hasPixelAt(41), "The tick at 41 is should be empty.");
+ ok(!hasPixelAt(59), "The tick at 59 is should be empty.");
+ ok(hasPixelAt(60), "The tick at 60 is should not be empty.");
+ ok(!hasPixelAt(61), "The tick at 61 is should be empty.");
+ ok(!hasPixelAt(79), "The tick at 79 is should be empty.");
+ ok(hasPixelAt(80), "The tick at 80 is should not be empty.");
+ ok(!hasPixelAt(81), "The tick at 81 is should be empty.");
+ ok(!hasPixelAt(159), "The tick at 159 is should be empty.");
+ ok(hasPixelAt(160), "The tick at 160 is should not be empty.");
+ ok(!hasPixelAt(161), "The tick at 161 is should be empty.");
+
+ ok(isPixelBrighterAtThan(0, 20),
+ "The tick at 0 should be brighter than the one at 20");
+ ok(isPixelBrighterAtThan(40, 20),
+ "The tick at 40 should be brighter than the one at 20");
+ ok(isPixelBrighterAtThan(40, 60),
+ "The tick at 40 should be brighter than the one at 60");
+ ok(isPixelBrighterAtThan(80, 60),
+ "The tick at 80 should be brighter than the one at 60");
+
+ ok(isPixelBrighterAtThan(80, 100),
+ "The tick at 80 should be brighter than the one at 100");
+ ok(isPixelBrighterAtThan(120, 100),
+ "The tick at 120 should be brighter than the one at 100");
+ ok(isPixelBrighterAtThan(120, 140),
+ "The tick at 120 should be brighter than the one at 140");
+ ok(isPixelBrighterAtThan(160, 140),
+ "The tick at 160 should be brighter than the one at 140");
+
+ ok(isPixelEquallyBright(20, 60),
+ "The tick at 20 should be equally bright to the one at 60");
+ ok(isPixelEquallyBright(100, 140),
+ "The tick at 100 should be equally bright to the one at 140");
+
+ ok(isPixelEquallyBright(40, 120),
+ "The tick at 40 should be equally bright to the one at 120");
+
+ ok(isPixelEquallyBright(0, 80),
+ "The tick at 80 should be equally bright to the one at 160");
+ ok(isPixelEquallyBright(80, 160),
+ "The tick at 80 should be equally bright to the one at 160");
+
+ function hasPixelAt(x) {
+ let i = (x | 0) * 4;
+ return data[i] && data[i + 1] && data[i + 2] && data[i + 3];
+ }
+
+ function isPixelBrighterAtThan(x1, x2) {
+ let i = (x1 | 0) * 4;
+ let j = (x2 | 0) * 4;
+ return data[i + 3] > data [j + 3];
+ }
+
+ function isPixelEquallyBright(x1, x2) {
+ let i = (x1 | 0) * 4;
+ let j = (x2 | 0) * 4;
+ return data[i + 3] == data [j + 3];
+ }
+
+ return teardown(monitor);
+});
diff --git a/devtools/client/netmonitor/test/browser_net_timing-division.js b/devtools/client/netmonitor/test/browser_net_timing-division.js
new file mode 100644
index 000000000..0114ba235
--- /dev/null
+++ b/devtools/client/netmonitor/test/browser_net_timing-division.js
@@ -0,0 +1,61 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+/**
+ * Tests if timing intervals are divided againts seconds when appropriate.
+ */
+
+add_task(function* () {
+ let { tab, monitor } = yield initNetMonitor(CUSTOM_GET_URL);
+ info("Starting test... ");
+
+ let { $all, NetMonitorView } = monitor.panelWin;
+ let { RequestsMenu } = NetMonitorView;
+
+ RequestsMenu.lazyUpdate = false;
+
+ let wait = waitForNetworkEvents(monitor, 2);
+ // Timeout needed for having enough divisions on the time scale.
+ yield ContentTask.spawn(tab.linkedBrowser, {}, function* () {
+ content.wrappedJSObject.performRequests(2, null, 3000);
+ });
+ yield wait;
+
+ let milDivs = $all(".requests-menu-timings-division[division-scale=millisecond]");
+ let secDivs = $all(".requests-menu-timings-division[division-scale=second]");
+ let minDivs = $all(".requests-menu-timings-division[division-scale=minute]");
+
+ info("Number of millisecond divisions: " + milDivs.length);
+ info("Number of second divisions: " + secDivs.length);
+ info("Number of minute divisions: " + minDivs.length);
+
+ for (let div of milDivs) {
+ info("Millisecond division: " + div.getAttribute("value"));
+ }
+ for (let div of secDivs) {
+ info("Second division: " + div.getAttribute("value"));
+ }
+ for (let div of minDivs) {
+ info("Minute division: " + div.getAttribute("value"));
+ }
+
+ is(RequestsMenu.itemCount, 2,
+ "There should be only two requests made.");
+
+ let firstRequest = RequestsMenu.getItemAtIndex(0);
+ let lastRequest = RequestsMenu.getItemAtIndex(1);
+
+ info("First request happened at: " +
+ firstRequest.attachment.responseHeaders.headers.find(e => e.name == "Date").value);
+ info("Last request happened at: " +
+ lastRequest.attachment.responseHeaders.headers.find(e => e.name == "Date").value);
+
+ ok(secDivs.length,
+ "There should be at least one division on the seconds time scale.");
+ ok(secDivs[0].getAttribute("value").match(/\d+\.\d{2}\s\w+/),
+ "The division on the seconds time scale looks legit.");
+
+ return teardown(monitor);
+});
diff --git a/devtools/client/netmonitor/test/browser_net_truncate.js b/devtools/client/netmonitor/test/browser_net_truncate.js
new file mode 100644
index 000000000..bfb5c896d
--- /dev/null
+++ b/devtools/client/netmonitor/test/browser_net_truncate.js
@@ -0,0 +1,44 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+/**
+ * Verifies that truncated response bodies still have the correct reported size.
+ */
+
+function test() {
+ let { L10N } = require("devtools/client/netmonitor/l10n");
+ const { RESPONSE_BODY_LIMIT } = require("devtools/shared/webconsole/network-monitor");
+
+ const URL = EXAMPLE_URL + "sjs_truncate-test-server.sjs?limit=" + RESPONSE_BODY_LIMIT;
+
+ // Another slow test on Linux debug.
+ requestLongerTimeout(2);
+
+ initNetMonitor(URL).then(({ tab, monitor }) => {
+ info("Starting test... ");
+
+ let { NetMonitorView } = monitor.panelWin;
+ let { RequestsMenu } = NetMonitorView;
+
+ RequestsMenu.lazyUpdate = false;
+
+ waitForNetworkEvents(monitor, 1)
+ .then(() => teardown(monitor))
+ .then(finish);
+
+ monitor.panelWin.once(monitor.panelWin.EVENTS.RECEIVED_RESPONSE_CONTENT, () => {
+ let requestItem = RequestsMenu.getItemAtIndex(0);
+
+ verifyRequestItemTarget(RequestsMenu, requestItem, "GET", URL, {
+ type: "plain",
+ fullMimeType: "text/plain; charset=utf-8",
+ transferred: L10N.getFormatStrWithNumbers("networkMenu.sizeMB", 2),
+ size: L10N.getFormatStrWithNumbers("networkMenu.sizeMB", 2),
+ });
+ });
+
+ tab.linkedBrowser.reload();
+ });
+}
diff --git a/devtools/client/netmonitor/test/dropmarker.svg b/devtools/client/netmonitor/test/dropmarker.svg
new file mode 100644
index 000000000..3e2987682
--- /dev/null
+++ b/devtools/client/netmonitor/test/dropmarker.svg
@@ -0,0 +1,6 @@
+<!-- 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/. -->
+<svg xmlns="http://www.w3.org/2000/svg" width="8" height="4" viewBox="0 0 8 4">
+ <polygon points="0,0 4,4 8,0" fill="#b6babf"/>
+</svg>
diff --git a/devtools/client/netmonitor/test/head.js b/devtools/client/netmonitor/test/head.js
new file mode 100644
index 000000000..d733cc1d4
--- /dev/null
+++ b/devtools/client/netmonitor/test/head.js
@@ -0,0 +1,518 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+"use strict";
+
+/* import-globals-from ../../framework/test/shared-head.js */
+
+// shared-head.js handles imports, constants, and utility functions
+Services.scriptloader.loadSubScript(
+ "chrome://mochitests/content/browser/devtools/client/framework/test/shared-head.js",
+ this);
+
+var NetworkHelper = require("devtools/shared/webconsole/network-helper");
+var { Toolbox } = require("devtools/client/framework/toolbox");
+
+const EXAMPLE_URL = "http://example.com/browser/devtools/client/netmonitor/test/";
+const HTTPS_EXAMPLE_URL = "https://example.com/browser/devtools/client/netmonitor/test/";
+
+const API_CALLS_URL = EXAMPLE_URL + "html_api-calls-test-page.html";
+const SIMPLE_URL = EXAMPLE_URL + "html_simple-test-page.html";
+const NAVIGATE_URL = EXAMPLE_URL + "html_navigate-test-page.html";
+const CONTENT_TYPE_URL = EXAMPLE_URL + "html_content-type-test-page.html";
+const CONTENT_TYPE_WITHOUT_CACHE_URL = EXAMPLE_URL + "html_content-type-without-cache-test-page.html";
+const CONTENT_TYPE_WITHOUT_CACHE_REQUESTS = 8;
+const CYRILLIC_URL = EXAMPLE_URL + "html_cyrillic-test-page.html";
+const STATUS_CODES_URL = EXAMPLE_URL + "html_status-codes-test-page.html";
+const POST_DATA_URL = EXAMPLE_URL + "html_post-data-test-page.html";
+const POST_JSON_URL = EXAMPLE_URL + "html_post-json-test-page.html";
+const POST_RAW_URL = EXAMPLE_URL + "html_post-raw-test-page.html";
+const POST_RAW_WITH_HEADERS_URL = EXAMPLE_URL + "html_post-raw-with-headers-test-page.html";
+const PARAMS_URL = EXAMPLE_URL + "html_params-test-page.html";
+const JSONP_URL = EXAMPLE_URL + "html_jsonp-test-page.html";
+const JSON_LONG_URL = EXAMPLE_URL + "html_json-long-test-page.html";
+const JSON_MALFORMED_URL = EXAMPLE_URL + "html_json-malformed-test-page.html";
+const JSON_CUSTOM_MIME_URL = EXAMPLE_URL + "html_json-custom-mime-test-page.html";
+const JSON_TEXT_MIME_URL = EXAMPLE_URL + "html_json-text-mime-test-page.html";
+const SORTING_URL = EXAMPLE_URL + "html_sorting-test-page.html";
+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 SIMPLE_SJS = EXAMPLE_URL + "sjs_simple-test-server.sjs";
+const CONTENT_TYPE_SJS = EXAMPLE_URL + "sjs_content-type-test-server.sjs";
+const HTTPS_CONTENT_TYPE_SJS = HTTPS_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";
+
+// All tests are asynchronous.
+waitForExplicitFinish();
+
+const gEnableLogging = Services.prefs.getBoolPref("devtools.debugger.log");
+// To enable logging for try runs, just set the pref to true.
+Services.prefs.setBoolPref("devtools.debugger.log", false);
+
+// Uncomment this pref to dump all devtools emitted events to the console.
+// Services.prefs.setBoolPref("devtools.dump.emit", true);
+
+// Always reset some prefs to their original values after the test finishes.
+const gDefaultFilters = Services.prefs.getCharPref("devtools.netmonitor.filters");
+
+registerCleanupFunction(() => {
+ info("finish() was called, cleaning up...");
+
+ Services.prefs.setBoolPref("devtools.debugger.log", gEnableLogging);
+ Services.prefs.setCharPref("devtools.netmonitor.filters", gDefaultFilters);
+ Services.prefs.clearUserPref("devtools.cache.disabled");
+});
+
+function waitForNavigation(aTarget) {
+ let deferred = promise.defer();
+ aTarget.once("will-navigate", () => {
+ aTarget.once("navigate", () => {
+ deferred.resolve();
+ });
+ });
+ return deferred.promise;
+}
+
+function reconfigureTab(aTarget, aOptions) {
+ let deferred = promise.defer();
+ aTarget.activeTab.reconfigure(aOptions, deferred.resolve);
+ return deferred.promise;
+}
+
+function toggleCache(aTarget, aDisabled) {
+ let options = { cacheDisabled: aDisabled, performReload: true };
+ let navigationFinished = waitForNavigation(aTarget);
+
+ // Disable the cache for any toolbox that it is opened from this point on.
+ Services.prefs.setBoolPref("devtools.cache.disabled", aDisabled);
+
+ return reconfigureTab(aTarget, options).then(() => navigationFinished);
+}
+
+function initNetMonitor(aUrl, aWindow, aEnableCache) {
+ info("Initializing a network monitor pane.");
+
+ return Task.spawn(function* () {
+ let tab = yield addTab(aUrl);
+ info("Net tab added successfully: " + aUrl);
+
+ let target = TargetFactory.forTab(tab);
+
+ yield target.makeRemote();
+ info("Target remoted.");
+
+ if (!aEnableCache) {
+ info("Disabling cache and reloading page.");
+ yield toggleCache(target, true);
+ info("Cache disabled when the current and all future toolboxes are open.");
+ // Remove any requests generated by the reload while toggling the cache to
+ // avoid interfering with the test.
+ isnot([...target.activeConsole.getNetworkEvents()].length, 0,
+ "Request to reconfigure the tab was recorded.");
+ target.activeConsole.clearNetworkRequests();
+ }
+
+ let toolbox = yield gDevTools.showToolbox(target, "netmonitor");
+ info("Network monitor pane shown successfully.");
+
+ let monitor = toolbox.getCurrentPanel();
+ return {tab, monitor};
+ });
+}
+
+function restartNetMonitor(monitor, newUrl) {
+ info("Restarting the specified network monitor.");
+
+ return Task.spawn(function* () {
+ let tab = monitor.target.tab;
+ let url = newUrl || tab.linkedBrowser.currentURI.spec;
+
+ let onDestroyed = monitor.once("destroyed");
+ yield removeTab(tab);
+ yield onDestroyed;
+
+ return initNetMonitor(url);
+ });
+}
+
+function teardown(monitor) {
+ info("Destroying the specified network monitor.");
+
+ return Task.spawn(function* () {
+ let tab = monitor.target.tab;
+
+ let onDestroyed = monitor.once("destroyed");
+ yield removeTab(tab);
+ yield onDestroyed;
+ });
+}
+
+function waitForNetworkEvents(aMonitor, aGetRequests, aPostRequests = 0) {
+ let deferred = promise.defer();
+
+ let panel = aMonitor.panelWin;
+ let events = panel.EVENTS;
+
+ let progress = {};
+ let genericEvents = 0;
+ let postEvents = 0;
+
+ let awaitedEventsToListeners = [
+ ["UPDATING_REQUEST_HEADERS", onGenericEvent],
+ ["RECEIVED_REQUEST_HEADERS", onGenericEvent],
+ ["UPDATING_REQUEST_COOKIES", onGenericEvent],
+ ["RECEIVED_REQUEST_COOKIES", onGenericEvent],
+ ["UPDATING_REQUEST_POST_DATA", onPostEvent],
+ ["RECEIVED_REQUEST_POST_DATA", onPostEvent],
+ ["UPDATING_RESPONSE_HEADERS", onGenericEvent],
+ ["RECEIVED_RESPONSE_HEADERS", onGenericEvent],
+ ["UPDATING_RESPONSE_COOKIES", onGenericEvent],
+ ["RECEIVED_RESPONSE_COOKIES", onGenericEvent],
+ ["STARTED_RECEIVING_RESPONSE", onGenericEvent],
+ ["UPDATING_RESPONSE_CONTENT", onGenericEvent],
+ ["RECEIVED_RESPONSE_CONTENT", onGenericEvent],
+ ["UPDATING_EVENT_TIMINGS", onGenericEvent],
+ ["RECEIVED_EVENT_TIMINGS", onGenericEvent]
+ ];
+
+ function initProgressForURL(url) {
+ if (progress[url]) return;
+ progress[url] = {};
+ awaitedEventsToListeners.forEach(([e]) => progress[url][e] = 0);
+ }
+
+ function updateProgressForURL(url, event) {
+ initProgressForURL(url);
+ progress[url][Object.keys(events).find(e => events[e] == event)] = 1;
+ }
+
+ function onGenericEvent(event, actor) {
+ genericEvents++;
+ maybeResolve(event, actor);
+ }
+
+ function onPostEvent(event, actor) {
+ postEvents++;
+ maybeResolve(event, actor);
+ }
+
+ function maybeResolve(event, actor) {
+ info("> Network events progress: " +
+ genericEvents + "/" + ((aGetRequests + aPostRequests) * 13) + ", " +
+ postEvents + "/" + (aPostRequests * 2) + ", " +
+ "got " + event + " for " + actor);
+
+ let networkInfo =
+ panel.NetMonitorController.webConsoleClient.getNetworkRequest(actor);
+ let url = networkInfo.request.url;
+ updateProgressForURL(url, event);
+
+ // Uncomment this to get a detailed progress logging (when debugging a test)
+ // info("> Current state: " + JSON.stringify(progress, null, 2));
+
+ // There are 15 updates which need to be fired for a request to be
+ // considered finished. The "requestPostData" packet isn't fired for
+ // non-POST requests.
+ if (genericEvents >= (aGetRequests + aPostRequests) * 13 &&
+ postEvents >= aPostRequests * 2) {
+
+ awaitedEventsToListeners.forEach(([e, l]) => panel.off(events[e], l));
+ executeSoon(deferred.resolve);
+ }
+ }
+
+ awaitedEventsToListeners.forEach(([e, l]) => panel.on(events[e], l));
+ return deferred.promise;
+}
+
+function verifyRequestItemTarget(aRequestItem, aMethod, aUrl, aData = {}) {
+ info("> Verifying: " + aMethod + " " + aUrl + " " + aData.toSource());
+ // This bloats log sizes significantly in automation (bug 992485)
+ // info("> Request: " + aRequestItem.attachment.toSource());
+
+ 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, 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;
+ let remoteAddress = attachment.remoteAddress;
+
+ if (fuzzyUrl) {
+ ok(attachment.method.startsWith(aMethod), "The attached method is correct.");
+ ok(attachment.url.startsWith(aUrl), "The attached url is correct.");
+ } else {
+ is(attachment.method, aMethod, "The attached method is correct.");
+ is(attachment.url, aUrl, "The attached url is correct.");
+ }
+
+ is(target.querySelector(".requests-menu-method").getAttribute("value"),
+ aMethod, "The displayed method is correct.");
+
+ if (fuzzyUrl) {
+ ok(target.querySelector(".requests-menu-file").getAttribute("value").startsWith(
+ name + (query ? "?" + query : "")), "The displayed file is correct.");
+ ok(target.querySelector(".requests-menu-file").getAttribute("tooltiptext").startsWith(unicodeUrl),
+ "The tooltip file is correct.");
+ } else {
+ is(target.querySelector(".requests-menu-file").getAttribute("value"),
+ name + (query ? "?" + query : ""), "The displayed file is correct.");
+ is(target.querySelector(".requests-menu-file").getAttribute("tooltiptext"),
+ unicodeUrl, "The tooltip file is correct.");
+ }
+
+ is(target.querySelector(".requests-menu-domain").getAttribute("value"),
+ hostPort, "The displayed domain is correct.");
+
+ let domainTooltip = hostPort + (remoteAddress ? " (" + remoteAddress + ")" : "");
+ is(target.querySelector(".requests-menu-domain").getAttribute("tooltiptext"),
+ domainTooltip, "The tooltip domain is correct.");
+
+ if (status !== undefined) {
+ let value = target.querySelector(".requests-menu-status-icon").getAttribute("code");
+ let codeValue = target.querySelector(".requests-menu-status-code").getAttribute("value");
+ 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 causeLabel = target.querySelector(".requests-menu-cause-label");
+ let value = causeLabel.getAttribute("value");
+ let tooltip = causeLabel.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.");
+ }
+ if (transferred !== undefined) {
+ let value = target.querySelector(".requests-menu-transferred").getAttribute("value");
+ let tooltip = target.querySelector(".requests-menu-transferred").getAttribute("tooltiptext");
+ info("Displayed transferred size: " + value);
+ info("Tooltip transferred size: " + tooltip);
+ is(value, transferred, "The displayed transferred size is correct.");
+ is(tooltip, transferred, "The tooltip transferred size is correct.");
+ }
+ if (size !== undefined) {
+ let value = target.querySelector(".requests-menu-size").getAttribute("value");
+ let tooltip = target.querySelector(".requests-menu-size").getAttribute("tooltiptext");
+ info("Displayed size: " + value);
+ info("Tooltip size: " + tooltip);
+ is(value, size, "The displayed size is correct.");
+ is(tooltip, size, "The tooltip size is correct.");
+ }
+ if (time !== undefined) {
+ let value = target.querySelector(".requests-menu-timings-total").getAttribute("value");
+ let tooltip = target.querySelector(".requests-menu-timings-total").getAttribute("tooltiptext");
+ info("Displayed time: " + value);
+ info("Tooltip time: " + tooltip);
+ ok(~~(value.match(/[0-9]+/)) >= 0, "The displayed time is correct.");
+ ok(~~(tooltip.match(/[0-9]+/)) >= 0, "The tooltip time is correct.");
+ }
+
+ if (visibleIndex != -1) {
+ if (visibleIndex % 2 == 0) {
+ ok(aRequestItem.target.hasAttribute("even"),
+ aRequestItem.value + " should have 'even' attribute.");
+ ok(!aRequestItem.target.hasAttribute("odd"),
+ aRequestItem.value + " shouldn't have 'odd' attribute.");
+ } else {
+ ok(!aRequestItem.target.hasAttribute("even"),
+ aRequestItem.value + " shouldn't have 'even' attribute.");
+ ok(aRequestItem.target.hasAttribute("odd"),
+ aRequestItem.value + " should have 'odd' attribute.");
+ }
+ }
+}
+
+/**
+ * Helper function for waiting for an event to fire before resolving a promise.
+ * Example: waitFor(aMonitor.panelWin, aMonitor.panelWin.EVENTS.TAB_UPDATED);
+ *
+ * @param object subject
+ * The event emitter object that is being listened to.
+ * @param string eventName
+ * The name of the event to listen to.
+ * @return object
+ * Returns a promise that resolves upon firing of the event.
+ */
+function waitFor(subject, eventName) {
+ let deferred = promise.defer();
+ subject.once(eventName, deferred.resolve);
+ return deferred.promise;
+}
+
+/**
+ * Tests if a button for a filter of given type is the only one checked.
+ *
+ * @param string filterType
+ * The type of the filter that should be the only one checked.
+ */
+function testFilterButtons(monitor, filterType) {
+ let doc = monitor.panelWin.document;
+ let target = doc.querySelector("#requests-menu-filter-" + filterType + "-button");
+ ok(target, `Filter button '${filterType}' was found`);
+ let buttons = [...doc.querySelectorAll(".menu-filter-button")];
+ ok(buttons.length > 0, "More than zero filter buttons were found");
+
+ // Only target should be checked.
+ let checkStatus = buttons.map(button => button == target ? 1 : 0);
+ testFilterButtonsCustom(monitor, checkStatus);
+}
+
+/**
+ * Tests if filter buttons have 'checked' attributes set correctly.
+ *
+ * @param array aIsChecked
+ * An array specifying if a button at given index should have a
+ * 'checked' attribute. For example, if the third item of the array
+ * evaluates to true, the third button should be checked.
+ */
+function testFilterButtonsCustom(aMonitor, aIsChecked) {
+ let doc = aMonitor.panelWin.document;
+ let buttons = doc.querySelectorAll(".menu-filter-button");
+ for (let i = 0; i < aIsChecked.length; i++) {
+ let button = buttons[i];
+ if (aIsChecked[i]) {
+ is(button.classList.contains("checked"), true,
+ "The " + button.id + " button should have a 'checked' class.");
+ } else {
+ is(button.classList.contains("checked"), false,
+ "The " + button.id + " button should not have a 'checked' class.");
+ }
+ }
+}
+
+/**
+ * Loads shared/frame-script-utils.js in the specified tab.
+ *
+ * @param tab
+ * Optional tab to load the frame script in. Defaults to the current tab.
+ */
+function loadCommonFrameScript(tab) {
+ let browser = tab ? tab.linkedBrowser : gBrowser.selectedBrowser;
+
+ browser.messageManager.loadFrameScript(FRAME_SCRIPT_UTILS_URL, false);
+}
+
+/**
+ * Perform the specified requests in the context of the page content.
+ *
+ * @param Array requests
+ * An array of objects specifying the requests to perform. See
+ * shared/frame-script-utils.js for more information.
+ *
+ * @return A promise that resolves once the requests complete.
+ */
+function performRequestsInContent(requests) {
+ info("Performing requests in the context of the content.");
+ return executeInContent("devtools:test:xhr", requests);
+}
+
+/**
+ * Send an async message to the frame script (chrome -> content) and wait for a
+ * response message with the same name (content -> chrome).
+ *
+ * @param String name
+ * The message name. Should be one of the messages defined
+ * shared/frame-script-utils.js
+ * @param Object data
+ * Optional data to send along
+ * @param Object objects
+ * Optional CPOW objects to send along
+ * @param Boolean expectResponse
+ * If set to false, don't wait for a response with the same name from the
+ * content script. Defaults to true.
+ *
+ * @return Promise
+ * Resolves to the response data if a response is expected, immediately
+ * resolves otherwise
+ */
+function executeInContent(name, data = {}, objects = {}, expectResponse = true) {
+ let mm = gBrowser.selectedBrowser.messageManager;
+
+ mm.sendAsyncMessage(name, data, objects);
+ if (expectResponse) {
+ return waitForContentMessage(name);
+ } else {
+ return promise.resolve();
+ }
+}
+
+/**
+ * Wait for a content -> chrome message on the message manager (the window
+ * messagemanager is used).
+ * @param {String} name The message name
+ * @return {Promise} A promise that resolves to the response data when the
+ * message has been received
+ */
+function waitForContentMessage(name) {
+ let mm = gBrowser.selectedBrowser.messageManager;
+
+ let def = promise.defer();
+ mm.addMessageListener(name, function onMessage(msg) {
+ mm.removeMessageListener(name, onMessage);
+ def.resolve(msg);
+ });
+ return def.promise;
+}
+
+/**
+ * Open the requestMenu menu and return all of it's items in a flat array
+ * @param {netmonitorPanel} netmonitor
+ * @param {Event} event mouse event with screenX and screenX coordinates
+ * @return An array of MenuItems
+ */
+function openContextMenuAndGetAllItems(netmonitor, event) {
+ let menu = netmonitor.RequestsMenu.contextMenu.open(event);
+
+ // Flatten all menu items into a single array to make searching through it easier
+ let allItems = [].concat.apply([], menu.items.map(function addItem(item) {
+ if (item.submenu) {
+ return addItem(item.submenu.items);
+ }
+ return item;
+ }));
+
+ return allItems;
+}
diff --git a/devtools/client/netmonitor/test/html_api-calls-test-page.html b/devtools/client/netmonitor/test/html_api-calls-test-page.html
new file mode 100644
index 000000000..e31872319
--- /dev/null
+++ b/devtools/client/netmonitor/test/html_api-calls-test-page.html
@@ -0,0 +1,46 @@
+<!-- Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ -->
+<!doctype html>
+
+<html>
+ <head>
+ <meta charset="utf-8"/>
+ <meta http-equiv="Cache-Control" content="no-cache, no-store, must-revalidate" />
+ <meta http-equiv="Pragma" content="no-cache" />
+ <meta http-equiv="Expires" content="0" />
+ <title>Network Monitor test page</title>
+ </head>
+
+ <body>
+ <p>API calls request test</p>
+
+ <script type="text/javascript">
+ function get(aAddress, aCallback) {
+ var xhr = new XMLHttpRequest();
+ xhr.open("GET", aAddress, true);
+
+ xhr.onreadystatechange = function() {
+ if (this.readyState == this.DONE) {
+ aCallback();
+ }
+ };
+ xhr.send();
+ }
+
+ function performRequests() {
+ get("/api/fileName.xml", function() {
+ get("/api/file%E2%98%A2.xml", function() {
+ get("/api/ascii/get/", function() {
+ get("/api/unicode/%E2%98%A2/", function() {
+ get("/api/search/?q=search%E2%98%A2", function() {
+ // Done.
+ });
+ });
+ });
+ });
+ });
+ }
+ </script>
+ </body>
+
+</html>
diff --git a/devtools/client/netmonitor/test/html_brotli-test-page.html b/devtools/client/netmonitor/test/html_brotli-test-page.html
new file mode 100644
index 000000000..d5afae4b3
--- /dev/null
+++ b/devtools/client/netmonitor/test/html_brotli-test-page.html
@@ -0,0 +1,38 @@
+<!-- Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ -->
+<!doctype html>
+
+<html>
+ <head>
+ <meta charset="utf-8"/>
+ <meta http-equiv="Cache-Control" content="no-cache, no-store, must-revalidate" />
+ <meta http-equiv="Pragma" content="no-cache" />
+ <meta http-equiv="Expires" content="0" />
+ <title>Network Monitor test page</title>
+ </head>
+
+ <body>
+ <p>Brotli test</p>
+
+ <script type="text/javascript">
+ function get(aAddress, aCallback) {
+ var xhr = new XMLHttpRequest();
+ xhr.open("GET", aAddress, true);
+
+ xhr.onreadystatechange = function() {
+ if (this.readyState == this.DONE) {
+ aCallback();
+ }
+ };
+ xhr.send(null);
+ }
+
+ function performRequests() {
+ get("sjs_content-type-test-server.sjs?fmt=br", function() {
+ // Done.
+ });
+ }
+ </script>
+ </body>
+
+</html>
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 000000000..d2b86682b
--- /dev/null
+++ b/devtools/client/netmonitor/test/html_cause-test-page.html
@@ -0,0 +1,48 @@
+<!-- Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ -->
+<!doctype html>
+
+<html>
+ <head>
+ <meta charset="utf-8"/>
+ <meta http-equiv="Cache-Control" content="no-cache, no-store, must-revalidate" />
+ <meta http-equiv="Pragma" content="no-cache" />
+ <meta http-equiv="Expires" content="0" />
+ <title>Network Monitor test page</title>
+ <link rel="stylesheet" type="text/css" href="stylesheet_request" />
+ </head>
+
+ <body>
+ <p>Request cause test</p>
+ <img src="img_request" />
+ <script type="text/javascript">
+ function performXhrRequest() {
+ var xhr = new XMLHttpRequest();
+ xhr.open("GET", "xhr_request", true);
+ xhr.send();
+ }
+
+ function performFetchRequest() {
+ fetch("fetch_request");
+ }
+
+ function performBeaconRequest() {
+ navigator.sendBeacon("beacon_request");
+ }
+
+ performXhrRequest();
+ performFetchRequest();
+
+ // Perform some requests with async stacks
+ Promise.resolve().then(function performPromiseFetchRequest() {
+ fetch("promise_fetch_request");
+ setTimeout(function performTimeoutFetchRequest() {
+ fetch("timeout_fetch_request");
+
+ // Finally, send a beacon request
+ performBeaconRequest();
+ }, 0);
+ });
+ </script>
+ </body>
+</html>
diff --git a/devtools/client/netmonitor/test/html_content-type-test-page.html b/devtools/client/netmonitor/test/html_content-type-test-page.html
new file mode 100644
index 000000000..23ecf1f44
--- /dev/null
+++ b/devtools/client/netmonitor/test/html_content-type-test-page.html
@@ -0,0 +1,48 @@
+<!-- Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ -->
+<!doctype html>
+
+<html>
+ <head>
+ <meta charset="utf-8"/>
+ <meta http-equiv="Cache-Control" content="no-cache, no-store, must-revalidate" />
+ <meta http-equiv="Pragma" content="no-cache" />
+ <meta http-equiv="Expires" content="0" />
+ <title>Network Monitor test page</title>
+ </head>
+
+ <body>
+ <p>Content type test</p>
+
+ <script type="text/javascript">
+ function get(aAddress, aCallback) {
+ var xhr = new XMLHttpRequest();
+ xhr.open("GET", aAddress, true);
+
+ xhr.onreadystatechange = function() {
+ if (this.readyState == this.DONE) {
+ aCallback();
+ }
+ };
+ xhr.send(null);
+ }
+
+ function performRequests() {
+ get("sjs_content-type-test-server.sjs?fmt=xml", function() {
+ get("sjs_content-type-test-server.sjs?fmt=css", function() {
+ get("sjs_content-type-test-server.sjs?fmt=js", function() {
+ get("sjs_content-type-test-server.sjs?fmt=json", function() {
+ get("sjs_content-type-test-server.sjs?fmt=bogus", function() {
+ get("test-image.png", function() {
+ // Done.
+ });
+ });
+ });
+ });
+ });
+ });
+ }
+ </script>
+ </body>
+
+</html>
diff --git a/devtools/client/netmonitor/test/html_content-type-without-cache-test-page.html b/devtools/client/netmonitor/test/html_content-type-without-cache-test-page.html
new file mode 100644
index 000000000..f27e6e105
--- /dev/null
+++ b/devtools/client/netmonitor/test/html_content-type-without-cache-test-page.html
@@ -0,0 +1,52 @@
+<!-- Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ -->
+<!doctype html>
+
+<html>
+ <head>
+ <meta charset="utf-8"/>
+ <meta http-equiv="Cache-Control" content="no-cache, no-store, must-revalidate" />
+ <meta http-equiv="Pragma" content="no-cache" />
+ <meta http-equiv="Expires" content="0" />
+ <title>Network Monitor test page</title>
+ </head>
+
+ <body>
+ <p>Content type test</p>
+
+ <script type="text/javascript">
+ function get(aAddress, aCallback) {
+ var xhr = new XMLHttpRequest();
+ xhr.open("GET", aAddress, true);
+
+ xhr.onreadystatechange = function() {
+ if (this.readyState == this.DONE) {
+ aCallback();
+ }
+ };
+ xhr.send(null);
+ }
+
+ function performRequests() {
+ get("sjs_content-type-test-server.sjs?fmt=xml", function() {
+ get("sjs_content-type-test-server.sjs?fmt=css", function() {
+ get("sjs_content-type-test-server.sjs?fmt=js", function() {
+ get("sjs_content-type-test-server.sjs?fmt=json", function() {
+ get("sjs_content-type-test-server.sjs?fmt=bogus", function() {
+ get("test-image.png?v=" + Math.random(), function() {
+ get("sjs_content-type-test-server.sjs?fmt=gzip", function() {
+ get("sjs_content-type-test-server.sjs?fmt=br", function() {
+ // Done.
+ });
+ });
+ });
+ });
+ });
+ });
+ });
+ });
+ }
+ </script>
+ </body>
+
+</html>
diff --git a/devtools/client/netmonitor/test/html_copy-as-curl.html b/devtools/client/netmonitor/test/html_copy-as-curl.html
new file mode 100644
index 000000000..3ddcfbced
--- /dev/null
+++ b/devtools/client/netmonitor/test/html_copy-as-curl.html
@@ -0,0 +1,30 @@
+<!-- Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ -->
+<!doctype html>
+
+<html>
+ <head>
+ <meta charset="utf-8"/>
+ <meta http-equiv="Cache-Control" content="no-cache, no-store, must-revalidate" />
+ <meta http-equiv="Pragma" content="no-cache" />
+ <meta http-equiv="Expires" content="0" />
+ <title>Network Monitor test page</title>
+ </head>
+
+ <body>
+ <p>Performing a GET request</p>
+
+ <script type="text/javascript">
+ function performRequest(aUrl) {
+ var xhr = new XMLHttpRequest();
+ xhr.open("GET", aUrl, true);
+ xhr.setRequestHeader("Accept-Language", window.navigator.language);
+ xhr.setRequestHeader("X-Custom-Header-1", "Custom value");
+ xhr.setRequestHeader("X-Custom-Header-2", "8.8.8.8");
+ xhr.setRequestHeader("X-Custom-Header-3", "Mon, 3 Mar 2014 11:11:11 GMT");
+ xhr.send(null);
+ }
+ </script>
+ </body>
+
+</html>
diff --git a/devtools/client/netmonitor/test/html_cors-test-page.html b/devtools/client/netmonitor/test/html_cors-test-page.html
new file mode 100644
index 000000000..179b2ed00
--- /dev/null
+++ b/devtools/client/netmonitor/test/html_cors-test-page.html
@@ -0,0 +1,31 @@
+<!-- Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ -->
+<!doctype html>
+
+<html>
+ <head>
+ <meta charset="utf-8"/>
+ <meta http-equiv="Cache-Control" content="no-cache, no-store, must-revalidate" />
+ <meta http-equiv="Pragma" content="no-cache" />
+ <meta http-equiv="Expires" content="0" />
+ <title>Network Monitor test page</title>
+ </head>
+
+ <body>
+ <p>POST with CORS test page</p>
+
+ <script type="text/javascript">
+ function post(url, contentType, postData) {
+ var xhr = new XMLHttpRequest();
+ xhr.open("POST", url, true);
+ xhr.setRequestHeader("Content-Type", contentType);
+ xhr.send(postData);
+ }
+
+ function performRequests(url, contentType, postData) {
+ post(url, contentType, postData);
+ }
+ </script>
+ </body>
+
+</html>
diff --git a/devtools/client/netmonitor/test/html_curl-utils.html b/devtools/client/netmonitor/test/html_curl-utils.html
new file mode 100644
index 000000000..8ff7ecdf0
--- /dev/null
+++ b/devtools/client/netmonitor/test/html_curl-utils.html
@@ -0,0 +1,102 @@
+<!-- Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ -->
+<!doctype html>
+
+<html>
+ <head>
+ <meta charset="utf-8"/>
+ <meta http-equiv="Cache-Control" content="no-cache, no-store, must-revalidate" />
+ <meta http-equiv="Pragma" content="no-cache" />
+ <meta http-equiv="Expires" content="0" />
+ <title>Network Monitor test page</title>
+ </head>
+
+ <body>
+ <p>Performing requests</p>
+
+ <p>
+ <canvas width="100" height="100"></canvas>
+ </p>
+
+ <hr/>
+
+ <form method="post" action="#" enctype="multipart/form-data" target="target" id="post-form">
+ <input type="text" name="param1" value="value1"/>
+ <input type="text" name="param2" value="value2"/>
+ <input type="text" name="param3" value="value3"/>
+ <input type="submit"/>
+ </form>
+ <iframe name="target"></iframe>
+
+ <script type="text/javascript">
+
+ function ajaxGet(aUrl, aCallback) {
+ var xhr = new XMLHttpRequest();
+ xhr.open("GET", aUrl + "?param1=value1&param2=value2&param3=value3", true);
+ xhr.setRequestHeader("X-Requested-With", "XMLHttpRequest");
+ xhr.onload = function() {
+ aCallback();
+ };
+ xhr.send();
+ }
+
+ function ajaxPost(aUrl, aCallback) {
+ var xhr = new XMLHttpRequest();
+ xhr.open("POST", aUrl, true);
+ xhr.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");
+ xhr.setRequestHeader("X-Requested-With", "XMLHttpRequest");
+ xhr.onload = function() {
+ aCallback();
+ };
+ var params = "param1=value1&param2=value2&param3=value3";
+ xhr.send(params);
+ }
+
+ function ajaxMultipart(aUrl, aCallback) {
+ var xhr = new XMLHttpRequest();
+ xhr.open("POST", aUrl, true);
+ xhr.setRequestHeader("X-Requested-With", "XMLHttpRequest");
+ xhr.onload = function() {
+ aCallback();
+ };
+
+ getCanvasElem().toBlob((blob) => {
+ var formData = new FormData();
+ formData.append("param1", "value1");
+ formData.append("file", blob, "filename.png");
+ xhr.send(formData);
+ });
+ }
+
+ function submitForm() {
+ var form = document.querySelector("#post-form");
+ form.submit();
+ }
+
+ function getCanvasElem() {
+ return document.querySelector("canvas");
+ }
+
+ function initCanvas() {
+ var canvas = getCanvasElem();
+ var ctx = canvas.getContext("2d");
+ ctx.fillRect(0,0,100,100);
+ ctx.clearRect(20,20,60,60);
+ ctx.strokeRect(25,25,50,50);
+ }
+
+ function performRequests(aUrl) {
+ ajaxGet(aUrl, () => {
+ ajaxPost(aUrl, () => {
+ ajaxMultipart(aUrl, () => {
+ submitForm();
+ });
+ });
+ });
+ }
+
+ initCanvas();
+ </script>
+ </body>
+
+</html>
diff --git a/devtools/client/netmonitor/test/html_custom-get-page.html b/devtools/client/netmonitor/test/html_custom-get-page.html
new file mode 100644
index 000000000..19e40f93a
--- /dev/null
+++ b/devtools/client/netmonitor/test/html_custom-get-page.html
@@ -0,0 +1,44 @@
+<!-- Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ -->
+<!doctype html>
+
+<html>
+ <head>
+ <meta charset="utf-8"/>
+ <meta http-equiv="Cache-Control" content="no-cache, no-store, must-revalidate" />
+ <meta http-equiv="Pragma" content="no-cache" />
+ <meta http-equiv="Expires" content="0" />
+ <title>Network Monitor test page</title>
+ </head>
+
+ <body>
+ <p>Performing a custom number of GETs</p>
+
+ <script type="text/javascript">
+ function get(aAddress, aCallback) {
+ var xhr = new XMLHttpRequest();
+ xhr.open("GET", aAddress, true);
+
+ xhr.onreadystatechange = function() {
+ if (this.readyState == this.DONE) {
+ aCallback();
+ }
+ };
+ xhr.send(null);
+ }
+
+ // Use a count parameter to defeat caching.
+ var count = 0;
+
+ function performRequests(aTotal, aUrl, aTimeout = 0) {
+ if (!aTotal) {
+ return;
+ }
+ get(aUrl || "request_" + (count++), function() {
+ setTimeout(performRequests.bind(this, --aTotal, aUrl, aTimeout), aTimeout);
+ });
+ }
+ </script>
+ </body>
+
+</html>
diff --git a/devtools/client/netmonitor/test/html_cyrillic-test-page.html b/devtools/client/netmonitor/test/html_cyrillic-test-page.html
new file mode 100644
index 000000000..8735ac674
--- /dev/null
+++ b/devtools/client/netmonitor/test/html_cyrillic-test-page.html
@@ -0,0 +1,39 @@
+<!-- Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ -->
+<!doctype html>
+
+<html>
+ <head>
+ <meta charset="utf-8"/>
+ <meta http-equiv="Cache-Control" content="no-cache, no-store, must-revalidate" />
+ <meta http-equiv="Pragma" content="no-cache" />
+ <meta http-equiv="Expires" content="0" />
+ <title>Network Monitor test page</title>
+ </head>
+
+ <body>
+ <p>Cyrillic type test</p>
+ <p>Братан, ты вообще качаешься?</p>
+
+ <script type="text/javascript">
+ function get(aAddress, aCallback) {
+ var xhr = new XMLHttpRequest();
+ xhr.open("GET", aAddress, true);
+
+ xhr.onreadystatechange = function() {
+ if (this.readyState == this.DONE) {
+ aCallback();
+ }
+ };
+ xhr.send(null);
+ }
+
+ function performRequests() {
+ get("sjs_content-type-test-server.sjs?fmt=txt", function() {
+ // Done.
+ });
+ }
+ </script>
+ </body>
+
+</html>
diff --git a/devtools/client/netmonitor/test/html_filter-test-page.html b/devtools/client/netmonitor/test/html_filter-test-page.html
new file mode 100644
index 000000000..eb5d02ed9
--- /dev/null
+++ b/devtools/client/netmonitor/test/html_filter-test-page.html
@@ -0,0 +1,60 @@
+<!-- Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ -->
+<!doctype html>
+
+<html>
+ <head>
+ <meta charset="utf-8"/>
+ <meta http-equiv="Cache-Control" content="no-cache, no-store, must-revalidate" />
+ <meta http-equiv="Pragma" content="no-cache" />
+ <meta http-equiv="Expires" content="0" />
+ <title>Network Monitor test page</title>
+ </head>
+
+ <body>
+ <p>Filtering test</p>
+
+ <script type="text/javascript">
+ function get(aAddress, aCallback) {
+ var xhr = new XMLHttpRequest();
+ // Use a random parameter to defeat caching.
+ xhr.open("GET", aAddress + "&" + Math.random(), true);
+
+ xhr.onreadystatechange = function() {
+ if (this.readyState == this.DONE) {
+ aCallback();
+ }
+ };
+ xhr.send(null);
+ }
+
+ function performRequests(aOptions) {
+ var options = JSON.parse(aOptions);
+ get("sjs_content-type-test-server.sjs?fmt=html&res=" + options.htmlContent, function() {
+ get("sjs_content-type-test-server.sjs?fmt=css", function() {
+ get("sjs_content-type-test-server.sjs?fmt=js", function() {
+ if (!options.getMedia) {
+ return;
+ }
+ get("sjs_content-type-test-server.sjs?fmt=font", function() {
+ get("sjs_content-type-test-server.sjs?fmt=image", function() {
+ get("sjs_content-type-test-server.sjs?fmt=audio", function() {
+ get("sjs_content-type-test-server.sjs?fmt=video", function() {
+ if (!options.getFlash) {
+ return;
+ }
+ get("sjs_content-type-test-server.sjs?fmt=flash", function() {
+ // Done.
+ });
+ });
+ });
+ });
+ });
+ });
+ });
+ });
+ }
+ </script>
+ </body>
+
+</html>
diff --git a/devtools/client/netmonitor/test/html_frame-subdocument.html b/devtools/client/netmonitor/test/html_frame-subdocument.html
new file mode 100644
index 000000000..9e800582c
--- /dev/null
+++ b/devtools/client/netmonitor/test/html_frame-subdocument.html
@@ -0,0 +1,48 @@
+<!-- Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ -->
+<!doctype html>
+
+<html>
+ <head>
+ <meta charset="utf-8"/>
+ <meta http-equiv="Cache-Control" content="no-cache, no-store, must-revalidate" />
+ <meta http-equiv="Pragma" content="no-cache" />
+ <meta http-equiv="Expires" content="0" />
+ <title>Network Monitor test page</title>
+ <link rel="stylesheet" type="text/css" href="stylesheet_request" />
+ </head>
+
+ <body>
+ <p>Request frame test</p>
+ <img src="img_request" />
+ <script type="text/javascript">
+ function performXhrRequest() {
+ var xhr = new XMLHttpRequest();
+ xhr.open("GET", "xhr_request", true);
+ xhr.send();
+ }
+
+ function performFetchRequest() {
+ fetch("fetch_request");
+ }
+
+ function performBeaconRequest() {
+ navigator.sendBeacon("beacon_request");
+ }
+
+ performXhrRequest();
+ performFetchRequest();
+
+ // Perform some requests with async stacks
+ Promise.resolve().then(function performPromiseFetchRequest() {
+ fetch("promise_fetch_request");
+ setTimeout(function performTimeoutFetchRequest() {
+ fetch("timeout_fetch_request");
+
+ // Finally, send a beacon request
+ performBeaconRequest();
+ }, 0);
+ });
+ </script>
+ </body>
+</html>
diff --git a/devtools/client/netmonitor/test/html_frame-test-page.html b/devtools/client/netmonitor/test/html_frame-test-page.html
new file mode 100644
index 000000000..66f6620af
--- /dev/null
+++ b/devtools/client/netmonitor/test/html_frame-test-page.html
@@ -0,0 +1,49 @@
+<!-- Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ -->
+<!doctype html>
+
+<html>
+ <head>
+ <meta charset="utf-8"/>
+ <meta http-equiv="Cache-Control" content="no-cache, no-store, must-revalidate" />
+ <meta http-equiv="Pragma" content="no-cache" />
+ <meta http-equiv="Expires" content="0" />
+ <title>Network Monitor test page</title>
+ <link rel="stylesheet" type="text/css" href="stylesheet_request" />
+ </head>
+
+ <body>
+ <p>Request frame test</p>
+ <img src="img_request" />
+ <iframe src="html_frame-subdocument.html"></iframe>
+ <script type="text/javascript">
+ function performXhrRequest() {
+ var xhr = new XMLHttpRequest();
+ xhr.open("GET", "xhr_request", true);
+ xhr.send();
+ }
+
+ function performFetchRequest() {
+ fetch("fetch_request");
+ }
+
+ function performBeaconRequest() {
+ navigator.sendBeacon("beacon_request");
+ }
+
+ performXhrRequest();
+ performFetchRequest();
+
+ // Perform some requests with async stacks
+ Promise.resolve().then(function performPromiseFetchRequest() {
+ fetch("promise_fetch_request");
+ setTimeout(function performTimeoutFetchRequest() {
+ fetch("timeout_fetch_request");
+
+ // Finally, send a beacon request
+ performBeaconRequest();
+ }, 0);
+ });
+ </script>
+ </body>
+</html>
diff --git a/devtools/client/netmonitor/test/html_image-tooltip-test-page.html b/devtools/client/netmonitor/test/html_image-tooltip-test-page.html
new file mode 100644
index 000000000..c39db909e
--- /dev/null
+++ b/devtools/client/netmonitor/test/html_image-tooltip-test-page.html
@@ -0,0 +1,26 @@
+<!-- Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ -->
+<!doctype html>
+
+<html>
+ <head>
+ <meta charset="utf-8"/>
+ <meta http-equiv="Cache-Control" content="no-cache, no-store, must-revalidate" />
+ <meta http-equiv="Pragma" content="no-cache" />
+ <meta http-equiv="Expires" content="0" />
+ <title>Network Monitor test page</title>
+ </head>
+
+ <body>
+ <p>tooltip test</p>
+
+ <script type="text/javascript">
+ function performRequests() {
+ var xhr = new XMLHttpRequest();
+ xhr.open("GET", "test-image.png?v=" + Math.random(), true);
+ xhr.send(null);
+ }
+ </script>
+ </body>
+
+</html>
diff --git a/devtools/client/netmonitor/test/html_infinite-get-page.html b/devtools/client/netmonitor/test/html_infinite-get-page.html
new file mode 100644
index 000000000..f51b718ad
--- /dev/null
+++ b/devtools/client/netmonitor/test/html_infinite-get-page.html
@@ -0,0 +1,41 @@
+<!-- Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ -->
+<!doctype html>
+
+<html>
+ <head>
+ <meta charset="utf-8"/>
+ <meta http-equiv="Cache-Control" content="no-cache, no-store, must-revalidate" />
+ <meta http-equiv="Pragma" content="no-cache" />
+ <meta http-equiv="Expires" content="0" />
+ <title>Network Monitor test page</title>
+ </head>
+
+ <body>
+ <p>Infinite GETs</p>
+
+ <script type="text/javascript">
+ function get(aAddress, aCallback) {
+ var xhr = new XMLHttpRequest();
+ xhr.open("GET", aAddress, true);
+
+ xhr.onreadystatechange = function() {
+ if (this.readyState == this.DONE) {
+ aCallback();
+ }
+ };
+ xhr.send(null);
+ }
+
+ // Use a count parameter to defeat caching.
+ var count = 0;
+
+ (function performRequests() {
+ get("request_" + (count++), function() {
+ setTimeout(performRequests, 50);
+ });
+ })();
+ </script>
+ </body>
+
+</html>
diff --git a/devtools/client/netmonitor/test/html_json-custom-mime-test-page.html b/devtools/client/netmonitor/test/html_json-custom-mime-test-page.html
new file mode 100644
index 000000000..646fc60ea
--- /dev/null
+++ b/devtools/client/netmonitor/test/html_json-custom-mime-test-page.html
@@ -0,0 +1,38 @@
+<!-- Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ -->
+<!doctype html>
+
+<html>
+ <head>
+ <meta charset="utf-8"/>
+ <meta http-equiv="Cache-Control" content="no-cache, no-store, must-revalidate" />
+ <meta http-equiv="Pragma" content="no-cache" />
+ <meta http-equiv="Expires" content="0" />
+ <title>Network Monitor test page</title>
+ </head>
+
+ <body>
+ <p>JSONP test</p>
+
+ <script type="text/javascript">
+ function get(aAddress, aCallback) {
+ var xhr = new XMLHttpRequest();
+ xhr.open("GET", aAddress, true);
+
+ xhr.onreadystatechange = function() {
+ if (this.readyState == this.DONE) {
+ aCallback();
+ }
+ };
+ xhr.send(null);
+ }
+
+ function performRequests() {
+ get("sjs_content-type-test-server.sjs?fmt=json-custom-mime", function() {
+ // Done.
+ });
+ }
+ </script>
+ </body>
+
+</html>
diff --git a/devtools/client/netmonitor/test/html_json-long-test-page.html b/devtools/client/netmonitor/test/html_json-long-test-page.html
new file mode 100644
index 000000000..b538b4c27
--- /dev/null
+++ b/devtools/client/netmonitor/test/html_json-long-test-page.html
@@ -0,0 +1,38 @@
+<!-- Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ -->
+<!doctype html>
+
+<html>
+ <head>
+ <meta charset="utf-8"/>
+ <meta http-equiv="Cache-Control" content="no-cache, no-store, must-revalidate" />
+ <meta http-equiv="Pragma" content="no-cache" />
+ <meta http-equiv="Expires" content="0" />
+ <title>Network Monitor test page</title>
+ </head>
+
+ <body>
+ <p>JSON long string test</p>
+
+ <script type="text/javascript">
+ function get(aAddress, aCallback) {
+ var xhr = new XMLHttpRequest();
+ xhr.open("GET", aAddress, true);
+
+ xhr.onreadystatechange = function() {
+ if (this.readyState == this.DONE) {
+ aCallback();
+ }
+ };
+ xhr.send(null);
+ }
+
+ function performRequests() {
+ get("sjs_content-type-test-server.sjs?fmt=json-long", function() {
+ // Done.
+ });
+ }
+ </script>
+ </body>
+
+</html>
diff --git a/devtools/client/netmonitor/test/html_json-malformed-test-page.html b/devtools/client/netmonitor/test/html_json-malformed-test-page.html
new file mode 100644
index 000000000..0c8627ab5
--- /dev/null
+++ b/devtools/client/netmonitor/test/html_json-malformed-test-page.html
@@ -0,0 +1,38 @@
+<!-- Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ -->
+<!doctype html>
+
+<html>
+ <head>
+ <meta charset="utf-8"/>
+ <meta http-equiv="Cache-Control" content="no-cache, no-store, must-revalidate" />
+ <meta http-equiv="Pragma" content="no-cache" />
+ <meta http-equiv="Expires" content="0" />
+ <title>Network Monitor test page</title>
+ </head>
+
+ <body>
+ <p>JSON malformed test</p>
+
+ <script type="text/javascript">
+ function get(aAddress, aCallback) {
+ var xhr = new XMLHttpRequest();
+ xhr.open("GET", aAddress, true);
+
+ xhr.onreadystatechange = function() {
+ if (this.readyState == this.DONE) {
+ aCallback();
+ }
+ };
+ xhr.send(null);
+ }
+
+ function performRequests() {
+ get("sjs_content-type-test-server.sjs?fmt=json-malformed", function() {
+ // Done.
+ });
+ }
+ </script>
+ </body>
+
+</html>
diff --git a/devtools/client/netmonitor/test/html_json-text-mime-test-page.html b/devtools/client/netmonitor/test/html_json-text-mime-test-page.html
new file mode 100644
index 000000000..2c64e2531
--- /dev/null
+++ b/devtools/client/netmonitor/test/html_json-text-mime-test-page.html
@@ -0,0 +1,38 @@
+<!-- Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ -->
+<!doctype html>
+
+<html>
+ <head>
+ <meta charset="utf-8"/>
+ <meta http-equiv="Cache-Control" content="no-cache, no-store, must-revalidate" />
+ <meta http-equiv="Pragma" content="no-cache" />
+ <meta http-equiv="Expires" content="0" />
+ <title>Network Monitor test page</title>
+ </head>
+
+ <body>
+ <p>JSON text test</p>
+
+ <script type="text/javascript">
+ function get(aAddress, aCallback) {
+ var xhr = new XMLHttpRequest();
+ xhr.open("GET", aAddress, true);
+
+ xhr.onreadystatechange = function() {
+ if (this.readyState == this.DONE) {
+ aCallback();
+ }
+ };
+ xhr.send(null);
+ }
+
+ function performRequests() {
+ get("sjs_content-type-test-server.sjs?fmt=json-text-mime", function() {
+ // Done.
+ });
+ }
+ </script>
+ </body>
+
+</html>
diff --git a/devtools/client/netmonitor/test/html_jsonp-test-page.html b/devtools/client/netmonitor/test/html_jsonp-test-page.html
new file mode 100644
index 000000000..78c0da08b
--- /dev/null
+++ b/devtools/client/netmonitor/test/html_jsonp-test-page.html
@@ -0,0 +1,40 @@
+<!-- Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ -->
+<!doctype html>
+
+<html>
+ <head>
+ <meta charset="utf-8"/>
+ <meta http-equiv="Cache-Control" content="no-cache, no-store, must-revalidate" />
+ <meta http-equiv="Pragma" content="no-cache" />
+ <meta http-equiv="Expires" content="0" />
+ <title>Network Monitor test page</title>
+ </head>
+
+ <body>
+ <p>JSONP test</p>
+
+ <script type="text/javascript">
+ function get(aAddress, aCallback) {
+ var xhr = new XMLHttpRequest();
+ xhr.open("GET", aAddress, true);
+
+ xhr.onreadystatechange = function() {
+ if (this.readyState == this.DONE) {
+ aCallback();
+ }
+ };
+ xhr.send(null);
+ }
+
+ function performRequests() {
+ get("sjs_content-type-test-server.sjs?fmt=jsonp&jsonp=$_0123Fun", function() {
+ get("sjs_content-type-test-server.sjs?fmt=jsonp2&jsonp=$_4567Sad", function() {
+ // Done.
+ });
+ });
+ }
+ </script>
+ </body>
+
+</html>
diff --git a/devtools/client/netmonitor/test/html_navigate-test-page.html b/devtools/client/netmonitor/test/html_navigate-test-page.html
new file mode 100644
index 000000000..23f00f3df
--- /dev/null
+++ b/devtools/client/netmonitor/test/html_navigate-test-page.html
@@ -0,0 +1,18 @@
+<!-- Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ -->
+<!doctype html>
+
+<html>
+ <head>
+ <meta charset="utf-8"/>
+ <meta http-equiv="Cache-Control" content="no-cache, no-store, must-revalidate" />
+ <meta http-equiv="Pragma" content="no-cache" />
+ <meta http-equiv="Expires" content="0" />
+ <title>Network Monitor test page</title>
+ </head>
+
+ <body>
+ <p>Navigation test</p>
+ </body>
+
+</html>
diff --git a/devtools/client/netmonitor/test/html_params-test-page.html b/devtools/client/netmonitor/test/html_params-test-page.html
new file mode 100644
index 000000000..3f30e3d76
--- /dev/null
+++ b/devtools/client/netmonitor/test/html_params-test-page.html
@@ -0,0 +1,67 @@
+<!-- Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ -->
+<!doctype html>
+
+<html>
+ <head>
+ <meta charset="utf-8"/>
+ <meta http-equiv="Cache-Control" content="no-cache, no-store, must-revalidate" />
+ <meta http-equiv="Pragma" content="no-cache" />
+ <meta http-equiv="Expires" content="0" />
+ <title>Network Monitor test page</title>
+ </head>
+
+ <body>
+ <p>Request params type test</p>
+
+ <script type="text/javascript">
+ function get(aAddress, aQuery) {
+ var xhr = new XMLHttpRequest();
+ xhr.open("GET", aAddress + aQuery, true);
+ xhr.send();
+ }
+
+ function post(aAddress, aQuery, aContentType, aPostBody) {
+ var xhr = new XMLHttpRequest();
+ xhr.open("POST", aAddress + aQuery, true);
+ xhr.setRequestHeader("content-type", aContentType);
+ xhr.send(aPostBody);
+ }
+
+ function performRequests() {
+ var urlencoded = "application/x-www-form-urlencoded";
+
+ setTimeout(function() {
+ post("baz", "?a", urlencoded, '{ "foo": "bar" }');
+
+ setTimeout(function() {
+ post("baz", "?a=b", urlencoded, '{ "foo": "bar" }');
+
+ setTimeout(function() {
+ post("baz", "?a=b", urlencoded, '?foo=bar');
+
+ setTimeout(function() {
+ post("baz", "?a", undefined, '{ "foo": "bar" }');
+
+ setTimeout(function() {
+ post("baz", "?a=b", undefined, '{ "foo": "bar" }');
+
+ setTimeout(function() {
+ post("baz", "?a=b", undefined, '?foo=bar');
+
+ setTimeout(function() {
+ get("baz", "");
+
+ // Done.
+ }, 10);
+ }, 10);
+ }, 10);
+ }, 10);
+ }, 10);
+ }, 10);
+ }, 10);
+ }
+ </script>
+ </body>
+
+</html>
diff --git a/devtools/client/netmonitor/test/html_post-data-test-page.html b/devtools/client/netmonitor/test/html_post-data-test-page.html
new file mode 100644
index 000000000..8dedc7b60
--- /dev/null
+++ b/devtools/client/netmonitor/test/html_post-data-test-page.html
@@ -0,0 +1,77 @@
+<!-- Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ -->
+<!doctype html>
+
+<html>
+ <head>
+ <meta charset="utf-8"/>
+ <meta http-equiv="Cache-Control" content="no-cache, no-store, must-revalidate" />
+ <meta http-equiv="Pragma" content="no-cache" />
+ <meta http-equiv="Expires" content="0" />
+ <title>Network Monitor test page</title>
+ <style>
+ input {
+ display: block;
+ margin: 12px;
+ }
+ </style>
+ </head>
+
+ <body>
+ <p>POST data test</p>
+ <form enctype="multipart/form-data" method="post" name="form-name">
+ <input type="text" name="text" placeholder="text" value="Some text..."/>
+ <input type="email" name="email" placeholder="email"/>
+ <input type="range" name="range" value="42"/>
+ <input type="button" value="Post me!" onclick="window.form()">
+ </form>
+
+ <script type="text/javascript">
+ function post(aAddress, aMessage, aCallback) {
+ var xhr = new XMLHttpRequest();
+ xhr.open("POST", aAddress, true);
+ xhr.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");
+
+ var data = "";
+ for (var i in aMessage) {
+ data += "&" + i + "=" + aMessage[i];
+ }
+
+ xhr.onreadystatechange = function() {
+ if (this.readyState == this.DONE) {
+ aCallback();
+ }
+ };
+ xhr.send(data);
+ }
+
+ function form(aAddress, aForm, aCallback) {
+ var formData = new FormData(document.forms.namedItem(aForm));
+ formData.append("Custom field", "Extra data");
+
+ var xhr = new XMLHttpRequest();
+ xhr.open("POST", aAddress, true);
+
+ xhr.onreadystatechange = function() {
+ if (this.readyState == this.DONE) {
+ aCallback();
+ }
+ };
+ xhr.send(formData);
+ }
+
+ function performRequests() {
+ var url = "sjs_simple-test-server.sjs";
+ var url1 = url + "?foo=bar&baz=42&type=urlencoded";
+ var url2 = url + "?foo=bar&baz=42&type=multipart";
+
+ post(url1, { foo: "bar", baz: 123 }, function() {
+ form(url2, "form-name", function() {
+ // Done.
+ });
+ });
+ }
+ </script>
+ </body>
+
+</html>
diff --git a/devtools/client/netmonitor/test/html_post-json-test-page.html b/devtools/client/netmonitor/test/html_post-json-test-page.html
new file mode 100644
index 000000000..129feaf08
--- /dev/null
+++ b/devtools/client/netmonitor/test/html_post-json-test-page.html
@@ -0,0 +1,39 @@
+<!-- Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ -->
+<!doctype html>
+
+<html>
+ <head>
+ <meta charset="utf-8"/>
+ <meta http-equiv="Cache-Control" content="no-cache, no-store, must-revalidate" />
+ <meta http-equiv="Pragma" content="no-cache" />
+ <meta http-equiv="Expires" content="0" />
+ <title>Network Monitor test page</title>
+ </head>
+
+ <body>
+ <p>POST raw test</p>
+
+ <script type="text/javascript">
+ function post(address, message, callback) {
+ let xhr = new XMLHttpRequest();
+ xhr.open("POST", address, true);
+ xhr.setRequestHeader("Content-Type", "application/json");
+
+ xhr.onreadystatechange = function () {
+ if (this.readyState == this.DONE) {
+ callback();
+ }
+ };
+ xhr.send(message);
+ }
+
+ function performRequests() {
+ post("sjs_simple-test-server.sjs", JSON.stringify({a: 1}), function () {
+ // Done.
+ });
+ }
+ </script>
+ </body>
+
+</html>
diff --git a/devtools/client/netmonitor/test/html_post-raw-test-page.html b/devtools/client/netmonitor/test/html_post-raw-test-page.html
new file mode 100644
index 000000000..b4456348c
--- /dev/null
+++ b/devtools/client/netmonitor/test/html_post-raw-test-page.html
@@ -0,0 +1,40 @@
+<!-- Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ -->
+<!doctype html>
+
+<html>
+ <head>
+ <meta charset="utf-8"/>
+ <meta http-equiv="Cache-Control" content="no-cache, no-store, must-revalidate" />
+ <meta http-equiv="Pragma" content="no-cache" />
+ <meta http-equiv="Expires" content="0" />
+ <title>Network Monitor test page</title>
+ </head>
+
+ <body>
+ <p>POST raw test</p>
+
+ <script type="text/javascript">
+ function post(aAddress, aMessage, aCallback) {
+ var xhr = new XMLHttpRequest();
+ xhr.open("POST", aAddress, true);
+ xhr.setRequestHeader("content-type", "application/x-www-form-urlencoded");
+
+ xhr.onreadystatechange = function() {
+ if (this.readyState == this.DONE) {
+ aCallback();
+ }
+ };
+ xhr.send(aMessage);
+ }
+
+ function performRequests() {
+ var rawData = "foo=bar&baz=123";
+ post("sjs_simple-test-server.sjs", rawData, function() {
+ // Done.
+ });
+ }
+ </script>
+ </body>
+
+</html>
diff --git a/devtools/client/netmonitor/test/html_post-raw-with-headers-test-page.html b/devtools/client/netmonitor/test/html_post-raw-with-headers-test-page.html
new file mode 100644
index 000000000..3bb8f9071
--- /dev/null
+++ b/devtools/client/netmonitor/test/html_post-raw-with-headers-test-page.html
@@ -0,0 +1,45 @@
+<!-- Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ -->
+<!doctype html>
+
+<html>
+ <head>
+ <meta charset="utf-8"/>
+ <meta http-equiv="Cache-Control" content="no-cache, no-store, must-revalidate" />
+ <meta http-equiv="Pragma" content="no-cache" />
+ <meta http-equiv="Expires" content="0" />
+ <title>Network Monitor test page</title>
+ </head>
+
+ <body>
+ <p>POST raw with headers test</p>
+
+ <script type="text/javascript">
+ function post(aAddress, aMessage, aCallback) {
+ var xhr = new XMLHttpRequest();
+ xhr.open("POST", aAddress, true);
+
+ xhr.onreadystatechange = function() {
+ if (this.readyState == this.DONE) {
+ aCallback();
+ }
+ };
+ xhr.send(aMessage);
+ }
+
+ function performRequests() {
+ var rawData = [
+ "content-type: application/x-www-form-urlencoded\r",
+ "custom-header: hello world!\r",
+ "\r",
+ "\r",
+ "foo=bar&baz=123"
+ ];
+ post("sjs_simple-test-server.sjs", rawData.join("\n"), function() {
+ // Done.
+ });
+ }
+ </script>
+ </body>
+
+</html>
diff --git a/devtools/client/netmonitor/test/html_send-beacon.html b/devtools/client/netmonitor/test/html_send-beacon.html
new file mode 100644
index 000000000..95cc005bd
--- /dev/null
+++ b/devtools/client/netmonitor/test/html_send-beacon.html
@@ -0,0 +1,23 @@
+<!-- Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ -->
+<!doctype html>
+
+<html>
+ <head>
+ <meta charset="utf-8"/>
+ <meta http-equiv="Cache-Control" content="no-cache, no-store, must-revalidate" />
+ <meta http-equiv="Pragma" content="no-cache" />
+ <meta http-equiv="Expires" content="0" />
+ <title>Network Monitor test page</title>
+ </head>
+
+ <body>
+ <p>Send beacon test</p>
+
+ <script type="text/javascript">
+ function performRequest() {
+ navigator.sendBeacon("beacon_request");
+ }
+ </script>
+ </body>
+</html>
diff --git a/devtools/client/netmonitor/test/html_simple-test-page.html b/devtools/client/netmonitor/test/html_simple-test-page.html
new file mode 100644
index 000000000..846681dbd
--- /dev/null
+++ b/devtools/client/netmonitor/test/html_simple-test-page.html
@@ -0,0 +1,18 @@
+<!-- Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ -->
+<!doctype html>
+
+<html>
+ <head>
+ <meta charset="utf-8"/>
+ <meta http-equiv="Cache-Control" content="no-cache, no-store, must-revalidate" />
+ <meta http-equiv="Pragma" content="no-cache" />
+ <meta http-equiv="Expires" content="0" />
+ <title>Network Monitor test page</title>
+ </head>
+
+ <body>
+ <p>Simple test</p>
+ </body>
+
+</html>
diff --git a/devtools/client/netmonitor/test/html_single-get-page.html b/devtools/client/netmonitor/test/html_single-get-page.html
new file mode 100644
index 000000000..0055d4ee0
--- /dev/null
+++ b/devtools/client/netmonitor/test/html_single-get-page.html
@@ -0,0 +1,36 @@
+<!-- Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ -->
+<!doctype html>
+
+<html>
+ <head>
+ <meta charset="utf-8"/>
+ <meta http-equiv="Cache-Control" content="no-cache, no-store, must-revalidate" />
+ <meta http-equiv="Pragma" content="no-cache" />
+ <meta http-equiv="Expires" content="0" />
+ <title>Network Monitor test page</title>
+ </head>
+
+ <body>
+ <p>Performing a custom number of GETs</p>
+
+ <script type="text/javascript">
+ function get(aAddress, aCallback) {
+ var xhr = new XMLHttpRequest();
+ xhr.open("GET", aAddress, true);
+
+ xhr.onreadystatechange = function() {
+ if (this.readyState == this.DONE) {
+ aCallback();
+ }
+ };
+ xhr.send(null);
+ }
+
+ (function performRequests() {
+ get("request_0", function() {});
+ })();
+ </script>
+ </body>
+
+</html>
diff --git a/devtools/client/netmonitor/test/html_sorting-test-page.html b/devtools/client/netmonitor/test/html_sorting-test-page.html
new file mode 100644
index 000000000..640c58b8e
--- /dev/null
+++ b/devtools/client/netmonitor/test/html_sorting-test-page.html
@@ -0,0 +1,18 @@
+<!-- Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ -->
+<!doctype html>
+
+<html>
+ <head>
+ <meta charset="utf-8"/>
+ <meta http-equiv="Cache-Control" content="no-cache, no-store, must-revalidate" />
+ <meta http-equiv="Pragma" content="no-cache" />
+ <meta http-equiv="Expires" content="0" />
+ <title>Network Monitor test page</title>
+ </head>
+
+ <body>
+ <p>Sorting test</p>
+ </body>
+
+</html>
diff --git a/devtools/client/netmonitor/test/html_statistics-test-page.html b/devtools/client/netmonitor/test/html_statistics-test-page.html
new file mode 100644
index 000000000..b4b15b82b
--- /dev/null
+++ b/devtools/client/netmonitor/test/html_statistics-test-page.html
@@ -0,0 +1,40 @@
+<!-- Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ -->
+<!doctype html>
+
+<html>
+ <head>
+ <meta charset="utf-8"/>
+ <meta http-equiv="Cache-Control" content="no-cache, no-store, must-revalidate" />
+ <meta http-equiv="Pragma" content="no-cache" />
+ <meta http-equiv="Expires" content="0" />
+ <title>Network Monitor test page</title>
+ </head>
+
+ <body>
+ <p>Statistics test</p>
+
+ <script type="text/javascript">
+ function get(aAddress) {
+ var xhr = new XMLHttpRequest();
+ xhr.open("GET", aAddress, true);
+ xhr.send(null);
+ }
+
+ get("sjs_content-type-test-server.sjs?sts=304&fmt=txt");
+ get("sjs_content-type-test-server.sjs?sts=304&fmt=xml");
+ get("sjs_content-type-test-server.sjs?sts=304&fmt=html");
+ get("sjs_content-type-test-server.sjs?sts=304&fmt=css");
+ get("sjs_content-type-test-server.sjs?sts=304&fmt=js");
+ get("sjs_content-type-test-server.sjs?sts=304&fmt=json");
+ get("sjs_content-type-test-server.sjs?sts=304&fmt=jsonp");
+ get("sjs_content-type-test-server.sjs?sts=304&fmt=font");
+ get("sjs_content-type-test-server.sjs?sts=304&fmt=image");
+ get("sjs_content-type-test-server.sjs?sts=304&fmt=audio");
+ get("sjs_content-type-test-server.sjs?sts=304&fmt=video");
+ get("sjs_content-type-test-server.sjs?sts=304&fmt=flash");
+ get("test-image.png?v=" + Math.random());
+ </script>
+ </body>
+
+</html>
diff --git a/devtools/client/netmonitor/test/html_status-codes-test-page.html b/devtools/client/netmonitor/test/html_status-codes-test-page.html
new file mode 100644
index 000000000..4be779bd4
--- /dev/null
+++ b/devtools/client/netmonitor/test/html_status-codes-test-page.html
@@ -0,0 +1,55 @@
+<!-- Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ -->
+<!doctype html>
+
+<html>
+ <head>
+ <meta charset="utf-8"/>
+ <meta http-equiv="Cache-Control" content="no-cache, no-store, must-revalidate" />
+ <meta http-equiv="Pragma" content="no-cache" />
+ <meta http-equiv="Expires" content="0" />
+ <title>Network Monitor test page</title>
+ </head>
+
+ <body>
+ <p>Status codes test</p>
+
+ <script type="text/javascript">
+ function get(aAddress, aCallback) {
+ var xhr = new XMLHttpRequest();
+ xhr.open("GET", aAddress, true);
+
+ xhr.onreadystatechange = function() {
+ if (this.readyState == this.DONE) {
+ aCallback();
+ }
+ };
+ xhr.send(null);
+ }
+
+ function performRequests() {
+ get("sjs_status-codes-test-server.sjs?sts=100", function() {
+ get("sjs_status-codes-test-server.sjs?sts=200", function() {
+ get("sjs_status-codes-test-server.sjs?sts=300", function() {
+ get("sjs_status-codes-test-server.sjs?sts=400", function() {
+ get("sjs_status-codes-test-server.sjs?sts=500", function() {
+ // Done.
+ });
+ });
+ });
+ });
+ });
+ }
+
+ function performCachedRequests() {
+ get("sjs_status-codes-test-server.sjs?sts=ok&cached", function() {
+ get("sjs_status-codes-test-server.sjs?sts=redirect&cached", function() {
+ // Done.
+ });
+ });
+ }
+
+ </script>
+ </body>
+
+</html>
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
new file mode 100644
index 000000000..3c70c7dcb
--- /dev/null
+++ b/devtools/client/netmonitor/test/service-workers/status-codes-service-worker.js
@@ -0,0 +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/ */
+
+"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
new file mode 100644
index 000000000..65c79ee00
--- /dev/null
+++ b/devtools/client/netmonitor/test/service-workers/status-codes.html
@@ -0,0 +1,59 @@
+<!-- Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ -->
+<!doctype html>
+
+<html>
+ <head>
+ <meta charset="utf-8"/>
+ <meta http-equiv="Cache-Control" content="no-cache, no-store, must-revalidate" />
+ <meta http-equiv="Pragma" content="no-cache" />
+ <meta http-equiv="Expires" content="0" />
+ <title>Network Monitor test page</title>
+ </head>
+
+ <body>
+ <p>Status codes test</p>
+
+ <script type="text/javascript">
+ let swRegistration;
+
+ function registerServiceWorker() {
+ 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 new Promise(function doXHR(done) {
+ let xhr = new XMLHttpRequest();
+ xhr.open("GET", "test/200", true);
+ xhr.onreadystatechange = done;
+ xhr.send(null);
+ });
+ }
+ </script>
+ </body>
+
+</html>
diff --git a/devtools/client/netmonitor/test/sjs_content-type-test-server.sjs b/devtools/client/netmonitor/test/sjs_content-type-test-server.sjs
new file mode 100644
index 000000000..ee9a82e27
--- /dev/null
+++ b/devtools/client/netmonitor/test/sjs_content-type-test-server.sjs
@@ -0,0 +1,273 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+const { classes: Cc, interfaces: Ci } = Components;
+
+function gzipCompressString(string, obs) {
+
+ let scs = Cc["@mozilla.org/streamConverters;1"]
+ .getService(Ci.nsIStreamConverterService);
+ let listener = Cc["@mozilla.org/network/stream-loader;1"]
+ .createInstance(Ci.nsIStreamLoader);
+ listener.init(obs);
+ let converter = scs.asyncConvertData("uncompressed", "gzip",
+ listener, null);
+ let stringStream = Cc["@mozilla.org/io/string-input-stream;1"]
+ .createInstance(Ci.nsIStringInputStream);
+ stringStream.data = string;
+ converter.onStartRequest(null, null);
+ converter.onDataAvailable(null, null, stringStream, 0, string.length);
+ converter.onStopRequest(null, null, null);
+}
+
+function doubleGzipCompressString(string, observer) {
+ let observer2 = {
+ onStreamComplete: function(loader, context, status, length, result) {
+ let buffer = String.fromCharCode.apply(this, result);
+ gzipCompressString(buffer, observer);
+ }
+ };
+ gzipCompressString(string, observer2);
+}
+
+function handleRequest(request, response) {
+ response.processAsync();
+
+ let params = request.queryString.split("&");
+ let format = (params.filter((s) => s.includes("fmt="))[0] || "").split("=")[1];
+ let status = (params.filter((s) => s.includes("sts="))[0] || "").split("=")[1] || 200;
+
+ let cachedCount = 0;
+ let cacheExpire = 60; // seconds
+
+ function setCacheHeaders() {
+ if (status != 304) {
+ response.setHeader("Cache-Control", "no-cache, no-store, must-revalidate");
+ response.setHeader("Pragma", "no-cache");
+ response.setHeader("Expires", "0");
+ return;
+ }
+ // Spice things up a little!
+ if (cachedCount % 2) {
+ response.setHeader("Cache-Control", "max-age=" + cacheExpire, false);
+ } else {
+ response.setHeader("Expires", Date(Date.now() + cacheExpire * 1000), false);
+ }
+ cachedCount++;
+ }
+
+ let timer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
+ timer.initWithCallback(() => {
+ // to avoid garbage collection
+ timer = null;
+ switch (format) {
+ case "txt": {
+ response.setStatusLine(request.httpVersion, status, "DA DA DA");
+ response.setHeader("Content-Type", "text/plain", false);
+ setCacheHeaders();
+ response.write("Братан, ты вообще качаешься?");
+ response.finish();
+ break;
+ }
+ case "xml": {
+ response.setStatusLine(request.httpVersion, status, "OK");
+ response.setHeader("Content-Type", "text/xml; charset=utf-8", false);
+ setCacheHeaders();
+ response.write("<label value='greeting'>Hello XML!</label>");
+ response.finish();
+ break;
+ }
+ case "html": {
+ let content = (params.filter((s) => s.includes("res="))[0] || "").split("=")[1];
+ response.setStatusLine(request.httpVersion, status, "OK");
+ response.setHeader("Content-Type", "text/html; charset=utf-8", false);
+ setCacheHeaders();
+ response.write(content || "<p>Hello HTML!</p>");
+ response.finish();
+ break;
+ }
+ case "html-long": {
+ let str = new Array(102400 /* 100 KB in bytes */).join(".");
+ response.setStatusLine(request.httpVersion, status, "OK");
+ response.setHeader("Content-Type", "text/html; charset=utf-8", false);
+ setCacheHeaders();
+ response.write("<p>" + str + "</p>");
+ response.finish();
+ break;
+ }
+ case "css": {
+ response.setStatusLine(request.httpVersion, status, "OK");
+ response.setHeader("Content-Type", "text/css; charset=utf-8", false);
+ setCacheHeaders();
+ response.write("body:pre { content: 'Hello CSS!' }");
+ response.finish();
+ break;
+ }
+ case "js": {
+ response.setStatusLine(request.httpVersion, status, "OK");
+ response.setHeader("Content-Type", "application/javascript; charset=utf-8", false);
+ setCacheHeaders();
+ response.write("function() { return 'Hello JS!'; }");
+ response.finish();
+ break;
+ }
+ case "json": {
+ response.setStatusLine(request.httpVersion, status, "OK");
+ response.setHeader("Content-Type", "application/json; charset=utf-8", false);
+ setCacheHeaders();
+ response.write("{ \"greeting\": \"Hello JSON!\" }");
+ response.finish();
+ break;
+ }
+ case "jsonp": {
+ let fun = (params.filter((s) => s.includes("jsonp="))[0] || "").split("=")[1];
+ response.setStatusLine(request.httpVersion, status, "OK");
+ response.setHeader("Content-Type", "text/json; charset=utf-8", false);
+ setCacheHeaders();
+ response.write(fun + "({ \"greeting\": \"Hello JSONP!\" })");
+ response.finish();
+ break;
+ }
+ case "jsonp2": {
+ let fun = (params.filter((s) => s.includes("jsonp="))[0] || "").split("=")[1];
+ response.setStatusLine(request.httpVersion, status, "OK");
+ response.setHeader("Content-Type", "text/json; charset=utf-8", false);
+ setCacheHeaders();
+ response.write(" " + fun + " ( { \"greeting\": \"Hello weird JSONP!\" } ) ; ");
+ response.finish();
+ break;
+ }
+ case "json-long": {
+ let str = "{ \"greeting\": \"Hello long string JSON!\" },";
+ response.setStatusLine(request.httpVersion, status, "OK");
+ response.setHeader("Content-Type", "text/json; charset=utf-8", false);
+ setCacheHeaders();
+ response.write("[" + new Array(2048).join(str).slice(0, -1) + "]");
+ response.finish();
+ break;
+ }
+ case "json-malformed": {
+ response.setStatusLine(request.httpVersion, status, "OK");
+ response.setHeader("Content-Type", "text/json; charset=utf-8", false);
+ setCacheHeaders();
+ response.write("{ \"greeting\": \"Hello malformed JSON!\" },");
+ response.finish();
+ break;
+ }
+ case "json-text-mime": {
+ response.setStatusLine(request.httpVersion, status, "OK");
+ response.setHeader("Content-Type", "text/plain; charset=utf-8", false);
+ setCacheHeaders();
+ response.write("{ \"greeting\": \"Hello third-party JSON!\" }");
+ response.finish();
+ break;
+ }
+ case "json-custom-mime": {
+ response.setStatusLine(request.httpVersion, status, "OK");
+ response.setHeader("Content-Type", "text/x-bigcorp-json; charset=utf-8", false);
+ setCacheHeaders();
+ response.write("{ \"greeting\": \"Hello oddly-named JSON!\" }");
+ response.finish();
+ break;
+ }
+ case "font": {
+ response.setStatusLine(request.httpVersion, status, "OK");
+ response.setHeader("Content-Type", "font/woff", false);
+ setCacheHeaders();
+ response.finish();
+ break;
+ }
+ case "image": {
+ response.setStatusLine(request.httpVersion, status, "OK");
+ response.setHeader("Content-Type", "image/png", false);
+ setCacheHeaders();
+ response.finish();
+ break;
+ }
+ case "audio": {
+ response.setStatusLine(request.httpVersion, status, "OK");
+ response.setHeader("Content-Type", "audio/ogg", false);
+ setCacheHeaders();
+ response.finish();
+ break;
+ }
+ case "video": {
+ response.setStatusLine(request.httpVersion, status, "OK");
+ response.setHeader("Content-Type", "video/webm", false);
+ setCacheHeaders();
+ response.finish();
+ break;
+ }
+ case "flash": {
+ response.setStatusLine(request.httpVersion, status, "OK");
+ response.setHeader("Content-Type", "application/x-shockwave-flash", false);
+ setCacheHeaders();
+ response.finish();
+ break;
+ }
+ case "ws": {
+ response.setStatusLine(request.httpVersion, 101, "Switching Protocols");
+ response.setHeader("Connection", "upgrade", false);
+ response.setHeader("Upgrade", "websocket", false);
+ setCacheHeaders();
+ response.finish();
+ break;
+ }
+ case "gzip": {
+ // Note: we're doing a double gzip encoding to test multiple
+ // converters in network monitor.
+ response.setStatusLine(request.httpVersion, status, "OK");
+ response.setHeader("Content-Type", "text/plain", false);
+ response.setHeader("Content-Encoding", "gzip\t ,gzip", false);
+ setCacheHeaders();
+ let observer = {
+ onStreamComplete: function(loader, context, status, length, result) {
+ let buffer = String.fromCharCode.apply(this, result);
+ response.setHeader("Content-Length", "" + buffer.length, false);
+ response.write(buffer);
+ response.finish();
+ }
+ };
+ let data = new Array(1000).join("Hello gzip!");
+ doubleGzipCompressString(data, observer);
+ break;
+ }
+ case "br": {
+ response.setStatusLine(request.httpVersion, status, "Connected");
+ response.setHeader("Content-Type", "text/plain", false);
+ response.setHeader("Content-Encoding", "br", false);
+ setCacheHeaders();
+ response.setHeader("Content-Length", "10", false);
+ // Use static data since we cannot encode brotli.
+ response.write("\x1b\x3f\x00\x00\x24\xb0\xe2\x99\x80\x12");
+ response.finish();
+ break;
+ }
+ case "hls-m3u8": {
+ response.setStatusLine(request.httpVersion, status, "OK");
+ response.setHeader("Content-Type", "application/x-mpegurl", false);
+ setCacheHeaders();
+ response.write("#EXTM3U\n");
+ response.finish();
+ break;
+ }
+ case "mpeg-dash": {
+ response.setStatusLine(request.httpVersion, status, "OK");
+ response.setHeader("Content-Type", "video/vnd.mpeg.dash.mpd", false);
+ setCacheHeaders();
+ response.write('<?xml version="1.0" encoding="UTF-8"?>\n');
+ response.write('<MPD xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"></MPD>\n');
+ response.finish();
+ break;
+ }
+ default: {
+ response.setStatusLine(request.httpVersion, 404, "Not Found");
+ response.setHeader("Content-Type", "text/html; charset=utf-8", false);
+ setCacheHeaders();
+ response.write("<blink>Not Found</blink>");
+ response.finish();
+ break;
+ }
+ }
+ }, 10, Ci.nsITimer.TYPE_ONE_SHOT); // Make sure this request takes a few ms.
+}
diff --git a/devtools/client/netmonitor/test/sjs_cors-test-server.sjs b/devtools/client/netmonitor/test/sjs_cors-test-server.sjs
new file mode 100644
index 000000000..0bab80901
--- /dev/null
+++ b/devtools/client/netmonitor/test/sjs_cors-test-server.sjs
@@ -0,0 +1,17 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+function handleRequest(request, response) {
+ response.setStatusLine(request.httpVersion, 200, "Och Aye");
+
+ response.setHeader("Cache-Control", "no-cache, no-store, must-revalidate");
+ response.setHeader("Pragma", "no-cache");
+ response.setHeader("Expires", "0");
+
+ response.setHeader("Access-Control-Allow-Origin", "*", false);
+ response.setHeader("Access-Control-Allow-Headers", "content-type", false);
+
+ response.setHeader("Content-Type", "text/plain; charset=utf-8", false);
+
+ response.write("Access-Control-Allow-Origin: *");
+}
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 000000000..c5715886e
--- /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!");
+ }
+}
diff --git a/devtools/client/netmonitor/test/sjs_https-redirect-test-server.sjs b/devtools/client/netmonitor/test/sjs_https-redirect-test-server.sjs
new file mode 100644
index 000000000..14ea34559
--- /dev/null
+++ b/devtools/client/netmonitor/test/sjs_https-redirect-test-server.sjs
@@ -0,0 +1,19 @@
+/* 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");
+
+ response.setHeader("Access-Control-Allow-Origin", "*", false);
+
+ 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.write("Page was accessed over HTTPS!");
+ }
+
+}
diff --git a/devtools/client/netmonitor/test/sjs_simple-test-server.sjs b/devtools/client/netmonitor/test/sjs_simple-test-server.sjs
new file mode 100644
index 000000000..9a3d44b6d
--- /dev/null
+++ b/devtools/client/netmonitor/test/sjs_simple-test-server.sjs
@@ -0,0 +1,17 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+function handleRequest(request, response) {
+ response.setStatusLine(request.httpVersion, 200, "Och Aye");
+
+ response.setHeader("Cache-Control", "no-cache, no-store, must-revalidate");
+ response.setHeader("Pragma", "no-cache");
+ response.setHeader("Expires", "0");
+
+ response.setHeader("Set-Cookie", "bob=true; Max-Age=10; HttpOnly", true);
+ response.setHeader("Set-Cookie", "tom=cool; Max-Age=10; HttpOnly", true);
+
+ response.setHeader("Content-Type", "text/plain; charset=utf-8", false);
+ response.setHeader("Foo-Bar", "baz", false);
+ response.write("Hello world!");
+}
diff --git a/devtools/client/netmonitor/test/sjs_sorting-test-server.sjs b/devtools/client/netmonitor/test/sjs_sorting-test-server.sjs
new file mode 100644
index 000000000..54c62866b
--- /dev/null
+++ b/devtools/client/netmonitor/test/sjs_sorting-test-server.sjs
@@ -0,0 +1,26 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+const { classes: Cc, interfaces: Ci } = Components;
+
+function handleRequest(request, response) {
+ response.processAsync();
+
+ let params = request.queryString.split("&");
+ let index = params.filter((s) => s.includes("index="))[0].split("=")[1];
+
+ let timer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
+ timer.initWithCallback(() => {
+ // to avoid garbage collection
+ timer = null;
+ response.setStatusLine(request.httpVersion, index == 1 ? 101 : index * 100, "Meh");
+
+ response.setHeader("Cache-Control", "no-cache, no-store, must-revalidate");
+ response.setHeader("Pragma", "no-cache");
+ response.setHeader("Expires", "0");
+
+ response.setHeader("Content-Type", "text/" + index, false);
+ response.write(new Array(index * 10).join(index)); // + 0.01 KB
+ response.finish();
+ }, 10, Ci.nsITimer.TYPE_ONE_SHOT); // Make sure this request takes a few ms.
+}
diff --git a/devtools/client/netmonitor/test/sjs_status-codes-test-server.sjs b/devtools/client/netmonitor/test/sjs_status-codes-test-server.sjs
new file mode 100644
index 000000000..4f17d1235
--- /dev/null
+++ b/devtools/client/netmonitor/test/sjs_status-codes-test-server.sjs
@@ -0,0 +1,56 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+const { classes: Cc, interfaces: Ci } = Components;
+
+function handleRequest(request, response) {
+ response.processAsync();
+
+ let params = request.queryString.split("&");
+ let status = params.filter(s => s.includes("sts="))[0].split("=")[1];
+ let cached = params.filter(s => s === 'cached').length !== 0;
+
+ let timer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
+ timer.initWithCallback(() => {
+ // to avoid garbage collection
+ timer = null;
+ switch (status) {
+ case "100":
+ response.setStatusLine(request.httpVersion, 101, "Switching Protocols");
+ break;
+ case "200":
+ response.setStatusLine(request.httpVersion, 202, "Created");
+ break;
+ case "300":
+ response.setStatusLine(request.httpVersion, 303, "See Other");
+ break;
+ case "400":
+ response.setStatusLine(request.httpVersion, 404, "Not Found");
+ break;
+ case "500":
+ response.setStatusLine(request.httpVersion, 501, "Not Implemented");
+ break;
+ case "ok":
+ response.setStatusLine(request.httpVersion, 200, "OK");
+ break;
+ case "redirect":
+ response.setStatusLine(request.httpVersion, 301, "Moved Permanently");
+ response.setHeader("Location", "http://example.com/redirected");
+ break;
+ }
+
+ if(!cached) {
+ response.setHeader("Cache-Control", "no-cache, no-store, must-revalidate");
+ response.setHeader("Pragma", "no-cache");
+ response.setHeader("Expires", "0");
+ }
+ else {
+ response.setHeader("Cache-Control", "no-transform,public,max-age=300,s-maxage=900");
+ response.setHeader("Expires", "Thu, 01 Dec 2100 20:00:00 GMT");
+ }
+
+ response.setHeader("Content-Type", "text/plain; charset=utf-8", false);
+ response.write("Hello status code " + status + "!");
+ response.finish();
+ }, 10, Ci.nsITimer.TYPE_ONE_SHOT); // Make sure this request takes a few ms.
+}
diff --git a/devtools/client/netmonitor/test/sjs_truncate-test-server.sjs b/devtools/client/netmonitor/test/sjs_truncate-test-server.sjs
new file mode 100644
index 000000000..54db23d9a
--- /dev/null
+++ b/devtools/client/netmonitor/test/sjs_truncate-test-server.sjs
@@ -0,0 +1,18 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+function handleRequest(request, response) {
+ let params = request.queryString.split("&");
+ let limit = (params.filter((s) => s.includes("limit="))[0] || "").split("=")[1];
+
+ response.setStatusLine(request.httpVersion, 200, "Och Aye");
+
+ response.setHeader("Cache-Control", "no-cache, no-store, must-revalidate");
+ response.setHeader("Pragma", "no-cache");
+ response.setHeader("Expires", "0");
+ response.setHeader("Content-Type", "text/plain; charset=utf-8", false);
+
+ response.write("x".repeat(2 * parseInt(limit, 10)));
+
+ response.write("Hello world!");
+}
diff --git a/devtools/client/netmonitor/test/test-image.png b/devtools/client/netmonitor/test/test-image.png
new file mode 100644
index 000000000..769c63634
--- /dev/null
+++ b/devtools/client/netmonitor/test/test-image.png
Binary files differ