summaryrefslogtreecommitdiffstats
path: root/netwerk/test/browser
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-19 00:47:55 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-19 00:47:55 +0000
commit26a029d407be480d791972afb5975cf62c9360a6 (patch)
treef435a8308119effd964b339f76abb83a57c29483 /netwerk/test/browser
parentInitial commit. (diff)
downloadfirefox-upstream/124.0.1.tar.xz
firefox-upstream/124.0.1.zip
Adding upstream version 124.0.1.upstream/124.0.1
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'netwerk/test/browser')
-rw-r--r--netwerk/test/browser/103_preload.html6
-rw-r--r--netwerk/test/browser/103_preload.html^headers^1
-rw-r--r--netwerk/test/browser/103_preload.html^informationalResponse^2
-rw-r--r--netwerk/test/browser/103_preload_anchor.html6
-rw-r--r--netwerk/test/browser/103_preload_anchor.html^headers^1
-rw-r--r--netwerk/test/browser/103_preload_anchor.html^informationalResponse^2
-rw-r--r--netwerk/test/browser/103_preload_and_404.html6
-rw-r--r--netwerk/test/browser/103_preload_and_404.html^headers^1
-rw-r--r--netwerk/test/browser/103_preload_and_404.html^informationalResponse^2
-rw-r--r--netwerk/test/browser/103_preload_csp_imgsrc_none.html6
-rw-r--r--netwerk/test/browser/103_preload_csp_imgsrc_none.html^headers^2
-rw-r--r--netwerk/test/browser/103_preload_csp_imgsrc_none.html^informationalResponse^2
-rw-r--r--netwerk/test/browser/103_preload_iframe.html6
-rw-r--r--netwerk/test/browser/103_preload_iframe.html^headers^1
-rw-r--r--netwerk/test/browser/auth_post.sjs37
-rw-r--r--netwerk/test/browser/browser.toml188
-rw-r--r--netwerk/test/browser/browser_103_assets.js169
-rw-r--r--netwerk/test/browser/browser_103_assets_extension.js78
-rw-r--r--netwerk/test/browser/browser_103_cleanup.js47
-rw-r--r--netwerk/test/browser/browser_103_csp.js86
-rw-r--r--netwerk/test/browser/browser_103_csp_images.js170
-rw-r--r--netwerk/test/browser/browser_103_csp_styles.js86
-rw-r--r--netwerk/test/browser/browser_103_error.js121
-rw-r--r--netwerk/test/browser/browser_103_no_cancel_on_error.js67
-rw-r--r--netwerk/test/browser/browser_103_preconnect.js71
-rw-r--r--netwerk/test/browser/browser_103_preload.js138
-rw-r--r--netwerk/test/browser/browser_103_preload_2.js180
-rw-r--r--netwerk/test/browser/browser_103_private_window.js70
-rw-r--r--netwerk/test/browser/browser_103_redirect.js52
-rw-r--r--netwerk/test/browser/browser_103_redirect_from_server.js321
-rw-r--r--netwerk/test/browser/browser_103_referrer_policy.js167
-rw-r--r--netwerk/test/browser/browser_103_telemetry.js107
-rw-r--r--netwerk/test/browser/browser_103_user_load.js85
-rw-r--r--netwerk/test/browser/browser_NetUtil.js111
-rw-r--r--netwerk/test/browser/browser_about_cache.js136
-rw-r--r--netwerk/test/browser/browser_backgroundtask_purgeHTTPCache.js40
-rw-r--r--netwerk/test/browser/browser_bug1535877.js15
-rw-r--r--netwerk/test/browser/browser_bug1629307.js81
-rw-r--r--netwerk/test/browser/browser_child_resource.js246
-rw-r--r--netwerk/test/browser/browser_cookie_filtering_basic.js184
-rw-r--r--netwerk/test/browser/browser_cookie_filtering_cross_origin.js146
-rw-r--r--netwerk/test/browser/browser_cookie_filtering_insecure.js106
-rw-r--r--netwerk/test/browser/browser_cookie_filtering_oa.js190
-rw-r--r--netwerk/test/browser/browser_cookie_filtering_subdomain.js175
-rw-r--r--netwerk/test/browser/browser_cookie_sync_across_tabs.js79
-rw-r--r--netwerk/test/browser/browser_fetch_lnk.js23
-rw-r--r--netwerk/test/browser/browser_http_index_format.js48
-rw-r--r--netwerk/test/browser/browser_nsIFormPOSTActionChannel.js273
-rw-r--r--netwerk/test/browser/browser_post_auth.js65
-rw-r--r--netwerk/test/browser/browser_post_file.js71
-rw-r--r--netwerk/test/browser/browser_purgeCache_idle_daily.js86
-rw-r--r--netwerk/test/browser/browser_resource_navigation.js76
-rw-r--r--netwerk/test/browser/browser_speculative_connection_link_header.js57
-rw-r--r--netwerk/test/browser/browser_test_data_channel_observer.js35
-rw-r--r--netwerk/test/browser/browser_test_favicon.js26
-rw-r--r--netwerk/test/browser/browser_test_io_activity.js50
-rw-r--r--netwerk/test/browser/browser_test_offline_tab.js36
-rw-r--r--netwerk/test/browser/cookie_filtering_helper.sys.mjs166
-rw-r--r--netwerk/test/browser/cookie_filtering_resource.sjs35
-rw-r--r--netwerk/test/browser/cookie_filtering_secure_resource_com.html6
-rw-r--r--netwerk/test/browser/cookie_filtering_secure_resource_com.html^headers^2
-rw-r--r--netwerk/test/browser/cookie_filtering_secure_resource_org.html6
-rw-r--r--netwerk/test/browser/cookie_filtering_secure_resource_org.html^headers^2
-rw-r--r--netwerk/test/browser/cookie_filtering_square.png0
-rw-r--r--netwerk/test/browser/cookie_filtering_square.png^headers^2
-rw-r--r--netwerk/test/browser/damonbowling.jpgbin0 -> 44008 bytes
-rw-r--r--netwerk/test/browser/damonbowling.jpg^headers^2
-rw-r--r--netwerk/test/browser/dummy.html11
-rw-r--r--netwerk/test/browser/early_hint_asset.sjs50
-rw-r--r--netwerk/test/browser/early_hint_asset_html.sjs135
-rw-r--r--netwerk/test/browser/early_hint_csp_options_html.sjs120
-rw-r--r--netwerk/test/browser/early_hint_error.sjs35
-rw-r--r--netwerk/test/browser/early_hint_main_html.sjs62
-rw-r--r--netwerk/test/browser/early_hint_main_redirect.sjs67
-rw-r--r--netwerk/test/browser/early_hint_pixel.sjs37
-rw-r--r--netwerk/test/browser/early_hint_pixel_count.sjs9
-rw-r--r--netwerk/test/browser/early_hint_preconnect_html.sjs32
-rw-r--r--netwerk/test/browser/early_hint_preload_test_helper.sys.mjs159
-rw-r--r--netwerk/test/browser/early_hint_redirect.sjs21
-rw-r--r--netwerk/test/browser/early_hint_redirect_html.sjs24
-rw-r--r--netwerk/test/browser/early_hint_referrer_policy_html.sjs132
-rw-r--r--netwerk/test/browser/file_favicon.html7
-rw-r--r--netwerk/test/browser/file_link_header.sjs24
-rw-r--r--netwerk/test/browser/file_lnk.lnkbin0 -> 1531 bytes
-rw-r--r--netwerk/test/browser/ioactivity.html11
-rw-r--r--netwerk/test/browser/no_103_preload.html6
-rw-r--r--netwerk/test/browser/no_103_preload.html^headers^1
-rw-r--r--netwerk/test/browser/post.html14
-rw-r--r--netwerk/test/browser/redirect.sjs6
-rw-r--r--netwerk/test/browser/res.css4
-rw-r--r--netwerk/test/browser/res.css^headers^1
-rw-r--r--netwerk/test/browser/res.csv1
-rw-r--r--netwerk/test/browser/res.csv^headers^1
-rw-r--r--netwerk/test/browser/res.mp3bin0 -> 3530 bytes
-rw-r--r--netwerk/test/browser/res.unknown1
-rw-r--r--netwerk/test/browser/res_206.html11
-rw-r--r--netwerk/test/browser/res_206.html^headers^2
-rw-r--r--netwerk/test/browser/res_206.mp3bin0 -> 3530 bytes
-rw-r--r--netwerk/test/browser/res_206.mp3^headers^1
-rw-r--r--netwerk/test/browser/res_empty.zipbin0 -> 166 bytes
-rw-r--r--netwerk/test/browser/res_http_index_format1
-rw-r--r--netwerk/test/browser/res_http_index_format^headers^1
-rw-r--r--netwerk/test/browser/res_img.pngbin0 -> 129497 bytes
-rw-r--r--netwerk/test/browser/res_img_for_unknown_decoderbin0 -> 4421 bytes
-rw-r--r--netwerk/test/browser/res_img_for_unknown_decoder^headers^2
-rw-r--r--netwerk/test/browser/res_img_unknown.png11
-rw-r--r--netwerk/test/browser/res_invalid_partial.mp3bin0 -> 3530 bytes
-rw-r--r--netwerk/test/browser/res_invalid_partial.mp3^headers^2
-rw-r--r--netwerk/test/browser/res_nosniff.html11
-rw-r--r--netwerk/test/browser/res_nosniff.html^headers^2
-rw-r--r--netwerk/test/browser/res_nosniff2.html11
-rw-r--r--netwerk/test/browser/res_nosniff2.html^headers^2
-rw-r--r--netwerk/test/browser/res_not_200or206.mp3bin0 -> 3530 bytes
-rw-r--r--netwerk/test/browser/res_not_200or206.mp3^headers^1
-rw-r--r--netwerk/test/browser/res_not_ok.html11
-rw-r--r--netwerk/test/browser/res_not_ok.html^headers^1
-rw-r--r--netwerk/test/browser/res_object.html18
-rw-r--r--netwerk/test/browser/res_sub_document.html11
-rw-r--r--netwerk/test/browser/square.png0
-rw-r--r--netwerk/test/browser/test_1629307.html9
-rw-r--r--netwerk/test/browser/x_frame_options.html0
-rw-r--r--netwerk/test/browser/x_frame_options.html^headers^3
122 files changed, 5942 insertions, 0 deletions
diff --git a/netwerk/test/browser/103_preload.html b/netwerk/test/browser/103_preload.html
new file mode 100644
index 0000000000..9583815cfb
--- /dev/null
+++ b/netwerk/test/browser/103_preload.html
@@ -0,0 +1,6 @@
+<!DOCTYPE html>
+<html>
+<body>
+<img src="https://example.com/browser/netwerk/test/browser/square.png" width="100px">
+</body>
+</html>
diff --git a/netwerk/test/browser/103_preload.html^headers^ b/netwerk/test/browser/103_preload.html^headers^
new file mode 100644
index 0000000000..9e23c73b7f
--- /dev/null
+++ b/netwerk/test/browser/103_preload.html^headers^
@@ -0,0 +1 @@
+Cache-Control: no-cache
diff --git a/netwerk/test/browser/103_preload.html^informationalResponse^ b/netwerk/test/browser/103_preload.html^informationalResponse^
new file mode 100644
index 0000000000..b95a96e74b
--- /dev/null
+++ b/netwerk/test/browser/103_preload.html^informationalResponse^
@@ -0,0 +1,2 @@
+HTTP 103 Too Early
+Link: <https://example.com/browser/netwerk/test/browser/square.png>; rel=preload; as=image
diff --git a/netwerk/test/browser/103_preload_anchor.html b/netwerk/test/browser/103_preload_anchor.html
new file mode 100644
index 0000000000..c12fe92072
--- /dev/null
+++ b/netwerk/test/browser/103_preload_anchor.html
@@ -0,0 +1,6 @@
+<!DOCTYPE html>
+<html>
+<body>
+<img src="https://example.com/browser/netwerk/test/browser/early_hint_pixel.sjs?f5a05cb8-43e6-4868-bc0f-ca453ef87826" width="100px">
+</body>
+</html>
diff --git a/netwerk/test/browser/103_preload_anchor.html^headers^ b/netwerk/test/browser/103_preload_anchor.html^headers^
new file mode 100644
index 0000000000..9e23c73b7f
--- /dev/null
+++ b/netwerk/test/browser/103_preload_anchor.html^headers^
@@ -0,0 +1 @@
+Cache-Control: no-cache
diff --git a/netwerk/test/browser/103_preload_anchor.html^informationalResponse^ b/netwerk/test/browser/103_preload_anchor.html^informationalResponse^
new file mode 100644
index 0000000000..1099062e15
--- /dev/null
+++ b/netwerk/test/browser/103_preload_anchor.html^informationalResponse^
@@ -0,0 +1,2 @@
+HTTP 103 Early Hints
+Link: <netwerk/test/browser/early_hint_pixel.sjs?f5a05cb8-43e6-4868-bc0f-ca453ef87826>; rel=preload; as=image; anchor="/browser/"
diff --git a/netwerk/test/browser/103_preload_and_404.html b/netwerk/test/browser/103_preload_and_404.html
new file mode 100644
index 0000000000..f09f5cb085
--- /dev/null
+++ b/netwerk/test/browser/103_preload_and_404.html
@@ -0,0 +1,6 @@
+<html>
+ <head><title>404 Not Found</title></head>
+ <body>
+ <h1>404 Not Found</h1>
+ </body>
+</html>
diff --git a/netwerk/test/browser/103_preload_and_404.html^headers^ b/netwerk/test/browser/103_preload_and_404.html^headers^
new file mode 100644
index 0000000000..937e38c6c4
--- /dev/null
+++ b/netwerk/test/browser/103_preload_and_404.html^headers^
@@ -0,0 +1 @@
+HTTP 404 Not Found
diff --git a/netwerk/test/browser/103_preload_and_404.html^informationalResponse^ b/netwerk/test/browser/103_preload_and_404.html^informationalResponse^
new file mode 100644
index 0000000000..78cb7efea4
--- /dev/null
+++ b/netwerk/test/browser/103_preload_and_404.html^informationalResponse^
@@ -0,0 +1,2 @@
+HTTP 103 Early Hints
+Link: <https://example.com/browser/netwerk/test/browser/square.png>; rel=preload; as=image
diff --git a/netwerk/test/browser/103_preload_csp_imgsrc_none.html b/netwerk/test/browser/103_preload_csp_imgsrc_none.html
new file mode 100644
index 0000000000..367e80a6b3
--- /dev/null
+++ b/netwerk/test/browser/103_preload_csp_imgsrc_none.html
@@ -0,0 +1,6 @@
+<!DOCTYPE html>
+<html>
+<body>
+<img src="https://example.com/browser/netwerk/test/browser/early_hint_pixel.sjs?1ac2a5e1-90c7-4171-b0f0-676f7d899af3" width="100px">
+</body>
+</html>
diff --git a/netwerk/test/browser/103_preload_csp_imgsrc_none.html^headers^ b/netwerk/test/browser/103_preload_csp_imgsrc_none.html^headers^
new file mode 100644
index 0000000000..b4dedd0812
--- /dev/null
+++ b/netwerk/test/browser/103_preload_csp_imgsrc_none.html^headers^
@@ -0,0 +1,2 @@
+Cache-Control: no-cache
+Content-Security-Policy: img-src 'none'
diff --git a/netwerk/test/browser/103_preload_csp_imgsrc_none.html^informationalResponse^ b/netwerk/test/browser/103_preload_csp_imgsrc_none.html^informationalResponse^
new file mode 100644
index 0000000000..d82224fd07
--- /dev/null
+++ b/netwerk/test/browser/103_preload_csp_imgsrc_none.html^informationalResponse^
@@ -0,0 +1,2 @@
+HTTP 103 Too Early
+Link: <https://example.com/browser/netwerk/test/browser/early_hint_pixel.sjs?1ac2a5e1-90c7-4171-b0f0-676f7d899af3>; rel=preload; as=image
diff --git a/netwerk/test/browser/103_preload_iframe.html b/netwerk/test/browser/103_preload_iframe.html
new file mode 100644
index 0000000000..815a14220f
--- /dev/null
+++ b/netwerk/test/browser/103_preload_iframe.html
@@ -0,0 +1,6 @@
+<!DOCTYPE html>
+<html>
+<body>
+<iframe src="/browser/netwerk/test/browser/early_hint_main_html.sjs?early_hint_pixel.sjs=5ecccd01-dd3f-4bbd-bd3e-0491d7dd78a1" width="100px">
+</body>
+</html>
diff --git a/netwerk/test/browser/103_preload_iframe.html^headers^ b/netwerk/test/browser/103_preload_iframe.html^headers^
new file mode 100644
index 0000000000..9e23c73b7f
--- /dev/null
+++ b/netwerk/test/browser/103_preload_iframe.html^headers^
@@ -0,0 +1 @@
+Cache-Control: no-cache
diff --git a/netwerk/test/browser/auth_post.sjs b/netwerk/test/browser/auth_post.sjs
new file mode 100644
index 0000000000..8c3e723558
--- /dev/null
+++ b/netwerk/test/browser/auth_post.sjs
@@ -0,0 +1,37 @@
+const CC = Components.Constructor;
+const BinaryInputStream = CC(
+ "@mozilla.org/binaryinputstream;1",
+ "nsIBinaryInputStream",
+ "setInputStream"
+);
+
+function readStream(inputStream) {
+ let available = 0;
+ let result = [];
+ while ((available = inputStream.available()) > 0) {
+ result.push(inputStream.readBytes(available));
+ }
+
+ return result.join("");
+}
+
+function handleRequest(request, response) {
+ if (request.method != "POST") {
+ response.setStatusLine(request.httpVersion, 405, "Method Not Allowed");
+ return;
+ }
+
+ if (request.hasHeader("Authorization")) {
+ let data = "";
+ try {
+ data = readStream(new BinaryInputStream(request.bodyInputStream));
+ } catch (e) {
+ data = `${e}`;
+ }
+ response.bodyOutputStream.write(data, data.length);
+ return;
+ }
+
+ response.setStatusLine(request.httpVersion, 401, "Unauthorized");
+ response.setHeader("WWW-Authenticate", `basic realm="test"`, true);
+}
diff --git a/netwerk/test/browser/browser.toml b/netwerk/test/browser/browser.toml
new file mode 100644
index 0000000000..9a470d8c59
--- /dev/null
+++ b/netwerk/test/browser/browser.toml
@@ -0,0 +1,188 @@
+[DEFAULT]
+support-files = [
+ "dummy.html",
+ "ioactivity.html",
+ "redirect.sjs",
+ "auth_post.sjs",
+ "early_hint_main_html.sjs",
+ "early_hint_pixel_count.sjs",
+ "early_hint_redirect.sjs",
+ "early_hint_redirect_html.sjs",
+ "early_hint_pixel.sjs",
+ "early_hint_error.sjs",
+ "early_hint_asset.sjs",
+ "early_hint_asset_html.sjs",
+ "early_hint_csp_options_html.sjs",
+ "early_hint_preconnect_html.sjs",
+ "post.html",
+ "res.css",
+ "res.css^headers^",
+ "res.csv",
+ "res.csv^headers^",
+ "res_206.html",
+ "res_206.html^headers^",
+ "res_nosniff.html",
+ "res_nosniff.html^headers^",
+ "res_img.png",
+ "res_nosniff2.html",
+ "res_nosniff2.html^headers^",
+ "res_not_ok.html",
+ "res_not_ok.html^headers^",
+ "res.unknown",
+ "res_img_unknown.png",
+ "res.mp3",
+ "res_invalid_partial.mp3",
+ "res_invalid_partial.mp3^headers^",
+ "res_206.mp3",
+ "res_206.mp3^headers^",
+ "res_not_200or206.mp3",
+ "res_not_200or206.mp3^headers^",
+ "res_img_for_unknown_decoder",
+ "res_img_for_unknown_decoder^headers^",
+ "res_object.html",
+ "res_sub_document.html",
+ "res_empty.zip",
+ "res_http_index_format",
+ "res_http_index_format^headers^",
+ "square.png",
+ "103_preload.html",
+ "103_preload.html^informationalResponse^",
+ "103_preload.html^headers^",
+ "no_103_preload.html",
+ "no_103_preload.html^headers^",
+ "103_preload_anchor.html^informationalResponse^",
+ "103_preload_anchor.html^headers^",
+ "103_preload_anchor.html",
+ "103_preload_and_404.html^informationalResponse^",
+ "103_preload_and_404.html^headers^",
+ "103_preload_and_404.html",
+ "103_preload_iframe.html",
+ "103_preload_iframe.html^headers^",
+ "103_preload_csp_imgsrc_none.html",
+ "103_preload_csp_imgsrc_none.html^headers^",
+ "103_preload_csp_imgsrc_none.html^informationalResponse^",
+ "cookie_filtering_resource.sjs",
+ "cookie_filtering_secure_resource_com.html",
+ "cookie_filtering_secure_resource_com.html^headers^",
+ "cookie_filtering_secure_resource_org.html",
+ "cookie_filtering_secure_resource_org.html^headers^",
+ "cookie_filtering_square.png",
+ "cookie_filtering_square.png^headers^",
+ "x_frame_options.html",
+ "x_frame_options.html^headers^",
+ "test_1629307.html",
+ "file_link_header.sjs",
+]
+
+["browser_103_assets.js"]
+
+["browser_103_assets_extension.js"]
+
+["browser_103_cleanup.js"]
+
+["browser_103_csp.js"]
+support-files = ["early_hint_preload_test_helper.sys.mjs",]
+
+["browser_103_csp_images.js"]
+support-files = ["early_hint_preload_test_helper.sys.mjs",]
+
+["browser_103_csp_styles.js"]
+support-files = ["early_hint_preload_test_helper.sys.mjs",]
+
+["browser_103_error.js"]
+support-files = ["early_hint_preload_test_helper.sys.mjs",]
+
+["browser_103_no_cancel_on_error.js"]
+support-files = ["early_hint_preload_test_helper.sys.mjs",]
+
+["browser_103_preconnect.js"]
+
+["browser_103_preload.js"]
+support-files = ["early_hint_preload_test_helper.sys.mjs",]
+
+["browser_103_preload_2.js"]
+support-files = ["early_hint_preload_test_helper.sys.mjs",]
+
+["browser_103_private_window.js"]
+
+["browser_103_redirect.js"]
+support-files = ["early_hint_preload_test_helper.sys.mjs",]
+
+["browser_103_redirect_from_server.js"]
+
+["browser_103_referrer_policy.js"]
+support-files = [
+ "early_hint_referrer_policy_html.sjs",
+ "early_hint_preload_test_helper.sys.mjs",
+]
+
+["browser_103_telemetry.js"]
+
+["browser_103_user_load.js"]
+support-files = ["early_hint_preload_test_helper.sys.mjs",]
+
+["browser_NetUtil.js"]
+
+["browser_about_cache.js"]
+
+["browser_backgroundtask_purgeHTTPCache.js"]
+skip-if = [
+ "os == 'android'", # MultiInstanceLock doesn't work on Android
+ "os == 'mac'", # intermittent TV timeouts on Mac
+]
+
+["browser_bug1535877.js"]
+
+["browser_bug1629307.js"]
+fail-if = ["a11y_checks"] # Bug 1854523 clicked button may not be focusable
+
+["browser_child_resource.js"]
+run-if = ["crashreporter"]
+skip-if = ["os == 'win'"] # Bug 1775761
+
+["browser_cookie_filtering_basic.js"]
+
+["browser_cookie_filtering_cross_origin.js"]
+
+["browser_cookie_filtering_insecure.js"]
+
+["browser_cookie_filtering_oa.js"]
+
+["browser_cookie_filtering_subdomain.js"]
+
+["browser_cookie_sync_across_tabs.js"]
+
+["browser_fetch_lnk.js"]
+run-if = ["os == 'win'"]
+support-files = ["file_lnk.lnk",]
+
+["browser_http_index_format.js"]
+
+["browser_nsIFormPOSTActionChannel.js"]
+skip-if = ["true"] # protocol handler and channel does not work in content process
+
+["browser_post_auth.js"]
+skip-if = ["socketprocess_networking"] # Bug 1772209
+
+["browser_post_file.js"]
+
+["browser_purgeCache_idle_daily.js"]
+
+["browser_resource_navigation.js"]
+
+["browser_speculative_connection_link_header.js"]
+
+["browser_test_data_channel_observer.js"]
+
+["browser_test_favicon.js"]
+skip-if = ["verify && (os == 'linux' || os == 'mac')"]
+support-files = [
+ "damonbowling.jpg",
+ "damonbowling.jpg^headers^",
+ "file_favicon.html",
+]
+
+["browser_test_io_activity.js"]
+skip-if = ["socketprocess_networking"]
+
+["browser_test_offline_tab.js"]
diff --git a/netwerk/test/browser/browser_103_assets.js b/netwerk/test/browser/browser_103_assets.js
new file mode 100644
index 0000000000..4e32c8bf7c
--- /dev/null
+++ b/netwerk/test/browser/browser_103_assets.js
@@ -0,0 +1,169 @@
+/* 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/. */
+
+// On debug osx test machine, verify chaos mode takes slightly too long
+requestLongerTimeout(2);
+
+Services.prefs.setBoolPref("network.early-hints.enabled", true);
+
+const { request_count_checking } = ChromeUtils.importESModule(
+ "resource://testing-common/early_hint_preload_test_helper.sys.mjs"
+);
+
+// - testName is just there to be printed during Asserts when failing
+// - asset is the asset type, see early_hint_asset_html.sjs for possible values
+// for the asset type fetch see test_hint_fetch due to timing issues
+// - variant:
+// - "normal": no early hints, expects one normal request expected
+// - "hinted": early hints sent, expects one hinted request
+// - "reload": early hints sent, resources non-cacheable, two early-hint requests expected
+// - "cached": same as reload, but resources are cacheable, so only one hinted network request expected
+async function test_hint_asset(testName, asset, variant) {
+ // reset the count
+ let headers = new Headers();
+ headers.append("X-Early-Hint-Count-Start", "");
+ await fetch(
+ "http://example.com/browser/netwerk/test/browser/early_hint_pixel_count.sjs",
+ { headers }
+ );
+
+ let requestUrl = `https://example.com/browser/netwerk/test/browser/early_hint_asset_html.sjs?as=${asset}&hinted=${
+ variant !== "normal" ? "1" : "0"
+ }&cached=${variant === "cached" ? "1" : "0"}`;
+
+ let numConnectBackRemaining = 0;
+ if (variant === "hinted") {
+ numConnectBackRemaining = 1;
+ } else if (variant === "reload" || variant === "cached") {
+ numConnectBackRemaining = 2;
+ }
+
+ let observer = {
+ QueryInterface: ChromeUtils.generateQI(["nsIObserver"]),
+ observe(aSubject, aTopic, aData) {
+ if (aTopic == "earlyhints-connectback") {
+ numConnectBackRemaining -= 1;
+ }
+ },
+ };
+ Services.obs.addObserver(observer, "earlyhints-connectback");
+
+ await BrowserTestUtils.withNewTab(
+ {
+ gBrowser,
+ url: requestUrl,
+ waitForLoad: true,
+ },
+ async function (browser) {
+ if (asset === "fetch") {
+ // wait until the fetch is complete
+ await TestUtils.waitForCondition(_ => {
+ return SpecialPowers.spawn(browser, [], _ => {
+ return (
+ content.document.getElementsByTagName("h2")[0] != undefined &&
+ content.document.getElementsByTagName("h2")[0].textContent !==
+ "Fetching..." // default text set by early_hint_asset_html.sjs
+ );
+ });
+ });
+ }
+
+ // reload
+ if (variant === "reload" || variant === "cached") {
+ await BrowserTestUtils.reloadTab(gBrowser.selectedTab);
+ }
+
+ if (asset === "fetch") {
+ // wait until the fetch is complete
+ await TestUtils.waitForCondition(_ => {
+ return SpecialPowers.spawn(browser, [], _ => {
+ return (
+ content.document.getElementsByTagName("h2")[0] != undefined &&
+ content.document.getElementsByTagName("h2")[0].textContent !==
+ "Fetching..." // default text set by early_hint_asset_html.sjs
+ );
+ });
+ });
+ }
+ }
+ );
+ Services.obs.removeObserver(observer, "earlyhints-connectback");
+
+ let gotRequestCount = await fetch(
+ "http://example.com/browser/netwerk/test/browser/early_hint_pixel_count.sjs"
+ ).then(response => response.json());
+ Assert.equal(
+ numConnectBackRemaining,
+ 0,
+ `${testName} (${asset}) no remaining connect back expected`
+ );
+
+ let expectedRequestCount;
+ if (variant === "normal") {
+ expectedRequestCount = { hinted: 0, normal: 1 };
+ } else if (variant === "hinted") {
+ expectedRequestCount = { hinted: 1, normal: 0 };
+ } else if (variant === "reload") {
+ expectedRequestCount = { hinted: 2, normal: 0 };
+ } else if (variant === "cached") {
+ expectedRequestCount = { hinted: 1, normal: 0 };
+ }
+
+ await request_count_checking(
+ `${testName} (${asset})`,
+ gotRequestCount,
+ expectedRequestCount
+ );
+ if (variant === "cached") {
+ Services.cache2.clear();
+ }
+}
+
+// preload image
+add_task(async function test_103_asset_image() {
+ await test_hint_asset("test_103_asset_normal", "image", "normal");
+ await test_hint_asset("test_103_asset_hinted", "image", "hinted");
+ await test_hint_asset("test_103_asset_reload", "image", "reload");
+ // TODO(Bug 1815884): await test_hint_asset("test_103_asset_cached", "image", "cached");
+});
+
+// preload css
+add_task(async function test_103_asset_style() {
+ await test_hint_asset("test_103_asset_normal", "style", "normal");
+ await test_hint_asset("test_103_asset_hinted", "style", "hinted");
+ await test_hint_asset("test_103_asset_reload", "style", "reload");
+ // TODO(Bug 1815884): await test_hint_asset("test_103_asset_cached", "style", "cached");
+});
+
+// preload javascript
+add_task(async function test_103_asset_javascript() {
+ await test_hint_asset("test_103_asset_normal", "script", "normal");
+ await test_hint_asset("test_103_asset_hinted", "script", "hinted");
+ await test_hint_asset("test_103_asset_reload", "script", "reload");
+ await test_hint_asset("test_103_asset_cached", "script", "cached");
+});
+
+// preload javascript module
+add_task(async function test_103_asset_module() {
+ await test_hint_asset("test_103_asset_normal", "module", "normal");
+ await test_hint_asset("test_103_asset_hinted", "module", "hinted");
+ await test_hint_asset("test_103_asset_reload", "module", "reload");
+ await test_hint_asset("test_103_asset_cached", "module", "cached");
+});
+
+// preload font
+add_task(async function test_103_asset_font() {
+ await test_hint_asset("test_103_asset_normal", "font", "normal");
+ await test_hint_asset("test_103_asset_hinted", "font", "hinted");
+ await test_hint_asset("test_103_asset_reload", "font", "reload");
+ await test_hint_asset("test_103_asset_cached", "font", "cached");
+});
+
+// preload fetch
+add_task(async function test_103_asset_fetch() {
+ await test_hint_asset("test_103_asset_normal", "fetch", "normal");
+ await test_hint_asset("test_103_asset_hinted", "fetch", "hinted");
+ await test_hint_asset("test_103_asset_reload", "fetch", "reload");
+ await test_hint_asset("test_103_asset_cached", "fetch", "cached");
+});
diff --git a/netwerk/test/browser/browser_103_assets_extension.js b/netwerk/test/browser/browser_103_assets_extension.js
new file mode 100644
index 0000000000..dbd1061380
--- /dev/null
+++ b/netwerk/test/browser/browser_103_assets_extension.js
@@ -0,0 +1,78 @@
+/* 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/. */
+
+Services.prefs.setBoolPref("network.early-hints.enabled", true);
+
+registerCleanupFunction(function () {
+ Services.prefs.clearUserPref("network.early-hints.enabled");
+});
+
+const { request_count_checking } = ChromeUtils.importESModule(
+ "resource://testing-common/early_hint_preload_test_helper.sys.mjs"
+);
+
+// Test steps:
+// 1. Install an extension to observe the tabId.
+// 2. Load early_hint_asset_html.sjs?as=image, so we should see a hinted
+// request to early_hint_asset.sjs?as=image.
+// 3. Check if the hinted request has the same tabId as
+// early_hint_asset_html.sys.
+add_task(async function test_103_asset_with_extension() {
+ let extension = ExtensionTestUtils.loadExtension({
+ manifest: {
+ permissions: [
+ "webRequest",
+ "webRequestBlocking",
+ "https://example.com/*",
+ ],
+ },
+ background() {
+ let { browser } = this;
+ browser.webRequest.onBeforeRequest.addListener(
+ details => {
+ browser.test.sendMessage("request", {
+ url: details.url,
+ tabId: details.tabId,
+ });
+ return undefined;
+ },
+ { urls: ["https://example.com/*"] },
+ ["blocking"]
+ );
+ },
+ });
+
+ await extension.startup();
+
+ // reset the count
+ let headers = new Headers();
+ headers.append("X-Early-Hint-Count-Start", "");
+ await fetch(
+ "https://example.com/browser/netwerk/test/browser/early_hint_pixel_count.sjs",
+ { headers }
+ );
+
+ let baseUrl = "https://example.com/browser/netwerk/test/browser/";
+ let requestUrl = `${baseUrl}early_hint_asset_html.sjs?as=image&hinted=1`;
+ let assetUrl = `${baseUrl}early_hint_asset.sjs?as=image`;
+
+ await BrowserTestUtils.withNewTab(
+ {
+ gBrowser,
+ url: requestUrl,
+ waitForLoad: true,
+ },
+ async function () {}
+ );
+
+ const result1 = await extension.awaitMessage("request");
+ Assert.equal(result1.url, requestUrl);
+ let tabId = result1.tabId;
+
+ const result2 = await extension.awaitMessage("request");
+ Assert.equal(result2.url, assetUrl);
+ Assert.equal(result2.tabId, tabId);
+
+ await extension.unload();
+});
diff --git a/netwerk/test/browser/browser_103_cleanup.js b/netwerk/test/browser/browser_103_cleanup.js
new file mode 100644
index 0000000000..c823f5f01a
--- /dev/null
+++ b/netwerk/test/browser/browser_103_cleanup.js
@@ -0,0 +1,47 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+Services.prefs.setBoolPref("network.early-hints.enabled", true);
+
+add_task(async function test_103_cancel_parent_connect() {
+ Services.prefs.setIntPref("network.early-hints.parent-connect-timeout", 1);
+
+ let callback;
+ let promise = new Promise(resolve => {
+ callback = resolve;
+ });
+ let observed_cancel_reason = "";
+ let observer = {
+ QueryInterface: ChromeUtils.generateQI(["nsIObserver"]),
+ observe(aSubject, aTopic, aData) {
+ aSubject = aSubject.QueryInterface(Ci.nsIRequest);
+ if (
+ aTopic == "http-on-stop-request" &&
+ aSubject.name ==
+ "https://example.com/browser/netwerk/test/browser/square.png"
+ ) {
+ observed_cancel_reason = aSubject.canceledReason;
+ Services.obs.removeObserver(observer, "http-on-stop-request");
+ callback();
+ }
+ },
+ };
+ Services.obs.addObserver(observer, "http-on-stop-request");
+
+ // test that no crash or memory leak happens when cancelling before
+ await BrowserTestUtils.withNewTab(
+ {
+ gBrowser,
+ url: "https://example.com/browser/netwerk/test/browser/103_preload.html",
+ waitForLoad: true,
+ },
+ async function () {}
+ );
+ await promise;
+ Assert.equal(observed_cancel_reason, "parent-connect-timeout");
+
+ Services.prefs.clearUserPref("network.early-hints.parent-connect-timeout");
+});
diff --git a/netwerk/test/browser/browser_103_csp.js b/netwerk/test/browser/browser_103_csp.js
new file mode 100644
index 0000000000..1786bac454
--- /dev/null
+++ b/netwerk/test/browser/browser_103_csp.js
@@ -0,0 +1,86 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+Services.prefs.setBoolPref("network.early-hints.enabled", true);
+
+const { test_preload_hint_and_request } = ChromeUtils.importESModule(
+ "resource://testing-common/early_hint_preload_test_helper.sys.mjs"
+);
+
+add_task(async function test_preload_images_csp_in_early_hints_response() {
+ let tests = [
+ {
+ input: {
+ test_name: "image - no csp",
+ resource_type: "image",
+ csp: "",
+ csp_in_early_hint: "",
+ host: "",
+ hinted: true,
+ },
+ expected: { hinted: 1, normal: 0 },
+ },
+ {
+ input: {
+ test_name: "image img-src 'self';",
+ resource_type: "image",
+ csp: "",
+ csp_in_early_hint: "img-src 'self';",
+ host: "",
+ hinted: true,
+ },
+ expected: { hinted: 1, normal: 0 },
+ },
+ {
+ input: {
+ test_name: "image img-src 'self'; same host provided",
+ resource_type: "image",
+ csp: "",
+ csp_in_early_hint: "img-src 'self';",
+ host: "https://example.com/browser/netwerk/test/browser/",
+ hinted: true,
+ },
+ expected: { hinted: 1, normal: 0 },
+ },
+ {
+ input: {
+ test_name: "image img-src 'self'; other host provided",
+ resource_type: "image",
+ csp: "",
+ csp_in_early_hint: "img-src 'self';",
+ host: "https://example.org/browser/netwerk/test/browser/",
+ hinted: true,
+ },
+ expected: { hinted: 0, normal: 1 },
+ },
+ {
+ input: {
+ test_name: "image img-src 'none';",
+ resource_type: "image",
+ csp: "",
+ csp_in_early_hint: "img-src 'none';",
+ host: "",
+ hinted: true,
+ },
+ expected: { hinted: 0, normal: 1 },
+ },
+ {
+ input: {
+ test_name: "image img-src 'none'; same host provided",
+ resource_type: "image",
+ csp: "",
+ csp_in_early_hint: "img-src 'none';",
+ host: "https://example.com/browser/netwerk/test/browser/",
+ hinted: true,
+ },
+ expected: { hinted: 0, normal: 1 },
+ },
+ ];
+
+ for (let test of tests) {
+ await test_preload_hint_and_request(test.input, test.expected);
+ }
+});
diff --git a/netwerk/test/browser/browser_103_csp_images.js b/netwerk/test/browser/browser_103_csp_images.js
new file mode 100644
index 0000000000..c089f29898
--- /dev/null
+++ b/netwerk/test/browser/browser_103_csp_images.js
@@ -0,0 +1,170 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+Services.prefs.setBoolPref("network.early-hints.enabled", true);
+
+// This verifies hints, requests server-side and client-side that the image actually loaded
+async function test_image_preload_hint_request_loaded(
+ input,
+ expected_results,
+ image_should_load
+) {
+ // reset the count
+ let headers = new Headers();
+ headers.append("X-Early-Hint-Count-Start", "");
+ await fetch(
+ "https://example.com/browser/netwerk/test/browser/early_hint_pixel_count.sjs",
+ { headers }
+ );
+
+ let requestUrl = `https://example.com/browser/netwerk/test/browser/early_hint_csp_options_html.sjs?as=${
+ input.resource_type
+ }&hinted=${input.hinted ? "1" : "0"}${input.csp ? "&csp=" + input.csp : ""}${
+ input.csp_in_early_hint
+ ? "&csp_in_early_hint=" + input.csp_in_early_hint
+ : ""
+ }${input.host ? "&host=" + input.host : ""}`;
+
+ console.log("requestUrl: " + requestUrl);
+
+ await BrowserTestUtils.withNewTab(
+ {
+ gBrowser,
+ url: requestUrl,
+ waitForLoad: true,
+ },
+ async function (browser) {
+ let imageLoaded = await ContentTask.spawn(browser, [], function () {
+ let image = content.document.getElementById("test_image");
+ return image && image.complete && image.naturalHeight !== 0;
+ });
+ await Assert.ok(
+ image_should_load == imageLoaded,
+ "test_image_preload_hint_request_loaded: the image can be loaded as expected " +
+ requestUrl
+ );
+ }
+ );
+
+ let gotRequestCount = await fetch(
+ "https://example.com/browser/netwerk/test/browser/early_hint_pixel_count.sjs"
+ ).then(response => response.json());
+
+ await Assert.deepEqual(gotRequestCount, expected_results, input.test_name);
+
+ Services.cache2.clear();
+}
+
+// These tests verify whether or not the image actually loaded in the document
+add_task(async function test_images_loaded_with_csp() {
+ let tests = [
+ {
+ input: {
+ test_name: "image loaded - no csp",
+ resource_type: "image",
+ csp: "",
+ csp_in_early_hint: "",
+ host: "",
+ hinted: true,
+ },
+ expected: { hinted: 1, normal: 0 },
+ image_should_load: true,
+ },
+ {
+ input: {
+ test_name: "image loaded - img-src none",
+ resource_type: "image",
+ csp: "img-src 'none';",
+ csp_in_early_hint: "",
+ host: "",
+ hinted: true,
+ },
+ expected: { hinted: 1, normal: 0 },
+ image_should_load: false,
+ },
+ {
+ input: {
+ test_name: "image loaded - img-src none in EH response",
+ resource_type: "image",
+ csp: "",
+ csp_in_early_hint: "img-src 'none';",
+ host: "",
+ hinted: true,
+ },
+ expected: { hinted: 0, normal: 1 },
+ image_should_load: true,
+ },
+ {
+ input: {
+ test_name: "image loaded - img-src none in both headers",
+ resource_type: "image",
+ csp: "img-src 'none';",
+ csp_in_early_hint: "img-src 'none';",
+ host: "",
+ hinted: true,
+ },
+ expected: { hinted: 0, normal: 0 },
+ image_should_load: false,
+ },
+ {
+ input: {
+ test_name: "image loaded - img-src self",
+ resource_type: "image",
+ csp: "img-src 'self';",
+ csp_in_early_hint: "",
+ host: "",
+ hinted: true,
+ },
+ expected: { hinted: 1, normal: 0 },
+ image_should_load: true,
+ },
+ {
+ input: {
+ test_name: "image loaded - img-src self in EH response",
+ resource_type: "image",
+ csp: "",
+ csp_in_early_hint: "img-src 'self';",
+ host: "",
+ hinted: true,
+ },
+ expected: { hinted: 1, normal: 0 },
+ image_should_load: true,
+ },
+ {
+ input: {
+ test_name: "image loaded - conflicting csp, early hint skipped",
+ resource_type: "image",
+ csp: "img-src 'self';",
+ csp_in_early_hint: "img-src 'none';",
+ host: "",
+ hinted: true,
+ },
+ expected: { hinted: 0, normal: 1 },
+ image_should_load: true,
+ },
+ {
+ input: {
+ test_name:
+ "image loaded - conflicting csp, resource not loaded in document",
+ resource_type: "image",
+ csp: "img-src 'none';",
+ csp_in_early_hint: "img-src 'self';",
+ host: "",
+ hinted: true,
+ },
+ expected: { hinted: 1, normal: 0 },
+ image_should_load: false,
+ },
+ ];
+
+ for (let test of tests) {
+ await test_image_preload_hint_request_loaded(
+ test.input,
+ test.expected,
+ test.image_should_load
+ );
+ }
+});
diff --git a/netwerk/test/browser/browser_103_csp_styles.js b/netwerk/test/browser/browser_103_csp_styles.js
new file mode 100644
index 0000000000..59c2fc14be
--- /dev/null
+++ b/netwerk/test/browser/browser_103_csp_styles.js
@@ -0,0 +1,86 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+Services.prefs.setBoolPref("network.early-hints.enabled", true);
+
+const { test_preload_hint_and_request } = ChromeUtils.importESModule(
+ "resource://testing-common/early_hint_preload_test_helper.sys.mjs"
+);
+
+add_task(async function test_preload_styles_csp_in_response() {
+ let tests = [
+ {
+ input: {
+ test_name: "style - no csp",
+ resource_type: "style",
+ csp: "",
+ csp_in_early_hint: "",
+ host: "",
+ hinted: true,
+ },
+ expected: { hinted: 1, normal: 0 },
+ },
+ {
+ input: {
+ test_name: "style style-src 'self';",
+ resource_type: "style",
+ csp: "",
+ csp_in_early_hint: "style-src 'self';",
+ host: "",
+ hinted: true,
+ },
+ expected: { hinted: 1, normal: 0 },
+ },
+ {
+ input: {
+ test_name: "style style-src self; same host provided",
+ resource_type: "style",
+ csp: "",
+ csp_in_early_hint: "style-src 'self';",
+ host: "https://example.com/browser/netwerk/test/browser/",
+ hinted: true,
+ },
+ expected: { hinted: 1, normal: 0 },
+ },
+ {
+ input: {
+ test_name: "style style-src 'self'; other host provided",
+ resource_type: "style",
+ csp: "",
+ csp_in_early_hint: "style-src 'self';",
+ host: "https://example.org/browser/netwerk/test/browser/",
+ hinted: true,
+ },
+ expected: { hinted: 0, normal: 1 },
+ },
+ {
+ input: {
+ test_name: "style style-src 'none';",
+ resource_type: "style",
+ csp: "",
+ csp_in_early_hint: "style-src 'none';",
+ host: "",
+ hinted: true,
+ },
+ expected: { hinted: 0, normal: 1 },
+ },
+ {
+ input: {
+ test_name: "style style-src 'none'; other host provided",
+ resource_type: "style",
+ csp: "",
+ csp_in_early_hint: "style-src 'none';",
+ host: "https://example.org/browser/netwerk/test/browser/",
+ hinted: true,
+ },
+ expected: { hinted: 0, normal: 1 },
+ },
+ ];
+
+ for (let test of tests) {
+ await test_preload_hint_and_request(test.input, test.expected);
+ }
+});
diff --git a/netwerk/test/browser/browser_103_error.js b/netwerk/test/browser/browser_103_error.js
new file mode 100644
index 0000000000..a7a447aa7e
--- /dev/null
+++ b/netwerk/test/browser/browser_103_error.js
@@ -0,0 +1,121 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+Services.prefs.setBoolPref("network.early-hints.enabled", true);
+
+const { test_hint_preload } = ChromeUtils.importESModule(
+ "resource://testing-common/early_hint_preload_test_helper.sys.mjs"
+);
+
+// 400 Bad Request
+add_task(async function test_103_error_400() {
+ await test_hint_preload(
+ "test_103_error_400",
+ "https://example.com",
+ "https://example.com/browser/netwerk/test/browser/early_hint_error.sjs?400",
+ { hinted: 1, normal: 0 }
+ );
+});
+
+// 401 Unauthorized
+add_task(async function test_103_error_401() {
+ await test_hint_preload(
+ "test_103_error_401",
+ "https://example.com",
+ "https://example.com/browser/netwerk/test/browser/early_hint_error.sjs?401",
+ { hinted: 1, normal: 0 }
+ );
+});
+
+// 403 Forbidden
+add_task(async function test_103_error_403() {
+ await test_hint_preload(
+ "test_103_error_403",
+ "https://example.com",
+ "https://example.com/browser/netwerk/test/browser/early_hint_error.sjs?403",
+ { hinted: 1, normal: 0 }
+ );
+});
+
+// 404 Not Found
+add_task(async function test_103_error_404() {
+ await test_hint_preload(
+ "test_103_error_404",
+ "https://example.com",
+ "https://example.com/browser/netwerk/test/browser/early_hint_error.sjs?404",
+ { hinted: 1, normal: 0 }
+ );
+});
+
+// 408 Request Timeout
+add_task(async function test_103_error_408() {
+ await test_hint_preload(
+ "test_103_error_408",
+ "https://example.com",
+ "https://example.com/browser/netwerk/test/browser/early_hint_error.sjs?408",
+ { hinted: 1, normal: 0 }
+ );
+});
+
+// 410 Gone
+add_task(async function test_103_error_410() {
+ await test_hint_preload(
+ "test_103_error_410",
+ "https://example.com",
+ "https://example.com/browser/netwerk/test/browser/early_hint_error.sjs?410",
+ { hinted: 1, normal: 0 }
+ );
+});
+
+// 429 Too Many Requests
+add_task(async function test_103_error_429() {
+ await test_hint_preload(
+ "test_103_error_429",
+ "https://example.com",
+ "https://example.com/browser/netwerk/test/browser/early_hint_error.sjs?429",
+ { hinted: 1, normal: 0 }
+ );
+});
+
+// 500 Internal Server Error
+add_task(async function test_103_error_500() {
+ await test_hint_preload(
+ "test_103_error_500",
+ "https://example.com",
+ "https://example.com/browser/netwerk/test/browser/early_hint_error.sjs?500",
+ { hinted: 1, normal: 0 }
+ );
+});
+
+// 502 Bad Gateway
+add_task(async function test_103_error_502() {
+ await test_hint_preload(
+ "test_103_error_502",
+ "https://example.com",
+ "https://example.com/browser/netwerk/test/browser/early_hint_error.sjs?502",
+ { hinted: 1, normal: 0 }
+ );
+});
+
+// 503 Service Unavailable
+add_task(async function test_103_error_503() {
+ await test_hint_preload(
+ "test_103_error_503",
+ "https://example.com",
+ "https://example.com/browser/netwerk/test/browser/early_hint_error.sjs?503",
+ { hinted: 1, normal: 0 }
+ );
+});
+
+// 504 Gateway Timeout
+add_task(async function test_103_error_504() {
+ await test_hint_preload(
+ "test_103_error_504",
+ "https://example.com",
+ "https://example.com/browser/netwerk/test/browser/early_hint_error.sjs?504",
+ { hinted: 1, normal: 0 }
+ );
+});
diff --git a/netwerk/test/browser/browser_103_no_cancel_on_error.js b/netwerk/test/browser/browser_103_no_cancel_on_error.js
new file mode 100644
index 0000000000..2420441585
--- /dev/null
+++ b/netwerk/test/browser/browser_103_no_cancel_on_error.js
@@ -0,0 +1,67 @@
+/* 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/. */
+
+/* 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/. */
+
+Services.prefs.setBoolPref("network.early-hints.enabled", true);
+
+const { request_count_checking } = ChromeUtils.importESModule(
+ "resource://testing-common/early_hint_preload_test_helper.sys.mjs"
+);
+
+// - httpCode is the response code we're testing for. This file mostly covers 400 and 500 responses
+async function test_hint_completion_on_error(httpCode) {
+ // reset the count
+ let headers = new Headers();
+ headers.append("X-Early-Hint-Count-Start", "");
+ await fetch(
+ "https://example.com/browser/netwerk/test/browser/early_hint_pixel_count.sjs",
+ { headers }
+ );
+
+ let requestUrl = `https://example.com/browser/netwerk/test/browser/early_hint_asset_html.sjs?hinted=1&as=image&code=${httpCode}`;
+
+ await BrowserTestUtils.withNewTab(
+ {
+ gBrowser,
+ url: requestUrl,
+ waitForLoad: true,
+ },
+ async function () {}
+ );
+
+ let gotRequestCount = await fetch(
+ "https://example.com/browser/netwerk/test/browser/early_hint_pixel_count.sjs"
+ ).then(response => response.json());
+
+ await request_count_checking(`test_103_error_${httpCode}`, gotRequestCount, {
+ hinted: 1,
+ normal: 0,
+ });
+}
+
+// 400 Bad Request
+add_task(async function test_complete_103_on_400() {
+ await test_hint_completion_on_error(400);
+});
+add_task(async function test_complete_103_on_401() {
+ await test_hint_completion_on_error(401);
+});
+add_task(async function test_complete_103_on_402() {
+ await test_hint_completion_on_error(402);
+});
+add_task(async function test_complete_103_on_403() {
+ await test_hint_completion_on_error(403);
+});
+add_task(async function test_complete_103_on_500() {
+ await test_hint_completion_on_error(500);
+});
+add_task(async function test_complete_103_on_501() {
+ await test_hint_completion_on_error(501);
+});
+add_task(async function test_complete_103_on_502() {
+ await test_hint_completion_on_error(502);
+});
diff --git a/netwerk/test/browser/browser_103_preconnect.js b/netwerk/test/browser/browser_103_preconnect.js
new file mode 100644
index 0000000000..dcdcc1b138
--- /dev/null
+++ b/netwerk/test/browser/browser_103_preconnect.js
@@ -0,0 +1,71 @@
+/* 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/. */
+
+Services.prefs.setBoolPref("network.early-hints.enabled", true);
+Services.prefs.setBoolPref("network.early-hints.preconnect.enabled", true);
+Services.prefs.setBoolPref("network.http.debug-observations", true);
+Services.prefs.setIntPref("network.early-hints.preconnect.max_connections", 10);
+
+registerCleanupFunction(function () {
+ Services.prefs.clearUserPref("network.early-hints.enabled");
+ Services.prefs.clearUserPref("network.early-hints.preconnect.enabled");
+ Services.prefs.clearUserPref("network.http.debug-observations");
+ Services.prefs.clearUserPref(
+ "network.early-hints.preconnect.max_connections"
+ );
+});
+
+// Test steps:
+// 1. Load early_hint_preconnect_html.sjs
+// 2. In early_hint_preconnect_html.sjs, a 103 response with
+// "rel=preconnect" is returned.
+// 3. We use "speculative-connect-request" topic to observe whether the
+// speculative connection is attempted.
+// 4. Finally, we check if the observed URL is the same as the expected.
+async function test_hint_preconnect(href, crossOrigin) {
+ let requestUrl = `https://example.com/browser/netwerk/test/browser/early_hint_preconnect_html.sjs?href=${href}&crossOrigin=${crossOrigin}`;
+
+ let observed = "";
+ let observer = {
+ QueryInterface: ChromeUtils.generateQI(["nsIObserver"]),
+ observe(aSubject, aTopic, aData) {
+ if (aTopic == "speculative-connect-request") {
+ Services.obs.removeObserver(observer, "speculative-connect-request");
+ observed = aData;
+ }
+ },
+ };
+ Services.obs.addObserver(observer, "speculative-connect-request");
+
+ await BrowserTestUtils.withNewTab(
+ {
+ gBrowser,
+ url: requestUrl,
+ waitForLoad: true,
+ },
+ async function () {}
+ );
+
+ // Extracting "localhost:443"
+ let hostPortRegex = /\[.*\](.*?)\^/;
+ let hostPortMatch = hostPortRegex.exec(observed);
+ let hostPort = hostPortMatch ? hostPortMatch[1] : "";
+ // Extracting "%28https%2Cexample.com%29"
+ let partitionKeyRegex = /\^partitionKey=(.*)$/;
+ let partitionKeyMatch = partitionKeyRegex.exec(observed);
+ let partitionKey = partitionKeyMatch ? partitionKeyMatch[1] : "";
+ // See nsHttpConnectionInfo::BuildHashKey, the second character is A if this
+ // is an anonymous connection.
+ let anonymousFlag = observed[2];
+
+ Assert.equal(anonymousFlag, crossOrigin === "use-credentials" ? "." : "A");
+ Assert.equal(hostPort, "localhost:443");
+ Assert.equal(partitionKey, "%28https%2Cexample.com%29");
+}
+
+add_task(async function test_103_preconnect() {
+ await test_hint_preconnect("https://localhost", "use-credentials");
+ await test_hint_preconnect("https://localhost", "");
+ await test_hint_preconnect("https://localhost", "anonymous");
+});
diff --git a/netwerk/test/browser/browser_103_preload.js b/netwerk/test/browser/browser_103_preload.js
new file mode 100644
index 0000000000..977ae83d68
--- /dev/null
+++ b/netwerk/test/browser/browser_103_preload.js
@@ -0,0 +1,138 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+Services.prefs.setBoolPref("network.early-hints.enabled", true);
+// Disable mixed-content upgrading as this test is expecting HTTP image loads
+Services.prefs.setBoolPref(
+ "security.mixed_content.upgrade_display_content",
+ false
+);
+
+const { request_count_checking, test_preload_url, test_hint_preload } =
+ ChromeUtils.importESModule(
+ "resource://testing-common/early_hint_preload_test_helper.sys.mjs"
+ );
+
+// TODO testing:
+// * Abort main document load while early hint is still loading -> early hint should be aborted
+
+// Test that with early hint config option disabled, no early hint requests are made
+add_task(async function test_103_preload_disabled() {
+ Services.prefs.setBoolPref("network.early-hints.enabled", false);
+ await test_hint_preload(
+ "test_103_preload_disabled",
+ "https://example.com",
+ "https://example.com/browser/netwerk/test/browser/early_hint_pixel.sjs",
+ { hinted: 0, normal: 1 }
+ );
+ Services.prefs.setBoolPref("network.early-hints.enabled", true);
+});
+
+add_task(async function test_103_font_disabled() {
+ let url =
+ "https://example.com/browser/netwerk/test/browser/early_hint_asset_html.sjs?hinted=1&as=font";
+ Services.prefs.setBoolPref("gfx.downloadable_fonts.enabled", false);
+ await test_preload_url("font_loading_disabled", url, {
+ hinted: 0,
+ normal: 0,
+ });
+ Services.prefs.setBoolPref("gfx.downloadable_fonts.enabled", true);
+ await test_preload_url("font_loading_enabled", url, {
+ hinted: 1,
+ normal: 0,
+ });
+ Services.prefs.clearUserPref("gfx.downloadable_fonts.enabled");
+});
+
+// Preload with same origin in secure context with mochitest http proxy
+add_task(async function test_103_preload_https() {
+ await test_hint_preload(
+ "test_103_preload_https",
+ "https://example.org",
+ "/browser/netwerk/test/browser/early_hint_pixel.sjs",
+ { hinted: 1, normal: 0 }
+ );
+});
+
+// Preload with same origin in secure context
+add_task(async function test_103_preload() {
+ await test_hint_preload(
+ "test_103_preload",
+ "https://example.com",
+ "https://example.com/browser/netwerk/test/browser/early_hint_pixel.sjs",
+ { hinted: 1, normal: 0 }
+ );
+});
+
+// Cross origin preload in secure context
+add_task(async function test_103_preload_cor() {
+ await test_hint_preload(
+ "test_103_preload_cor",
+ "https://example.com",
+ "https://example.net/browser/netwerk/test/browser/early_hint_pixel.sjs",
+ { hinted: 1, normal: 0 }
+ );
+});
+
+// Cross origin preload in insecure context
+add_task(async function test_103_preload_insecure_cor() {
+ await test_hint_preload(
+ "test_103_preload_insecure_cor",
+ "https://example.com",
+ "http://mochi.test:8888/browser/netwerk/test/browser/early_hint_pixel.sjs",
+ { hinted: 0, normal: 1 }
+ );
+});
+
+// Same origin request with relative url
+add_task(async function test_103_relative_preload() {
+ await test_hint_preload(
+ "test_103_relative_preload",
+ "https://example.com",
+ "/browser/netwerk/test/browser/early_hint_pixel.sjs",
+ { hinted: 1, normal: 0 }
+ );
+});
+
+// Early hint from insecure context
+add_task(async function test_103_insecure_preload() {
+ await test_hint_preload(
+ "test_103_insecure_preload",
+ "http://mochi.test:8888",
+ "/browser/netwerk/test/browser/early_hint_pixel.sjs",
+ { hinted: 0, normal: 1 }
+ );
+});
+
+// Cross origin preload from secure context to insecure context on same domain
+add_task(async function test_103_preload_mixed_content() {
+ await test_hint_preload(
+ "test_103_preload_mixed_content",
+ "https://example.org",
+ "http://example.org/browser/netwerk/test/browser/early_hint_pixel.sjs",
+ { hinted: 0, normal: 1 }
+ );
+});
+
+// Same preload from localhost to localhost should preload
+add_task(async function test_103_preload_localhost_to_localhost() {
+ await test_hint_preload(
+ "test_103_preload_localhost_to_localhost",
+ "http://127.0.0.1:8888",
+ "http://127.0.0.1:8888/browser/netwerk/test/browser/early_hint_pixel.sjs",
+ { hinted: 1, normal: 0 }
+ );
+});
+
+// Relative url, correct file for requested uri
+add_task(async function test_103_preload_only_file() {
+ await test_hint_preload(
+ "test_103_preload_only_file",
+ "https://example.com",
+ "early_hint_pixel.sjs",
+ { hinted: 1, normal: 0 }
+ );
+});
diff --git a/netwerk/test/browser/browser_103_preload_2.js b/netwerk/test/browser/browser_103_preload_2.js
new file mode 100644
index 0000000000..c9f92fdef5
--- /dev/null
+++ b/netwerk/test/browser/browser_103_preload_2.js
@@ -0,0 +1,180 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+Services.prefs.setBoolPref("network.early-hints.enabled", true);
+
+const {
+ test_hint_preload,
+ test_hint_preload_internal,
+ request_count_checking,
+} = ChromeUtils.importESModule(
+ "resource://testing-common/early_hint_preload_test_helper.sys.mjs"
+);
+
+// two early hint responses
+add_task(async function test_103_two_preload_responses() {
+ await test_hint_preload_internal(
+ "103_two_preload_responses",
+ "https://example.com",
+ [
+ [
+ "https://example.com/browser/netwerk/test/browser/early_hint_pixel.sjs",
+ Services.uuid.generateUUID().toString(),
+ ],
+ ["", "new_response"], // indicate new early hint response
+ [
+ "https://example.com/browser/netwerk/test/browser/early_hint_pixel.sjs",
+ Services.uuid.generateUUID().toString(),
+ ],
+ ],
+ { hinted: 1, normal: 1 }
+ );
+});
+
+// two link header in one early hint response
+add_task(async function test_103_two_link_header() {
+ await test_hint_preload_internal(
+ "103_two_link_header",
+ "https://example.com",
+ [
+ [
+ "https://example.com/browser/netwerk/test/browser/early_hint_pixel.sjs",
+ Services.uuid.generateUUID().toString(),
+ ],
+ ["", ""], // indicate new link header in same reponse
+ [
+ "https://example.com/browser/netwerk/test/browser/early_hint_pixel.sjs",
+ Services.uuid.generateUUID().toString(),
+ ],
+ ],
+ { hinted: 2, normal: 0 }
+ );
+});
+
+// two links in one early hint link header
+add_task(async function test_103_two_links() {
+ await test_hint_preload_internal(
+ "103_two_links",
+ "https://example.com",
+ [
+ [
+ "https://example.com/browser/netwerk/test/browser/early_hint_pixel.sjs",
+ Services.uuid.generateUUID().toString(),
+ ],
+ [
+ "https://example.com/browser/netwerk/test/browser/early_hint_pixel.sjs",
+ Services.uuid.generateUUID().toString(),
+ ],
+ ],
+ { hinted: 2, normal: 0 }
+ );
+});
+
+// two early hint responses, only second one has a link header
+add_task(async function test_103_two_links() {
+ await test_hint_preload_internal(
+ "103_two_links",
+ "https://example.com",
+ [
+ ["", "non_link_header"], // indicate non-link related header
+ ["", "new_response"], // indicate new early hint response
+ [
+ "https://example.com/browser/netwerk/test/browser/early_hint_pixel.sjs",
+ Services.uuid.generateUUID().toString(),
+ ],
+ ],
+ { hinted: 1, normal: 0 }
+ );
+});
+
+// Preload twice same origin in secure context
+add_task(async function test_103_preload_twice() {
+ // pass two times the same uuid so that on the second request, the response is
+ // already in the cache
+ let uuid = Services.uuid.generateUUID();
+ await test_hint_preload(
+ "test_103_preload_twice_1",
+ "https://example.com",
+ "https://example.com/browser/netwerk/test/browser/early_hint_pixel.sjs",
+ { hinted: 1, normal: 0 },
+ uuid
+ );
+ await test_hint_preload(
+ "test_103_preload_twice_2",
+ "https://example.com",
+ "https://example.com/browser/netwerk/test/browser/early_hint_pixel.sjs",
+ { hinted: 0, normal: 0 },
+ uuid
+ );
+});
+
+// Test that preloads in iframes don't get triggered
+add_task(async function test_103_iframe() {
+ // reset the count
+ let headers = new Headers();
+ headers.append("X-Early-Hint-Count-Start", "");
+ await fetch(
+ "https://example.com/browser/netwerk/test/browser/early_hint_pixel_count.sjs",
+ { headers }
+ );
+
+ let iframeUri =
+ "https://example.com/browser/netwerk/test/browser/103_preload_iframe.html";
+
+ await BrowserTestUtils.withNewTab(
+ {
+ gBrowser,
+ url: iframeUri,
+ waitForLoad: true,
+ },
+ async function () {}
+ );
+
+ let gotRequestCount = await fetch(
+ "https://example.com/browser/netwerk/test/browser/early_hint_pixel_count.sjs"
+ ).then(response => response.json());
+ let expectedRequestCount = { hinted: 0, normal: 1 };
+
+ await request_count_checking(
+ "test_103_iframe",
+ gotRequestCount,
+ expectedRequestCount
+ );
+
+ Services.cache2.clear();
+});
+
+// Test that anchors are parsed
+add_task(async function test_103_anchor() {
+ // reset the count
+ let headers = new Headers();
+ headers.append("X-Early-Hint-Count-Start", "");
+ await fetch(
+ "https://example.com/browser/netwerk/test/browser/early_hint_pixel_count.sjs",
+ { headers }
+ );
+
+ let anchorUri =
+ "https://example.com/browser/netwerk/test/browser/103_preload_anchor.html";
+
+ await BrowserTestUtils.withNewTab(
+ {
+ gBrowser,
+ url: anchorUri,
+ waitForLoad: true,
+ },
+ async function () {}
+ );
+
+ let gotRequestCount = await fetch(
+ "https://example.com/browser/netwerk/test/browser/early_hint_pixel_count.sjs"
+ ).then(response => response.json());
+
+ await request_count_checking("test_103_anchor", gotRequestCount, {
+ hinted: 0,
+ normal: 1,
+ });
+});
diff --git a/netwerk/test/browser/browser_103_private_window.js b/netwerk/test/browser/browser_103_private_window.js
new file mode 100644
index 0000000000..7d23ea4b28
--- /dev/null
+++ b/netwerk/test/browser/browser_103_private_window.js
@@ -0,0 +1,70 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+Services.prefs.setBoolPref("network.early-hints.enabled", true);
+
+registerCleanupFunction(function () {
+ Services.prefs.clearUserPref("network.early-hints.enabled");
+});
+
+// Test steps:
+// 1. Load early_hint_asset_html.sjs with a provided uuid.
+// 2. In early_hint_asset_html.sjs, a 103 response with
+// a Link header<early_hint_asset.sjs> and the provided uuid is returned.
+// 3. We use "http-on-opening-request" topic to observe whether the
+// early hinted request is created.
+// 4. Finally, we check if the request has the correct `isPrivate` value.
+async function test_early_hints_load_url(usePrivateWin) {
+ let headers = new Headers();
+ headers.append("X-Early-Hint-Count-Start", "");
+ await fetch(
+ "https://example.com/browser/netwerk/test/browser/early_hint_pixel_count.sjs",
+ { headers }
+ );
+
+ // Open a browsing window.
+ const win = await BrowserTestUtils.openNewBrowserWindow({
+ private: usePrivateWin,
+ });
+
+ let id = Services.uuid.generateUUID().toString();
+ let expectedUrl = `https://example.com/browser/netwerk/test/browser/early_hint_asset.sjs?as=fetch&uuid=${id}`;
+ let observed = {};
+ let observer = {
+ QueryInterface: ChromeUtils.generateQI(["nsIObserver"]),
+ observe(aSubject, aTopic, aData) {
+ if (aTopic == "http-on-opening-request") {
+ let channel = aSubject.QueryInterface(Ci.nsIHttpChannel);
+ if (channel.URI.spec === expectedUrl) {
+ observed.actrualUrl = channel.URI.spec;
+ let isPrivate = channel.QueryInterface(
+ Ci.nsIPrivateBrowsingChannel
+ ).isChannelPrivate;
+ observed.isPrivate = isPrivate;
+ Services.obs.removeObserver(observer, "http-on-opening-request");
+ }
+ }
+ },
+ };
+ Services.obs.addObserver(observer, "http-on-opening-request");
+
+ let requestUrl = `https://example.com/browser/netwerk/test/browser/early_hint_asset_html.sjs?as=fetch&hinted=1&uuid=${id}`;
+
+ const browser = win.gBrowser.selectedTab.linkedBrowser;
+ let loaded = BrowserTestUtils.browserLoaded(browser, false, requestUrl);
+ BrowserTestUtils.startLoadingURIString(browser, requestUrl);
+ await loaded;
+
+ Assert.equal(observed.actrualUrl, expectedUrl);
+ Assert.equal(observed.isPrivate, usePrivateWin);
+
+ await BrowserTestUtils.closeWindow(win);
+}
+
+add_task(async function test_103_private_window() {
+ await test_early_hints_load_url(true);
+ await test_early_hints_load_url(false);
+});
diff --git a/netwerk/test/browser/browser_103_redirect.js b/netwerk/test/browser/browser_103_redirect.js
new file mode 100644
index 0000000000..d396813d50
--- /dev/null
+++ b/netwerk/test/browser/browser_103_redirect.js
@@ -0,0 +1,52 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+Services.prefs.setBoolPref("network.early-hints.enabled", true);
+
+const { test_hint_preload, request_count_checking } =
+ ChromeUtils.importESModule(
+ "resource://testing-common/early_hint_preload_test_helper.sys.mjs"
+ );
+
+// Early hint to redirect to same origin in secure context
+add_task(async function test_103_redirect_same_origin() {
+ await test_hint_preload(
+ "test_103_redirect_same_origin",
+ "https://example.com",
+ "https://example.com/browser/netwerk/test/browser/early_hint_redirect.sjs?https://example.com/browser/netwerk/test/browser/early_hint_pixel.sjs",
+ { hinted: 2, normal: 0 } // successful preload of redirect and resulting image
+ );
+});
+
+// Early hint to redirect to cross origin in secure context
+add_task(async function test_103_redirect_cross_origin() {
+ await test_hint_preload(
+ "test_103_redirect_cross_origin",
+ "https://example.com",
+ "https://example.com/browser/netwerk/test/browser/early_hint_redirect.sjs?https://example.net/browser/netwerk/test/browser/early_hint_pixel.sjs",
+ { hinted: 2, normal: 0 }
+ );
+});
+
+// Early hint to redirect to cross origin in insecure context
+add_task(async function test_103_redirect_insecure_cross_origin() {
+ await test_hint_preload(
+ "test_103_redirect_insecure_cross_origin",
+ "https://example.com",
+ "https://example.com/browser/netwerk/test/browser/early_hint_redirect.sjs?http://mochi.test:8888/browser/netwerk/test/browser/early_hint_pixel.sjs",
+ { hinted: 2, normal: 0 }
+ );
+});
+
+// Cross origin preload from secure context to redirected insecure context on same domain
+add_task(async function test_103_preload_redirect_mixed_content() {
+ await test_hint_preload(
+ "test_103_preload_redirect_mixed_content",
+ "https://example.org",
+ "https://example.org/browser/netwerk/test/browser/early_hint_redirect.sjs?http://example.org/browser/netwerk/test/browser/early_hint_pixel.sjs",
+ { hinted: 2, normal: 0 }
+ );
+});
diff --git a/netwerk/test/browser/browser_103_redirect_from_server.js b/netwerk/test/browser/browser_103_redirect_from_server.js
new file mode 100644
index 0000000000..0357d11516
--- /dev/null
+++ b/netwerk/test/browser/browser_103_redirect_from_server.js
@@ -0,0 +1,321 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+Services.prefs.setBoolPref("network.early-hints.enabled", true);
+
+registerCleanupFunction(function () {
+ Services.prefs.clearUserPref("network.early-hints.enabled");
+});
+
+// This function tests Early Hint responses before and in between HTTP redirects.
+//
+// Arguments:
+// - name: String identifying the test case for easier parsing in the log
+// - chain and destination: defines the redirect chain, see example below
+// note: ALL preloaded urls must be image urls
+// - expected: number of normal, cancelled and completed hinted responses.
+//
+// # Example
+// The parameter values of
+// ```
+// chain = [
+// {link:"https://link1", host:"https://host1.com"},
+// {link:"https://link2", host:"https://host2.com"},
+// ]
+// ```
+// and `destination = "https://host3.com/page.html" would result in the
+// following HTTP exchange (simplified):
+//
+// ```
+// > GET https://host1.com/redirect?something1
+//
+// < 103 Early Hints
+// < Link: <https://link1>;rel=preload;as=image
+// <
+// < 307 Temporary Redirect
+// < Location: https://host2.com/redirect?something2
+// <
+//
+// > GET https://host2.com/redirect?something2
+//
+// < 103 Early Hints
+// < Link: <https://link2>;rel=preload;as=image
+// <
+// < 307 Temporary Redirect
+// < Location: https://host3.com/page.html
+// <
+//
+// > GET https://host3.com/page.html
+//
+// < [...] Result depends on the final page
+// ```
+//
+// Legend:
+// * `>` indicates a request going from client to server
+// * `<` indicates a response going from server to client
+// * all lines are terminated with a `\r\n`
+//
+async function test_hint_redirect(
+ name,
+ chain,
+ destination,
+ hint_destination,
+ expected
+) {
+ // pass the full redirect chain as a url parameter. Each redirect is handled
+ // by `early_hint_redirect_html.sjs` which url-decodes the query string and
+ // redirects to the result
+ let links = [];
+ let url = destination;
+ for (let i = chain.length - 1; i >= 0; i--) {
+ let qp = new URLSearchParams();
+ if (chain[i].link != "") {
+ qp.append("link", "<" + chain[i].link + ">;rel=preload;as=image");
+ links.push(chain[i].link);
+ }
+ qp.append("location", url);
+
+ url = `${
+ chain[i].host
+ }/browser/netwerk/test/browser/early_hint_redirect_html.sjs?${qp.toString()}`;
+ }
+ if (hint_destination != "") {
+ links.push(hint_destination);
+ }
+
+ // reset the count
+ let headers = new Headers();
+ headers.append("X-Early-Hint-Count-Start", "");
+ await fetch(
+ "https://example.com/browser/netwerk/test/browser/early_hint_pixel_count.sjs",
+ { headers }
+ );
+
+ // main request and all other must get their respective OnStopRequest
+ let numRequestRemaining =
+ expected.normal + expected.hinted + expected.cancelled;
+ let observed = {
+ hinted: 0,
+ normal: 0,
+ cancelled: 0,
+ };
+ // store channelIds
+ let observedChannelIds = [];
+ let callback;
+ let promise = new Promise(resolve => {
+ callback = resolve;
+ });
+ if (numRequestRemaining > 0) {
+ let observer = {
+ QueryInterface: ChromeUtils.generateQI(["nsIObserver"]),
+ observe(aSubject, aTopic, aData) {
+ aSubject.QueryInterface(Ci.nsIIdentChannel);
+ let id = aSubject.channelId;
+ if (observedChannelIds.includes(id)) {
+ return;
+ }
+ aSubject.QueryInterface(Ci.nsIRequest);
+ dump("Observer aSubject.name " + aSubject.name + "\n");
+ if (aTopic == "http-on-stop-request" && links.includes(aSubject.name)) {
+ if (aSubject.status == Cr.NS_ERROR_ABORT) {
+ observed.cancelled += 1;
+ } else {
+ aSubject.QueryInterface(Ci.nsIHttpChannel);
+ let initiator = "";
+ try {
+ initiator = aSubject.getRequestHeader("X-Moz");
+ } catch {}
+ if (initiator == "early hint") {
+ observed.hinted += 1;
+ } else {
+ observed.normal += 1;
+ }
+ }
+ observedChannelIds.push(id);
+ numRequestRemaining -= 1;
+ dump("Observer numRequestRemaining " + numRequestRemaining + "\n");
+ }
+ if (numRequestRemaining == 0) {
+ Services.obs.removeObserver(observer, "http-on-stop-request");
+ callback();
+ }
+ },
+ };
+ Services.obs.addObserver(observer, "http-on-stop-request");
+ } else {
+ callback();
+ }
+
+ await BrowserTestUtils.withNewTab(
+ {
+ gBrowser,
+ url,
+ waitForLoad: true,
+ },
+ async function () {}
+ );
+
+ // wait until all requests are stopped, especially the cancelled ones
+ await promise;
+
+ let got = await fetch(
+ "https://example.com/browser/netwerk/test/browser/early_hint_pixel_count.sjs"
+ ).then(response => response.json());
+
+ // stringify to pretty print assert output
+ let g = JSON.stringify(observed);
+ let e = JSON.stringify(expected);
+ Assert.equal(
+ expected.normal,
+ observed.normal,
+ `${name} normal observed from client expected ${expected.normal} (${e}) got ${observed.normal} (${g})`
+ );
+ Assert.equal(
+ expected.hinted,
+ observed.hinted,
+ `${name} hinted observed from client expected ${expected.hinted} (${e}) got ${observed.hinted} (${g})`
+ );
+ Assert.equal(
+ expected.cancelled,
+ observed.cancelled,
+ `${name} cancelled observed from client expected ${expected.cancelled} (${e}) got ${observed.cancelled} (${g})`
+ );
+
+ // each cancelled request might be cancelled after the request was already
+ // made. Allow cancelled responses to count towards the hinted to avoid
+ // intermittent test failures.
+ Assert.ok(
+ expected.hinted <= got.hinted &&
+ got.hinted <= expected.hinted + expected.cancelled,
+ `${name}: unexpected amount of hinted request made got ${
+ got.hinted
+ }, expected between ${expected.hinted} and ${
+ expected.hinted + expected.cancelled
+ }`
+ );
+ Assert.ok(
+ got.normal == expected.normal,
+ `${name}: unexpected amount of normal request made expected ${expected.normal}, got ${got.normal}`
+ );
+ Assert.equal(numRequestRemaining, 0, "Requests remaining");
+}
+
+add_task(async function double_redirect_cross_origin() {
+ await test_hint_redirect(
+ "double_redirect_cross_origin_both_hints",
+ [
+ {
+ link: "https://example.org/browser/netwerk/test/browser/early_hint_asset.sjs?as=image",
+ host: "https://example.com/",
+ },
+ {
+ link: "https://example.org/browser/netwerk/test/browser/early_hint_asset.sjs?as=image",
+ host: "https://example.net",
+ },
+ ],
+ "https://example.org/browser/netwerk/test/browser/early_hint_asset_html.sjs?as=image&hinted=1",
+ "https://example.org/browser/netwerk/test/browser/early_hint_asset.sjs?as=image",
+ { hinted: 1, normal: 0, cancelled: 2 }
+ );
+ await test_hint_redirect(
+ "double_redirect_second_hint",
+ [
+ {
+ link: "",
+ host: "https://example.com/",
+ },
+ {
+ link: "https://example.org/browser/netwerk/test/browser/early_hint_asset.sjs?as=image",
+ host: "https://example.net",
+ },
+ ],
+ "https://example.org/browser/netwerk/test/browser/early_hint_asset_html.sjs?as=image&hinted=1",
+ "https://example.org/browser/netwerk/test/browser/early_hint_asset.sjs?as=image",
+ { hinted: 1, normal: 0, cancelled: 1 }
+ );
+ await test_hint_redirect(
+ "double_redirect_first_hint",
+ [
+ {
+ link: "https://example.org/browser/netwerk/test/browser/early_hint_asset.sjs?as=image",
+ host: "https://example.com/",
+ },
+ {
+ link: "https://example.org/browser/netwerk/test/browser/early_hint_asset.sjs?as=image",
+ host: "https://example.net",
+ },
+ ],
+ "https://example.org/browser/netwerk/test/browser/early_hint_asset_html.sjs?as=image&hinted=0",
+ "https://example.org/browser/netwerk/test/browser/early_hint_asset.sjs?as=image",
+ { hinted: 0, normal: 1, cancelled: 2 }
+ );
+});
+
+add_task(async function redirect_cross_origin() {
+ await test_hint_redirect(
+ "redirect_cross_origin_start_second_preload",
+ [
+ {
+ link: "https://example.org/browser/netwerk/test/browser/early_hint_asset.sjs?as=image",
+ host: "https://example.net",
+ },
+ ],
+ "https://example.org/browser/netwerk/test/browser/early_hint_asset_html.sjs?as=image&hinted=1",
+ "https://example.org/browser/netwerk/test/browser/early_hint_asset.sjs?as=image",
+ { hinted: 1, normal: 0, cancelled: 1 }
+ );
+ await test_hint_redirect(
+ "redirect_cross_origin_dont_use_first_preload",
+ [
+ {
+ link: "https://example.org/browser/netwerk/test/browser/early_hint_asset.sjs?as=image&a",
+ host: "https://example.net",
+ },
+ ],
+ "https://example.org/browser/netwerk/test/browser/early_hint_asset_html.sjs?as=image&hinted=0",
+ "https://example.org/browser/netwerk/test/browser/early_hint_asset.sjs?as=image",
+ { hinted: 0, normal: 1, cancelled: 1 }
+ );
+});
+
+add_task(async function redirect_same_origin() {
+ await test_hint_redirect(
+ "hint_before_redirect_same_origin",
+ [
+ {
+ link: "https://example.org/browser/netwerk/test/browser/early_hint_asset.sjs?as=image",
+ host: "https://example.org",
+ },
+ ],
+ "https://example.org/browser/netwerk/test/browser/early_hint_asset_html.sjs?as=image&hinted=1",
+ "https://example.org/browser/netwerk/test/browser/early_hint_asset.sjs?as=image",
+ { hinted: 1, normal: 0, cancelled: 0 }
+ );
+ await test_hint_redirect(
+ "hint_after_redirect_same_origin",
+ [
+ {
+ link: "https://example.org/browser/netwerk/test/browser/early_hint_asset.sjs?as=image",
+ host: "https://example.org",
+ },
+ ],
+ "https://example.org/browser/netwerk/test/browser/early_hint_asset_html.sjs?as=image&hinted=0",
+ "https://example.org/browser/netwerk/test/browser/early_hint_asset.sjs?as=image",
+ { hinted: 1, normal: 0, cancelled: 0 }
+ );
+ await test_hint_redirect(
+ "hint_after_redirect_same_origin",
+ [
+ {
+ link: "",
+ host: "https://example.org",
+ },
+ ],
+ "https://example.org/browser/netwerk/test/browser/early_hint_asset_html.sjs?as=image&hinted=1",
+ "https://example.org/browser/netwerk/test/browser/early_hint_asset.sjs?as=image",
+ { hinted: 1, normal: 0, cancelled: 0 }
+ );
+});
diff --git a/netwerk/test/browser/browser_103_referrer_policy.js b/netwerk/test/browser/browser_103_referrer_policy.js
new file mode 100644
index 0000000000..646b7255fd
--- /dev/null
+++ b/netwerk/test/browser/browser_103_referrer_policy.js
@@ -0,0 +1,167 @@
+/* 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/. */
+
+Services.prefs.setBoolPref("network.early-hints.enabled", true);
+
+async function test_referrer_policy(input, expected_results) {
+ // reset the count
+ let headers = new Headers();
+ headers.append("X-Early-Hint-Count-Start", "");
+ await fetch(
+ "https://example.com/browser/netwerk/test/browser/early_hint_pixel_count.sjs",
+ { headers }
+ );
+
+ await fetch(
+ "https://example.com/browser/netwerk/test/browser/early_hint_referrer_policy_html.sjs?action=reset_referrer_results"
+ );
+
+ let requestUrl = `https://example.com/browser/netwerk/test/browser/early_hint_referrer_policy_html.sjs?as=${
+ input.resource_type
+ }&hinted=${input.hinted ? "1" : "0"}${
+ input.header_referrer_policy
+ ? "&header_referrer_policy=" + input.header_referrer_policy
+ : ""
+ }
+ ${
+ input.link_referrer_policy
+ ? "&link_referrer_policy=" + input.link_referrer_policy
+ : ""
+ }`;
+
+ await BrowserTestUtils.withNewTab(
+ {
+ gBrowser,
+ url: requestUrl,
+ waitForLoad: true,
+ },
+ async function () {}
+ );
+
+ let gotRequestCount = await fetch(
+ "https://example.com/browser/netwerk/test/browser/early_hint_pixel_count.sjs"
+ ).then(response => response.json());
+
+ // Retrieve the request referrer from the server
+ let referrer_response = await fetch(
+ "https://example.com/browser/netwerk/test/browser/early_hint_referrer_policy_html.sjs?action=get_request_referrer_results"
+ ).then(response => response.text());
+
+ Assert.ok(
+ referrer_response === expected_results.referrer,
+ "Request referrer matches expected - " + input.test_name
+ );
+
+ await Assert.deepEqual(
+ gotRequestCount,
+ { hinted: expected_results.hinted, normal: expected_results.normal },
+ `${input.testName} (${input.resource_type}): Unexpected amount of requests made`
+ );
+}
+
+add_task(async function test_103_referrer_policies() {
+ let tests = [
+ {
+ input: {
+ test_name: "image - no policies",
+ resource_type: "image",
+ header_referrer_policy: "",
+ link_referrer_policy: "",
+ hinted: true,
+ },
+ expected: {
+ hinted: 1,
+ normal: 0,
+ referrer:
+ "https://example.com/browser/netwerk/test/browser/early_hint_referrer_policy_html.sjs?as=image&hinted=1",
+ },
+ },
+ {
+ input: {
+ test_name: "image - origin on header",
+ resource_type: "image",
+ header_referrer_policy: "origin",
+ link_referrer_policy: "",
+ hinted: true,
+ },
+ expected: { hinted: 1, normal: 0, referrer: "https://example.com/" },
+ },
+ {
+ input: {
+ test_name: "image - origin on link",
+ resource_type: "image",
+ header_referrer_policy: "",
+ link_referrer_policy: "origin",
+ hinted: true,
+ },
+ expected: { hinted: 1, normal: 0, referrer: "https://example.com/" },
+ },
+ {
+ input: {
+ test_name: "image - origin on both",
+ resource_type: "image",
+ header_referrer_policy: "origin",
+ link_referrer_policy: "origin",
+ hinted: true,
+ },
+ expected: { hinted: 1, normal: 0, referrer: "https://example.com/" },
+ },
+ {
+ input: {
+ test_name: "image - no-referrer on header",
+ resource_type: "image",
+ header_referrer_policy: "no-referrer",
+ link_referrer_policy: "",
+ hinted: true,
+ },
+ expected: { hinted: 1, normal: 0, referrer: "" },
+ },
+ {
+ input: {
+ test_name: "image - no-referrer on link",
+ resource_type: "image",
+ header_referrer_policy: "",
+ link_referrer_policy: "no-referrer",
+ hinted: true,
+ },
+ expected: { hinted: 1, normal: 0, referrer: "" },
+ },
+ {
+ input: {
+ test_name: "image - no-referrer on both",
+ resource_type: "image",
+ header_referrer_policy: "no-referrer",
+ link_referrer_policy: "no-referrer",
+ hinted: true,
+ },
+ expected: { hinted: 1, normal: 0, referrer: "" },
+ },
+ {
+ // link referrer policy takes precedence
+ input: {
+ test_name: "image - origin on header, no-referrer on link",
+ resource_type: "image",
+ header_referrer_policy: "origin",
+ link_referrer_policy: "no-referrer",
+ hinted: true,
+ },
+ expected: { hinted: 1, normal: 0, referrer: "" },
+ },
+ {
+ // link referrer policy takes precedence
+ input: {
+ test_name: "image - no-referrer on header, origin on link",
+ resource_type: "image",
+ header_referrer_policy: "no-referrer",
+ link_referrer_policy: "origin",
+ hinted: true,
+ },
+ expected: { hinted: 1, normal: 0, referrer: "https://example.com/" },
+ },
+ ];
+
+ for (let test of tests) {
+ await test_referrer_policy(test.input, test.expected);
+ }
+});
diff --git a/netwerk/test/browser/browser_103_telemetry.js b/netwerk/test/browser/browser_103_telemetry.js
new file mode 100644
index 0000000000..c6cc7dd070
--- /dev/null
+++ b/netwerk/test/browser/browser_103_telemetry.js
@@ -0,0 +1,107 @@
+"use strict";
+
+const { TelemetryTestUtils } = ChromeUtils.importESModule(
+ "resource://testing-common/TelemetryTestUtils.sys.mjs"
+);
+
+Services.prefs.setCharPref("dom.securecontext.allowlist", "example.com");
+
+var kTest103 =
+ "https://example.com/browser/netwerk/test/browser/103_preload.html";
+var kTestNo103 =
+ "https://example.com/browser/netwerk/test/browser/no_103_preload.html";
+var kTest404 =
+ "https://example.com/browser/netwerk/test/browser/103_preload_and_404.html";
+
+add_task(async function () {
+ let hist_hints = TelemetryTestUtils.getAndClearHistogram(
+ "EH_NUM_OF_HINTS_PER_PAGE"
+ );
+ let hist_final = TelemetryTestUtils.getAndClearHistogram("EH_FINAL_RESPONSE");
+ let hist_time = TelemetryTestUtils.getAndClearHistogram(
+ "EH_TIME_TO_FINAL_RESPONSE"
+ );
+
+ await BrowserTestUtils.openNewForegroundTab(gBrowser, kTest103, true);
+
+ // This is a 200 response with 103:
+ // EH_NUM_OF_HINTS_PER_PAGE should record 1.
+ // EH_FINAL_RESPONSE should record 1 on position 0 (R2xx).
+ // EH_TIME_TO_FINAL_RESPONSE should have a new value
+ // (we cannot determine what the timing will be therefore we only check that
+ // the histogram sum is > 0).
+ TelemetryTestUtils.assertHistogram(hist_hints, 1, 1);
+ TelemetryTestUtils.assertHistogram(hist_final, 0, 1);
+ const snapshot = hist_time.snapshot();
+ let found = false;
+ for (let [val] of Object.entries(snapshot.values)) {
+ if (val > 0) {
+ found = true;
+ }
+ }
+ Assert.ok(found);
+
+ gBrowser.removeCurrentTab();
+});
+
+add_task(async function () {
+ let hist_hints = TelemetryTestUtils.getAndClearHistogram(
+ "EH_NUM_OF_HINTS_PER_PAGE"
+ );
+ let hist_final = TelemetryTestUtils.getAndClearHistogram("EH_FINAL_RESPONSE");
+ let hist_time = TelemetryTestUtils.getAndClearHistogram(
+ "EH_TIME_TO_FINAL_RESPONSE"
+ );
+
+ await BrowserTestUtils.openNewForegroundTab(gBrowser, kTestNo103, true);
+
+ // This is a 200 response without 103:
+ // EH_NUM_OF_HINTS_PER_PAGE should record 0.
+ // EH_FINAL_RESPONSE andd EH_TIME_TO_FINAL_RESPONSE should not be recorded.
+ TelemetryTestUtils.assertHistogram(hist_hints, 0, 1);
+ const snapshot_final = hist_final.snapshot();
+ Assert.equal(snapshot_final.sum, 0);
+ const snapshot_time = hist_time.snapshot();
+ let found = false;
+ for (let [val] of Object.entries(snapshot_time.values)) {
+ if (val > 0) {
+ found = true;
+ }
+ }
+ Assert.ok(!found);
+
+ gBrowser.removeCurrentTab();
+});
+
+add_task(async function () {
+ let hist_hints = TelemetryTestUtils.getAndClearHistogram(
+ "EH_NUM_OF_HINTS_PER_PAGE"
+ );
+ let hist_final = TelemetryTestUtils.getAndClearHistogram("EH_FINAL_RESPONSE");
+ let hist_time = TelemetryTestUtils.getAndClearHistogram(
+ "EH_TIME_TO_FINAL_RESPONSE"
+ );
+
+ await BrowserTestUtils.openNewForegroundTab(gBrowser, kTest404, true);
+
+ // This is a 404 response with 103:
+ // EH_NUM_OF_HINTS_PER_PAGE and EH_TIME_TO_FINAL_RESPONSE should not be recorded.
+ // EH_FINAL_RESPONSE should record 1 on index 2 (R4xx).
+ const snapshot_hints = hist_hints.snapshot();
+ Assert.equal(snapshot_hints.sum, 0);
+ TelemetryTestUtils.assertHistogram(hist_final, 2, 1);
+ const snapshot_time = hist_time.snapshot();
+ let found = false;
+ for (let [val] of Object.entries(snapshot_time.values)) {
+ if (val > 0) {
+ found = true;
+ }
+ }
+ Assert.ok(!found);
+
+ gBrowser.removeCurrentTab();
+});
+
+add_task(async function cleanup() {
+ Services.prefs.clearUserPref("dom.securecontext.allowlist");
+});
diff --git a/netwerk/test/browser/browser_103_user_load.js b/netwerk/test/browser/browser_103_user_load.js
new file mode 100644
index 0000000000..94a30e1c8c
--- /dev/null
+++ b/netwerk/test/browser/browser_103_user_load.js
@@ -0,0 +1,85 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+// simulate user initiated loads by entering the URL in the URL-bar code based on
+// https://searchfox.org/mozilla-central/rev/5644fae86d5122519a0e34ee03117c88c6ed9b47/browser/components/urlbar/tests/browser/browser_enter.js
+
+ChromeUtils.defineESModuleGetters(this, {
+ UrlbarTestUtils: "resource://testing-common/UrlbarTestUtils.sys.mjs",
+});
+
+const {
+ request_count_checking,
+ test_hint_preload_internal,
+ test_hint_preload,
+} = ChromeUtils.importESModule(
+ "resource://testing-common/early_hint_preload_test_helper.sys.mjs"
+);
+
+const START_VALUE =
+ "https://example.com/browser/netwerk/test/browser/early_hint_asset_html.sjs?as=style&hinted=1";
+
+add_setup(async function () {
+ await SpecialPowers.pushPrefEnv({
+ set: [["browser.urlbar.suggest.quickactions", false]],
+ });
+});
+
+Services.prefs.setBoolPref("network.early-hints.enabled", true);
+
+// bug 1780822
+add_task(async function user_initiated_load() {
+ // reset the count
+ let headers = new Headers();
+ headers.append("X-Early-Hint-Count-Start", "");
+ await fetch(
+ "https://example.com/browser/netwerk/test/browser/early_hint_pixel_count.sjs",
+ { headers }
+ );
+
+ info("Simple user initiated load");
+ let tab = await BrowserTestUtils.openNewForegroundTab(gBrowser);
+
+ // under normal test conditions using the systemPrincipal as loadingPrincipal
+ // doesn't elicit a crash, changing the behavior for this test:
+ // https://searchfox.org/mozilla-central/rev/5644fae86d5122519a0e34ee03117c88c6ed9b47/dom/security/nsContentSecurityManager.cpp#1149-1150
+ Services.prefs.setBoolPref(
+ "security.disallow_non_local_systemprincipal_in_tests",
+ true
+ );
+
+ gURLBar.value = START_VALUE;
+ gURLBar.focus();
+ EventUtils.synthesizeKey("KEY_Enter");
+ await BrowserTestUtils.browserLoaded(gBrowser.selectedBrowser);
+
+ // reset the config option
+ Services.prefs.clearUserPref(
+ "security.disallow_non_local_systemprincipal_in_tests"
+ );
+
+ // Check url bar and selected tab.
+ is(
+ gURLBar.value,
+ UrlbarTestUtils.trimURL(START_VALUE),
+ "Urlbar should preserve the value on return keypress"
+ );
+ is(gBrowser.selectedTab, tab, "New URL was loaded in the current tab");
+
+ let gotRequestCount = await fetch(
+ "https://example.com/browser/netwerk/test/browser/early_hint_pixel_count.sjs"
+ ).then(response => response.json());
+ let expectedRequestCount = { hinted: 1, normal: 0 };
+
+ await request_count_checking(
+ "test_preload_user_initiated",
+ gotRequestCount,
+ expectedRequestCount
+ );
+
+ // Cleanup.
+ BrowserTestUtils.removeTab(gBrowser.selectedTab);
+});
diff --git a/netwerk/test/browser/browser_NetUtil.js b/netwerk/test/browser/browser_NetUtil.js
new file mode 100644
index 0000000000..09875a5280
--- /dev/null
+++ b/netwerk/test/browser/browser_NetUtil.js
@@ -0,0 +1,111 @@
+/*
+Any copyright is dedicated to the Public Domain.
+http://creativecommons.org/publicdomain/zero/1.0/
+*/
+"use strict";
+
+function test() {
+ waitForExplicitFinish();
+
+ // We overload this test to include verifying that httpd.js is
+ // importable as a testing-only JS module.
+ ChromeUtils.importESModule("resource://testing-common/httpd.sys.mjs");
+
+ nextTest();
+}
+
+function nextTest() {
+ if (tests.length) {
+ executeSoon(tests.shift());
+ } else {
+ executeSoon(finish);
+ }
+}
+
+var tests = [test_asyncFetchBadCert];
+
+function test_asyncFetchBadCert() {
+ // Try a load from an untrusted cert, with errors supressed
+ NetUtil.asyncFetch(
+ {
+ uri: "https://untrusted.example.com",
+ loadUsingSystemPrincipal: true,
+ },
+ function (aInputStream, aStatusCode, aRequest) {
+ ok(!Components.isSuccessCode(aStatusCode), "request failed");
+ ok(aRequest instanceof Ci.nsIHttpChannel, "request is an nsIHttpChannel");
+
+ // Now try again with a channel whose notificationCallbacks doesn't suprress errors
+ let channel = NetUtil.newChannel({
+ uri: "https://untrusted.example.com",
+ loadUsingSystemPrincipal: true,
+ });
+ channel.notificationCallbacks = {
+ QueryInterface: ChromeUtils.generateQI([
+ "nsIProgressEventSink",
+ "nsIInterfaceRequestor",
+ ]),
+ getInterface(aIID) {
+ return this.QueryInterface(aIID);
+ },
+ onProgress() {},
+ onStatus() {},
+ };
+ NetUtil.asyncFetch(
+ channel,
+ function (aInputStream, aStatusCode, aRequest) {
+ ok(!Components.isSuccessCode(aStatusCode), "request failed");
+ ok(
+ aRequest instanceof Ci.nsIHttpChannel,
+ "request is an nsIHttpChannel"
+ );
+
+ // Now try a valid request
+ NetUtil.asyncFetch(
+ {
+ uri: "https://example.com",
+ loadUsingSystemPrincipal: true,
+ },
+ function (aInputStream, aStatusCode, aRequest) {
+ info("aStatusCode for valid request: " + aStatusCode);
+ ok(Components.isSuccessCode(aStatusCode), "request succeeded");
+ ok(
+ aRequest instanceof Ci.nsIHttpChannel,
+ "request is an nsIHttpChannel"
+ );
+ ok(aRequest.requestSucceeded, "HTTP request succeeded");
+
+ nextTest();
+ }
+ );
+ }
+ );
+ }
+ );
+}
+
+function WindowListener(aURL, aCallback) {
+ this.callback = aCallback;
+ this.url = aURL;
+}
+WindowListener.prototype = {
+ onOpenWindow(aXULWindow) {
+ var domwindow = aXULWindow.docShell.domWindow;
+ var self = this;
+ domwindow.addEventListener(
+ "load",
+ function () {
+ if (domwindow.document.location.href != self.url) {
+ return;
+ }
+
+ // Allow other window load listeners to execute before passing to callback
+ executeSoon(function () {
+ self.callback(domwindow);
+ });
+ },
+ { once: true }
+ );
+ },
+ onCloseWindow(aXULWindow) {},
+};
diff --git a/netwerk/test/browser/browser_about_cache.js b/netwerk/test/browser/browser_about_cache.js
new file mode 100644
index 0000000000..9e9b2467d5
--- /dev/null
+++ b/netwerk/test/browser/browser_about_cache.js
@@ -0,0 +1,136 @@
+"use strict";
+
+/**
+ * Open a dummy page, then open about:cache and verify the opened page shows up in the cache.
+ */
+add_task(async function () {
+ await SpecialPowers.pushPrefEnv({
+ set: [["privacy.partition.network_state", false]],
+ });
+
+ const kRoot = getRootDirectory(gTestPath).replace(
+ "chrome://mochitests/content/",
+ "https://example.com/"
+ );
+ const kTestPage = kRoot + "dummy.html";
+ // Open the dummy page to get it cached.
+ let tab = await BrowserTestUtils.openNewForegroundTab(
+ gBrowser,
+ kTestPage,
+ true
+ );
+ BrowserTestUtils.removeTab(tab);
+
+ tab = await BrowserTestUtils.openNewForegroundTab(
+ gBrowser,
+ "about:cache",
+ true
+ );
+ let expectedPageCheck = function (uri) {
+ info("Saw load for " + uri);
+ // Can't easily use searchParms and new URL() because it's an about: URI...
+ return uri.startsWith("about:cache?") && uri.includes("storage=disk");
+ };
+ let diskPageLoaded = BrowserTestUtils.browserLoaded(
+ tab.linkedBrowser,
+ false,
+ expectedPageCheck
+ );
+ await SpecialPowers.spawn(tab.linkedBrowser, [], function () {
+ ok(
+ !content.document.nodePrincipal.isSystemPrincipal,
+ "about:cache should not have system principal"
+ );
+ let principal = content.document.nodePrincipal;
+ let channel = content.docShell.currentDocumentChannel;
+ ok(!channel.loadInfo.loadingPrincipal, "Loading principal should be null.");
+ is(
+ principal.spec,
+ content.document.location.href,
+ "Principal matches location"
+ );
+ let links = [...content.document.querySelectorAll("a[href*=disk]")];
+ is(links.length, 1, "Should have 1 link to the disk entries");
+ links[0].click();
+ });
+ await diskPageLoaded;
+ info("about:cache disk subpage loaded");
+
+ expectedPageCheck = function (uri) {
+ info("Saw load for " + uri);
+ return uri.startsWith("about:cache-entry") && uri.includes("dummy.html");
+ };
+ let triggeringURISpec = tab.linkedBrowser.currentURI.spec;
+ let entryLoaded = BrowserTestUtils.browserLoaded(
+ tab.linkedBrowser,
+ false,
+ expectedPageCheck
+ );
+ await SpecialPowers.spawn(
+ tab.linkedBrowser,
+ [kTestPage],
+ function (kTestPage) {
+ ok(
+ !content.document.nodePrincipal.isSystemPrincipal,
+ "about:cache with query params should still not have system principal"
+ );
+ let principal = content.document.nodePrincipal;
+ is(
+ principal.spec,
+ content.document.location.href,
+ "Principal matches location"
+ );
+ let channel = content.docShell.currentDocumentChannel;
+ principal = channel.loadInfo.triggeringPrincipal;
+ is(
+ principal.spec,
+ "about:cache",
+ "Triggering principal matches previous location"
+ );
+ ok(
+ !channel.loadInfo.loadingPrincipal,
+ "Loading principal should be null."
+ );
+ let links = [
+ ...content.document.querySelectorAll("a[href*='" + kTestPage + "']"),
+ ];
+ is(links.length, 1, "Should have 1 link to the entry for " + kTestPage);
+ links[0].click();
+ }
+ );
+ await entryLoaded;
+ info("about:cache entry loaded");
+
+ await SpecialPowers.spawn(
+ tab.linkedBrowser,
+ [triggeringURISpec],
+ function (triggeringURISpec) {
+ ok(
+ !content.document.nodePrincipal.isSystemPrincipal,
+ "about:cache-entry should also not have system principal"
+ );
+ let principal = content.document.nodePrincipal;
+ is(
+ principal.spec,
+ content.document.location.href,
+ "Principal matches location"
+ );
+ let channel = content.docShell.currentDocumentChannel;
+ principal = channel.loadInfo.triggeringPrincipal;
+ is(
+ principal.spec,
+ triggeringURISpec,
+ "Triggering principal matches previous location"
+ );
+ ok(
+ !channel.loadInfo.loadingPrincipal,
+ "Loading principal should be null."
+ );
+ ok(
+ content.document.querySelectorAll("th").length,
+ "Should have several table headers with data."
+ );
+ }
+ );
+ BrowserTestUtils.removeTab(tab);
+});
diff --git a/netwerk/test/browser/browser_backgroundtask_purgeHTTPCache.js b/netwerk/test/browser/browser_backgroundtask_purgeHTTPCache.js
new file mode 100644
index 0000000000..76af1451f5
--- /dev/null
+++ b/netwerk/test/browser/browser_backgroundtask_purgeHTTPCache.js
@@ -0,0 +1,40 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+add_task(async function test_startupCleanup() {
+ Services.prefs.setBoolPref(
+ "network.cache.shutdown_purge_in_background_task",
+ true
+ );
+ Services.prefs.setBoolPref("privacy.clearOnShutdown.cache", true);
+ Services.prefs.setBoolPref("privacy.sanitize.sanitizeOnShutdown", true);
+ let dir = Services.dirsvc.get("ProfD", Ci.nsIFile);
+ dir.append("cache2.2021-11-25-08-47-04.purge.bg_rm");
+ Assert.equal(dir.exists(), false, `Folder ${dir.path} should not exist`);
+ dir.create(Ci.nsIFile.DIRECTORY_TYPE, 0o744);
+ Assert.equal(
+ dir.exists(),
+ true,
+ `Folder ${dir.path} should have been created`
+ );
+
+ Services.obs.notifyObservers(null, "browser-delayed-startup-finished");
+
+ await TestUtils.waitForCondition(() => {
+ return !dir.exists();
+ });
+
+ Assert.equal(
+ dir.exists(),
+ false,
+ `Folder ${dir.path} should have been purged by background task`
+ );
+ Services.prefs.clearUserPref(
+ "network.cache.shutdown_purge_in_background_task"
+ );
+ Services.prefs.clearUserPref("privacy.clearOnShutdown.cache");
+ Services.prefs.clearUserPref("privacy.sanitize.sanitizeOnShutdown");
+});
diff --git a/netwerk/test/browser/browser_bug1535877.js b/netwerk/test/browser/browser_bug1535877.js
new file mode 100644
index 0000000000..0bd0a98d11
--- /dev/null
+++ b/netwerk/test/browser/browser_bug1535877.js
@@ -0,0 +1,15 @@
+"use strict";
+
+add_task(_ => {
+ try {
+ Cc["@mozilla.org/network/effective-tld-service;1"].createInstance(
+ Ci.nsISupports
+ );
+ } catch (e) {
+ is(
+ e.result,
+ Cr.NS_ERROR_XPC_CI_RETURNED_FAILURE,
+ "Component creation as an instance fails with expected code"
+ );
+ }
+});
diff --git a/netwerk/test/browser/browser_bug1629307.js b/netwerk/test/browser/browser_bug1629307.js
new file mode 100644
index 0000000000..03ea2476e2
--- /dev/null
+++ b/netwerk/test/browser/browser_bug1629307.js
@@ -0,0 +1,81 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+// Load a web page containing an iframe that requires authentication but includes the X-Frame-Options: SAMEORIGIN header.
+// Make sure that we don't needlessly show an authentication prompt for it.
+
+const { PromptTestUtils } = ChromeUtils.importESModule(
+ "resource://testing-common/PromptTestUtils.sys.mjs"
+);
+
+add_task(async function () {
+ SpecialPowers.pushPrefEnv({
+ set: [["network.auth.supress_auth_prompt_for_XFO_failures", true]],
+ });
+
+ let URL =
+ "https://example.com/browser/netwerk/test/browser/test_1629307.html";
+
+ let hasPrompt = false;
+
+ PromptTestUtils.handleNextPrompt(
+ window,
+ {
+ modalType: Services.prefs.getIntPref("prompts.modalType.httpAuth"),
+ promptType: "promptUserAndPass",
+ },
+ { buttonNumClick: 1 }
+ )
+ .then(function () {
+ hasPrompt = true;
+ })
+ .catch(function () {});
+
+ BrowserTestUtils.startLoadingURIString(gBrowser.selectedBrowser, URL);
+
+ // wait until the page and its iframe page is loaded
+ await BrowserTestUtils.browserLoaded(gBrowser.selectedBrowser, true, URL);
+
+ Assert.equal(
+ hasPrompt,
+ false,
+ "no prompt when loading page via iframe with x-auth options"
+ );
+});
+
+add_task(async function () {
+ SpecialPowers.pushPrefEnv({
+ set: [["network.auth.supress_auth_prompt_for_XFO_failures", false]],
+ });
+
+ let URL =
+ "https://example.com/browser/netwerk/test/browser/test_1629307.html";
+
+ let hasPrompt = false;
+
+ PromptTestUtils.handleNextPrompt(
+ window,
+ {
+ modalType: Services.prefs.getIntPref("prompts.modalType.httpAuth"),
+ promptType: "promptUserAndPass",
+ },
+ { buttonNumClick: 1 }
+ )
+ .then(function () {
+ hasPrompt = true;
+ })
+ .catch(function () {});
+
+ BrowserTestUtils.startLoadingURIString(gBrowser.selectedBrowser, URL);
+
+ await BrowserTestUtils.browserLoaded(gBrowser.selectedBrowser, true, URL);
+
+ Assert.equal(
+ hasPrompt,
+ true,
+ "prompt when loading page via iframe with x-auth options with pref network.auth.supress_auth_prompt_for_XFO_failures disabled"
+ );
+});
diff --git a/netwerk/test/browser/browser_child_resource.js b/netwerk/test/browser/browser_child_resource.js
new file mode 100644
index 0000000000..341a8fc8e3
--- /dev/null
+++ b/netwerk/test/browser/browser_child_resource.js
@@ -0,0 +1,246 @@
+/*
+Any copyright is dedicated to the Public Domain.
+http://creativecommons.org/publicdomain/zero/1.0/
+*/
+"use strict";
+
+// This must be loaded in the remote process for this test to be useful
+const TEST_URL = "https://example.com/browser/netwerk/test/browser/dummy.html";
+
+const expectedRemote = gMultiProcessBrowser ? "true" : "";
+
+const resProtocol = Cc[
+ "@mozilla.org/network/protocol;1?name=resource"
+].getService(Ci.nsIResProtocolHandler);
+
+function waitForEvent(obj, name, capturing, chromeEvent) {
+ info("Waiting for " + name);
+ return new Promise(resolve => {
+ function listener(event) {
+ info("Saw " + name);
+ obj.removeEventListener(name, listener, capturing, chromeEvent);
+ resolve(event);
+ }
+
+ obj.addEventListener(name, listener, capturing, chromeEvent);
+ });
+}
+
+function resolveURI(uri) {
+ uri = Services.io.newURI(uri);
+ try {
+ return resProtocol.resolveURI(uri);
+ } catch (e) {
+ return null;
+ }
+}
+
+function remoteResolveURI(uri) {
+ return SpecialPowers.spawn(gBrowser.selectedBrowser, [uri], uriToResolve => {
+ let resProtocol = Cc[
+ "@mozilla.org/network/protocol;1?name=resource"
+ ].getService(Ci.nsIResProtocolHandler);
+
+ uriToResolve = Services.io.newURI(uriToResolve);
+ try {
+ return resProtocol.resolveURI(uriToResolve);
+ } catch (e) {}
+ return null;
+ });
+}
+
+// Restarts the child process by crashing it then reloading the tab
+var restart = async function () {
+ let browser = gBrowser.selectedBrowser;
+ // If the tab isn't remote this would crash the main process so skip it
+ if (browser.getAttribute("remote") != "true") {
+ return browser;
+ }
+
+ await BrowserTestUtils.crashFrame(browser);
+
+ browser.reload();
+
+ await BrowserTestUtils.browserLoaded(browser);
+ is(
+ browser.getAttribute("remote"),
+ expectedRemote,
+ "Browser should be in the right process"
+ );
+ return browser;
+};
+
+// Sanity check that this test is going to be useful
+add_task(async function () {
+ await BrowserTestUtils.openNewForegroundTab(gBrowser, TEST_URL);
+
+ // This must be loaded in the remote process for this test to be useful
+ is(
+ gBrowser.selectedBrowser.getAttribute("remote"),
+ expectedRemote,
+ "Browser should be in the right process"
+ );
+
+ let local = resolveURI("resource://gre/modules/AppConstants.jsm");
+ let remote = await remoteResolveURI(
+ "resource://gre/modules/AppConstants.jsm"
+ );
+ is(local, remote, "AppConstants.jsm should resolve in both processes");
+
+ gBrowser.removeCurrentTab();
+});
+
+// Add a mapping, update it then remove it
+add_task(async function () {
+ await BrowserTestUtils.openNewForegroundTab(gBrowser, TEST_URL);
+
+ info("Set");
+ resProtocol.setSubstitution(
+ "testing",
+ Services.io.newURI("chrome://global/content")
+ );
+ let local = resolveURI("resource://testing/test.js");
+ let remote = await remoteResolveURI("resource://testing/test.js");
+ is(
+ local,
+ "chrome://global/content/test.js",
+ "Should resolve in main process"
+ );
+ is(
+ remote,
+ "chrome://global/content/test.js",
+ "Should resolve in child process"
+ );
+
+ info("Change");
+ resProtocol.setSubstitution(
+ "testing",
+ Services.io.newURI("chrome://global/skin")
+ );
+ local = resolveURI("resource://testing/test.js");
+ remote = await remoteResolveURI("resource://testing/test.js");
+ is(local, "chrome://global/skin/test.js", "Should resolve in main process");
+ is(remote, "chrome://global/skin/test.js", "Should resolve in child process");
+
+ info("Clear");
+ resProtocol.setSubstitution("testing", null);
+ local = resolveURI("resource://testing/test.js");
+ remote = await remoteResolveURI("resource://testing/test.js");
+ is(local, null, "Shouldn't resolve in main process");
+ is(remote, null, "Shouldn't resolve in child process");
+
+ gBrowser.removeCurrentTab();
+});
+
+// Add a mapping, restart the child process then check it is still there
+add_task(async function () {
+ await BrowserTestUtils.openNewForegroundTab(gBrowser, TEST_URL);
+
+ info("Set");
+ resProtocol.setSubstitution(
+ "testing",
+ Services.io.newURI("chrome://global/content")
+ );
+ let local = resolveURI("resource://testing/test.js");
+ let remote = await remoteResolveURI("resource://testing/test.js");
+ is(
+ local,
+ "chrome://global/content/test.js",
+ "Should resolve in main process"
+ );
+ is(
+ remote,
+ "chrome://global/content/test.js",
+ "Should resolve in child process"
+ );
+
+ await restart();
+
+ local = resolveURI("resource://testing/test.js");
+ remote = await remoteResolveURI("resource://testing/test.js");
+ is(
+ local,
+ "chrome://global/content/test.js",
+ "Should resolve in main process"
+ );
+ is(
+ remote,
+ "chrome://global/content/test.js",
+ "Should resolve in child process"
+ );
+
+ info("Change");
+ resProtocol.setSubstitution(
+ "testing",
+ Services.io.newURI("chrome://global/skin")
+ );
+
+ await restart();
+
+ local = resolveURI("resource://testing/test.js");
+ remote = await remoteResolveURI("resource://testing/test.js");
+ is(local, "chrome://global/skin/test.js", "Should resolve in main process");
+ is(remote, "chrome://global/skin/test.js", "Should resolve in child process");
+
+ info("Clear");
+ resProtocol.setSubstitution("testing", null);
+
+ await restart();
+
+ local = resolveURI("resource://testing/test.js");
+ remote = await remoteResolveURI("resource://testing/test.js");
+ is(local, null, "Shouldn't resolve in main process");
+ is(remote, null, "Shouldn't resolve in child process");
+
+ gBrowser.removeCurrentTab();
+});
+
+// Adding a mapping to a resource URI should work
+add_task(async function () {
+ await BrowserTestUtils.openNewForegroundTab(gBrowser, TEST_URL);
+
+ info("Set");
+ resProtocol.setSubstitution(
+ "testing",
+ Services.io.newURI("chrome://global/content")
+ );
+ resProtocol.setSubstitution(
+ "testing2",
+ Services.io.newURI("resource://testing")
+ );
+ let local = resolveURI("resource://testing2/test.js");
+ let remote = await remoteResolveURI("resource://testing2/test.js");
+ is(
+ local,
+ "chrome://global/content/test.js",
+ "Should resolve in main process"
+ );
+ is(
+ remote,
+ "chrome://global/content/test.js",
+ "Should resolve in child process"
+ );
+
+ info("Clear");
+ resProtocol.setSubstitution("testing", null);
+ local = resolveURI("resource://testing2/test.js");
+ remote = await remoteResolveURI("resource://testing2/test.js");
+ is(
+ local,
+ "chrome://global/content/test.js",
+ "Should resolve in main process"
+ );
+ is(
+ remote,
+ "chrome://global/content/test.js",
+ "Should resolve in child process"
+ );
+
+ resProtocol.setSubstitution("testing2", null);
+ local = resolveURI("resource://testing2/test.js");
+ remote = await remoteResolveURI("resource://testing2/test.js");
+ is(local, null, "Shouldn't resolve in main process");
+ is(remote, null, "Shouldn't resolve in child process");
+
+ gBrowser.removeCurrentTab();
+});
diff --git a/netwerk/test/browser/browser_cookie_filtering_basic.js b/netwerk/test/browser/browser_cookie_filtering_basic.js
new file mode 100644
index 0000000000..e51bed5bc5
--- /dev/null
+++ b/netwerk/test/browser/browser_cookie_filtering_basic.js
@@ -0,0 +1,184 @@
+/*
+ * Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+"use strict";
+
+const {
+ HTTPS_EXAMPLE_ORG,
+ HTTPS_EXAMPLE_COM,
+ HTTP_EXAMPLE_COM,
+ browserTestPath,
+ waitForAllExpectedTests,
+ cleanupObservers,
+ checkExpectedCookies,
+ fetchHelper,
+ preclean_test,
+ cleanup_test,
+} = ChromeUtils.importESModule(
+ "resource://testing-common/cookie_filtering_helper.sys.mjs"
+);
+
+// run suite with content listener
+// 1. initializes the content process and observer
+// 2. runs the test gamut
+// 3. cleans up the content process
+async function runSuiteWithContentListener(name, triggerSuiteFunc, expected) {
+ return async function (browser) {
+ info("Running content suite: " + name);
+ await SpecialPowers.spawn(browser, [expected, name], checkExpectedCookies);
+ await triggerSuiteFunc();
+ await SpecialPowers.spawn(browser, [], waitForAllExpectedTests);
+ await SpecialPowers.spawn(browser, [], cleanupObservers);
+ info("Complete content suite: " + name);
+ };
+}
+
+// TEST: Different domains (org)
+// * example.org cookies go to example.org process
+// * exmaple.com cookies do not go to example.org process
+async function test_basic_suite_org() {
+ // example.org - start content process when loading page
+ await BrowserTestUtils.withNewTab(
+ {
+ gBrowser,
+ url: browserTestPath(HTTPS_EXAMPLE_ORG),
+ },
+ await runSuiteWithContentListener(
+ "basic suite org",
+ triggerBasicSuite,
+ basicSuiteMatchingDomain(HTTPS_EXAMPLE_ORG)
+ )
+ );
+}
+
+// TEST: Different domains (com)
+// * example.com cookies go to example.com process
+// * example.org cookies do not go to example.com process
+// * insecure example.com cookies go to secure com process
+async function test_basic_suite_com() {
+ // example.com - start content process when loading page
+ await BrowserTestUtils.withNewTab(
+ {
+ gBrowser,
+ url: browserTestPath(HTTPS_EXAMPLE_COM),
+ },
+ await runSuiteWithContentListener(
+ "basic suite com",
+ triggerBasicSuite,
+ basicSuiteMatchingDomain(HTTPS_EXAMPLE_COM).concat(
+ basicSuiteMatchingDomain(HTTP_EXAMPLE_COM)
+ )
+ )
+ );
+}
+
+// TEST: Duplicate domain (org)
+// * example.org cookies go to multiple example.org processes
+async function test_basic_suite_org_duplicate() {
+ let expected = basicSuiteMatchingDomain(HTTPS_EXAMPLE_ORG);
+ let t1 = await BrowserTestUtils.openNewForegroundTab(
+ gBrowser,
+ browserTestPath(HTTPS_EXAMPLE_ORG)
+ );
+ let testStruct1 = {
+ name: "example.org primary",
+ browser: gBrowser.getBrowserForTab(t1),
+ tab: t1,
+ expected,
+ };
+
+ // example.org dup
+ let t3 = await BrowserTestUtils.openNewForegroundTab(
+ gBrowser,
+ browserTestPath(HTTPS_EXAMPLE_ORG)
+ );
+ let testStruct3 = {
+ name: "example.org dup",
+ browser: gBrowser.getBrowserForTab(t3),
+ tab: t3,
+ expected,
+ };
+
+ let parentpid = Services.appinfo.processID;
+ let pid1 = testStruct1.browser.frameLoader.remoteTab.osPid;
+ let pid3 = testStruct3.browser.frameLoader.remoteTab.osPid;
+ ok(
+ parentpid != pid1,
+ "Parent pid should differ from content process for 1st example.org"
+ );
+ ok(
+ parentpid != pid3,
+ "Parent pid should differ from content process for 2nd example.org"
+ );
+ ok(pid1 != pid3, "Content pids should differ from each other");
+
+ await SpecialPowers.spawn(
+ testStruct1.browser,
+ [testStruct1.expected, testStruct1.name],
+ checkExpectedCookies
+ );
+
+ await SpecialPowers.spawn(
+ testStruct3.browser,
+ [testStruct3.expected, testStruct3.name],
+ checkExpectedCookies
+ );
+
+ await triggerBasicSuite();
+
+ // wait for all tests and cleanup
+ await SpecialPowers.spawn(testStruct1.browser, [], waitForAllExpectedTests);
+ await SpecialPowers.spawn(testStruct3.browser, [], waitForAllExpectedTests);
+ await SpecialPowers.spawn(testStruct1.browser, [], cleanupObservers);
+ await SpecialPowers.spawn(testStruct3.browser, [], cleanupObservers);
+ BrowserTestUtils.removeTab(testStruct1.tab);
+ BrowserTestUtils.removeTab(testStruct3.tab);
+}
+
+function basicSuite() {
+ var suite = [];
+ suite.push(["test-cookie=aaa", HTTPS_EXAMPLE_ORG]);
+ suite.push(["test-cookie=bbb", HTTPS_EXAMPLE_ORG]);
+ suite.push(["test-cookie=dad", HTTPS_EXAMPLE_ORG]);
+ suite.push(["test-cookie=rad", HTTPS_EXAMPLE_ORG]);
+ suite.push(["test-cookie=orgwontsee", HTTPS_EXAMPLE_COM]);
+ suite.push(["test-cookie=sentinelorg", HTTPS_EXAMPLE_ORG]);
+ suite.push(["test-cookie=sentinelcom", HTTPS_EXAMPLE_COM]);
+ return suite;
+}
+
+function basicSuiteMatchingDomain(domain) {
+ var suite = basicSuite();
+ var result = [];
+ for (var [cookie, dom] of suite) {
+ if (dom == domain) {
+ result.push(cookie);
+ }
+ }
+ return result;
+}
+
+// triggers set-cookie, which will trigger cookie-changed messages
+// messages will be filtered against the cookie list created from above
+// only unfiltered messages should make it to the content process
+async function triggerBasicSuite() {
+ let triggerCookies = basicSuite();
+ for (var [cookie, domain] of triggerCookies) {
+ let secure = false;
+ if (domain.includes("https")) {
+ secure = true;
+ }
+
+ //trigger
+ var url = browserTestPath(domain) + "cookie_filtering_resource.sjs";
+ await fetchHelper(url, cookie, secure);
+ }
+}
+
+add_task(preclean_test);
+add_task(test_basic_suite_org); // 5
+add_task(test_basic_suite_com); // 2
+add_task(test_basic_suite_org_duplicate); // 13
+add_task(cleanup_test);
diff --git a/netwerk/test/browser/browser_cookie_filtering_cross_origin.js b/netwerk/test/browser/browser_cookie_filtering_cross_origin.js
new file mode 100644
index 0000000000..5a722846ef
--- /dev/null
+++ b/netwerk/test/browser/browser_cookie_filtering_cross_origin.js
@@ -0,0 +1,146 @@
+/*
+ * Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+"use strict";
+
+const {
+ HTTPS_EXAMPLE_ORG,
+ HTTPS_EXAMPLE_COM,
+ HTTP_EXAMPLE_COM,
+ browserTestPath,
+ waitForAllExpectedTests,
+ cleanupObservers,
+ checkExpectedCookies,
+ preclean_test,
+ cleanup_test,
+} = ChromeUtils.importESModule(
+ "resource://testing-common/cookie_filtering_helper.sys.mjs"
+);
+
+async function runSuiteWithContentListener(name, trigger_suite_func, expected) {
+ return async function (browser) {
+ info("Running content suite: " + name);
+ await SpecialPowers.spawn(browser, [expected, name], checkExpectedCookies);
+ await trigger_suite_func();
+ await SpecialPowers.spawn(browser, [], waitForAllExpectedTests);
+ await SpecialPowers.spawn(browser, [], cleanupObservers);
+ info("Complete content suite: " + name);
+ };
+}
+
+// TEST: Cross Origin Resource (com)
+// * process receives only COR cookies pertaining to same page
+async function test_cross_origin_resource_com() {
+ let comExpected = [];
+ comExpected.push("test-cookie=comhtml"); // 1
+ comExpected.push("test-cookie=png"); // 2
+ comExpected.push("test-cookie=orghtml"); // 3
+ // nothing for 4, 5, 6, 7 -> goes to .org process
+ comExpected.push("test-cookie=png"); // 8
+
+ await BrowserTestUtils.withNewTab(
+ {
+ gBrowser,
+ url: browserTestPath(HTTPS_EXAMPLE_COM),
+ },
+ await runSuiteWithContentListener(
+ "COR example.com",
+ triggerCrossOriginSuite,
+ comExpected
+ )
+ );
+ Services.cookies.removeAll();
+}
+
+// TEST: Cross Origin Resource (org)
+// * received COR cookies only pertaining to the process's page
+async function test_cross_origin_resource_org() {
+ let orgExpected = [];
+ // nothing for 1, 2 and 3 -> goes to .com
+ orgExpected.push("test-cookie=png"); // 4
+ orgExpected.push("test-cookie=orghtml"); // 5
+ orgExpected.push("test-cookie=png"); // 6
+ orgExpected.push("test-cookie=comhtml"); // 7
+ // 8 nothing for 8 -> goes to .com process
+ orgExpected.push("test-cookie=png"); // 9. Sentinel to verify no more came in
+
+ await BrowserTestUtils.withNewTab(
+ {
+ gBrowser,
+ url: browserTestPath(HTTPS_EXAMPLE_ORG),
+ },
+ await runSuiteWithContentListener(
+ "COR example.org",
+ triggerCrossOriginSuite,
+ orgExpected
+ )
+ );
+}
+
+// seems to block better than fetch for secondary resource
+// using for more predicatable recving
+async function requestBrowserPageWithFilename(
+ testName,
+ requestFrom,
+ filename,
+ param = ""
+) {
+ let url = requestFrom + "/browser/netwerk/test/browser/" + filename;
+
+ // add param if necessary
+ if (param != "") {
+ url += "?" + param;
+ }
+
+ info("requesting " + url + " (" + testName + ")");
+ await BrowserTestUtils.withNewTab(
+ {
+ gBrowser,
+ url,
+ },
+ async () => {}
+ );
+}
+
+async function triggerCrossOriginSuite() {
+ // SameSite - 1 com page, 2 com png
+ await requestBrowserPageWithFilename(
+ "SameSite resource (com)",
+ HTTPS_EXAMPLE_COM,
+ "cookie_filtering_secure_resource_com.html"
+ );
+
+ // COR - 3 com page, 4 org png
+ await requestBrowserPageWithFilename(
+ "COR (com-org)",
+ HTTPS_EXAMPLE_COM,
+ "cookie_filtering_secure_resource_org.html"
+ );
+
+ // SameSite - 5 org page, 6 org png
+ await requestBrowserPageWithFilename(
+ "SameSite resource (org)",
+ HTTPS_EXAMPLE_ORG,
+ "cookie_filtering_secure_resource_org.html"
+ );
+
+ // COR - 7 org page, 8 com png
+ await requestBrowserPageWithFilename(
+ "SameSite resource (org-com)",
+ HTTPS_EXAMPLE_ORG,
+ "cookie_filtering_secure_resource_com.html"
+ );
+
+ // Sentinel to verify no more cookies come in after last true test
+ await requestBrowserPageWithFilename(
+ "COR sentinel",
+ HTTPS_EXAMPLE_ORG,
+ "cookie_filtering_square.png"
+ );
+}
+
+add_task(preclean_test);
+add_task(test_cross_origin_resource_com); // 4
+add_task(test_cross_origin_resource_org); // 5
+add_task(cleanup_test);
diff --git a/netwerk/test/browser/browser_cookie_filtering_insecure.js b/netwerk/test/browser/browser_cookie_filtering_insecure.js
new file mode 100644
index 0000000000..679bfc5a56
--- /dev/null
+++ b/netwerk/test/browser/browser_cookie_filtering_insecure.js
@@ -0,0 +1,106 @@
+/*
+ * Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+"use strict";
+
+const {
+ HTTPS_EXAMPLE_ORG,
+ HTTPS_EXAMPLE_COM,
+ HTTP_EXAMPLE_COM,
+ browserTestPath,
+ waitForAllExpectedTests,
+ cleanupObservers,
+ checkExpectedCookies,
+ fetchHelper,
+ preclean_test,
+ cleanup_test,
+} = ChromeUtils.importESModule(
+ "resource://testing-common/cookie_filtering_helper.sys.mjs"
+);
+
+async function runSuiteWithContentListener(name, trigger_suite_func, expected) {
+ return async function (browser) {
+ info("Running content suite: " + name);
+ await SpecialPowers.spawn(browser, [expected, name], checkExpectedCookies);
+ await trigger_suite_func();
+ await SpecialPowers.spawn(browser, [], waitForAllExpectedTests);
+ await SpecialPowers.spawn(browser, [], cleanupObservers);
+ info("Complete content suite: " + name);
+ };
+}
+
+// TEST: In/Secure (insecure com)
+// * secure example.com cookie do not go to insecure example.com process
+// * insecure cookies go to insecure process
+// * secure request with insecure cookie will go to insecure process
+async function test_insecure_suite_insecure_com() {
+ var expected = [];
+ expected.push("test-cookie=png1");
+ expected.push("test-cookie=png2");
+ // insecure com will not recieve the secure com request with secure cookie
+ expected.push(""); // insecure com will lose visibility of secure com cookie
+ expected.push("test-cookie=png3");
+ info(expected);
+
+ await BrowserTestUtils.withNewTab(
+ {
+ gBrowser,
+ url: browserTestPath(HTTP_EXAMPLE_COM),
+ },
+ await runSuiteWithContentListener(
+ "insecure suite insecure com",
+ triggerInsecureSuite,
+ expected
+ )
+ );
+}
+
+// TEST: In/Secure (secure com)
+// * secure page will recieve all secure/insecure cookies
+async function test_insecure_suite_secure_com() {
+ var expected = [];
+ expected.push("test-cookie=png1");
+ expected.push("test-cookie=png2");
+ expected.push("test-cookie=secure-png");
+ expected.push("test-cookie=png3");
+ info(expected);
+ await BrowserTestUtils.withNewTab(
+ {
+ gBrowser,
+ url: browserTestPath(HTTPS_EXAMPLE_COM),
+ },
+ await runSuiteWithContentListener(
+ "insecure suite secure com",
+ triggerInsecureSuite,
+ expected
+ )
+ );
+}
+
+async function triggerInsecureSuite() {
+ const cookieSjsFilename = "cookie_filtering_resource.sjs";
+
+ // insecure page, insecure cookie
+ var url = browserTestPath(HTTP_EXAMPLE_COM) + cookieSjsFilename;
+ await fetchHelper(url, "test-cookie=png1", false);
+
+ // secure page req, insecure cookie
+ url = browserTestPath(HTTPS_EXAMPLE_COM) + cookieSjsFilename;
+ await fetchHelper(url, "test-cookie=png2", false);
+
+ // secure page, secure cookie
+ url = browserTestPath(HTTPS_EXAMPLE_COM) + cookieSjsFilename;
+ await fetchHelper(url, "test-cookie=secure-png", true);
+
+ // not testing insecure server returning secure cookie --
+
+ // sentinel
+ url = browserTestPath(HTTPS_EXAMPLE_COM) + cookieSjsFilename;
+ await fetchHelper(url, "test-cookie=png3", false);
+}
+
+add_task(preclean_test);
+add_task(test_insecure_suite_insecure_com); // 3
+add_task(test_insecure_suite_secure_com); // 4
+add_task(cleanup_test);
diff --git a/netwerk/test/browser/browser_cookie_filtering_oa.js b/netwerk/test/browser/browser_cookie_filtering_oa.js
new file mode 100644
index 0000000000..f69ad09e81
--- /dev/null
+++ b/netwerk/test/browser/browser_cookie_filtering_oa.js
@@ -0,0 +1,190 @@
+/*
+ * Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+"use strict";
+
+const {
+ HTTPS_EXAMPLE_ORG,
+ HTTPS_EXAMPLE_COM,
+ HTTP_EXAMPLE_COM,
+ browserTestPath,
+ waitForAllExpectedTests,
+ cleanupObservers,
+ checkExpectedCookies,
+ triggerSetCookieFromHttp,
+ triggerSetCookieFromHttpPrivate,
+ preclean_test,
+ cleanup_test,
+} = ChromeUtils.importESModule(
+ "resource://testing-common/cookie_filtering_helper.sys.mjs"
+);
+
+// TEST: OriginAttributes
+// * example.com OA-changed cookies don't go to example.com & vice-versa
+async function test_origin_attributes() {
+ var suite = oaSuite();
+
+ // example.com - start content process when loading page
+ let t2 = await BrowserTestUtils.openNewForegroundTab(
+ gBrowser,
+ browserTestPath(HTTPS_EXAMPLE_COM)
+ );
+ let testStruct2 = {
+ name: "OA example.com",
+ browser: gBrowser.getBrowserForTab(t2),
+ tab: t2,
+ expected: [suite[0], suite[4]],
+ };
+
+ // open a tab with altered OA: userContextId
+ let t4 = await BrowserTestUtils.openNewForegroundTab({
+ gBrowser,
+ opening: (function () {
+ return function () {
+ // info("calling addTab from lambda");
+ gBrowser.selectedTab = BrowserTestUtils.addTab(
+ gBrowser,
+ HTTPS_EXAMPLE_COM,
+ { userContextId: 1 }
+ );
+ };
+ })(),
+ });
+ let testStruct4 = {
+ name: "OA example.com (contextId)",
+ browser: gBrowser.getBrowserForTab(t4),
+ tab: t4,
+ expected: [suite[2], suite[5]],
+ };
+
+ // example.com
+ await SpecialPowers.spawn(
+ testStruct2.browser,
+ [testStruct2.expected, testStruct2.name],
+ checkExpectedCookies
+ );
+ // example.com with different OA: userContextId
+ await SpecialPowers.spawn(
+ testStruct4.browser,
+ [testStruct4.expected, testStruct4.name],
+ checkExpectedCookies
+ );
+
+ await triggerOriginAttributesEmulatedSuite();
+
+ await SpecialPowers.spawn(testStruct2.browser, [], waitForAllExpectedTests);
+ await SpecialPowers.spawn(testStruct4.browser, [], waitForAllExpectedTests);
+ await SpecialPowers.spawn(testStruct2.browser, [], cleanupObservers);
+ await SpecialPowers.spawn(testStruct4.browser, [], cleanupObservers);
+ BrowserTestUtils.removeTab(testStruct2.tab);
+ BrowserTestUtils.removeTab(testStruct4.tab);
+}
+
+// TEST: Private
+// * example.com private cookies don't go to example.com process & vice-v
+async function test_private() {
+ var suite = oaSuite();
+
+ // example.com
+ let t2 = await BrowserTestUtils.openNewForegroundTab(
+ gBrowser,
+ browserTestPath(HTTPS_EXAMPLE_COM)
+ );
+ let testStruct2 = {
+ name: "non-priv example.com",
+ browser: gBrowser.getBrowserForTab(t2),
+ tab: t2,
+ expected: [suite[0], suite[4]],
+ };
+
+ // private window example.com
+ let privateBrowserWindow = await BrowserTestUtils.openNewBrowserWindow({
+ private: true,
+ });
+ let privateTab = (privateBrowserWindow.gBrowser.selectedTab =
+ BrowserTestUtils.addTab(
+ privateBrowserWindow.gBrowser,
+ browserTestPath(HTTPS_EXAMPLE_COM)
+ ));
+ let testStruct5 = {
+ name: "private example.com",
+ browser: privateBrowserWindow.gBrowser.getBrowserForTab(privateTab),
+ tab: privateTab,
+ expected: [suite[3], suite[6]],
+ };
+ await BrowserTestUtils.browserLoaded(testStruct5.tab.linkedBrowser);
+
+ let parentpid = Services.appinfo.processID;
+ let privatePid = testStruct5.browser.frameLoader.remoteTab.osPid;
+ let pid = testStruct2.browser.frameLoader.remoteTab.osPid;
+ ok(parentpid != privatePid, "Parent and private processes are unique");
+ ok(parentpid != pid, "Parent and non-private processes are unique");
+ ok(privatePid != pid, "Private and non-private processes are unique");
+
+ // example.com
+ await SpecialPowers.spawn(
+ testStruct2.browser,
+ [testStruct2.expected, testStruct2.name],
+ checkExpectedCookies
+ );
+
+ // example.com private
+ await SpecialPowers.spawn(
+ testStruct5.browser,
+ [testStruct5.expected, testStruct5.name],
+ checkExpectedCookies
+ );
+
+ await triggerOriginAttributesEmulatedSuite();
+
+ // wait for all tests and cleanup
+ await SpecialPowers.spawn(testStruct2.browser, [], waitForAllExpectedTests);
+ await SpecialPowers.spawn(testStruct5.browser, [], waitForAllExpectedTests);
+ await SpecialPowers.spawn(testStruct2.browser, [], cleanupObservers);
+ await SpecialPowers.spawn(testStruct5.browser, [], cleanupObservers);
+ BrowserTestUtils.removeTab(testStruct2.tab);
+ BrowserTestUtils.removeTab(testStruct5.tab);
+ await BrowserTestUtils.closeWindow(privateBrowserWindow);
+}
+
+function oaSuite() {
+ var suite = [];
+ suite.push("test-cookie=orgwontsee"); // 0
+ suite.push("test-cookie=firstparty"); // 1
+ suite.push("test-cookie=usercontext"); // 2
+ suite.push("test-cookie=privateonly"); // 3
+ suite.push("test-cookie=sentinelcom"); // 4
+ suite.push("test-cookie=sentineloa"); // 5
+ suite.push("test-cookie=sentinelprivate"); // 6
+ return suite;
+}
+
+// emulated because we are not making actual page requests
+// we are just directly invoking the api
+async function triggerOriginAttributesEmulatedSuite() {
+ var suite = oaSuite();
+
+ let uriCom = NetUtil.newURI(HTTPS_EXAMPLE_COM);
+ triggerSetCookieFromHttp(uriCom, suite[0]);
+
+ // FPD (OA) changed
+ triggerSetCookieFromHttp(uriCom, suite[1], "foo.com");
+
+ // context id (OA) changed
+ triggerSetCookieFromHttp(uriCom, suite[2], "", 1);
+
+ // private
+ triggerSetCookieFromHttpPrivate(uriCom, suite[3]);
+
+ // sentinels
+ triggerSetCookieFromHttp(uriCom, suite[4]);
+ triggerSetCookieFromHttp(uriCom, suite[5], "", 1);
+ triggerSetCookieFromHttpPrivate(uriCom, suite[6]);
+}
+
+add_task(preclean_test);
+add_task(test_origin_attributes); // 4
+add_task(test_private); // 7
+add_task(cleanup_test);
diff --git a/netwerk/test/browser/browser_cookie_filtering_subdomain.js b/netwerk/test/browser/browser_cookie_filtering_subdomain.js
new file mode 100644
index 0000000000..78fcdb07dd
--- /dev/null
+++ b/netwerk/test/browser/browser_cookie_filtering_subdomain.js
@@ -0,0 +1,175 @@
+/*
+ * Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+"use strict";
+
+const {
+ HTTPS_EXAMPLE_ORG,
+ HTTPS_EXAMPLE_COM,
+ HTTP_EXAMPLE_COM,
+ browserTestPath,
+ waitForAllExpectedTests,
+ cleanupObservers,
+ checkExpectedCookies,
+ fetchHelper,
+ preclean_test,
+ cleanup_test,
+} = ChromeUtils.importESModule(
+ "resource://testing-common/cookie_filtering_helper.sys.mjs"
+);
+
+const HTTPS_SUBDOMAIN_1_EXAMPLE_COM = "https://test1.example.com";
+const HTTP_SUBDOMAIN_1_EXAMPLE_COM = "http://test1.example.com";
+const HTTPS_SUBDOMAIN_2_EXAMPLE_COM = "https://test2.example.com";
+const HTTP_SUBDOMAIN_2_EXAMPLE_COM = "http://test2.example.com";
+
+// run suite with content listener
+// 1. initializes the content process and observer
+// 2. runs the test gamut
+// 3. cleans up the content process
+async function runSuiteWithContentListener(name, triggerSuiteFunc, expected) {
+ return async function (browser) {
+ info("Running content suite: " + name);
+ await SpecialPowers.spawn(browser, [expected, name], checkExpectedCookies);
+ await triggerSuiteFunc();
+ await SpecialPowers.spawn(browser, [], waitForAllExpectedTests);
+ await SpecialPowers.spawn(browser, [], cleanupObservers);
+ info("Complete content suite: " + name);
+ };
+}
+
+// TEST: domain receives subdomain cookies
+async function test_domain() {
+ await BrowserTestUtils.withNewTab(
+ {
+ gBrowser,
+ url: browserTestPath(HTTPS_EXAMPLE_COM),
+ },
+ await runSuiteWithContentListener(
+ "test_domain",
+ triggerSuite,
+ cookiesFromSuite()
+ )
+ );
+}
+
+// TEST: insecure domain receives base and sub-domain insecure cookies
+async function test_insecure_domain() {
+ await BrowserTestUtils.withNewTab(
+ {
+ gBrowser,
+ url: browserTestPath(HTTP_EXAMPLE_COM),
+ },
+
+ await runSuiteWithContentListener("test_insecure_domain", triggerSuite, [
+ "",
+ "", // HTTPS fetch cookies show as empty strings
+ "test-cookie-insecure=insecure_domain",
+ "test-cookie-insecure=insecure_subdomain",
+ "",
+ ])
+ );
+}
+
+// TEST: subdomain receives base domain and other sub-domain cookies
+async function test_subdomain() {
+ await BrowserTestUtils.withNewTab(
+ {
+ gBrowser,
+ url: browserTestPath(HTTPS_SUBDOMAIN_2_EXAMPLE_COM),
+ },
+ await runSuiteWithContentListener(
+ "test_subdomain",
+ triggerSuite,
+ cookiesFromSuite()
+ )
+ );
+}
+
+// TEST: insecure subdomain receives base and sub-domain insecure cookies
+async function test_insecure_subdomain() {
+ await BrowserTestUtils.withNewTab(
+ {
+ gBrowser,
+ url: browserTestPath(HTTP_SUBDOMAIN_2_EXAMPLE_COM),
+ },
+ await runSuiteWithContentListener(
+ "test_insecure_subdomain",
+ triggerSuite,
+
+ [
+ "",
+ "", // HTTPS fetch cookies show as empty strings
+ "test-cookie-insecure=insecure_domain",
+ "test-cookie-insecure=insecure_subdomain",
+ "",
+ ]
+ )
+ );
+}
+
+function suite() {
+ var suite = [];
+ suite.push(["test-cookie=domain", HTTPS_EXAMPLE_COM]);
+ suite.push(["test-cookie=subdomain", HTTPS_SUBDOMAIN_1_EXAMPLE_COM]);
+ suite.push(["test-cookie-insecure=insecure_domain", HTTP_EXAMPLE_COM]);
+ suite.push([
+ "test-cookie-insecure=insecure_subdomain",
+ HTTP_SUBDOMAIN_1_EXAMPLE_COM,
+ ]);
+ suite.push(["test-cookie=sentinel", HTTPS_EXAMPLE_COM]);
+ return suite;
+}
+
+function cookiesFromSuite() {
+ var cookies = [];
+ for (var [cookie] of suite()) {
+ cookies.push(cookie);
+ }
+ return cookies;
+}
+
+function cookiesMatchingDomain(domain) {
+ var s = suite();
+ var result = [];
+ for (var [cookie, dom] of s) {
+ if (dom == domain) {
+ result.push(cookie);
+ }
+ }
+ return result;
+}
+
+function justSitename(maybeSchemefulMaybeSubdomainSite) {
+ let mssArray = maybeSchemefulMaybeSubdomainSite.split("://");
+ let maybesubdomain = mssArray[mssArray.length - 1];
+ let msdArray = maybesubdomain.split(".");
+ return msdArray.slice(msdArray.length - 2, msdArray.length).join(".");
+}
+
+// triggers set-cookie, which will trigger cookie-changed messages
+// messages will be filtered against the cookie list created from above
+// only unfiltered messages should make it to the content process
+async function triggerSuite() {
+ let triggerCookies = suite();
+ for (var [cookie, schemefulDomain] of triggerCookies) {
+ let secure = false;
+ if (schemefulDomain.includes("https")) {
+ secure = true;
+ }
+
+ var url =
+ browserTestPath(schemefulDomain) + "cookie_filtering_resource.sjs";
+ await fetchHelper(url, cookie, secure, justSitename(schemefulDomain));
+ Services.cookies.removeAll(); // clean cookies across secure/insecure runs
+ }
+}
+
+add_task(preclean_test);
+add_task(test_domain); // 5
+add_task(test_insecure_domain); // 2
+add_task(test_subdomain); // 5
+add_task(test_insecure_subdomain); // 2
+add_task(cleanup_test);
diff --git a/netwerk/test/browser/browser_cookie_sync_across_tabs.js b/netwerk/test/browser/browser_cookie_sync_across_tabs.js
new file mode 100644
index 0000000000..127bb2555b
--- /dev/null
+++ b/netwerk/test/browser/browser_cookie_sync_across_tabs.js
@@ -0,0 +1,79 @@
+/*
+ * Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+"use strict";
+
+add_task(async function () {
+ info("Make sure cookie changes in one process are visible in the other");
+
+ const kRoot = getRootDirectory(gTestPath).replace(
+ "chrome://mochitests/content/",
+ "https://example.com/"
+ );
+ const kTestPage = kRoot + "dummy.html";
+
+ Services.cookies.removeAll();
+
+ let tab1 = await BrowserTestUtils.openNewForegroundTab({
+ gBrowser,
+ url: kTestPage,
+ forceNewProcess: true,
+ });
+ let tab2 = await BrowserTestUtils.openNewForegroundTab({
+ gBrowser,
+ url: kTestPage,
+ forceNewProcess: true,
+ });
+
+ let browser1 = gBrowser.getBrowserForTab(tab1);
+ let browser2 = gBrowser.getBrowserForTab(tab2);
+
+ let pid1 = browser1.frameLoader.remoteTab.osPid;
+ let pid2 = browser2.frameLoader.remoteTab.osPid;
+
+ // Note, this might not be true once fission is implemented (Bug 1451850)
+ ok(pid1 != pid2, "We should have different processes here.");
+
+ await SpecialPowers.spawn(browser1, [], async function () {
+ is(content.document.cookie, "", "Expecting no cookies");
+ });
+
+ await SpecialPowers.spawn(browser2, [], async function () {
+ is(content.document.cookie, "", "Expecting no cookies");
+ });
+
+ await SpecialPowers.spawn(browser1, [], async function () {
+ content.document.cookie = "a1=test";
+ });
+
+ await SpecialPowers.spawn(browser2, [], async function () {
+ is(content.document.cookie, "a1=test", "Cookie should be set");
+ content.document.cookie = "a1=other_test";
+ });
+
+ await SpecialPowers.spawn(browser1, [], async function () {
+ is(content.document.cookie, "a1=other_test", "Cookie should be set");
+ content.document.cookie = "a2=again";
+ });
+
+ await SpecialPowers.spawn(browser2, [], async function () {
+ is(
+ content.document.cookie,
+ "a1=other_test; a2=again",
+ "Cookie should be set"
+ );
+ content.document.cookie = "a1=; expires=Thu, 01-Jan-1970 00:00:01 GMT;";
+ content.document.cookie = "a2=; expires=Thu, 01-Jan-1970 00:00:01 GMT;";
+ });
+
+ await SpecialPowers.spawn(browser1, [], async function () {
+ is(content.document.cookie, "", "Cookies should be cleared");
+ });
+
+ BrowserTestUtils.removeTab(tab1);
+ BrowserTestUtils.removeTab(tab2);
+
+ ok(true, "Got to the end of the test!");
+});
diff --git a/netwerk/test/browser/browser_fetch_lnk.js b/netwerk/test/browser/browser_fetch_lnk.js
new file mode 100644
index 0000000000..ea8ef57984
--- /dev/null
+++ b/netwerk/test/browser/browser_fetch_lnk.js
@@ -0,0 +1,23 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+add_task(async () => {
+ const FILE_PAGE = Services.io.newFileURI(
+ new FileUtils.File(getTestFilePath("dummy.html"))
+ ).spec;
+ await BrowserTestUtils.withNewTab(FILE_PAGE, async browser => {
+ try {
+ await SpecialPowers.spawn(browser, [], () =>
+ content.fetch("./file_lnk.lnk")
+ );
+ ok(
+ false,
+ "Loading lnk must fail if it links to a file from other directory"
+ );
+ } catch (err) {
+ is(err.constructor.name, "TypeError", "Should fail on Windows");
+ }
+ });
+});
diff --git a/netwerk/test/browser/browser_http_index_format.js b/netwerk/test/browser/browser_http_index_format.js
new file mode 100644
index 0000000000..7aa2f84a86
--- /dev/null
+++ b/netwerk/test/browser/browser_http_index_format.js
@@ -0,0 +1,48 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+const FILE_URI = Services.appinfo.OS == "WINNT" ? "file://C:/" : "file:///";
+
+const RESOURCE_URI = "resource:///";
+
+const ZIP_FILE = "res_empty.zip";
+const ZIP_DIR = getChromeDir(getResolvedURI(gTestPath));
+ZIP_DIR.append(ZIP_FILE);
+ZIP_DIR.normalize();
+const ZIP_URI = Services.io.newFileURI(ZIP_DIR).spec;
+const JAR_URI = "jar:" + ZIP_URI + "!/";
+
+const BASE_URI = "http://mochi.test:8888/browser/netwerk/test/browser/";
+const INDEX_URI = BASE_URI + "res_http_index_format";
+
+async function expectIndexToDisplay(aUrl, aExpectToDisplay) {
+ info(`Opening ${aUrl} to check if index will be displayed`);
+ const tab = await BrowserTestUtils.openNewForegroundTab(gBrowser, aUrl, true);
+ await SpecialPowers.spawn(
+ tab.linkedBrowser,
+ [aExpectToDisplay],
+ aExpectToDisplay => {
+ is(
+ !!content.document.getElementsByTagName("table").length,
+ aExpectToDisplay,
+ "The index should be displayed"
+ );
+ }
+ );
+ await BrowserTestUtils.removeTab(tab);
+}
+
+add_task(async function test_http_index_format() {
+ await expectIndexToDisplay(FILE_URI, true);
+ await expectIndexToDisplay(RESOURCE_URI, true);
+ await expectIndexToDisplay(JAR_URI, true);
+ await expectIndexToDisplay(INDEX_URI, false);
+
+ await SpecialPowers.pushPrefEnv({
+ set: [["network.http_index_format.allowed_schemes", "*"]],
+ });
+
+ await expectIndexToDisplay(INDEX_URI, true);
+});
diff --git a/netwerk/test/browser/browser_nsIFormPOSTActionChannel.js b/netwerk/test/browser/browser_nsIFormPOSTActionChannel.js
new file mode 100644
index 0000000000..e791794579
--- /dev/null
+++ b/netwerk/test/browser/browser_nsIFormPOSTActionChannel.js
@@ -0,0 +1,273 @@
+/*
+ * Tests for bug 1241377: A channel with nsIFormPOSTActionChannel interface
+ * should be able to accept form POST.
+ */
+
+"use strict";
+
+const SCHEME = "x-bug1241377";
+
+const FORM_BASE = SCHEME + "://dummy/form/";
+const NORMAL_FORM_URI = FORM_BASE + "normal.html";
+const UPLOAD_FORM_URI = FORM_BASE + "upload.html";
+const POST_FORM_URI = FORM_BASE + "post.html";
+
+const ACTION_BASE = SCHEME + "://dummy/action/";
+const NORMAL_ACTION_URI = ACTION_BASE + "normal.html";
+const UPLOAD_ACTION_URI = ACTION_BASE + "upload.html";
+const POST_ACTION_URI = ACTION_BASE + "post.html";
+
+function CustomProtocolHandler() {}
+CustomProtocolHandler.prototype = {
+ /** nsIProtocolHandler */
+ get scheme() {
+ return SCHEME;
+ },
+ newChannel(aURI, aLoadInfo) {
+ return new CustomChannel(aURI, aLoadInfo);
+ },
+ allowPort(port, scheme) {
+ return port != -1;
+ },
+
+ /** nsISupports */
+ QueryInterface: ChromeUtils.generateQI(["nsIProtocolHandler"]),
+};
+
+function CustomChannel(aURI, aLoadInfo) {
+ this.uri = aURI;
+ this.loadInfo = aLoadInfo;
+
+ this._uploadStream = null;
+
+ var interfaces = [Ci.nsIRequest, Ci.nsIChannel];
+ if (this.uri.spec == POST_ACTION_URI) {
+ interfaces.push(Ci.nsIFormPOSTActionChannel);
+ } else if (this.uri.spec == UPLOAD_ACTION_URI) {
+ interfaces.push(Ci.nsIUploadChannel);
+ }
+ this.QueryInterface = ChromeUtils.generateQI(interfaces);
+}
+CustomChannel.prototype = {
+ /** nsIUploadChannel */
+ get uploadStream() {
+ return this._uploadStream;
+ },
+ set uploadStream(val) {
+ throw Components.Exception("", Cr.NS_ERROR_NOT_IMPLEMENTED);
+ },
+ setUploadStream(aStream, aContentType, aContentLength) {
+ this._uploadStream = aStream;
+ },
+
+ /** nsIChannel */
+ get originalURI() {
+ return this.uri;
+ },
+ get URI() {
+ return this.uri;
+ },
+ owner: null,
+ notificationCallbacks: null,
+ get securityInfo() {
+ return null;
+ },
+ get contentType() {
+ return "text/html";
+ },
+ set contentType(val) {},
+ contentCharset: "UTF-8",
+ get contentLength() {
+ return -1;
+ },
+ set contentLength(val) {
+ throw Components.Exception("", Cr.NS_ERROR_NOT_IMPLEMENTED);
+ },
+ open() {
+ throw Components.Exception("", Cr.NS_ERROR_NOT_IMPLEMENTED);
+ },
+ asyncOpen(aListener) {
+ var data = `
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8">
+<title>test bug 1241377</title>
+</head>
+<body>
+`;
+
+ if (this.uri.spec.startsWith(FORM_BASE)) {
+ data += `
+<form id="form" action="${this.uri.spec.replace(FORM_BASE, ACTION_BASE)}"
+ method="post" enctype="text/plain" target="frame">
+<input type="hidden" name="foo" value="bar">
+<input type="submit">
+</form>
+
+<iframe id="frame" name="frame" width="200" height="200"></iframe>
+
+<script type="text/javascript">
+<!--
+document.getElementById('form').submit();
+//-->
+</script>
+`;
+ } else if (this.uri.spec.startsWith(ACTION_BASE)) {
+ var postData = "";
+ var headers = {};
+ if (this._uploadStream) {
+ var bstream = Cc["@mozilla.org/binaryinputstream;1"].createInstance(
+ Ci.nsIBinaryInputStream
+ );
+ bstream.setInputStream(this._uploadStream);
+ postData = bstream.readBytes(bstream.available());
+
+ if (this._uploadStream instanceof Ci.nsIMIMEInputStream) {
+ this._uploadStream.visitHeaders((name, value) => {
+ headers[name] = value;
+ });
+ }
+ }
+ data += `
+<input id="upload_stream" value="${this._uploadStream ? "yes" : "no"}">
+<input id="post_data" value="${btoa(postData)}">
+<input id="upload_headers" value='${JSON.stringify(headers)}'>
+`;
+ }
+
+ data += `
+</body>
+</html>
+`;
+
+ var stream = Cc["@mozilla.org/io/string-input-stream;1"].createInstance(
+ Ci.nsIStringInputStream
+ );
+ stream.setData(data, data.length);
+
+ var runnable = {
+ run: () => {
+ try {
+ aListener.onStartRequest(this, null);
+ } catch (e) {}
+ try {
+ aListener.onDataAvailable(this, stream, 0, stream.available());
+ } catch (e) {}
+ try {
+ aListener.onStopRequest(this, null, Cr.NS_OK);
+ } catch (e) {}
+ },
+ };
+ Services.tm.dispatchToMainThread(runnable);
+ },
+
+ /** nsIRequest */
+ get name() {
+ return this.uri.spec;
+ },
+ isPending() {
+ return false;
+ },
+ get status() {
+ return Cr.NS_OK;
+ },
+ cancel(status) {},
+ loadGroup: null,
+ loadFlags:
+ Ci.nsIRequest.LOAD_NORMAL |
+ Ci.nsIRequest.INHIBIT_CACHING |
+ Ci.nsIRequest.LOAD_BYPASS_CACHE,
+};
+
+function frameScript() {
+ /* eslint-env mozilla/frame-script */
+ /* eslint-disable mozilla/no-arbitrary-setTimeout */
+ addMessageListener("Test:WaitForIFrame", function () {
+ var check = function () {
+ if (content) {
+ var frame = content.document.getElementById("frame");
+ if (frame) {
+ var upload_stream =
+ frame.contentDocument.getElementById("upload_stream");
+ var post_data = frame.contentDocument.getElementById("post_data");
+ var headers = frame.contentDocument.getElementById("upload_headers");
+ if (upload_stream && post_data && headers) {
+ sendAsyncMessage("Test:IFrameLoaded", [
+ upload_stream.value,
+ post_data.value,
+ headers.value,
+ ]);
+ return;
+ }
+ }
+ }
+
+ setTimeout(check, 100);
+ };
+
+ check();
+ });
+ /* eslint-enable mozilla/no-arbitrary-setTimeout */
+}
+
+function loadTestTab(uri) {
+ gBrowser.selectedTab = BrowserTestUtils.addTab(gBrowser, uri);
+ var browser = gBrowser.selectedBrowser;
+
+ let manager = browser.messageManager;
+ browser.messageManager.loadFrameScript(
+ "data:,(" + frameScript.toString() + ")();",
+ true
+ );
+
+ return new Promise(resolve => {
+ function listener({ data: [hasUploadStream, postData, headers] }) {
+ manager.removeMessageListener("Test:IFrameLoaded", listener);
+ resolve([hasUploadStream, atob(postData), JSON.parse(headers)]);
+ }
+
+ manager.addMessageListener("Test:IFrameLoaded", listener);
+ manager.sendAsyncMessage("Test:WaitForIFrame");
+ });
+}
+
+add_task(async function () {
+ var handler = new CustomProtocolHandler();
+ Services.io.registerProtocolHandler(
+ SCHEME,
+ handler,
+ Ci.nsIProtocolHandler.URI_NORELATIVE |
+ Ci.nsIProtocolHandler.URI_IS_LOCAL_RESOURCE,
+ -1
+ );
+ registerCleanupFunction(function () {
+ Services.io.unregisterProtocolHandler(SCHEME);
+ });
+});
+
+add_task(async function () {
+ var [hasUploadStream] = await loadTestTab(NORMAL_FORM_URI);
+ is(hasUploadStream, "no", "normal action should not have uploadStream");
+
+ gBrowser.removeCurrentTab();
+});
+
+add_task(async function () {
+ var [hasUploadStream] = await loadTestTab(UPLOAD_FORM_URI);
+ is(hasUploadStream, "no", "upload action should not have uploadStream");
+
+ gBrowser.removeCurrentTab();
+});
+
+add_task(async function () {
+ var [hasUploadStream, postData, headers] = await loadTestTab(POST_FORM_URI);
+
+ is(hasUploadStream, "yes", "post action should have uploadStream");
+ is(postData, "foo=bar\r\n", "POST data is received correctly");
+
+ is(headers["Content-Type"], "text/plain", "Content-Type header is correct");
+ is(headers["Content-Length"], undefined, "Content-Length header is correct");
+
+ gBrowser.removeCurrentTab();
+});
diff --git a/netwerk/test/browser/browser_post_auth.js b/netwerk/test/browser/browser_post_auth.js
new file mode 100644
index 0000000000..24104f96d6
--- /dev/null
+++ b/netwerk/test/browser/browser_post_auth.js
@@ -0,0 +1,65 @@
+"use strict";
+
+const { PromptTestUtils } = ChromeUtils.importESModule(
+ "resource://testing-common/PromptTestUtils.sys.mjs"
+);
+
+const FOLDER = getRootDirectory(gTestPath).replace(
+ "chrome://mochitests/content/",
+ "http://mochi.test:8888/"
+);
+
+add_task(async function () {
+ let tab = await BrowserTestUtils.openNewForegroundTab(
+ gBrowser,
+ "about:blank"
+ );
+ let browserLoadedPromise = BrowserTestUtils.browserLoaded(
+ tab.linkedBrowser,
+ true,
+ `${FOLDER}post.html`
+ );
+ BrowserTestUtils.startLoadingURIString(
+ tab.linkedBrowser,
+ `${FOLDER}post.html`
+ );
+ await browserLoadedPromise;
+
+ let finalLoadPromise = BrowserTestUtils.browserLoaded(
+ tab.linkedBrowser,
+ true,
+ `${FOLDER}auth_post.sjs`
+ );
+
+ await SpecialPowers.spawn(tab.linkedBrowser, [], async function () {
+ let file = new content.File(
+ [new content.Blob(["1234".repeat(1024 * 500)], { type: "text/plain" })],
+ "test-name"
+ );
+ content.document.getElementById("input_file").mozSetFileArray([file]);
+ content.document.getElementById("form").submit();
+ });
+
+ let promptPromise = PromptTestUtils.handleNextPrompt(
+ tab.linkedBrowser,
+ {
+ modalType: Services.prefs.getIntPref("prompts.modalType.httpAuth"),
+ promptType: "promptUserAndPass",
+ },
+ { buttonNumClick: 0, loginInput: "user", passwordInput: "pass" }
+ );
+
+ await promptPromise;
+
+ await finalLoadPromise;
+
+ await SpecialPowers.spawn(tab.linkedBrowser, [], async function () {
+ Assert.ok(content.location.href.includes("auth_post.sjs"));
+ Assert.ok(content.document.body.innerHTML.includes("1234"));
+ });
+
+ BrowserTestUtils.removeTab(tab);
+
+ // Clean up any active logins we added during the test.
+ Services.obs.notifyObservers(null, "net:clear-active-logins");
+});
diff --git a/netwerk/test/browser/browser_post_file.js b/netwerk/test/browser/browser_post_file.js
new file mode 100644
index 0000000000..8b156dff9e
--- /dev/null
+++ b/netwerk/test/browser/browser_post_file.js
@@ -0,0 +1,71 @@
+/*
+ * Tests for bug 1241100: Post to local file should not overwrite the file.
+ */
+"use strict";
+
+async function createTestFile(filename, content) {
+ let path = PathUtils.join(PathUtils.tempDir, filename);
+ await IOUtils.writeUTF8(path, content);
+ return path;
+}
+
+add_task(async function () {
+ var postFilename = "post_file.html";
+ var actionFilename = "action_file.html";
+
+ var postFileContent = `
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8">
+<title>post file</title>
+</head>
+<body onload="document.getElementById('form').submit();">
+<form id="form" action="${actionFilename}" method="post" enctype="text/plain" target="frame">
+<input type="hidden" name="foo" value="bar">
+<input type="submit">
+</form>
+<iframe id="frame" name="frame"></iframe>
+</body>
+</html>
+`;
+
+ var actionFileContent = `
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8">
+<title>action file</title>
+</head>
+<body>
+<div id="action_file_ok">ok</div>
+</body>
+</html>
+`;
+
+ var postPath = await createTestFile(postFilename, postFileContent);
+ var actionPath = await createTestFile(actionFilename, actionFileContent);
+
+ var postURI = PathUtils.toFileURI(postPath);
+ var actionURI = PathUtils.toFileURI(actionPath);
+
+ let tab = await BrowserTestUtils.openNewForegroundTab(
+ gBrowser,
+ "about:blank"
+ );
+ let browserLoadedPromise = BrowserTestUtils.browserLoaded(
+ tab.linkedBrowser,
+ true,
+ actionURI
+ );
+ BrowserTestUtils.startLoadingURIString(tab.linkedBrowser, postURI);
+ await browserLoadedPromise;
+
+ var actionFileContentAfter = await IOUtils.readUTF8(actionPath);
+ is(actionFileContentAfter, actionFileContent, "action file is not modified");
+
+ await IOUtils.remove(postPath);
+ await IOUtils.remove(actionPath);
+
+ gBrowser.removeCurrentTab();
+});
diff --git a/netwerk/test/browser/browser_purgeCache_idle_daily.js b/netwerk/test/browser/browser_purgeCache_idle_daily.js
new file mode 100644
index 0000000000..21ba46af1d
--- /dev/null
+++ b/netwerk/test/browser/browser_purgeCache_idle_daily.js
@@ -0,0 +1,86 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+add_task(async function test_idle_cleanup() {
+ Services.fog.testResetFOG();
+ Services.prefs.setBoolPref(
+ "network.cache.shutdown_purge_in_background_task",
+ true
+ );
+ Services.prefs.setBoolPref("privacy.clearOnShutdown.cache", true);
+ Services.prefs.setBoolPref("privacy.sanitize.sanitizeOnShutdown", true);
+ let dir = Services.dirsvc.get("ProfD", Ci.nsIFile);
+ dir.append("cache2.2021-11-25-08-47-04.purge.bg_rm");
+ Assert.equal(dir.exists(), false, `Folder ${dir.path} should not exist`);
+ dir.create(Ci.nsIFile.DIRECTORY_TYPE, 0o744);
+ Assert.equal(
+ dir.exists(),
+ true,
+ `Folder ${dir.path} should have been created`
+ );
+
+ Services.obs.notifyObservers(null, "idle-daily");
+
+ await TestUtils.waitForCondition(() => {
+ return !dir.exists();
+ });
+
+ Assert.equal(
+ dir.exists(),
+ false,
+ `Folder ${dir.path} should have been purged by background task`
+ );
+ Assert.equal(
+ await Glean.networking.residualCacheFolderCount.testGetValue(),
+ 1
+ );
+ Assert.equal(
+ await Glean.networking.residualCacheFolderRemoval.success.testGetValue(),
+ 1
+ );
+ Assert.equal(
+ await Glean.networking.residualCacheFolderRemoval.failure.testGetValue(),
+ null
+ );
+
+ // Check that telemetry properly detects folders failing to be deleted when readonly
+ // Making folders readonly only works on windows
+ if (AppConstants.platform == "win") {
+ dir.create(Ci.nsIFile.DIRECTORY_TYPE, 0o744);
+ dir.QueryInterface(Ci.nsILocalFileWin).readOnly = true;
+
+ Services.obs.notifyObservers(null, "idle-daily");
+
+ await BrowserTestUtils.waitForCondition(async () => {
+ return (
+ (await Glean.networking.residualCacheFolderRemoval.failure.testGetValue()) ==
+ 1
+ );
+ });
+
+ Assert.equal(
+ await Glean.networking.residualCacheFolderCount.testGetValue(),
+ 2
+ );
+ Assert.equal(
+ await Glean.networking.residualCacheFolderRemoval.success.testGetValue(),
+ 1
+ );
+ Assert.equal(
+ await Glean.networking.residualCacheFolderRemoval.failure.testGetValue(),
+ 1
+ );
+
+ dir.QueryInterface(Ci.nsILocalFileWin).readOnly = false;
+ dir.remove(true);
+ }
+
+ Services.prefs.clearUserPref(
+ "network.cache.shutdown_purge_in_background_task"
+ );
+ Services.prefs.clearUserPref("privacy.clearOnShutdown.cache");
+ Services.prefs.clearUserPref("privacy.sanitize.sanitizeOnShutdown");
+});
diff --git a/netwerk/test/browser/browser_resource_navigation.js b/netwerk/test/browser/browser_resource_navigation.js
new file mode 100644
index 0000000000..56ec280b83
--- /dev/null
+++ b/netwerk/test/browser/browser_resource_navigation.js
@@ -0,0 +1,76 @@
+/*
+ * Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+"use strict";
+
+add_task(async function () {
+ info("Make sure navigation through links in resource:// pages work");
+
+ await BrowserTestUtils.withNewTab(
+ { gBrowser, url: "resource://gre/" },
+ async function (browser) {
+ // Following a directory link shall properly open the directory (bug 1224046)
+ await SpecialPowers.spawn(browser, [], function () {
+ let link = Array.prototype.filter.call(
+ content.document.getElementsByClassName("dir"),
+ function (element) {
+ let name = element.textContent;
+ // Depending whether resource:// is backed by jar: or file://,
+ // directories either have a trailing slash or they don't.
+ if (name.endsWith("/")) {
+ name = name.slice(0, -1);
+ }
+ return name == "components";
+ }
+ )[0];
+ // First ensure the link is in the viewport
+ link.scrollIntoView();
+ // Then click on it.
+ link.click();
+ });
+
+ await BrowserTestUtils.browserLoaded(
+ browser,
+ undefined,
+ "resource://gre/components/"
+ );
+
+ // Following the parent link shall properly open the parent (bug 1366180)
+ await SpecialPowers.spawn(browser, [], function () {
+ let link = content.document
+ .getElementById("UI_goUp")
+ .getElementsByTagName("a")[0];
+ // The link should always be high enough in the page to be in the viewport.
+ link.click();
+ });
+
+ await BrowserTestUtils.browserLoaded(
+ browser,
+ undefined,
+ "resource://gre/"
+ );
+
+ // Following a link to a given file shall properly open the file.
+ await SpecialPowers.spawn(browser, [], function () {
+ let link = Array.prototype.filter.call(
+ content.document.getElementsByClassName("file"),
+ function (element) {
+ return element.textContent == "greprefs.js";
+ }
+ )[0];
+ link.scrollIntoView();
+ link.click();
+ });
+
+ await BrowserTestUtils.browserLoaded(
+ browser,
+ undefined,
+ "resource://gre/greprefs.js"
+ );
+
+ ok(true, "Got to the end of the test!");
+ }
+ );
+});
diff --git a/netwerk/test/browser/browser_speculative_connection_link_header.js b/netwerk/test/browser/browser_speculative_connection_link_header.js
new file mode 100644
index 0000000000..24549d30b0
--- /dev/null
+++ b/netwerk/test/browser/browser_speculative_connection_link_header.js
@@ -0,0 +1,57 @@
+/* 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/. */
+
+Services.prefs.setBoolPref("network.http.debug-observations", true);
+
+registerCleanupFunction(function () {
+ Services.prefs.clearUserPref("network.http.debug-observations");
+});
+
+// Test steps:
+// 1. Load file_link_header.sjs
+// 2.`<link rel="preconnect" href="https://localhost">` is in
+// file_link_header.sjs, so we will create a speculative connection.
+// 3. We use "speculative-connect-request" topic to observe whether the
+// speculative connection is attempted.
+// 4. Finally, we check if the observed host and partition key are the same and
+// as the expected.
+add_task(async function test_link_preconnect() {
+ let requestUrl = `https://example.com/browser/netwerk/test/browser/file_link_header.sjs`;
+
+ let observed = "";
+ let observer = {
+ QueryInterface: ChromeUtils.generateQI(["nsIObserver"]),
+ observe(aSubject, aTopic, aData) {
+ if (aTopic == "speculative-connect-request") {
+ Services.obs.removeObserver(observer, "speculative-connect-request");
+ observed = aData;
+ }
+ },
+ };
+ Services.obs.addObserver(observer, "speculative-connect-request");
+
+ await BrowserTestUtils.withNewTab(
+ {
+ gBrowser,
+ url: requestUrl,
+ waitForLoad: true,
+ },
+ async function () {}
+ );
+
+ // The hash key should be like:
+ // ".S........[tlsflags0x00000000]localhost:443^partitionKey=%28https%2Cexample.com%29"
+
+ // Extracting "localhost:443"
+ let hostPortRegex = /\[.*\](.*?)\^/;
+ let hostPortMatch = hostPortRegex.exec(observed);
+ let hostPort = hostPortMatch ? hostPortMatch[1] : "";
+ // Extracting "%28https%2Cexample.com%29"
+ let partitionKeyRegex = /\^partitionKey=(.*)$/;
+ let partitionKeyMatch = partitionKeyRegex.exec(observed);
+ let partitionKey = partitionKeyMatch ? partitionKeyMatch[1] : "";
+
+ Assert.equal(hostPort, "localhost:443");
+ Assert.equal(partitionKey, "%28https%2Cexample.com%29");
+});
diff --git a/netwerk/test/browser/browser_test_data_channel_observer.js b/netwerk/test/browser/browser_test_data_channel_observer.js
new file mode 100644
index 0000000000..e03bbc72e6
--- /dev/null
+++ b/netwerk/test/browser/browser_test_data_channel_observer.js
@@ -0,0 +1,35 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+const TEST_URI = "data:text/html;charset=utf-8,<h1>Test";
+
+let created = false;
+
+add_task(async function test_data_channel_observer() {
+ setupObserver();
+ let tab = await BrowserTestUtils.addTab(gBrowser, TEST_URI);
+ await BrowserTestUtils.waitForCondition(() => created);
+ ok(created, "We received observer notification");
+ await BrowserTestUtils.removeTab(tab);
+});
+
+function setupObserver() {
+ const observer = {
+ QueryInterface: ChromeUtils.generateQI(["nsIObserver"]),
+
+ observe: function observe(subject, topic) {
+ switch (topic) {
+ case "data-channel-opened":
+ let channelURI = subject.QueryInterface(Ci.nsIChannel).URI.spec;
+ if (channelURI === TEST_URI) {
+ Services.obs.removeObserver(observer, "data-channel-opened");
+ created = true;
+ }
+ break;
+ }
+ },
+ };
+ Services.obs.addObserver(observer, "data-channel-opened");
+}
diff --git a/netwerk/test/browser/browser_test_favicon.js b/netwerk/test/browser/browser_test_favicon.js
new file mode 100644
index 0000000000..99cc6b0922
--- /dev/null
+++ b/netwerk/test/browser/browser_test_favicon.js
@@ -0,0 +1,26 @@
+// Tests third party cookie blocking using a favicon loaded from a different
+// domain. The cookie should be considered third party.
+"use strict";
+add_task(async function () {
+ const iconUrl =
+ "http://example.org/browser/netwerk/test/browser/damonbowling.jpg";
+ const pageUrl =
+ "http://example.com/browser/netwerk/test/browser/file_favicon.html";
+ await SpecialPowers.pushPrefEnv({
+ set: [["network.cookie.cookieBehavior", 1]],
+ });
+
+ let promise = TestUtils.topicObserved("cookie-rejected", subject => {
+ let uri = subject.QueryInterface(Ci.nsIURI);
+ return uri.spec == iconUrl;
+ });
+
+ // Kick off a page load that will load the favicon.
+ let tab = await BrowserTestUtils.openNewForegroundTab(gBrowser, pageUrl);
+ registerCleanupFunction(async function () {
+ BrowserTestUtils.removeTab(tab);
+ });
+
+ await promise;
+ ok(true, "foreign favicon cookie was blocked");
+});
diff --git a/netwerk/test/browser/browser_test_io_activity.js b/netwerk/test/browser/browser_test_io_activity.js
new file mode 100644
index 0000000000..1e9cb29b6d
--- /dev/null
+++ b/netwerk/test/browser/browser_test_io_activity.js
@@ -0,0 +1,50 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set ts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+"use strict";
+const ROOT_URL = getRootDirectory(gTestPath).replace(
+ "chrome://mochitests/content/",
+ "https://example.com/"
+);
+const TEST_URL = "about:license";
+const TEST_URL2 = ROOT_URL + "ioactivity.html";
+
+var gotSocket = false;
+var gotFile = false;
+var gotSqlite = false;
+var gotEmptyData = false;
+
+function processResults(results) {
+ for (let data of results) {
+ console.log(data.location);
+ gotEmptyData = data.rx == 0 && data.tx == 0 && !gotEmptyData;
+ gotSocket = data.location.startsWith("socket://127.0.0.1:") || gotSocket;
+ gotFile = data.location.endsWith("aboutLicense.css") || gotFile;
+ gotSqlite = data.location.endsWith("places.sqlite") || gotSqlite;
+ // check for the write-ahead file as well
+ gotSqlite = data.location.endsWith("places.sqlite-wal") || gotSqlite;
+ }
+}
+
+add_task(async function testRequestIOActivity() {
+ await SpecialPowers.pushPrefEnv({
+ set: [["io.activity.enabled", true]],
+ });
+ waitForExplicitFinish();
+ Services.obs.notifyObservers(null, "profile-initial-state");
+
+ await BrowserTestUtils.withNewTab(TEST_URL, async function (browser) {
+ await BrowserTestUtils.withNewTab(TEST_URL2, async function (browser) {
+ let results = await ChromeUtils.requestIOActivity();
+ processResults(results);
+
+ ok(gotSocket, "A socket was used");
+ // test deactivated for now
+ // ok(gotFile, "A file was used");
+ ok(gotSqlite, "A sqlite DB was used");
+ ok(!gotEmptyData, "Every I/O event had data");
+ });
+ });
+});
diff --git a/netwerk/test/browser/browser_test_offline_tab.js b/netwerk/test/browser/browser_test_offline_tab.js
new file mode 100644
index 0000000000..bf60b4f462
--- /dev/null
+++ b/netwerk/test/browser/browser_test_offline_tab.js
@@ -0,0 +1,36 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+add_task(async function test_set_tab_offline() {
+ await BrowserTestUtils.withNewTab("https://example.com", async browser => {
+ // Set the tab to offline
+ gBrowser.selectedBrowser.browsingContext.forceOffline = true;
+
+ await SpecialPowers.spawn(browser, [], async () => {
+ try {
+ await content.fetch("https://example.com/empty.html");
+ ok(false, "Should not load since tab is offline");
+ } catch (err) {
+ is(err.name, "TypeError", "Should fail since tab is offline");
+ }
+ });
+ });
+});
+
+add_task(async function test_set_tab_online() {
+ await BrowserTestUtils.withNewTab("https://example.com", async browser => {
+ // Set the tab to online
+ gBrowser.selectedBrowser.browsingContext.forceOffline = false;
+
+ await SpecialPowers.spawn(browser, [], async () => {
+ try {
+ await content.fetch("https://example.com/empty.html");
+ ok(true, "Should load since tab is online");
+ } catch (err) {
+ ok(false, "Should not fail since tab is online");
+ }
+ });
+ });
+});
diff --git a/netwerk/test/browser/cookie_filtering_helper.sys.mjs b/netwerk/test/browser/cookie_filtering_helper.sys.mjs
new file mode 100644
index 0000000000..ab9d721359
--- /dev/null
+++ b/netwerk/test/browser/cookie_filtering_helper.sys.mjs
@@ -0,0 +1,166 @@
+/*
+ * Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+// The functions in this file will run in the content process in a test
+// scope.
+/* eslint-env mozilla/simpletest */
+/* global ContentTaskUtils, content */
+
+import { NetUtil } from "resource://gre/modules/NetUtil.sys.mjs";
+
+const info = console.log;
+
+export var HTTPS_EXAMPLE_ORG = "https://example.org";
+export var HTTPS_EXAMPLE_COM = "https://example.com";
+export var HTTP_EXAMPLE_COM = "http://example.com";
+
+export function browserTestPath(uri) {
+ return uri + "/browser/netwerk/test/browser/";
+}
+
+export function waitForAllExpectedTests() {
+ return ContentTaskUtils.waitForCondition(() => {
+ return content.testDone === true;
+ });
+}
+
+export function cleanupObservers() {
+ Services.obs.notifyObservers(null, "cookie-content-filter-cleanup");
+}
+
+export async function preclean_test() {
+ // enable all cookies for the set-cookie trigger via setCookieStringFromHttp
+ Services.prefs.setIntPref("network.cookie.cookieBehavior", 0);
+ Services.prefs.setBoolPref(
+ "network.cookieJarSettings.unblocked_for_testing",
+ true
+ );
+
+ Services.prefs.setBoolPref("network.cookie.sameSite.laxByDefault", false);
+ Services.prefs.setBoolPref(
+ "network.cookie.sameSite.noneRequiresSecure",
+ false
+ );
+ Services.prefs.setBoolPref("network.cookie.sameSite.schemeful", false);
+
+ Services.cookies.removeAll();
+}
+
+export async function cleanup_test() {
+ Services.prefs.clearUserPref("network.cookie.cookieBehavior");
+ Services.prefs.clearUserPref(
+ "network.cookieJarSettings.unblocked_for_testing"
+ );
+
+ Services.prefs.clearUserPref("network.cookie.sameSite.laxByDefault");
+ Services.prefs.clearUserPref("network.cookie.sameSite.noneRequiresSecure");
+ Services.prefs.clearUserPref("network.cookie.sameSite.schemeful");
+
+ Services.cookies.removeAll();
+}
+
+export async function fetchHelper(url, cookie, secure, domain = "") {
+ let headers = new Headers();
+
+ headers.append("return-set-cookie", cookie);
+
+ if (!secure) {
+ headers.append("return-insecure-cookie", cookie);
+ }
+
+ if (domain != "") {
+ headers.append("return-cookie-domain", domain);
+ }
+
+ info("fetching " + url);
+ await fetch(url, { headers });
+}
+
+// cookie header strings with multiple name=value pairs delimited by \n
+// will trigger multiple "cookie-changed" signals
+export function triggerSetCookieFromHttp(uri, cookie, fpd = "", ucd = 0) {
+ info("about to trigger set-cookie: " + uri + " " + cookie);
+ let channel = NetUtil.newChannel({
+ uri,
+ loadUsingSystemPrincipal: true,
+ contentPolicyType: Ci.nsIContentPolicy.TYPE_DOCUMENT,
+ });
+
+ if (fpd != "") {
+ channel.loadInfo.originAttributes = { firstPartyDomain: fpd };
+ }
+
+ if (ucd != 0) {
+ channel.loadInfo.originAttributes = { userContextId: ucd };
+ }
+ Services.cookies.setCookieStringFromHttp(uri, cookie, channel);
+}
+
+export async function triggerSetCookieFromHttpPrivate(uri, cookie) {
+ info("about to trigger set-cookie: " + uri + " " + cookie);
+ let channel = NetUtil.newChannel({
+ uri,
+ loadUsingSystemPrincipal: true,
+ contentPolicyType: Ci.nsIContentPolicy.TYPE_DOCUMENT,
+ }).QueryInterface(Ci.nsIPrivateBrowsingChannel);
+ channel.loadInfo.originAttributes = { privateBrowsingId: 1 };
+ channel.setPrivate(true);
+ Services.cookies.setCookieStringFromHttp(uri, cookie, channel);
+}
+
+// observer/listener function that will be run on the content processes
+// listens and checks for the expected cookies
+export function checkExpectedCookies(expected, browserName) {
+ const COOKIE_FILTER_TEST_MESSAGE = "content-added-cookie";
+ const COOKIE_FILTER_TEST_CLEANUP = "cookie-content-filter-cleanup";
+
+ // Counting the expected number of tests is vital to the integrity of these
+ // tests due to the fact that this test suite relies on triggering tests
+ // to occur on multiple content processes.
+ // As such, test modifications/bugs often lead to silent failures.
+ // Hence, we count to ensure we didn't break anything
+ // To reduce risk here, we modularize each test as much as possible to
+ // increase liklihood that a silent failure will trigger a no-test
+ // error/warning
+ content.testDone = false;
+ let testNumber = 0;
+
+ // setup observer that continues listening/testing
+ function obs(subject, topic) {
+ // cleanup trigger recieved -> tear down the observer
+ if (topic == COOKIE_FILTER_TEST_CLEANUP) {
+ info("cleaning up: " + browserName);
+ Services.obs.removeObserver(obs, COOKIE_FILTER_TEST_MESSAGE);
+ Services.obs.removeObserver(obs, COOKIE_FILTER_TEST_CLEANUP);
+ return;
+ }
+
+ // test trigger recv'd -> perform test on cookie contents
+ if (topic == COOKIE_FILTER_TEST_MESSAGE) {
+ info("Checking if cookie visible: " + browserName);
+ let result = content.document.cookie;
+ let resultStr =
+ "Result " +
+ result +
+ " == expected: " +
+ expected[testNumber] +
+ " in " +
+ browserName;
+ ok(result == expected[testNumber], resultStr);
+ testNumber++;
+ if (testNumber >= expected.length) {
+ info("finishing browser tests: " + browserName);
+ content.testDone = true;
+ }
+ return;
+ }
+
+ ok(false, "Didn't handle cookie message properly"); //
+ }
+
+ info("setting up observers: " + browserName);
+ Services.obs.addObserver(obs, COOKIE_FILTER_TEST_MESSAGE);
+ Services.obs.addObserver(obs, COOKIE_FILTER_TEST_CLEANUP);
+}
diff --git a/netwerk/test/browser/cookie_filtering_resource.sjs b/netwerk/test/browser/cookie_filtering_resource.sjs
new file mode 100644
index 0000000000..979d56dc9c
--- /dev/null
+++ b/netwerk/test/browser/cookie_filtering_resource.sjs
@@ -0,0 +1,35 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+function handleRequest(request, response) {
+ response.setStatusLine(request.httpVersion, 200, "OK");
+ response.setHeader("Cache-Control", "no-cache", false);
+ response.setHeader("Content-Type", "text/html", false);
+
+ // configure set-cookie domain
+ let domain = "";
+ if (request.hasHeader("return-cookie-domain")) {
+ domain = "; Domain=" + request.getHeader("return-cookie-domain");
+ }
+
+ // configure set-cookie sameSite
+ let authStr = "; Secure";
+ if (request.hasHeader("return-insecure-cookie")) {
+ authStr = "";
+ }
+
+ // use headers to decide if we have them
+ if (request.hasHeader("return-set-cookie")) {
+ response.setHeader(
+ "Set-Cookie",
+ request.getHeader("return-set-cookie") + authStr + domain,
+ false
+ );
+ }
+
+ let body = "<!DOCTYPE html> <html> <body> true </body> </html>";
+ response.write(body);
+}
diff --git a/netwerk/test/browser/cookie_filtering_secure_resource_com.html b/netwerk/test/browser/cookie_filtering_secure_resource_com.html
new file mode 100644
index 0000000000..e25a719644
--- /dev/null
+++ b/netwerk/test/browser/cookie_filtering_secure_resource_com.html
@@ -0,0 +1,6 @@
+ <!DOCTYPE html>
+<html>
+<body>
+<img src="https://example.com/browser/netwerk/test/browser/cookie_filtering_square.png" width="100px">
+</body>
+</html>
diff --git a/netwerk/test/browser/cookie_filtering_secure_resource_com.html^headers^ b/netwerk/test/browser/cookie_filtering_secure_resource_com.html^headers^
new file mode 100644
index 0000000000..2bdf118064
--- /dev/null
+++ b/netwerk/test/browser/cookie_filtering_secure_resource_com.html^headers^
@@ -0,0 +1,2 @@
+Cache-Control: no-store
+Set-Cookie: test-cookie=comhtml
diff --git a/netwerk/test/browser/cookie_filtering_secure_resource_org.html b/netwerk/test/browser/cookie_filtering_secure_resource_org.html
new file mode 100644
index 0000000000..7221dc370d
--- /dev/null
+++ b/netwerk/test/browser/cookie_filtering_secure_resource_org.html
@@ -0,0 +1,6 @@
+ <!DOCTYPE html>
+<html>
+<body>
+<img src="https://example.org/browser/netwerk/test/browser/cookie_filtering_square.png" width="100px">
+</body>
+</html>
diff --git a/netwerk/test/browser/cookie_filtering_secure_resource_org.html^headers^ b/netwerk/test/browser/cookie_filtering_secure_resource_org.html^headers^
new file mode 100644
index 0000000000..924c150ccc
--- /dev/null
+++ b/netwerk/test/browser/cookie_filtering_secure_resource_org.html^headers^
@@ -0,0 +1,2 @@
+Cache-Control: no-store
+Set-Cookie: test-cookie=orghtml
diff --git a/netwerk/test/browser/cookie_filtering_square.png b/netwerk/test/browser/cookie_filtering_square.png
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/netwerk/test/browser/cookie_filtering_square.png
diff --git a/netwerk/test/browser/cookie_filtering_square.png^headers^ b/netwerk/test/browser/cookie_filtering_square.png^headers^
new file mode 100644
index 0000000000..912856ae4a
--- /dev/null
+++ b/netwerk/test/browser/cookie_filtering_square.png^headers^
@@ -0,0 +1,2 @@
+Cache-Control: no-cache
+Set-Cookie: test-cookie=png
diff --git a/netwerk/test/browser/damonbowling.jpg b/netwerk/test/browser/damonbowling.jpg
new file mode 100644
index 0000000000..8bdb2b6042
--- /dev/null
+++ b/netwerk/test/browser/damonbowling.jpg
Binary files differ
diff --git a/netwerk/test/browser/damonbowling.jpg^headers^ b/netwerk/test/browser/damonbowling.jpg^headers^
new file mode 100644
index 0000000000..77f4f49089
--- /dev/null
+++ b/netwerk/test/browser/damonbowling.jpg^headers^
@@ -0,0 +1,2 @@
+Cache-Control: no-store
+Set-Cookie: damon=bowling
diff --git a/netwerk/test/browser/dummy.html b/netwerk/test/browser/dummy.html
new file mode 100644
index 0000000000..8025fcdb20
--- /dev/null
+++ b/netwerk/test/browser/dummy.html
@@ -0,0 +1,11 @@
+<!DOCTYPE html>
+
+<head>
+<meta http-equiv="content-type" content="text/html; charset=utf-8">
+</head>
+
+<html>
+<body>
+ <p>Dummy Page</p>
+</body>
+</html>
diff --git a/netwerk/test/browser/early_hint_asset.sjs b/netwerk/test/browser/early_hint_asset.sjs
new file mode 100644
index 0000000000..ba52e757ff
--- /dev/null
+++ b/netwerk/test/browser/early_hint_asset.sjs
@@ -0,0 +1,50 @@
+"use strict";
+
+function handleRequest(request, response) {
+ let hinted =
+ request.hasHeader("X-Moz") && request.getHeader("X-Moz") === "early hint";
+ let count = JSON.parse(getSharedState("earlyHintCount"));
+ if (hinted) {
+ count.hinted += 1;
+ } else {
+ count.normal += 1;
+ }
+ setSharedState("earlyHintCount", JSON.stringify(count));
+
+ let content = "";
+ let qs = new URLSearchParams(request.queryString);
+ let asset = qs.get("as");
+
+ if (qs.get("cached") === "1") {
+ response.setHeader("Cache-Control", "max-age=604800", false);
+ } else {
+ response.setHeader("Cache-Control", "no-cache", false);
+ }
+
+ if (asset === "image") {
+ response.setHeader("Content-Type", "image/png", false);
+ // set to green/black horizontal stripes (71 bytes)
+ content = atob(
+ hinted
+ ? "iVBORw0KGgoAAAANSUhEUgAAAAIAAAACCAIAAAD91JpzAAAADklEQVQIW2OU+i/FAAcADoABNV8XGBMAAAAASUVORK5CYII="
+ : "iVBORw0KGgoAAAANSUhEUgAAAAIAAAACCAIAAAD91JpzAAAAE0lEQVQIW2P4//+/N8MkBiAGsgA1bAe1SzDY8gAAAABJRU5ErkJggg=="
+ );
+ } else if (asset === "style") {
+ response.setHeader("Content-Type", "text/css", false);
+ // green background on hint response, purple response otherwise
+ content = `#square { background: ${hinted ? "#1aff1a" : "#4b0092"}`;
+ } else if (asset === "script") {
+ response.setHeader("Content-Type", "application/javascript", false);
+ // green background on hint response, purple response otherwise
+ content = `window.onload = function() {
+ document.getElementById('square').style.background = "${
+ hinted ? "#1aff1a" : "#4b0092"
+ }";
+ }`;
+ } else if (asset === "fetch") {
+ response.setHeader("Content-Type", "text/plain", false);
+ content = hinted ? "hinted" : "normal";
+ }
+
+ response.write(content);
+}
diff --git a/netwerk/test/browser/early_hint_asset_html.sjs b/netwerk/test/browser/early_hint_asset_html.sjs
new file mode 100644
index 0000000000..eb5156d4f8
--- /dev/null
+++ b/netwerk/test/browser/early_hint_asset_html.sjs
@@ -0,0 +1,135 @@
+"use strict";
+
+function handleRequest(request, response) {
+ let qs = new URLSearchParams(request.queryString);
+ let asset = qs.get("as");
+ let hinted = qs.get("hinted") === "1";
+ let httpCode = qs.get("code");
+ let uuid = qs.get("uuid");
+ let cached = qs.get("cached") === "1";
+
+ let url = `early_hint_asset.sjs?as=${asset}${uuid ? `&uuid=${uuid}` : ""}${
+ cached ? "&cached=1" : ""
+ }`;
+
+ // write to raw socket
+ response.seizePower();
+ let link = "";
+ if (hinted) {
+ response.write("HTTP/1.1 103 Early Hint\r\n");
+ if (asset === "fetch" || asset === "font") {
+ // fetch and font has to specify the crossorigin attribute
+ // https://developer.mozilla.org/en-US/docs/Web/HTML/Element/link#attr-as
+ link = `Link: <${url}>; rel=preload; as=${asset}; crossorigin=anonymous\r\n`;
+ response.write(link);
+ } else if (asset === "module") {
+ // module preloads are handled differently
+ link = `Link: <${url}>; rel=modulepreload\r\n`;
+ response.write(link);
+ } else {
+ link = `Link: <${url}>; rel=preload; as=${asset}\r\n`;
+ response.write(link);
+ }
+ response.write("\r\n");
+ }
+
+ let body = "";
+ if (asset === "image") {
+ body = `<!DOCTYPE html>
+ <html>
+ <body>
+ <img src="${url}" width="100px">
+ </body>
+ </html>`;
+ } else if (asset === "style") {
+ body = `<!DOCTYPE html>
+ <html>
+ <head>
+ <link rel="stylesheet" type="text/css" href="${url}">
+ </head>
+ <body>
+ <h1>Test preload css<h1>
+ <div id="square" style="width:100px;height:100px;">
+ </body>
+ </html>
+ `;
+ } else if (asset === "script") {
+ body = `<!DOCTYPE html>
+ <html>
+ <head>
+ <script src="${url}"></script>
+ </head>
+ <body>
+ <h1>Test preload javascript<h1>
+ <div id="square" style="width:100px;height:100px;">
+ </body>
+ </html>
+ `;
+ } else if (asset === "module") {
+ // this code assumes that the .sjs for the module is in the same directory
+ var file_name = url.split("/");
+ file_name = file_name[file_name.length - 1];
+ body = `<!DOCTYPE html>
+ <html>
+ <head>
+ </head>
+ <body>
+ <h1>Test preload module<h1>
+ <div id="square" style="width:100px;height:100px;">
+ <script type="module">
+ import { draw } from "./${file_name}";
+ draw();
+ </script>
+ </body>
+ </html>
+ `;
+ } else if (asset === "fetch") {
+ body = `<!DOCTYPE html>
+ <html>
+ <body onload="onLoad()">
+ <script>
+ function onLoad() {
+ fetch("${url}")
+ .then(r => r.text())
+ .then(r => document.getElementsByTagName("h2")[0].textContent = r);
+ }
+ </script>
+ <h1>Test preload fetch</h1>
+ <h2>Fetching...</h2>
+ </body>
+ </html>
+ `;
+ } else if (asset === "font") {
+ body = `<!DOCTYPE html>
+ <html>
+ <head>
+ <style>
+ @font-face {
+ font-family: "preloadFont";
+ src: url("${url}");
+ }
+ body {
+ font-family: "preloadFont";
+ }
+ </style>
+ </head>
+ <body>
+ <h1>Test preload font<h1>
+ </body>
+ </html>
+ `;
+ }
+
+ if (!httpCode) {
+ response.write(`HTTP/1.1 200 OK\r\n`);
+ } else {
+ response.write(`HTTP/1.1 ${httpCode} Error\r\n`);
+ }
+ response.write(link);
+ response.write("Content-Type: text/html;charset=utf-8\r\n");
+ response.write("Cache-Control: no-cache\r\n");
+ response.write(`Content-Length: ${body.length}\r\n`);
+ response.write("\r\n");
+ response.write(body);
+ response.finish();
+}
diff --git a/netwerk/test/browser/early_hint_csp_options_html.sjs b/netwerk/test/browser/early_hint_csp_options_html.sjs
new file mode 100644
index 0000000000..73a6a539d8
--- /dev/null
+++ b/netwerk/test/browser/early_hint_csp_options_html.sjs
@@ -0,0 +1,120 @@
+"use strict";
+
+function handleRequest(request, response) {
+ let qs = new URLSearchParams(request.queryString);
+ let asset = qs.get("as");
+ let hinted = qs.get("hinted") !== "0";
+ let httpCode = qs.get("code");
+ let csp = qs.get("csp");
+ let csp_in_early_hint = qs.get("csp_in_early_hint");
+ let host = qs.get("host");
+
+ // eslint-disable-next-line mozilla/use-services
+ let uuidGenerator = Cc["@mozilla.org/uuid-generator;1"].getService(
+ Ci.nsIUUIDGenerator
+ );
+ let uuid = uuidGenerator.generateUUID().toString();
+ let url = `early_hint_pixel.sjs?as=${asset}&uuid=${uuid}`;
+ if (host) {
+ url = host + url;
+ }
+
+ // write to raw socket
+ response.seizePower();
+
+ if (hinted) {
+ response.write("HTTP/1.1 103 Early Hint\r\n");
+ if (csp_in_early_hint) {
+ response.write(
+ `Content-Security-Policy: ${csp_in_early_hint.replaceAll('"', "")}\r\n`
+ );
+ }
+ response.write(`Link: <${url}>; rel=preload; as=${asset}\r\n`);
+ response.write("\r\n");
+ }
+
+ let body = "";
+ if (asset === "image") {
+ body = `<!DOCTYPE html>
+ <html>
+ <body>
+ <img id="test_image" src="${url}" width="100px">
+ </body>
+ </html>`;
+ } else if (asset === "style") {
+ body = `<!DOCTYPE html>
+ <html>
+ <head>
+ <link rel="stylesheet" type="text/css" href="${url}">
+ </head>
+ <body>
+ <h1>Test preload css<h1>
+ <div id="square" style="width:100px;height:100px;">
+ </body>
+ </html>
+ `;
+ } else if (asset === "script") {
+ body = `<!DOCTYPE html>
+ <html>
+ <head>
+ <script src="${url}"></script>
+ </head>
+ <body>
+ <h1>Test preload javascript<h1>
+ <div id="square" style="width:100px;height:100px;">
+ </body>
+ </html>
+ `;
+ } else if (asset === "fetch") {
+ body = `<!DOCTYPE html>
+ <html>
+ <body onload="onLoad()">
+ <script>
+ function onLoad() {
+ fetch("${url}")
+ .then(r => r.text())
+ .then(r => document.getElementsByTagName("h2")[0].textContent = r);
+ }
+ </script>
+ <h1>Test preload fetch</h1>
+ <h2>Fetching...</h2>
+ </body>
+ </html>
+ `;
+ } else if (asset === "font") {
+ body = `<!DOCTYPE html>
+ <html>
+ <head>
+ <style>
+ @font-face {
+ font-family: "preloadFont";
+ src: url("${url}") format("woff");
+ }
+ body {
+ font-family: "preloadFont";
+ }
+ </style>
+ </head>
+ <body>
+ <h1>Test preload font<h1>
+ </body>
+ </html>
+ `;
+ }
+
+ if (!httpCode) {
+ response.write(`HTTP/1.1 200 OK\r\n`);
+ } else {
+ response.write(`HTTP/1.1 ${httpCode} Error\r\n`);
+ }
+ response.write("Content-Type: text/html;charset=utf-8\r\n");
+ response.write("Cache-Control: no-cache\r\n");
+ response.write(`Content-Length: ${body.length}\r\n`);
+ if (csp) {
+ response.write(`Content-Security-Policy: ${csp.replaceAll('"', "")}\r\n`);
+ }
+ response.write("\r\n");
+ response.write(body);
+
+ response.finish();
+}
diff --git a/netwerk/test/browser/early_hint_error.sjs b/netwerk/test/browser/early_hint_error.sjs
new file mode 100644
index 0000000000..d3d0da4bd5
--- /dev/null
+++ b/netwerk/test/browser/early_hint_error.sjs
@@ -0,0 +1,35 @@
+"use strict";
+
+function handleRequest(request, response) {
+ response.setStatusLine(
+ request.httpVersion,
+ parseInt(request.queryString),
+ "Dynamic error"
+ );
+ response.setHeader("Content-Type", "image/png", false);
+ response.setHeader("Cache-Control", "max-age=604800", false);
+
+ // count requests
+ let image;
+ let count = JSON.parse(getSharedState("earlyHintCount"));
+ if (
+ request.hasHeader("X-Moz") &&
+ request.getHeader("X-Moz") === "early hint"
+ ) {
+ count.hinted += 1;
+ // set to green/black horizontal stripes (71 bytes)
+ image = atob(
+ "iVBORw0KGgoAAAANSUhEUgAAAAIAAAACCAIAAAD91JpzAAAADklEQVQIW2OU+i/FAAcADoABNV8X" +
+ "GBMAAAAASUVORK5CYII="
+ );
+ } else {
+ count.normal += 1;
+ // set to purple/white checkered pattern (76 bytes)
+ image = atob(
+ "iVBORw0KGgoAAAANSUhEUgAAAAIAAAACCAIAAAD91JpzAAAAE0lEQVQIW2P4//+/N8MkBiAGsgA1" +
+ "bAe1SzDY8gAAAABJRU5ErkJggg=="
+ );
+ }
+ setSharedState("earlyHintCount", JSON.stringify(count));
+ response.write(image);
+}
diff --git a/netwerk/test/browser/early_hint_main_html.sjs b/netwerk/test/browser/early_hint_main_html.sjs
new file mode 100644
index 0000000000..dc95273c9d
--- /dev/null
+++ b/netwerk/test/browser/early_hint_main_html.sjs
@@ -0,0 +1,62 @@
+"use strict";
+
+function handleRequest(request, response) {
+ // write to raw socket
+ response.seizePower();
+
+ let qs = new URLSearchParams(request.queryString);
+ let imgs = [];
+ let new_hint = true;
+ let new_header = true;
+ for (const [imgUrl, uuid] of qs.entries()) {
+ if (new_hint) {
+ // we need to write a new header
+ new_hint = false;
+ response.write("HTTP/1.1 103 Early Hint\r\n");
+ }
+ if (!imgUrl.length) {
+ // next hint in new early hint response when empty string is passed
+ new_header = true;
+ if (uuid === "new_response") {
+ new_hint = true;
+ response.write("\r\n");
+ } else if (uuid === "non_link_header") {
+ response.write("Content-Length: 25\r\n");
+ }
+ response.write("\r\n");
+ } else {
+ // either append link in new header or in same header
+ if (new_header) {
+ new_header = false;
+ response.write("Link: ");
+ } else {
+ response.write(", ");
+ }
+ // add query string to make request unique this has the drawback that
+ // the preloaded image can't accept query strings on it's own / or has
+ // to strip the appended "?uuid" from the query string before parsing
+ imgs.push(`<img src="${imgUrl}?${uuid}" width="100px">`);
+ response.write(`<${imgUrl}?${uuid}>; rel=preload; as=image`);
+ }
+ }
+ if (!new_hint) {
+ // add separator to main document
+ response.write("\r\n\r\n");
+ }
+
+ let body = `<!DOCTYPE html>
+<html>
+<body>
+${imgs.join("\n")}
+</body>
+</html>`;
+
+ // main document response
+ response.write("HTTP/1.1 200 OK\r\n");
+ response.write("Content-Type: text/html;charset=utf-8\r\n");
+ response.write("Cache-Control: no-cache\r\n");
+ response.write(`Content-Length: ${body.length}\r\n`);
+ response.write("\r\n");
+ response.write(body);
+ response.finish();
+}
diff --git a/netwerk/test/browser/early_hint_main_redirect.sjs b/netwerk/test/browser/early_hint_main_redirect.sjs
new file mode 100644
index 0000000000..0da69d5cee
--- /dev/null
+++ b/netwerk/test/browser/early_hint_main_redirect.sjs
@@ -0,0 +1,67 @@
+"use strict";
+
+// In an SJS file we need to get the setTimeout bits ourselves, despite
+// what eslint might think applies for browser tests.
+// eslint-disable-next-line mozilla/no-redeclare-with-import-autofix
+let { setTimeout } = ChromeUtils.importESModule(
+ "resource://gre/modules/Timer.sys.mjs"
+);
+
+async function handleRequest(request, response) {
+ let hinted =
+ request.hasHeader("X-Moz") && request.getHeader("X-Moz") === "early hint";
+ let count = JSON.parse(getSharedState("earlyHintCount"));
+ if (hinted) {
+ count.hinted += 1;
+ } else {
+ count.normal += 1;
+ }
+ setSharedState("earlyHintCount", JSON.stringify(count));
+ response.setHeader("Cache-Control", "max-age=604800", false);
+
+ let content = "";
+ let qs = new URLSearchParams(request.queryString);
+ let asset = qs.get("as");
+
+ if (asset === "image") {
+ response.setHeader("Content-Type", "image/png", false);
+ // set to green/black horizontal stripes (71 bytes)
+ content = atob(
+ hinted
+ ? "iVBORw0KGgoAAAANSUhEUgAAAAIAAAACCAIAAAD91JpzAAAADklEQVQIW2OU+i/FAAcADoABNV8XGBMAAAAASUVORK5CYII="
+ : "iVBORw0KGgoAAAANSUhEUgAAAAIAAAACCAIAAAD91JpzAAAAE0lEQVQIW2P4//+/N8MkBiAGsgA1bAe1SzDY8gAAAABJRU5ErkJggg=="
+ );
+ } else if (asset === "style") {
+ response.setHeader("Content-Type", "text/css", false);
+ // green background on hint response, purple response otherwise
+ content = `#square { background: ${hinted ? "#1aff1a" : "#4b0092"}`;
+ } else if (asset === "script") {
+ response.setHeader("Content-Type", "application/javascript", false);
+ // green background on hint response, purple response otherwise
+ content = `window.onload = function() {
+ document.getElementById('square').style.background = "${
+ hinted ? "#1aff1a" : "#4b0092"
+ }";
+ }`;
+ } else if (asset === "module") {
+ response.setHeader("Content-Type", "application/javascript", false);
+ // green background on hint response, purple response otherwise
+ content = `export function draw() {
+ document.getElementById('square').style.background = "${
+ hinted ? "#1aff1a" : "#4b0092"
+ }";
+ }`;
+ } else if (asset === "fetch") {
+ response.setHeader("Content-Type", "text/plain", false);
+ content = hinted ? "hinted" : "normal";
+ } else if (asset === "font") {
+ response.setHeader("Content-Type", "font/svg+xml", false);
+ content = '<font><font-face font-family="preloadFont" /></font>';
+ }
+ response.processAsync();
+ setTimeout(() => {
+ response.write(content);
+ response.finish();
+ }, 0);
+ //response.write(content);
+}
diff --git a/netwerk/test/browser/early_hint_pixel.sjs b/netwerk/test/browser/early_hint_pixel.sjs
new file mode 100644
index 0000000000..56a64e9af2
--- /dev/null
+++ b/netwerk/test/browser/early_hint_pixel.sjs
@@ -0,0 +1,37 @@
+"use strict";
+
+function handleRequest(request, response) {
+ response.setHeader("Content-Type", "image/png", false);
+ response.setHeader("Cache-Control", "max-age=604800", false);
+
+ // the typo in "Referer" is part of the http spec
+ if (request.hasHeader("Referer")) {
+ setSharedState("requestReferrer", request.getHeader("Referer"));
+ } else {
+ setSharedState("requestReferrer", "");
+ }
+
+ let count = JSON.parse(getSharedState("earlyHintCount"));
+ let image;
+ // send different sized images depending whether this is an early hint request
+ if (
+ request.hasHeader("X-Moz") &&
+ request.getHeader("X-Moz") === "early hint"
+ ) {
+ count.hinted += 1;
+ // set to green/black horizontal stripes (71 bytes)
+ image = atob(
+ "iVBORw0KGgoAAAANSUhEUgAAAAIAAAACCAIAAAD91JpzAAAADklEQVQIW2OU+i/FAAcADoABNV8X" +
+ "GBMAAAAASUVORK5CYII="
+ );
+ } else {
+ count.normal += 1;
+ // set to purple/white checkered pattern (76 bytes)
+ image = atob(
+ "iVBORw0KGgoAAAANSUhEUgAAAAIAAAACCAIAAAD91JpzAAAAE0lEQVQIW2P4//+/N8MkBiAGsgA1" +
+ "bAe1SzDY8gAAAABJRU5ErkJggg=="
+ );
+ }
+ setSharedState("earlyHintCount", JSON.stringify(count));
+ response.write(image);
+}
diff --git a/netwerk/test/browser/early_hint_pixel_count.sjs b/netwerk/test/browser/early_hint_pixel_count.sjs
new file mode 100644
index 0000000000..b59dd035de
--- /dev/null
+++ b/netwerk/test/browser/early_hint_pixel_count.sjs
@@ -0,0 +1,9 @@
+"use strict";
+
+function handleRequest(request, response) {
+ if (request.hasHeader("X-Early-Hint-Count-Start")) {
+ setSharedState("earlyHintCount", JSON.stringify({ hinted: 0, normal: 0 }));
+ }
+ response.setHeader("Content-Type", "application/json", false);
+ response.write(getSharedState("earlyHintCount"));
+}
diff --git a/netwerk/test/browser/early_hint_preconnect_html.sjs b/netwerk/test/browser/early_hint_preconnect_html.sjs
new file mode 100644
index 0000000000..044c842142
--- /dev/null
+++ b/netwerk/test/browser/early_hint_preconnect_html.sjs
@@ -0,0 +1,32 @@
+"use strict";
+
+function handleRequest(request, response) {
+ let qs = new URLSearchParams(request.queryString);
+ let href = qs.get("href");
+ let crossOrigin = qs.get("crossOrigin");
+
+ // write to raw socket
+ response.seizePower();
+
+ response.write("HTTP/1.1 103 Early Hint\r\n");
+ response.write(
+ `Link: <${href}>; rel=preconnect; crossOrigin=${crossOrigin}\r\n`
+ );
+ response.write("\r\n");
+
+ let body = `<!DOCTYPE html>
+ <html>
+ <body>
+ <h1>Test rel=preconnect<h1>
+ </body>
+ </html>`;
+
+ response.write("HTTP/1.1 200 OK\r\n");
+ response.write("Content-Type: text/html;charset=utf-8\r\n");
+ response.write("Cache-Control: no-cache\r\n");
+ response.write(`Content-Length: ${body.length}\r\n`);
+ response.write("\r\n");
+ response.write(body);
+
+ response.finish();
+}
diff --git a/netwerk/test/browser/early_hint_preload_test_helper.sys.mjs b/netwerk/test/browser/early_hint_preload_test_helper.sys.mjs
new file mode 100644
index 0000000000..afe6a6bb70
--- /dev/null
+++ b/netwerk/test/browser/early_hint_preload_test_helper.sys.mjs
@@ -0,0 +1,159 @@
+/* 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/. */
+
+import { Assert } from "resource://testing-common/Assert.sys.mjs";
+import { BrowserTestUtils } from "resource://testing-common/BrowserTestUtils.sys.mjs";
+
+const { gBrowser } = Services.wm.getMostRecentWindow("navigator:browser");
+
+export async function request_count_checking(testName, got, expected) {
+ // stringify to pretty print assert output
+ let g = JSON.stringify(got);
+ let e = JSON.stringify(expected);
+ // each early hint request can starts one hinted request, but doesn't yet
+ // complete the early hint request during the test case
+ Assert.ok(
+ got.hinted == expected.hinted,
+ `${testName}: unexpected amount of hinted request made expected ${expected.hinted} (${e}), got ${got.hinted} (${g})`
+ );
+ // when the early hint request doesn't complete fast enough, another request
+ // is currently sent from the main document
+ let expected_normal = expected.normal;
+ Assert.ok(
+ got.normal == expected_normal,
+ `${testName}: unexpected amount of normal request made expected ${expected_normal} (${e}), got ${got.normal} (${g})`
+ );
+}
+
+export async function test_hint_preload(
+ testName,
+ requestFrom,
+ imgUrl,
+ expectedRequestCount,
+ uuid = undefined
+) {
+ // generate a uuid if none were passed
+ if (uuid == undefined) {
+ uuid = Services.uuid.generateUUID();
+ }
+ await test_hint_preload_internal(
+ testName,
+ requestFrom,
+ [[imgUrl, uuid.toString()]],
+ expectedRequestCount
+ );
+}
+
+// - testName is just there to be printed during Asserts when failing
+// - the baseUrl can't have query strings, because they are currently used to pass
+// the early hint the server responds with
+// - urls are in the form [[url1, uuid1], ...]. The uuids are there to make each preload
+// unique and not available in the cache from other test cases
+// - expectedRequestCount is the sum of all requested objects { normal: count, hinted: count }
+export async function test_hint_preload_internal(
+ testName,
+ requestFrom,
+ imgUrls,
+ expectedRequestCount
+) {
+ // reset the count
+ let headers = new Headers();
+ headers.append("X-Early-Hint-Count-Start", "");
+ await fetch(
+ "http://example.com/browser/netwerk/test/browser/early_hint_pixel_count.sjs",
+ { headers }
+ );
+
+ let requestUrl =
+ requestFrom +
+ "/browser/netwerk/test/browser/early_hint_main_html.sjs?" +
+ new URLSearchParams(imgUrls).toString(); // encode the hinted images as query string
+
+ await BrowserTestUtils.withNewTab(
+ {
+ gBrowser,
+ url: requestUrl,
+ waitForLoad: true,
+ },
+ async function () {}
+ );
+
+ let gotRequestCount = await fetch(
+ "http://example.com/browser/netwerk/test/browser/early_hint_pixel_count.sjs"
+ ).then(response => response.json());
+
+ await request_count_checking(testName, gotRequestCount, expectedRequestCount);
+}
+
+// Verify that CSP policies in both the 103 response as well as the main response are respected.
+// e.g.
+// 103 Early Hint
+// Content-Security-Policy: style-src: self;
+// Link: </style.css>; rel=preload; as=style
+// 200 OK
+// Content-Security-Policy: style-src: none;
+// Link: </font.ttf>; rel=preload; as=font
+
+// Server-side we verify that:
+// - the hinted preload request was made as expected
+// - the load request request was made as expected
+// Client-side, we verify that the image was loaded or not loaded, depending on the scenario
+
+// This verifies preload hints and requests
+export async function test_preload_hint_and_request(input, expected_results) {
+ // reset the count
+ let headers = new Headers();
+ headers.append("X-Early-Hint-Count-Start", "");
+ await fetch(
+ "https://example.com/browser/netwerk/test/browser/early_hint_pixel_count.sjs",
+ { headers }
+ );
+
+ let requestUrl = `https://example.com/browser/netwerk/test/browser/early_hint_csp_options_html.sjs?as=${
+ input.resource_type
+ }&hinted=${input.hinted ? "1" : "0"}${input.csp ? "&csp=" + input.csp : ""}${
+ input.csp_in_early_hint
+ ? "&csp_in_early_hint=" + input.csp_in_early_hint
+ : ""
+ }${input.host ? "&host=" + input.host : ""}`;
+
+ await BrowserTestUtils.openNewForegroundTab(gBrowser, requestUrl, true);
+
+ let gotRequestCount = await fetch(
+ "https://example.com/browser/netwerk/test/browser/early_hint_pixel_count.sjs"
+ ).then(response => response.json());
+
+ await Assert.deepEqual(gotRequestCount, expected_results, input.test_name);
+
+ gBrowser.removeCurrentTab();
+ Services.cache2.clear();
+}
+
+// simple loading of one url and then checking the request count against the
+// passed expected count
+export async function test_preload_url(testName, url, expectedRequestCount) {
+ // reset the count
+ let headers = new Headers();
+ headers.append("X-Early-Hint-Count-Start", "");
+ await fetch(
+ "http://example.com/browser/netwerk/test/browser/early_hint_pixel_count.sjs",
+ { headers }
+ );
+
+ await BrowserTestUtils.withNewTab(
+ {
+ gBrowser,
+ url,
+ waitForLoad: true,
+ },
+ async function () {}
+ );
+
+ let gotRequestCount = await fetch(
+ "http://example.com/browser/netwerk/test/browser/early_hint_pixel_count.sjs"
+ ).then(response => response.json());
+
+ await request_count_checking(testName, gotRequestCount, expectedRequestCount);
+ Services.cache2.clear();
+}
diff --git a/netwerk/test/browser/early_hint_redirect.sjs b/netwerk/test/browser/early_hint_redirect.sjs
new file mode 100644
index 0000000000..6bcb6bdc86
--- /dev/null
+++ b/netwerk/test/browser/early_hint_redirect.sjs
@@ -0,0 +1,21 @@
+"use strict";
+
+function handleRequest(request, response) {
+ // increase count
+ let count = JSON.parse(getSharedState("earlyHintCount"));
+ if (
+ request.hasHeader("X-Moz") &&
+ request.getHeader("X-Moz") === "early hint"
+ ) {
+ count.hinted += 1;
+ } else {
+ count.normal += 1;
+ }
+ setSharedState("earlyHintCount", JSON.stringify(count));
+
+ // respond with redirect
+ response.setStatusLine(request.httpVersion, 301, "Moved Permanently");
+ let location = request.queryString;
+ response.setHeader("Location", location, false);
+ response.write("Hello world!");
+}
diff --git a/netwerk/test/browser/early_hint_redirect_html.sjs b/netwerk/test/browser/early_hint_redirect_html.sjs
new file mode 100644
index 0000000000..c111af0978
--- /dev/null
+++ b/netwerk/test/browser/early_hint_redirect_html.sjs
@@ -0,0 +1,24 @@
+"use strict";
+
+// usage via url parameters:
+// - link: if set sends a link header with the given link value as an early hint repsonse
+// - location: sets destination of 301 response
+
+function handleRequest(request, response) {
+ let qs = new URLSearchParams(request.queryString);
+ let link = qs.get("link");
+ let location = qs.get("location");
+
+ // write to raw socket
+ response.seizePower();
+ if (link != undefined) {
+ response.write("HTTP/1.1 103 Early Hint\r\n");
+ response.write(`Link: ${link}\r\n`);
+ response.write("\r\n");
+ }
+
+ response.write("HTTP/1.1 307 Temporary Redirect\r\n");
+ response.write(`Location: ${location}\r\n`);
+ response.write("\r\n");
+ response.finish();
+}
diff --git a/netwerk/test/browser/early_hint_referrer_policy_html.sjs b/netwerk/test/browser/early_hint_referrer_policy_html.sjs
new file mode 100644
index 0000000000..3c8a626de1
--- /dev/null
+++ b/netwerk/test/browser/early_hint_referrer_policy_html.sjs
@@ -0,0 +1,132 @@
+"use strict";
+
+function handleRequest(request, response) {
+ let qs = new URLSearchParams(request.queryString);
+ let asset = qs.get("as");
+ var action = qs.get("action");
+ let hinted = qs.get("hinted") !== "0";
+ let httpCode = qs.get("code");
+ let header_referrer_policy = qs.get("header_referrer_policy");
+ let link_referrer_policy = qs.get("link_referrer_policy");
+
+ // eslint-disable-next-line mozilla/use-services
+ let uuidGenerator = Cc["@mozilla.org/uuid-generator;1"].getService(
+ Ci.nsIUUIDGenerator
+ );
+ let uuid = uuidGenerator.generateUUID().toString();
+ let url = `early_hint_pixel.sjs?as=${asset}&uuid=${uuid}`;
+
+ if (action === "get_request_referrer_results") {
+ response.setHeader("Cache-Control", "no-cache", false);
+ response.setHeader("Content-Type", "text/plain", false);
+ response.write(getSharedState("requestReferrer"));
+ return;
+ } else if (action === "reset_referrer_results") {
+ response.setHeader("Cache-Control", "no-cache", false);
+ response.setHeader("Content-Type", "text/plain", false);
+ response.write(setSharedState("requestReferrer", "not set"));
+ return;
+ }
+
+ // write to raw socket
+ response.seizePower();
+
+ if (hinted) {
+ response.write("HTTP/1.1 103 Early Hint\r\n");
+
+ if (header_referrer_policy) {
+ response.write(
+ `Referrer-Policy: ${header_referrer_policy.replaceAll('"', "")}\r\n`
+ );
+ }
+
+ response.write(
+ `Link: <${url}>; rel=preload; as=${asset}; ${
+ link_referrer_policy ? "referrerpolicy=" + link_referrer_policy : ""
+ } \r\n`
+ );
+ response.write("\r\n");
+ }
+
+ let body = "";
+ if (asset === "image") {
+ body = `<!DOCTYPE html>
+ <html>
+ <body>
+ <img src="${url}" width="100px">
+ </body>
+ </html>`;
+ } else if (asset === "style") {
+ body = `<!DOCTYPE html>
+ <html>
+ <head>
+ <link rel="stylesheet" type="text/css" href="${url}">
+ </head>
+ <body>
+ <h1>Test preload css<h1>
+ <div id="square" style="width:100px;height:100px;">
+ </body>
+ </html>
+ `;
+ } else if (asset === "script") {
+ body = `<!DOCTYPE html>
+ <html>
+ <head>
+ <script src="${url}"></script>
+ </head>
+ <body>
+ <h1>Test preload javascript<h1>
+ <div id="square" style="width:100px;height:100px;">
+ </body>
+ </html>
+ `;
+ } else if (asset === "fetch") {
+ body = `<!DOCTYPE html>
+ <html>
+ <body onload="onLoad()">
+ <script>
+ function onLoad() {
+ fetch("${url}")
+ .then(r => r.text())
+ .then(r => document.getElementsByTagName("h2")[0].textContent = r);
+ }
+ </script>
+ <h1>Test preload fetch</h1>
+ <h2>Fetching...</h2>
+ </body>
+ </html>
+ `;
+ } else if (asset === "font") {
+ body = `<!DOCTYPE html>
+ <html>
+ <head>
+ <style>
+ @font-face {
+ font-family: "preloadFont";
+ src: url("${url}") format("woff");
+ }
+ body {
+ font-family: "preloadFont";
+ }
+ </style>
+ </head>
+ <body>
+ <h1>Test preload font<h1>
+ </body>
+ </html>
+ `;
+ }
+
+ if (!httpCode) {
+ response.write(`HTTP/1.1 200 OK\r\n`);
+ } else {
+ response.write(`HTTP/1.1 ${httpCode} Error\r\n`);
+ }
+ response.write("Content-Type: text/html;charset=utf-8\r\n");
+ response.write("Cache-Control: no-cache\r\n");
+ response.write(`Content-Length: ${body.length}\r\n`);
+ response.write("\r\n");
+ response.write(body);
+
+ response.finish();
+}
diff --git a/netwerk/test/browser/file_favicon.html b/netwerk/test/browser/file_favicon.html
new file mode 100644
index 0000000000..77532a3a53
--- /dev/null
+++ b/netwerk/test/browser/file_favicon.html
@@ -0,0 +1,7 @@
+<!DOCTYPE html>
+
+<html>
+ <head>
+ <link rel="shortcut icon" href="http://example.org/browser/netwerk/test/browser/damonbowling.jpg">
+ </head>
+</html>
diff --git a/netwerk/test/browser/file_link_header.sjs b/netwerk/test/browser/file_link_header.sjs
new file mode 100644
index 0000000000..6bab515d19
--- /dev/null
+++ b/netwerk/test/browser/file_link_header.sjs
@@ -0,0 +1,24 @@
+"use strict";
+
+function handleRequest(request, response) {
+ // write to raw socket
+ response.seizePower();
+ let body = `<!DOCTYPE html>
+ <html>
+ <head>
+ <link rel="preconnect" href="https://localhost">
+ </head>
+ <body>
+ <h1>Test rel=preconnect<h1>
+ </body>
+ </html>`;
+
+ response.write("HTTP/1.1 200 OK\r\n");
+ response.write("Content-Type: text/html;charset=utf-8\r\n");
+ response.write("Cache-Control: no-cache\r\n");
+ response.write(`Content-Length: ${body.length}\r\n`);
+ response.write("\r\n");
+ response.write(body);
+
+ response.finish();
+}
diff --git a/netwerk/test/browser/file_lnk.lnk b/netwerk/test/browser/file_lnk.lnk
new file mode 100644
index 0000000000..abce7587d2
--- /dev/null
+++ b/netwerk/test/browser/file_lnk.lnk
Binary files differ
diff --git a/netwerk/test/browser/ioactivity.html b/netwerk/test/browser/ioactivity.html
new file mode 100644
index 0000000000..5e23f6f117
--- /dev/null
+++ b/netwerk/test/browser/ioactivity.html
@@ -0,0 +1,11 @@
+<!DOCTYPE html>
+
+<head>
+<meta http-equiv="content-type" content="text/html; charset=utf-8">
+</head>
+
+<html>
+<body>
+ <p>IOActivity Test Page</p>
+</body>
+</html>
diff --git a/netwerk/test/browser/no_103_preload.html b/netwerk/test/browser/no_103_preload.html
new file mode 100644
index 0000000000..64f5e79259
--- /dev/null
+++ b/netwerk/test/browser/no_103_preload.html
@@ -0,0 +1,6 @@
+<!DOCTYPE html>
+<html>
+<body>
+<img src="http://example.com/browser/netwerk/test/browser/early_hint_pixel.sjs" width="100px">
+</body>
+</html>
diff --git a/netwerk/test/browser/no_103_preload.html^headers^ b/netwerk/test/browser/no_103_preload.html^headers^
new file mode 100644
index 0000000000..9e23c73b7f
--- /dev/null
+++ b/netwerk/test/browser/no_103_preload.html^headers^
@@ -0,0 +1 @@
+Cache-Control: no-cache
diff --git a/netwerk/test/browser/post.html b/netwerk/test/browser/post.html
new file mode 100644
index 0000000000..9d238c2b97
--- /dev/null
+++ b/netwerk/test/browser/post.html
@@ -0,0 +1,14 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8">
+<title>post file</title>
+</head>
+<body">
+<form id="form" action="auth_post.sjs" method="post" enctype="multipart/form-data">
+<input type="hidden" id="input_hidden" name="foo" value="bar">
+<input id="input_file" name="test_file" type="file">
+<input type="submit">
+</form>
+</body>
+</html>
diff --git a/netwerk/test/browser/redirect.sjs b/netwerk/test/browser/redirect.sjs
new file mode 100644
index 0000000000..09e7d9b1e4
--- /dev/null
+++ b/netwerk/test/browser/redirect.sjs
@@ -0,0 +1,6 @@
+function handleRequest(request, response) {
+ response.setStatusLine(request.httpVersion, 301, "Moved Permanently");
+ let location = request.queryString;
+ response.setHeader("Location", location, false);
+ response.write("Hello world!");
+}
diff --git a/netwerk/test/browser/res.css b/netwerk/test/browser/res.css
new file mode 100644
index 0000000000..eab83656ed
--- /dev/null
+++ b/netwerk/test/browser/res.css
@@ -0,0 +1,4 @@
+/* François was here. */
+#purple-text {
+ color: purple;
+}
diff --git a/netwerk/test/browser/res.css^headers^ b/netwerk/test/browser/res.css^headers^
new file mode 100644
index 0000000000..e13897f157
--- /dev/null
+++ b/netwerk/test/browser/res.css^headers^
@@ -0,0 +1 @@
+Content-Type: text/css; charset=utf-8
diff --git a/netwerk/test/browser/res.csv b/netwerk/test/browser/res.csv
new file mode 100644
index 0000000000..b0246d5964
--- /dev/null
+++ b/netwerk/test/browser/res.csv
@@ -0,0 +1 @@
+1,2,3
diff --git a/netwerk/test/browser/res.csv^headers^ b/netwerk/test/browser/res.csv^headers^
new file mode 100644
index 0000000000..8d30131059
--- /dev/null
+++ b/netwerk/test/browser/res.csv^headers^
@@ -0,0 +1 @@
+Content-Type: text/csv;
diff --git a/netwerk/test/browser/res.mp3 b/netwerk/test/browser/res.mp3
new file mode 100644
index 0000000000..bad506cf18
--- /dev/null
+++ b/netwerk/test/browser/res.mp3
Binary files differ
diff --git a/netwerk/test/browser/res.unknown b/netwerk/test/browser/res.unknown
new file mode 100644
index 0000000000..3546645658
--- /dev/null
+++ b/netwerk/test/browser/res.unknown
@@ -0,0 +1 @@
+unknown
diff --git a/netwerk/test/browser/res_206.html b/netwerk/test/browser/res_206.html
new file mode 100644
index 0000000000..8025fcdb20
--- /dev/null
+++ b/netwerk/test/browser/res_206.html
@@ -0,0 +1,11 @@
+<!DOCTYPE html>
+
+<head>
+<meta http-equiv="content-type" content="text/html; charset=utf-8">
+</head>
+
+<html>
+<body>
+ <p>Dummy Page</p>
+</body>
+</html>
diff --git a/netwerk/test/browser/res_206.html^headers^ b/netwerk/test/browser/res_206.html^headers^
new file mode 100644
index 0000000000..5a3e3a24c8
--- /dev/null
+++ b/netwerk/test/browser/res_206.html^headers^
@@ -0,0 +1,2 @@
+HTTP 206
+Content-Type: text/html;
diff --git a/netwerk/test/browser/res_206.mp3 b/netwerk/test/browser/res_206.mp3
new file mode 100644
index 0000000000..bad506cf18
--- /dev/null
+++ b/netwerk/test/browser/res_206.mp3
Binary files differ
diff --git a/netwerk/test/browser/res_206.mp3^headers^ b/netwerk/test/browser/res_206.mp3^headers^
new file mode 100644
index 0000000000..6e7e4d23ba
--- /dev/null
+++ b/netwerk/test/browser/res_206.mp3^headers^
@@ -0,0 +1 @@
+HTTP 206
diff --git a/netwerk/test/browser/res_empty.zip b/netwerk/test/browser/res_empty.zip
new file mode 100644
index 0000000000..b613b60c02
--- /dev/null
+++ b/netwerk/test/browser/res_empty.zip
Binary files differ
diff --git a/netwerk/test/browser/res_http_index_format b/netwerk/test/browser/res_http_index_format
new file mode 100644
index 0000000000..e42645a762
--- /dev/null
+++ b/netwerk/test/browser/res_http_index_format
@@ -0,0 +1 @@
+100: This is a sample application/http-index-format directory index listing.
diff --git a/netwerk/test/browser/res_http_index_format^headers^ b/netwerk/test/browser/res_http_index_format^headers^
new file mode 100644
index 0000000000..f076e27a72
--- /dev/null
+++ b/netwerk/test/browser/res_http_index_format^headers^
@@ -0,0 +1 @@
+Content-Type: application/http-index-format
diff --git a/netwerk/test/browser/res_img.png b/netwerk/test/browser/res_img.png
new file mode 100644
index 0000000000..94e7eb6db2
--- /dev/null
+++ b/netwerk/test/browser/res_img.png
Binary files differ
diff --git a/netwerk/test/browser/res_img_for_unknown_decoder b/netwerk/test/browser/res_img_for_unknown_decoder
new file mode 100644
index 0000000000..74d74fde5a
--- /dev/null
+++ b/netwerk/test/browser/res_img_for_unknown_decoder
Binary files differ
diff --git a/netwerk/test/browser/res_img_for_unknown_decoder^headers^ b/netwerk/test/browser/res_img_for_unknown_decoder^headers^
new file mode 100644
index 0000000000..defde38020
--- /dev/null
+++ b/netwerk/test/browser/res_img_for_unknown_decoder^headers^
@@ -0,0 +1,2 @@
+Content-Type:
+Content-Encoding: gzip
diff --git a/netwerk/test/browser/res_img_unknown.png b/netwerk/test/browser/res_img_unknown.png
new file mode 100644
index 0000000000..8025fcdb20
--- /dev/null
+++ b/netwerk/test/browser/res_img_unknown.png
@@ -0,0 +1,11 @@
+<!DOCTYPE html>
+
+<head>
+<meta http-equiv="content-type" content="text/html; charset=utf-8">
+</head>
+
+<html>
+<body>
+ <p>Dummy Page</p>
+</body>
+</html>
diff --git a/netwerk/test/browser/res_invalid_partial.mp3 b/netwerk/test/browser/res_invalid_partial.mp3
new file mode 100644
index 0000000000..bad506cf18
--- /dev/null
+++ b/netwerk/test/browser/res_invalid_partial.mp3
Binary files differ
diff --git a/netwerk/test/browser/res_invalid_partial.mp3^headers^ b/netwerk/test/browser/res_invalid_partial.mp3^headers^
new file mode 100644
index 0000000000..0213f38e4e
--- /dev/null
+++ b/netwerk/test/browser/res_invalid_partial.mp3^headers^
@@ -0,0 +1,2 @@
+HTTP 206
+Content-Range: bytes 100-1024/*
diff --git a/netwerk/test/browser/res_nosniff.html b/netwerk/test/browser/res_nosniff.html
new file mode 100644
index 0000000000..8025fcdb20
--- /dev/null
+++ b/netwerk/test/browser/res_nosniff.html
@@ -0,0 +1,11 @@
+<!DOCTYPE html>
+
+<head>
+<meta http-equiv="content-type" content="text/html; charset=utf-8">
+</head>
+
+<html>
+<body>
+ <p>Dummy Page</p>
+</body>
+</html>
diff --git a/netwerk/test/browser/res_nosniff.html^headers^ b/netwerk/test/browser/res_nosniff.html^headers^
new file mode 100644
index 0000000000..024cdcf5ab
--- /dev/null
+++ b/netwerk/test/browser/res_nosniff.html^headers^
@@ -0,0 +1,2 @@
+X-Content-Type-Options: nosniff
+Content-Type: text/html;
diff --git a/netwerk/test/browser/res_nosniff2.html b/netwerk/test/browser/res_nosniff2.html
new file mode 100644
index 0000000000..8025fcdb20
--- /dev/null
+++ b/netwerk/test/browser/res_nosniff2.html
@@ -0,0 +1,11 @@
+<!DOCTYPE html>
+
+<head>
+<meta http-equiv="content-type" content="text/html; charset=utf-8">
+</head>
+
+<html>
+<body>
+ <p>Dummy Page</p>
+</body>
+</html>
diff --git a/netwerk/test/browser/res_nosniff2.html^headers^ b/netwerk/test/browser/res_nosniff2.html^headers^
new file mode 100644
index 0000000000..e46db01e23
--- /dev/null
+++ b/netwerk/test/browser/res_nosniff2.html^headers^
@@ -0,0 +1,2 @@
+X-Content-Type-Options: nosniff
+Content-Type: text/test
diff --git a/netwerk/test/browser/res_not_200or206.mp3 b/netwerk/test/browser/res_not_200or206.mp3
new file mode 100644
index 0000000000..bad506cf18
--- /dev/null
+++ b/netwerk/test/browser/res_not_200or206.mp3
Binary files differ
diff --git a/netwerk/test/browser/res_not_200or206.mp3^headers^ b/netwerk/test/browser/res_not_200or206.mp3^headers^
new file mode 100644
index 0000000000..dd0b48aaa0
--- /dev/null
+++ b/netwerk/test/browser/res_not_200or206.mp3^headers^
@@ -0,0 +1 @@
+HTTP 226
diff --git a/netwerk/test/browser/res_not_ok.html b/netwerk/test/browser/res_not_ok.html
new file mode 100644
index 0000000000..8025fcdb20
--- /dev/null
+++ b/netwerk/test/browser/res_not_ok.html
@@ -0,0 +1,11 @@
+<!DOCTYPE html>
+
+<head>
+<meta http-equiv="content-type" content="text/html; charset=utf-8">
+</head>
+
+<html>
+<body>
+ <p>Dummy Page</p>
+</body>
+</html>
diff --git a/netwerk/test/browser/res_not_ok.html^headers^ b/netwerk/test/browser/res_not_ok.html^headers^
new file mode 100644
index 0000000000..5d15d79e46
--- /dev/null
+++ b/netwerk/test/browser/res_not_ok.html^headers^
@@ -0,0 +1 @@
+HTTP 302 Found
diff --git a/netwerk/test/browser/res_object.html b/netwerk/test/browser/res_object.html
new file mode 100644
index 0000000000..8097415d17
--- /dev/null
+++ b/netwerk/test/browser/res_object.html
@@ -0,0 +1,18 @@
+<!DOCTYPE html>
+
+<head>
+<meta http-equiv="content-type" content="text/html; charset=utf-8">
+</head>
+
+<html>
+<body>
+ <p>Dummy Page</p>
+ <script>
+ let foo = async () => {
+ let url = "https://example.com/browser/netwerk/test/browser/res_img.png";
+ await fetch(url, { mode: "no-cors" });
+ }
+ foo();
+ </script>
+</body>
+</html>
diff --git a/netwerk/test/browser/res_sub_document.html b/netwerk/test/browser/res_sub_document.html
new file mode 100644
index 0000000000..8025fcdb20
--- /dev/null
+++ b/netwerk/test/browser/res_sub_document.html
@@ -0,0 +1,11 @@
+<!DOCTYPE html>
+
+<head>
+<meta http-equiv="content-type" content="text/html; charset=utf-8">
+</head>
+
+<html>
+<body>
+ <p>Dummy Page</p>
+</body>
+</html>
diff --git a/netwerk/test/browser/square.png b/netwerk/test/browser/square.png
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/netwerk/test/browser/square.png
diff --git a/netwerk/test/browser/test_1629307.html b/netwerk/test/browser/test_1629307.html
new file mode 100644
index 0000000000..01f2a0439e
--- /dev/null
+++ b/netwerk/test/browser/test_1629307.html
@@ -0,0 +1,9 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+</head>
+<body>
+ <iframe
+ src="https://example.org/browser/netwerk/test/browser/x_frame_options.html"></iframe>
+</body>
+</html>
diff --git a/netwerk/test/browser/x_frame_options.html b/netwerk/test/browser/x_frame_options.html
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/netwerk/test/browser/x_frame_options.html
diff --git a/netwerk/test/browser/x_frame_options.html^headers^ b/netwerk/test/browser/x_frame_options.html^headers^
new file mode 100644
index 0000000000..dc4bb949f5
--- /dev/null
+++ b/netwerk/test/browser/x_frame_options.html^headers^
@@ -0,0 +1,3 @@
+HTTP 401 UNAUTHORIZED
+X-Frame-Options: SAMEORIGIN
+WWW-Authenticate: basic realm="login required"