diff --git a/dom/security/metrics.yaml b/dom/security/metrics.yaml new file mode 100644 index 000000000000..02084407b11b --- /dev/null +++ b/dom/security/metrics.yaml @@ -0,0 +1,153 @@ +# 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/. + +# Adding a new metric? We have docs for that! +# https://firefox-source-docs.mozilla.org/toolkit/components/glean/user/new_definitions_file.html + +--- +$schema: moz://mozilla.org/schemas/glean/metrics/2-0-0 +$tags: + - 'Core :: DOM: Security' + +httpsfirst: + upgraded: + type: counter + description: > + Counts how often a load is marked to be upgraded to HTTPS because of + HTTPS-First (`dom.security.https_first` enabled). + bugs: + - https://bugzilla.mozilla.org/show_bug.cgi?id=1868380 + data_reviews: + - https://bugzilla.mozilla.org/show_bug.cgi?id=1868380#c10 + data_sensitivity: + - technical + notification_emails: + - mjurgens@mozilla.com + - seceng-telemetry@mozilla.com + expires: never + + upgraded_schemeless: + type: counter + description: > + Counts how often a load is marked to be upgraded to HTTPS because of + schemeless HTTPS-First (`dom.security.https_first` disabled, but load + marked as schemeless). + bugs: + - https://bugzilla.mozilla.org/show_bug.cgi?id=1868380 + data_reviews: + - https://bugzilla.mozilla.org/show_bug.cgi?id=1868380#c10 + data_sensitivity: + - technical + notification_emails: + - mjurgens@mozilla.com + - seceng-telemetry@mozilla.com + expires: never + + downgraded: + type: counter + description: > + How many regular HTTPS-First (`dom.security.https_first` enabled) + upgrades get downgraded again. + bugs: + - https://bugzilla.mozilla.org/show_bug.cgi?id=1868380 + data_reviews: + - https://bugzilla.mozilla.org/show_bug.cgi?id=1868380#c10 + data_sensitivity: + - technical + notification_emails: + - mjurgens@mozilla.com + - seceng-telemetry@mozilla.com + expires: never + + downgraded_schemeless: + type: counter + description: > + How many schemeless HTTPS-First (`dom.security.https_first` disabled, but + load marked as schemeless) upgrades get downgraded again. + bugs: + - https://bugzilla.mozilla.org/show_bug.cgi?id=1868380 + data_reviews: + - https://bugzilla.mozilla.org/show_bug.cgi?id=1868380#c10 + data_sensitivity: + - technical + notification_emails: + - mjurgens@mozilla.com + - seceng-telemetry@mozilla.com + expires: never + + downgraded_on_timer: + type: rate + description: > + How many HTTPS-First (`dom.security.https_first` enabled) upgrades get + downgraded again because the HTTP request fired after 3s received a answer + faster than the HTTPS request. + denominator_metric: httpsfirst.downgraded + bugs: + - https://bugzilla.mozilla.org/show_bug.cgi?id=1868380 + data_reviews: + - https://bugzilla.mozilla.org/show_bug.cgi?id=1868380#c10 + data_sensitivity: + - technical + notification_emails: + - mjurgens@mozilla.com + - seceng-telemetry@mozilla.com + expires: never + + downgraded_on_timer_schemeless: + type: rate + description: > + How many of schemeless HTTPS-First (`dom.security.https_first` disabled, + but load marked as schemeless) upgrades get downgraded again because the + HTTP request fired after 3s received a answer faster than the HTTPS + request + denominator_metric: httpsfirst.downgraded_schemeless + bugs: + - https://bugzilla.mozilla.org/show_bug.cgi?id=1868380 + data_reviews: + - https://bugzilla.mozilla.org/show_bug.cgi?id=1868380#c10 + data_sensitivity: + - technical + notification_emails: + - mjurgens@mozilla.com + - seceng-telemetry@mozilla.com + expires: never + + downgrade_time: + type: timing_distribution + description: > + If a HTTPS-First (`dom.security.https_first` enabled) upgrade isn't + successful, measures the timespan between the navigation start and the + downgrade. This is essentially the overhead caused by HTTPS-First if a + site does not support HTTPS. + time_unit: millisecond + bugs: + - https://bugzilla.mozilla.org/show_bug.cgi?id=1868380 + data_reviews: + - https://bugzilla.mozilla.org/show_bug.cgi?id=1868380#c10 + data_sensitivity: + - technical + notification_emails: + - mjurgens@mozilla.com + - seceng-telemetry@mozilla.com + expires: never + + downgrade_time_schemeless: + type: timing_distribution + description: > + If a schemeless HTTPS-First (`dom.security.https_first` disabled, but + load marked as schemeless) upgrade isn't successful, measures the + timespan between the navigation start and the downgrade. This is + essentially the overhead caused by HTTPS-First if a site does not support + HTTPS. + time_unit: millisecond + bugs: + - https://bugzilla.mozilla.org/show_bug.cgi?id=1868380 + data_reviews: + - https://bugzilla.mozilla.org/show_bug.cgi?id=1868380#c10 + data_sensitivity: + - technical + notification_emails: + - mjurgens@mozilla.com + - seceng-telemetry@mozilla.com + expires: never diff --git a/dom/security/nsHTTPSOnlyUtils.cpp b/dom/security/nsHTTPSOnlyUtils.cpp index 2a3880ba70a7..535efaba4ebf 100644 --- a/dom/security/nsHTTPSOnlyUtils.cpp +++ b/dom/security/nsHTTPSOnlyUtils.cpp @@ -6,6 +6,8 @@ #include "mozilla/Components.h" #include "mozilla/ClearOnShutdown.h" +#include "mozilla/TimeStamp.h" +#include "mozilla/glean/GleanMetrics.h" #include "mozilla/NullPrincipal.h" #include "mozilla/StaticPrefs_dom.h" #include "mozilla/net/DNS.h" @@ -438,7 +440,7 @@ bool nsHTTPSOnlyUtils::ShouldUpgradeHttpsFirstRequest(nsIURI* aURI, // We can upgrade the request - let's log to the console and set the status // so we know that we upgraded the request. if (aLoadInfo->GetWasSchemelessInput() && - mozilla::StaticPrefs::dom_security_https_first_schemeless()) { + !IsHttpsFirstModeEnabled(isPrivateWin)) { nsAutoCString urlCString; aURI->GetSpec(urlCString); NS_ConvertUTF8toUTF16 urlString(urlCString); @@ -447,6 +449,8 @@ bool nsHTTPSOnlyUtils::ShouldUpgradeHttpsFirstRequest(nsIURI* aURI, nsHTTPSOnlyUtils::LogLocalizedString("HTTPSFirstSchemeless", params, nsIScriptError::warningFlag, aLoadInfo, aURI, true); + + mozilla::glean::httpsfirst::upgraded_schemeless.Add(); } else { nsAutoCString scheme; @@ -461,7 +465,12 @@ bool nsHTTPSOnlyUtils::ShouldUpgradeHttpsFirstRequest(nsIURI* aURI, isSpeculative ? "HTTPSOnlyUpgradeSpeculativeConnection" : "HTTPSOnlyUpgradeRequest", params, nsIScriptError::warningFlag, aLoadInfo, aURI, true); + + if (!isSpeculative) { + mozilla::glean::httpsfirst::upgraded.Add(); + } } + // Set flag so we know that we upgraded the request httpsOnlyStatus |= nsILoadInfo::HTTPS_ONLY_UPGRADED_HTTPS_FIRST; aLoadInfo->SetHttpsOnlyStatus(httpsOnlyStatus); @@ -470,9 +479,11 @@ bool nsHTTPSOnlyUtils::ShouldUpgradeHttpsFirstRequest(nsIURI* aURI, /* static */ already_AddRefed -nsHTTPSOnlyUtils::PotentiallyDowngradeHttpsFirstRequest(nsIChannel* aChannel, - nsresult aStatus) { - nsCOMPtr loadInfo = aChannel->LoadInfo(); +nsHTTPSOnlyUtils::PotentiallyDowngradeHttpsFirstRequest( + mozilla::net::DocumentLoadListener* aDocumentLoadListener, + nsresult aStatus) { + nsCOMPtr channel = aDocumentLoadListener->GetChannel(); + nsCOMPtr loadInfo = channel->LoadInfo(); uint32_t httpsOnlyStatus = loadInfo->GetHttpsOnlyStatus(); // Only downgrade if we this request was upgraded using HTTPS-First Mode if (!(httpsOnlyStatus & nsILoadInfo::HTTPS_ONLY_UPGRADED_HTTPS_FIRST)) { @@ -488,7 +499,7 @@ nsHTTPSOnlyUtils::PotentiallyDowngradeHttpsFirstRequest(nsIChannel* aChannel, // to check each NS_OK for those errors. // Only downgrade an NS_OK status if it is an 4xx or 5xx error. if (NS_SUCCEEDED(aStatus)) { - nsCOMPtr httpChannel = do_QueryInterface(aChannel); + nsCOMPtr httpChannel = do_QueryInterface(channel); // If no httpChannel exists we have nothing to do here. if (!httpChannel) { return nullptr; @@ -532,7 +543,7 @@ nsHTTPSOnlyUtils::PotentiallyDowngradeHttpsFirstRequest(nsIChannel* aChannel, } nsCOMPtr uri; - nsresult rv = aChannel->GetURI(getter_AddRefs(uri)); + nsresult rv = channel->GetURI(getter_AddRefs(uri)); NS_ENSURE_SUCCESS(rv, nullptr); nsAutoCString spec; @@ -584,6 +595,33 @@ nsHTTPSOnlyUtils::PotentiallyDowngradeHttpsFirstRequest(nsIChannel* aChannel, nsIScriptError::warningFlag, loadInfo, uri, true); + // Record telemety + nsDOMNavigationTiming* timing = aDocumentLoadListener->GetTiming(); + if (timing) { + mozilla::TimeStamp navigationStart = timing->GetNavigationStartTimeStamp(); + if (navigationStart) { + mozilla::TimeDuration duration = + mozilla::TimeStamp::Now() - navigationStart; + bool isPrivateWin = + loadInfo->GetOriginAttributes().mPrivateBrowsingId > 0; + + if (loadInfo->GetWasSchemelessInput() && + !IsHttpsFirstModeEnabled(isPrivateWin)) { + mozilla::glean::httpsfirst::downgraded_schemeless.Add(); + if (timing) { + mozilla::glean::httpsfirst::downgrade_time_schemeless + .AccumulateRawDuration(duration); + } + } else { + mozilla::glean::httpsfirst::downgraded.Add(); + if (timing) { + mozilla::glean::httpsfirst::downgrade_time.AccumulateRawDuration( + duration); + } + } + } + } + return newURI.forget(); } @@ -954,6 +992,19 @@ TestHTTPAnswerRunnable::OnStartRequest(nsIRequest* aRequest) { nsresult httpsOnlyChannelStatus; httpsOnlyChannel->GetStatus(&httpsOnlyChannelStatus); if (httpsOnlyChannelStatus == NS_OK) { + bool isPrivateWin = + loadInfo->GetOriginAttributes().mPrivateBrowsingId > 0; + if (!nsHTTPSOnlyUtils::IsHttpsOnlyModeEnabled(isPrivateWin)) { + // Record HTTPS-First Telemetry + if (loadInfo->GetWasSchemelessInput() && + !nsHTTPSOnlyUtils::IsHttpsFirstModeEnabled(isPrivateWin)) { + mozilla::glean::httpsfirst::downgraded_on_timer_schemeless + .AddToNumerator(); + } else { + mozilla::glean::httpsfirst::downgraded_on_timer.AddToNumerator(); + } + } + httpsOnlyChannel->Cancel(NS_ERROR_NET_TIMEOUT_EXTERNAL); } } diff --git a/dom/security/nsHTTPSOnlyUtils.h b/dom/security/nsHTTPSOnlyUtils.h index 73a52190828a..7e36bfadbd04 100644 --- a/dom/security/nsHTTPSOnlyUtils.h +++ b/dom/security/nsHTTPSOnlyUtils.h @@ -95,12 +95,13 @@ class nsHTTPSOnlyUtils { /** * Determines if the request was previously upgraded with HTTPS-First, creates * a downgraded URI and logs to console. - * @param aStatus Status code - * @param aChannel Failed channel - * @return URI with http-scheme or nullptr + * @param aStatus Status code + * @param aDocumentLoadListener Failed document load listener + * @return URI with http-scheme or nullptr */ static already_AddRefed PotentiallyDowngradeHttpsFirstRequest( - nsIChannel* aChannel, nsresult aStatus); + mozilla::net::DocumentLoadListener* aDocumentLoadListener, + nsresult aStatus); /** * Checks if the error code is on a block-list of codes that are probably diff --git a/dom/security/test/https-first/browser_httpsfirst.js b/dom/security/test/https-first/browser_httpsfirst.js index 733474dcc12e..c4437f6051a9 100644 --- a/dom/security/test/https-first/browser_httpsfirst.js +++ b/dom/security/test/https-first/browser_httpsfirst.js @@ -31,6 +31,7 @@ add_task(async function () { await SpecialPowers.pushPrefEnv({ set: [["dom.security.https_first", false]], }); + Services.fog.testResetFOG(); await runPrefTest( "http://example.com", @@ -42,6 +43,23 @@ add_task(async function () { set: [["dom.security.https_first", true]], }); + for (const key of [ + "upgraded", + "upgradedSchemeless", + "downgraded", + "downgradedSchemeless", + "downgradedOnTimer", + "downgradedOnTimerSchemeless", + "downgradeTime", + "downgradeTimeSchemeless", + ]) { + is( + Glean.httpsfirst[key].testGetValue(), + null, + `No telemetry should have been recorded yet for ${key}` + ); + } + await runPrefTest( "http://example.com", "Should upgrade upgradeable website", @@ -71,4 +89,21 @@ add_task(async function () { "Should downgrade after timeout.", "http://" ); + + info("Checking expected telemetry"); + is(Glean.httpsfirst.upgraded.testGetValue(), 5); + is(Glean.httpsfirst.upgradedSchemeless.testGetValue(), null); + is(Glean.httpsfirst.downgraded.testGetValue(), 3); + is(Glean.httpsfirst.downgradedSchemeless.testGetValue(), null); + is(Glean.httpsfirst.downgradedOnTimer.testGetValue().numerator, 1); + is(Glean.httpsfirst.downgradedOnTimerSchemeless.testGetValue(), null); + const downgradeSeconds = + Glean.httpsfirst.downgradeTime.testGetValue().sum / 1_000_000_000; + ok( + downgradeSeconds > 2 && downgradeSeconds < 30, + `Summed downgrade time should be above 2 and below 30 seconds (is ${downgradeSeconds.toFixed( + 2 + )}s)` + ); + is(null, Glean.httpsfirst.downgradeTimeSchemeless.testGetValue()); }); diff --git a/dom/security/test/https-first/browser_schemeless.js b/dom/security/test/https-first/browser_schemeless.js index 9687f15072e5..64b078983dc4 100644 --- a/dom/security/test/https-first/browser_schemeless.js +++ b/dom/security/test/https-first/browser_schemeless.js @@ -153,6 +153,7 @@ async function runTest(aInput, aDesc, aExpectedScheme) { add_task(async function () { requestLongerTimeout(10); + Services.fog.testResetFOG(); await SpecialPowers.pushPrefEnv({ set: [ @@ -183,9 +184,36 @@ add_task(async function () { "http" ); + for (const key of [ + "upgraded", + "upgradedSchemeless", + "downgraded", + "downgradedSchemeless", + "downgradedOnTimer", + "downgradedOnTimerSchemeless", + "downgradeTime", + "downgradeTimeSchemeless", + ]) { + is( + Glean.httpsfirst[key].testGetValue(), + null, + `No telemetry should have been recorded yet for ${key}` + ); + } + await runTest( "example.com", "Should upgrade upgradeable website without explicit scheme", "https" ); + + info("Checking expected telemetry"); + is(Glean.httpsfirst.upgraded.testGetValue(), null); + is(Glean.httpsfirst.upgradedSchemeless.testGetValue(), 5); + is(Glean.httpsfirst.downgraded.testGetValue(), null); + is(Glean.httpsfirst.downgradedSchemeless.testGetValue(), null); + is(Glean.httpsfirst.downgradedOnTimer.testGetValue(), null); + is(Glean.httpsfirst.downgradedOnTimerSchemeless.testGetValue(), null); + is(Glean.httpsfirst.downgradeTime.testGetValue(), null); + is(Glean.httpsfirst.downgradeTimeSchemeless.testGetValue(), null); }); diff --git a/netwerk/ipc/DocumentLoadListener.cpp b/netwerk/ipc/DocumentLoadListener.cpp index b10e5f28d6b4..5577ab282ee7 100644 --- a/netwerk/ipc/DocumentLoadListener.cpp +++ b/netwerk/ipc/DocumentLoadListener.cpp @@ -2379,8 +2379,8 @@ bool DocumentLoadListener::MaybeHandleLoadErrorWithURIFixup(nsresult aStatus) { // we can downgrade the scheme to HTTP again. bool isHTTPSFirstFixup = false; if (!newURI) { - newURI = nsHTTPSOnlyUtils::PotentiallyDowngradeHttpsFirstRequest(mChannel, - aStatus); + newURI = + nsHTTPSOnlyUtils::PotentiallyDowngradeHttpsFirstRequest(this, aStatus); isHTTPSFirstFixup = true; } diff --git a/netwerk/ipc/DocumentLoadListener.h b/netwerk/ipc/DocumentLoadListener.h index 98aacc98f819..a8e4ffa7ab72 100644 --- a/netwerk/ipc/DocumentLoadListener.h +++ b/netwerk/ipc/DocumentLoadListener.h @@ -300,6 +300,7 @@ class DocumentLoadListener : public nsIInterfaceRequestor, uint32_t GetLoadType() const { return mLoadStateLoadType; } bool IsDownload() const { return mIsDownload; } bool IsLoadingJSURI() const { return mIsLoadingJSURI; } + nsDOMNavigationTiming* GetTiming() { return mTiming; } mozilla::dom::LoadingSessionHistoryInfo* GetLoadingSessionHistoryInfo() { return mLoadingSessionHistoryInfo.get(); diff --git a/toolkit/components/glean/metrics_index.py b/toolkit/components/glean/metrics_index.py index 5edc2b61575b..987120dbffae 100644 --- a/toolkit/components/glean/metrics_index.py +++ b/toolkit/components/glean/metrics_index.py @@ -22,6 +22,7 @@ gecko_metrics = [ "dom/media/webrtc/metrics.yaml", "dom/metrics.yaml", "dom/performance/metrics.yaml", + "dom/security/metrics.yaml", "gfx/metrics.yaml", "image/decoders/metrics.yaml", "js/xpconnect/metrics.yaml",