summaryrefslogtreecommitdiffstats
path: root/devtools/shared/network-observer/test/xpcshell
diff options
context:
space:
mode:
Diffstat (limited to 'devtools/shared/network-observer/test/xpcshell')
-rw-r--r--devtools/shared/network-observer/test/xpcshell/head.js9
-rw-r--r--devtools/shared/network-observer/test/xpcshell/test_network_helper.js90
-rw-r--r--devtools/shared/network-observer/test/xpcshell/test_security-info-certificate.js84
-rw-r--r--devtools/shared/network-observer/test/xpcshell/test_security-info-parser.js54
-rw-r--r--devtools/shared/network-observer/test/xpcshell/test_security-info-protocol-version.js48
-rw-r--r--devtools/shared/network-observer/test/xpcshell/test_security-info-state.js122
-rw-r--r--devtools/shared/network-observer/test/xpcshell/test_security-info-static-hpkp.js41
-rw-r--r--devtools/shared/network-observer/test/xpcshell/test_security-info-weakness-reasons.js39
-rw-r--r--devtools/shared/network-observer/test/xpcshell/test_throttle.js162
-rw-r--r--devtools/shared/network-observer/test/xpcshell/xpcshell.ini15
10 files changed, 664 insertions, 0 deletions
diff --git a/devtools/shared/network-observer/test/xpcshell/head.js b/devtools/shared/network-observer/test/xpcshell/head.js
new file mode 100644
index 0000000000..93b66e4632
--- /dev/null
+++ b/devtools/shared/network-observer/test/xpcshell/head.js
@@ -0,0 +1,9 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+ChromeUtils.defineESModuleGetters(this, {
+ NetworkHelper:
+ "resource://devtools/shared/network-observer/NetworkHelper.sys.mjs",
+});
diff --git a/devtools/shared/network-observer/test/xpcshell/test_network_helper.js b/devtools/shared/network-observer/test/xpcshell/test_network_helper.js
new file mode 100644
index 0000000000..ff514ab98e
--- /dev/null
+++ b/devtools/shared/network-observer/test/xpcshell/test_network_helper.js
@@ -0,0 +1,90 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+function run_test() {
+ test_isTextMimeType();
+ test_parseCookieHeader();
+}
+
+function test_isTextMimeType() {
+ Assert.equal(NetworkHelper.isTextMimeType("text/plain"), true);
+ Assert.equal(NetworkHelper.isTextMimeType("application/javascript"), true);
+ Assert.equal(NetworkHelper.isTextMimeType("application/json"), true);
+ Assert.equal(NetworkHelper.isTextMimeType("text/css"), true);
+ Assert.equal(NetworkHelper.isTextMimeType("text/html"), true);
+ Assert.equal(NetworkHelper.isTextMimeType("image/svg+xml"), true);
+ Assert.equal(NetworkHelper.isTextMimeType("application/xml"), true);
+
+ // Test custom JSON subtype
+ Assert.equal(
+ NetworkHelper.isTextMimeType("application/vnd.tent.posts-feed.v0+json"),
+ true
+ );
+ Assert.equal(
+ NetworkHelper.isTextMimeType("application/vnd.tent.posts-feed.v0-json"),
+ true
+ );
+ // Test custom XML subtype
+ Assert.equal(
+ NetworkHelper.isTextMimeType("application/vnd.tent.posts-feed.v0+xml"),
+ true
+ );
+ Assert.equal(
+ NetworkHelper.isTextMimeType("application/vnd.tent.posts-feed.v0-xml"),
+ false
+ );
+ // Test case-insensitive
+ Assert.equal(
+ NetworkHelper.isTextMimeType("application/vnd.BIG-CORP+json"),
+ true
+ );
+ // Test non-text type
+ Assert.equal(NetworkHelper.isTextMimeType("image/png"), false);
+ // Test invalid types
+ Assert.equal(NetworkHelper.isTextMimeType("application/foo-+json"), false);
+ Assert.equal(NetworkHelper.isTextMimeType("application/-foo+json"), false);
+ Assert.equal(
+ NetworkHelper.isTextMimeType("application/foo--bar+json"),
+ false
+ );
+
+ // Test we do not cause internal errors with unoptimized regex. Bug 961097
+ Assert.equal(
+ NetworkHelper.isTextMimeType("application/vnd.google.safebrowsing-chunk"),
+ false
+ );
+}
+
+function test_parseCookieHeader() {
+ let result = NetworkHelper.parseSetCookieHeaders(["Test=1; SameSite=Strict"]);
+ Assert.deepEqual(result, [{ name: "Test", value: "1", samesite: "Strict" }]);
+
+ result = NetworkHelper.parseSetCookieHeaders(["Test=1; SameSite=strict"]);
+ Assert.deepEqual(result, [{ name: "Test", value: "1", samesite: "Strict" }]);
+
+ result = NetworkHelper.parseSetCookieHeaders(["Test=1; SameSite=STRICT"]);
+ Assert.deepEqual(result, [{ name: "Test", value: "1", samesite: "Strict" }]);
+
+ result = NetworkHelper.parseSetCookieHeaders(["Test=1; SameSite=None"]);
+ Assert.deepEqual(result, [{ name: "Test", value: "1", samesite: "None" }]);
+
+ result = NetworkHelper.parseSetCookieHeaders(["Test=1; SameSite=NONE"]);
+ Assert.deepEqual(result, [{ name: "Test", value: "1", samesite: "None" }]);
+
+ result = NetworkHelper.parseSetCookieHeaders(["Test=1; SameSite=lax"]);
+ Assert.deepEqual(result, [{ name: "Test", value: "1", samesite: "Lax" }]);
+
+ result = NetworkHelper.parseSetCookieHeaders(["Test=1; SameSite=Lax"]);
+ Assert.deepEqual(result, [{ name: "Test", value: "1", samesite: "Lax" }]);
+
+ result = NetworkHelper.parseSetCookieHeaders([
+ "Test=1; SameSite=Lax",
+ "Foo=2; SameSite=None",
+ ]);
+ Assert.deepEqual(result, [
+ { name: "Test", value: "1", samesite: "Lax" },
+ { name: "Foo", value: "2", samesite: "None" },
+ ]);
+}
diff --git a/devtools/shared/network-observer/test/xpcshell/test_security-info-certificate.js b/devtools/shared/network-observer/test/xpcshell/test_security-info-certificate.js
new file mode 100644
index 0000000000..00f482b8ca
--- /dev/null
+++ b/devtools/shared/network-observer/test/xpcshell/test_security-info-certificate.js
@@ -0,0 +1,84 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+"use strict";
+
+// Tests that NetworkHelper.parseCertificateInfo parses certificate information
+// correctly.
+
+const DUMMY_CERT = {
+ getBase64DERString() {
+ // This is the base64-encoded contents of the "DigiCert ECC Secure Server CA"
+ // intermediate certificate as issued by "DigiCert Global Root CA". It was
+ // chosen as a test certificate because it has an issuer common name,
+ // organization, and organizational unit that are somewhat distinct from
+ // its subject common name and organization name.
+ return "MIIDrDCCApSgAwIBAgIQCssoukZe5TkIdnRw883GEjANBgkqhkiG9w0BAQwFADBhMQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cuZGlnaWNlcnQuY29tMSAwHgYDVQQDExdEaWdpQ2VydCBHbG9iYWwgUm9vdCBDQTAeFw0xMzAzMDgxMjAwMDBaFw0yMzAzMDgxMjAwMDBaMEwxCzAJBgNVBAYTAlVTMRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxJjAkBgNVBAMTHURpZ2lDZXJ0IEVDQyBTZWN1cmUgU2VydmVyIENBMHYwEAYHKoZIzj0CAQYFK4EEACIDYgAE4ghC6nfYJN6gLGSkE85AnCNyqQIKDjc/ITa4jVMU9tWRlUvzlgKNcR7E2Munn17voOZ/WpIRllNv68DLP679Wz9HJOeaBy6Wvqgvu1cYr3GkvXg6HuhbPGtkESvMNCuMo4IBITCCAR0wEgYDVR0TAQH/BAgwBgEB/wIBADAOBgNVHQ8BAf8EBAMCAYYwNAYIKwYBBQUHAQEEKDAmMCQGCCsGAQUFBzABhhhodHRwOi8vb2NzcC5kaWdpY2VydC5jb20wQgYDVR0fBDswOTA3oDWgM4YxaHR0cDovL2NybDMuZGlnaWNlcnQuY29tL0RpZ2lDZXJ0R2xvYmFsUm9vdENBLmNybDA9BgNVHSAENjA0MDIGBFUdIAAwKjAoBggrBgEFBQcCARYcaHR0cHM6Ly93d3cuZGlnaWNlcnQuY29tL0NQUzAdBgNVHQ4EFgQUo53mH/naOU/AbuiRy5Wl2jHiCp8wHwYDVR0jBBgwFoAUA95QNVbRTLtm8KPiGxvDl7I90VUwDQYJKoZIhvcNAQEMBQADggEBAMeKoENL7HTJxavVHzA1Nm6YVntIrAVjrnuaVyRXzG/63qttnMe2uuzO58pzZNvfBDcKAEmzP58mrZGMIOgfiA4q+2Y3yDDo0sIkp0VILeoBUEoxlBPfjV/aKrtJPGHzecicZpIalir0ezZYoyxBEHQa0+1IttK7igZFcTMQMHp6mCHdJLnsnLWSB62DxsRq+HfmNb4TDydkskO/g+l3VtsIh5RHFPVfKK+jaEyDj2D3loB5hWp2Jp2VDCADjT7ueihlZGak2YPqmXTNbk19HOuNssWvFhtOyPNV6og4ETQdEa8/B6hPatJ0ES8q/HO3X8IVQwVs1n3aAr0im0/T+Xc=";
+ },
+};
+
+add_task(async function run_test() {
+ info("Testing NetworkHelper.parseCertificateInfo.");
+
+ const result = await NetworkHelper.parseCertificateInfo(
+ DUMMY_CERT,
+ new Map()
+ );
+
+ // Subject
+ equal(
+ result.subject.commonName,
+ "DigiCert ECC Secure Server CA",
+ "Common name is correct."
+ );
+ equal(
+ result.subject.organization,
+ "DigiCert Inc",
+ "Organization is correct."
+ );
+ equal(
+ result.subject.organizationUnit,
+ undefined,
+ "Organizational unit is correct."
+ );
+
+ // Issuer
+ equal(
+ result.issuer.commonName,
+ "DigiCert Global Root CA",
+ "Common name of the issuer is correct."
+ );
+ equal(
+ result.issuer.organization,
+ "DigiCert Inc",
+ "Organization of the issuer is correct."
+ );
+ equal(
+ result.issuer.organizationUnit,
+ "www.digicert.com",
+ "Organizational unit of the issuer is correct."
+ );
+
+ // Validity
+ equal(
+ result.validity.start,
+ "Fri, 08 Mar 2013 12:00:00 GMT",
+ "Start of the validity period is correct."
+ );
+ equal(
+ result.validity.end,
+ "Wed, 08 Mar 2023 12:00:00 GMT",
+ "End of the validity period is correct."
+ );
+
+ // Fingerprints
+ equal(
+ result.fingerprint.sha1,
+ "56:EE:7C:27:06:83:16:2D:83:BA:EA:CC:79:0E:22:47:1A:DA:AB:E8",
+ "Certificate SHA1 fingerprint is correct."
+ );
+ equal(
+ result.fingerprint.sha256,
+ "45:84:46:BA:75:D9:32:E9:14:F2:3C:2B:57:B7:D1:92:ED:DB:C2:18:1D:95:8E:11:81:AD:52:51:74:7A:1E:E8",
+ "Certificate SHA256 fingerprint is correct."
+ );
+});
diff --git a/devtools/shared/network-observer/test/xpcshell/test_security-info-parser.js b/devtools/shared/network-observer/test/xpcshell/test_security-info-parser.js
new file mode 100644
index 0000000000..9515851a8b
--- /dev/null
+++ b/devtools/shared/network-observer/test/xpcshell/test_security-info-parser.js
@@ -0,0 +1,54 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+"use strict";
+
+// Test that NetworkHelper.parseSecurityInfo returns correctly formatted object.
+
+const wpl = Ci.nsIWebProgressListener;
+const MockCertificate = {
+ getBase64DERString() {
+ // This is the same test certificate as in
+ // test_security-info-certificate.js for consistency.
+ return "MIIDrDCCApSgAwIBAgIQCssoukZe5TkIdnRw883GEjANBgkqhkiG9w0BAQwFADBhMQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cuZGlnaWNlcnQuY29tMSAwHgYDVQQDExdEaWdpQ2VydCBHbG9iYWwgUm9vdCBDQTAeFw0xMzAzMDgxMjAwMDBaFw0yMzAzMDgxMjAwMDBaMEwxCzAJBgNVBAYTAlVTMRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxJjAkBgNVBAMTHURpZ2lDZXJ0IEVDQyBTZWN1cmUgU2VydmVyIENBMHYwEAYHKoZIzj0CAQYFK4EEACIDYgAE4ghC6nfYJN6gLGSkE85AnCNyqQIKDjc/ITa4jVMU9tWRlUvzlgKNcR7E2Munn17voOZ/WpIRllNv68DLP679Wz9HJOeaBy6Wvqgvu1cYr3GkvXg6HuhbPGtkESvMNCuMo4IBITCCAR0wEgYDVR0TAQH/BAgwBgEB/wIBADAOBgNVHQ8BAf8EBAMCAYYwNAYIKwYBBQUHAQEEKDAmMCQGCCsGAQUFBzABhhhodHRwOi8vb2NzcC5kaWdpY2VydC5jb20wQgYDVR0fBDswOTA3oDWgM4YxaHR0cDovL2NybDMuZGlnaWNlcnQuY29tL0RpZ2lDZXJ0R2xvYmFsUm9vdENBLmNybDA9BgNVHSAENjA0MDIGBFUdIAAwKjAoBggrBgEFBQcCARYcaHR0cHM6Ly93d3cuZGlnaWNlcnQuY29tL0NQUzAdBgNVHQ4EFgQUo53mH/naOU/AbuiRy5Wl2jHiCp8wHwYDVR0jBBgwFoAUA95QNVbRTLtm8KPiGxvDl7I90VUwDQYJKoZIhvcNAQEMBQADggEBAMeKoENL7HTJxavVHzA1Nm6YVntIrAVjrnuaVyRXzG/63qttnMe2uuzO58pzZNvfBDcKAEmzP58mrZGMIOgfiA4q+2Y3yDDo0sIkp0VILeoBUEoxlBPfjV/aKrtJPGHzecicZpIalir0ezZYoyxBEHQa0+1IttK7igZFcTMQMHp6mCHdJLnsnLWSB62DxsRq+HfmNb4TDydkskO/g+l3VtsIh5RHFPVfKK+jaEyDj2D3loB5hWp2Jp2VDCADjT7ueihlZGak2YPqmXTNbk19HOuNssWvFhtOyPNV6og4ETQdEa8/B6hPatJ0ES8q/HO3X8IVQwVs1n3aAr0im0/T+Xc=";
+ },
+};
+
+// This *cannot* be used as an nsITransportSecurityInfo (since that interface is
+// builtinclass) but the methods being tested aren't defined by XPCOM and aren't
+// calling QueryInterface, so this usage is fine.
+const MockSecurityInfo = {
+ securityState: wpl.STATE_IS_SECURE,
+ errorCode: 0,
+ cipherName: "TLS_ECDH_ECDSA_WITH_AES_128_GCM_SHA256",
+ // TLS_VERSION_1_2
+ protocolVersion: 3,
+ serverCert: MockCertificate,
+};
+
+add_task(async function run_test() {
+ const result = await NetworkHelper.parseSecurityInfo(
+ MockSecurityInfo,
+ {},
+ {},
+ new Map()
+ );
+
+ equal(result.state, "secure", "State is correct.");
+
+ equal(
+ result.cipherSuite,
+ MockSecurityInfo.cipherName,
+ "Cipher suite is correct."
+ );
+
+ equal(result.protocolVersion, "TLSv1.2", "Protocol version is correct.");
+
+ deepEqual(
+ result.cert,
+ await NetworkHelper.parseCertificateInfo(MockCertificate, new Map()),
+ "Certificate information is correct."
+ );
+
+ equal(result.hpkp, false, "HPKP is false when URI is not available.");
+ equal(result.hsts, false, "HSTS is false when URI is not available.");
+});
diff --git a/devtools/shared/network-observer/test/xpcshell/test_security-info-protocol-version.js b/devtools/shared/network-observer/test/xpcshell/test_security-info-protocol-version.js
new file mode 100644
index 0000000000..a81f7ce73c
--- /dev/null
+++ b/devtools/shared/network-observer/test/xpcshell/test_security-info-protocol-version.js
@@ -0,0 +1,48 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+"use strict";
+
+// Tests that NetworkHelper.formatSecurityProtocol returns correct
+// protocol version strings.
+
+const TEST_CASES = [
+ {
+ description: "TLS_VERSION_1",
+ input: 1,
+ expected: "TLSv1",
+ },
+ {
+ description: "TLS_VERSION_1.1",
+ input: 2,
+ expected: "TLSv1.1",
+ },
+ {
+ description: "TLS_VERSION_1.2",
+ input: 3,
+ expected: "TLSv1.2",
+ },
+ {
+ description: "TLS_VERSION_1.3",
+ input: 4,
+ expected: "TLSv1.3",
+ },
+ {
+ description: "invalid version",
+ input: -1,
+ expected: "Unknown",
+ },
+];
+
+function run_test() {
+ info("Testing NetworkHelper.formatSecurityProtocol.");
+
+ for (const { description, input, expected } of TEST_CASES) {
+ info("Testing " + description);
+
+ equal(
+ NetworkHelper.formatSecurityProtocol(input),
+ expected,
+ "Got the expected protocol string."
+ );
+ }
+}
diff --git a/devtools/shared/network-observer/test/xpcshell/test_security-info-state.js b/devtools/shared/network-observer/test/xpcshell/test_security-info-state.js
new file mode 100644
index 0000000000..be622b2019
--- /dev/null
+++ b/devtools/shared/network-observer/test/xpcshell/test_security-info-state.js
@@ -0,0 +1,122 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+"use strict";
+
+// Tests that security info parser gives correct general security state for
+// different cases.
+
+const wpl = Ci.nsIWebProgressListener;
+
+// This *cannot* be used as an nsITransportSecurityInfo (since that interface is
+// builtinclass) but the methods being tested aren't defined by XPCOM and aren't
+// calling QueryInterface, so this usage is fine.
+const MockSecurityInfo = {
+ securityState: wpl.STATE_IS_BROKEN,
+ errorCode: 0,
+ // nsISSLStatus.TLS_VERSION_1_2
+ protocolVersion: 3,
+ cipherName: "TLS_ECDH_ECDSA_WITH_AES_128_GCM_SHA256",
+};
+
+add_task(async function run_test() {
+ await test_nullSecurityInfo();
+ await test_insecureSecurityInfoWithNSSError();
+ await test_insecureSecurityInfoWithoutNSSError();
+ await test_brokenSecurityInfo();
+ await test_secureSecurityInfo();
+});
+
+/**
+ * Test that undefined security information is returns "insecure".
+ */
+async function test_nullSecurityInfo() {
+ const result = await NetworkHelper.parseSecurityInfo(null, {}, {}, new Map());
+ equal(
+ result.state,
+ "insecure",
+ "state == 'insecure' when securityInfo was undefined"
+ );
+}
+
+/**
+ * Test that STATE_IS_INSECURE with NSSError returns "broken"
+ */
+async function test_insecureSecurityInfoWithNSSError() {
+ MockSecurityInfo.securityState = wpl.STATE_IS_INSECURE;
+
+ // Taken from security/manager/ssl/tests/unit/head_psm.js.
+ MockSecurityInfo.errorCode = -8180;
+
+ const result = await NetworkHelper.parseSecurityInfo(
+ MockSecurityInfo,
+ {},
+ {},
+ new Map()
+ );
+ equal(
+ result.state,
+ "broken",
+ "state == 'broken' if securityState contains STATE_IS_INSECURE flag AND " +
+ "errorCode is NSS error."
+ );
+
+ MockSecurityInfo.errorCode = 0;
+}
+
+/**
+ * Test that STATE_IS_INSECURE without NSSError returns "insecure"
+ */
+async function test_insecureSecurityInfoWithoutNSSError() {
+ MockSecurityInfo.securityState = wpl.STATE_IS_INSECURE;
+
+ const result = await NetworkHelper.parseSecurityInfo(
+ MockSecurityInfo,
+ {},
+ {},
+ new Map()
+ );
+ equal(
+ result.state,
+ "insecure",
+ "state == 'insecure' if securityState contains STATE_IS_INSECURE flag BUT " +
+ "errorCode is not NSS error."
+ );
+}
+
+/**
+ * Test that STATE_IS_SECURE returns "secure"
+ */
+async function test_secureSecurityInfo() {
+ MockSecurityInfo.securityState = wpl.STATE_IS_SECURE;
+
+ const result = await NetworkHelper.parseSecurityInfo(
+ MockSecurityInfo,
+ {},
+ {},
+ new Map()
+ );
+ equal(
+ result.state,
+ "secure",
+ "state == 'secure' if securityState contains STATE_IS_SECURE flag"
+ );
+}
+
+/**
+ * Test that STATE_IS_BROKEN returns "weak"
+ */
+async function test_brokenSecurityInfo() {
+ MockSecurityInfo.securityState = wpl.STATE_IS_BROKEN;
+
+ const result = await NetworkHelper.parseSecurityInfo(
+ MockSecurityInfo,
+ {},
+ {},
+ new Map()
+ );
+ equal(
+ result.state,
+ "weak",
+ "state == 'weak' if securityState contains STATE_IS_BROKEN flag"
+ );
+}
diff --git a/devtools/shared/network-observer/test/xpcshell/test_security-info-static-hpkp.js b/devtools/shared/network-observer/test/xpcshell/test_security-info-static-hpkp.js
new file mode 100644
index 0000000000..f1aa883b93
--- /dev/null
+++ b/devtools/shared/network-observer/test/xpcshell/test_security-info-static-hpkp.js
@@ -0,0 +1,41 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+"use strict";
+
+// Test that NetworkHelper.parseSecurityInfo correctly detects static hpkp pins
+
+const wpl = Ci.nsIWebProgressListener;
+
+// This *cannot* be used as an nsITransportSecurityInfo (since that interface is
+// builtinclass) but the methods being tested aren't defined by XPCOM and aren't
+// calling QueryInterface, so this usage is fine.
+const MockSecurityInfo = {
+ securityState: wpl.STATE_IS_SECURE,
+ errorCode: 0,
+ cipherName: "TLS_ECDH_ECDSA_WITH_AES_128_GCM_SHA256",
+ // TLS_VERSION_1_2
+ protocolVersion: 3,
+ serverCert: {
+ getBase64DERString() {
+ // This is the same test certificate as in
+ // test_security-info-certificate.js for consistency.
+ return "MIIDrDCCApSgAwIBAgIQCssoukZe5TkIdnRw883GEjANBgkqhkiG9w0BAQwFADBhMQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cuZGlnaWNlcnQuY29tMSAwHgYDVQQDExdEaWdpQ2VydCBHbG9iYWwgUm9vdCBDQTAeFw0xMzAzMDgxMjAwMDBaFw0yMzAzMDgxMjAwMDBaMEwxCzAJBgNVBAYTAlVTMRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxJjAkBgNVBAMTHURpZ2lDZXJ0IEVDQyBTZWN1cmUgU2VydmVyIENBMHYwEAYHKoZIzj0CAQYFK4EEACIDYgAE4ghC6nfYJN6gLGSkE85AnCNyqQIKDjc/ITa4jVMU9tWRlUvzlgKNcR7E2Munn17voOZ/WpIRllNv68DLP679Wz9HJOeaBy6Wvqgvu1cYr3GkvXg6HuhbPGtkESvMNCuMo4IBITCCAR0wEgYDVR0TAQH/BAgwBgEB/wIBADAOBgNVHQ8BAf8EBAMCAYYwNAYIKwYBBQUHAQEEKDAmMCQGCCsGAQUFBzABhhhodHRwOi8vb2NzcC5kaWdpY2VydC5jb20wQgYDVR0fBDswOTA3oDWgM4YxaHR0cDovL2NybDMuZGlnaWNlcnQuY29tL0RpZ2lDZXJ0R2xvYmFsUm9vdENBLmNybDA9BgNVHSAENjA0MDIGBFUdIAAwKjAoBggrBgEFBQcCARYcaHR0cHM6Ly93d3cuZGlnaWNlcnQuY29tL0NQUzAdBgNVHQ4EFgQUo53mH/naOU/AbuiRy5Wl2jHiCp8wHwYDVR0jBBgwFoAUA95QNVbRTLtm8KPiGxvDl7I90VUwDQYJKoZIhvcNAQEMBQADggEBAMeKoENL7HTJxavVHzA1Nm6YVntIrAVjrnuaVyRXzG/63qttnMe2uuzO58pzZNvfBDcKAEmzP58mrZGMIOgfiA4q+2Y3yDDo0sIkp0VILeoBUEoxlBPfjV/aKrtJPGHzecicZpIalir0ezZYoyxBEHQa0+1IttK7igZFcTMQMHp6mCHdJLnsnLWSB62DxsRq+HfmNb4TDydkskO/g+l3VtsIh5RHFPVfKK+jaEyDj2D3loB5hWp2Jp2VDCADjT7ueihlZGak2YPqmXTNbk19HOuNssWvFhtOyPNV6og4ETQdEa8/B6hPatJ0ES8q/HO3X8IVQwVs1n3aAr0im0/T+Xc=";
+ },
+ },
+};
+
+const MockHttpInfo = {
+ hostname: "include-subdomains.pinning.example.com",
+ private: false,
+};
+
+add_task(async function run_test() {
+ Services.prefs.setIntPref("security.cert_pinning.enforcement_level", 1);
+ const result = await NetworkHelper.parseSecurityInfo(
+ MockSecurityInfo,
+ {},
+ MockHttpInfo,
+ new Map()
+ );
+ equal(result.hpkp, true, "Static HPKP detected.");
+});
diff --git a/devtools/shared/network-observer/test/xpcshell/test_security-info-weakness-reasons.js b/devtools/shared/network-observer/test/xpcshell/test_security-info-weakness-reasons.js
new file mode 100644
index 0000000000..71d7675284
--- /dev/null
+++ b/devtools/shared/network-observer/test/xpcshell/test_security-info-weakness-reasons.js
@@ -0,0 +1,39 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+"use strict";
+
+// Tests that NetworkHelper.getReasonsForWeakness returns correct reasons for
+// weak requests.
+
+const wpl = Ci.nsIWebProgressListener;
+const TEST_CASES = [
+ {
+ description: "weak cipher",
+ input: wpl.STATE_IS_BROKEN | wpl.STATE_USES_WEAK_CRYPTO,
+ expected: ["cipher"],
+ },
+ {
+ description: "only STATE_IS_BROKEN flag",
+ input: wpl.STATE_IS_BROKEN,
+ expected: [],
+ },
+ {
+ description: "only STATE_IS_SECURE flag",
+ input: wpl.STATE_IS_SECURE,
+ expected: [],
+ },
+];
+
+function run_test() {
+ info("Testing NetworkHelper.getReasonsForWeakness.");
+
+ for (const { description, input, expected } of TEST_CASES) {
+ info("Testing " + description);
+
+ deepEqual(
+ NetworkHelper.getReasonsForWeakness(input),
+ expected,
+ "Got the expected reasons for weakness."
+ );
+ }
+}
diff --git a/devtools/shared/network-observer/test/xpcshell/test_throttle.js b/devtools/shared/network-observer/test/xpcshell/test_throttle.js
new file mode 100644
index 0000000000..5f8ef589fb
--- /dev/null
+++ b/devtools/shared/network-observer/test/xpcshell/test_throttle.js
@@ -0,0 +1,162 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+/* eslint-disable mozilla/use-chromeutils-generateqi */
+
+const { NetworkThrottleManager } = ChromeUtils.importESModule(
+ "resource://devtools/shared/network-observer/NetworkThrottleManager.sys.mjs"
+);
+const nsIScriptableInputStream = Ci.nsIScriptableInputStream;
+
+function TestStreamListener() {
+ this.state = "initial";
+}
+TestStreamListener.prototype = {
+ onStartRequest() {
+ this.setState("start");
+ },
+
+ onStopRequest() {
+ this.setState("stop");
+ },
+
+ onDataAvailable(request, inputStream, offset, count) {
+ const sin = Cc["@mozilla.org/scriptableinputstream;1"].createInstance(
+ nsIScriptableInputStream
+ );
+ sin.init(inputStream);
+ this.data = sin.read(count);
+ this.setState("data");
+ },
+
+ setState(state) {
+ this.state = state;
+ if (this._deferred) {
+ this._deferred.resolve(state);
+ this._deferred = null;
+ }
+ },
+
+ onStateChanged() {
+ if (!this._deferred) {
+ let resolve, reject;
+ const promise = new Promise(function (res, rej) {
+ resolve = res;
+ reject = rej;
+ });
+ this._deferred = { resolve, reject, promise };
+ }
+ return this._deferred.promise;
+ },
+};
+
+function TestChannel() {
+ this.state = "initial";
+ this.testListener = new TestStreamListener();
+ this._throttleQueue = null;
+}
+TestChannel.prototype = {
+ QueryInterface() {
+ return this;
+ },
+
+ get throttleQueue() {
+ return this._throttleQueue;
+ },
+
+ set throttleQueue(q) {
+ this._throttleQueue = q;
+ this.state = "throttled";
+ },
+
+ setNewListener(listener) {
+ this.listener = listener;
+ this.state = "listener";
+ return this.testListener;
+ },
+};
+
+add_task(async function () {
+ const throttler = new NetworkThrottleManager({
+ latencyMean: 1,
+ latencyMax: 1,
+ downloadBPSMean: 500,
+ downloadBPSMax: 500,
+ uploadBPSMean: 500,
+ uploadBPSMax: 500,
+ });
+
+ const uploadChannel = new TestChannel();
+ throttler.manageUpload(uploadChannel);
+ equal(
+ uploadChannel.state,
+ "throttled",
+ "NetworkThrottleManager set throttleQueue"
+ );
+
+ const downloadChannel = new TestChannel();
+ const testListener = downloadChannel.testListener;
+
+ const listener = throttler.manage(downloadChannel);
+ equal(
+ downloadChannel.state,
+ "listener",
+ "NetworkThrottleManager called setNewListener"
+ );
+
+ equal(testListener.state, "initial", "test listener in initial state");
+
+ // This method must be passed through immediately.
+ listener.onStartRequest(null);
+ equal(testListener.state, "start", "test listener started");
+
+ const TEST_INPUT = "hi bob";
+
+ const testStream = Cc["@mozilla.org/storagestream;1"].createInstance(
+ Ci.nsIStorageStream
+ );
+ testStream.init(512, 512);
+ const out = testStream.getOutputStream(0);
+ out.write(TEST_INPUT, TEST_INPUT.length);
+ out.close();
+ const testInputStream = testStream.newInputStream(0);
+
+ const activityDistributor = Cc[
+ "@mozilla.org/network/http-activity-distributor;1"
+ ].getService(Ci.nsIHttpActivityDistributor);
+ let activitySeen = false;
+ listener.addActivityCallback(
+ () => {
+ activitySeen = true;
+ },
+ null,
+ null,
+ null,
+ activityDistributor.ACTIVITY_SUBTYPE_RESPONSE_COMPLETE,
+ null,
+ TEST_INPUT.length,
+ null
+ );
+
+ // onDataAvailable is required to immediately read the data.
+ listener.onDataAvailable(null, testInputStream, 0, 6);
+ equal(testInputStream.available(), 0, "no more data should be available");
+ equal(
+ testListener.state,
+ "start",
+ "test listener should not have received data"
+ );
+ equal(activitySeen, false, "activity not distributed yet");
+
+ let newState = await testListener.onStateChanged();
+ equal(newState, "data", "test listener received data");
+ equal(testListener.data, TEST_INPUT, "test listener received all the data");
+ equal(activitySeen, true, "activity has been distributed");
+
+ const onChange = testListener.onStateChanged();
+ listener.onStopRequest(null, null);
+ newState = await onChange;
+ equal(newState, "stop", "onStateChanged reported");
+});
diff --git a/devtools/shared/network-observer/test/xpcshell/xpcshell.ini b/devtools/shared/network-observer/test/xpcshell/xpcshell.ini
new file mode 100644
index 0000000000..3f5c1b674a
--- /dev/null
+++ b/devtools/shared/network-observer/test/xpcshell/xpcshell.ini
@@ -0,0 +1,15 @@
+[DEFAULT]
+tags = devtools
+head = head.js
+firefox-appdir = browser
+skip-if = toolkit == 'android'
+support-files =
+
+[test_network_helper.js]
+[test_security-info-certificate.js]
+[test_security-info-parser.js]
+[test_security-info-protocol-version.js]
+[test_security-info-state.js]
+[test_security-info-static-hpkp.js]
+[test_security-info-weakness-reasons.js]
+[test_throttle.js]