Create HttpErrorTabHelper to track 3PC Breakage Indicator UKM

This CL creates a `WebContentsObserver` that is used to track cases of
Http Errors as an indicator of possible third-party cookie breakage. This metric is added as a case to the pre-existing UKM
`ThirdPartyCookies.BreakageIndicator`.

Bug: 1463370
Change-Id: Icd5dc766892ddd628e641e60b25856073001373c
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/4845630
Reviewed-by: Chris Fredrickson <cfredric@chromium.org>
Reviewed-by: Ben Kelly <wanderview@chromium.org>
Reviewed-by: Brendon Tiszka <tiszka@chromium.org>
Commit-Queue: Sam LeDoux <sledoux@google.com>
Cr-Commit-Position: refs/heads/main@{#1216318}
diff --git a/chrome/browser/BUILD.gn b/chrome/browser/BUILD.gn
index f45cffaa..6041acc 100644
--- a/chrome/browser/BUILD.gn
+++ b/chrome/browser/BUILD.gn
@@ -1847,6 +1847,8 @@
     "tpcd/heuristics/opener_heuristic_tab_helper.h",
     "tpcd/heuristics/opener_heuristic_utils.cc",
     "tpcd/heuristics/opener_heuristic_utils.h",
+    "tpcd/http_error_observer/http_error_tab_helper.cc",
+    "tpcd/http_error_observer/http_error_tab_helper.h",
     "tpcd/metadata/devtools_observer.cc",
     "tpcd/metadata/devtools_observer.h",
     "tpcd/metadata/updater_service.cc",
diff --git a/chrome/browser/tpcd/http_error_observer/BUILD.gn b/chrome/browser/tpcd/http_error_observer/BUILD.gn
new file mode 100644
index 0000000..9908afe
--- /dev/null
+++ b/chrome/browser/tpcd/http_error_observer/BUILD.gn
@@ -0,0 +1,22 @@
+# Copyright 2023 The Chromium Authors
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+source_set("browser_tests") {
+  testonly = true
+  sources = [ "http_error_tab_helper_browsertest.cc" ]
+
+  defines = [ "HAS_OUT_OF_PROC_TEST_RUNNER" ]
+
+  deps = [
+    "//base",
+    "//chrome/browser",
+    "//chrome/test:test_support",
+    "//components/ukm:test_support",
+    "//content/test:browsertest_support",
+    "//content/test:test_support",
+    "//net:test_support",
+    "//services/metrics/public/cpp:ukm_builders",
+    "//testing/gtest",
+  ]
+}
diff --git a/chrome/browser/tpcd/http_error_observer/http_error_tab_helper.cc b/chrome/browser/tpcd/http_error_observer/http_error_tab_helper.cc
new file mode 100644
index 0000000..15f7572
--- /dev/null
+++ b/chrome/browser/tpcd/http_error_observer/http_error_tab_helper.cc
@@ -0,0 +1,68 @@
+// Copyright 2023 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/tpcd/http_error_observer/http_error_tab_helper.h"
+
+#include "chrome/browser/content_settings/cookie_settings_factory.h"
+#include "chrome/browser/profiles/profile.h"
+#include "components/content_settings/browser/page_specific_content_settings.h"
+#include "components/content_settings/core/browser/cookie_settings.h"
+#include "content/public/browser/render_frame_host.h"
+#include "content/public/browser/web_contents_observer.h"
+#include "content/public/browser/web_contents_user_data.h"
+#include "net/cookies/cookie_util.h"
+#include "services/metrics/public/cpp/ukm_builders.h"
+#include "services/metrics/public/cpp/ukm_recorder.h"
+#include "services/metrics/public/cpp/ukm_source_id.h"
+#include "third_party/blink/public/mojom/loader/resource_load_info.mojom.h"
+
+HttpErrorTabHelper::~HttpErrorTabHelper() = default;
+
+// This override of the ResourceLoadComplete Method will be used to record
+// instances of the ThirdPartyCookiesBreakageIndicator UKM
+void HttpErrorTabHelper::ResourceLoadComplete(
+    content::RenderFrameHost* render_frame_host,
+    const content::GlobalRequestID& request_id,
+    const blink::mojom::ResourceLoadInfo& resource_load_info) {
+  // The response code will determine whether or not we record the metric. Only
+  // record for https status codes indicating a server or client error.
+  int response_code = resource_load_info.http_status_code;
+  if (response_code < 400 || response_code >= 600) {
+    return;
+  }
+  Profile* profile =
+      Profile::FromBrowserContext(render_frame_host->GetBrowserContext());
+  scoped_refptr<content_settings::CookieSettings> cs =
+      CookieSettingsFactory::GetForProfile(profile);
+
+  // For this metric, we define "cookies blocked in settings" based on the
+  // global opt-in to third-party cookie blocking as well as no overriding
+  // content setting on the top-level site.
+  bool cookies_blocked_in_settings =
+      cs && cs->ShouldBlockThirdPartyCookies() &&
+      !cs->IsThirdPartyAccessAllowed(resource_load_info.final_url, nullptr);
+
+  // Also measure if 3P cookies were actually blocked on the site.
+  content_settings::PageSpecificContentSettings* pscs =
+      content_settings::PageSpecificContentSettings::GetForFrame(
+          render_frame_host);
+  bool cookies_blocked =
+      pscs && pscs->blocked_local_shared_objects().GetObjectCount() > 0;
+
+  ukm::builders::ThirdPartyCookies_BreakageIndicator(
+      web_contents()->GetPrimaryMainFrame()->GetPageUkmSourceId())
+      .SetBreakageIndicatorType(
+          static_cast<int>(net::cookie_util::BreakageIndicatorType::HTTP_ERROR))
+      .SetTPCBlocked(cookies_blocked)
+      .SetTPCBlockedInSettings(cookies_blocked_in_settings)
+      .Record(ukm::UkmRecorder::Get());
+}
+
+HttpErrorTabHelper::HttpErrorTabHelper(content::WebContents* web_contents)
+    : content::WebContentsObserver(web_contents),
+      content::WebContentsUserData<HttpErrorTabHelper>(*web_contents) {
+  CHECK(web_contents);
+}
+
+WEB_CONTENTS_USER_DATA_KEY_IMPL(HttpErrorTabHelper);
diff --git a/chrome/browser/tpcd/http_error_observer/http_error_tab_helper.h b/chrome/browser/tpcd/http_error_observer/http_error_tab_helper.h
new file mode 100644
index 0000000..775777d
--- /dev/null
+++ b/chrome/browser/tpcd/http_error_observer/http_error_tab_helper.h
@@ -0,0 +1,38 @@
+// Copyright 2023 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_BROWSER_TPCD_HTTP_ERROR_OBSERVER_HTTP_ERROR_TAB_HELPER_H_
+#define CHROME_BROWSER_TPCD_HTTP_ERROR_OBSERVER_HTTP_ERROR_TAB_HELPER_H_
+
+#include "base/sequence_checker.h"
+#include "content/public/browser/render_frame_host.h"
+#include "content/public/browser/web_contents_observer.h"
+#include "content/public/browser/web_contents_user_data.h"
+
+class HttpErrorTabHelper
+    : public content::WebContentsObserver,
+      public content::WebContentsUserData<HttpErrorTabHelper> {
+ public:
+  HttpErrorTabHelper(const HttpErrorTabHelper&) = delete;
+  HttpErrorTabHelper& operator=(const HttpErrorTabHelper&) = delete;
+  ~HttpErrorTabHelper() override;
+
+  // WebContentsObserver:
+  void ResourceLoadComplete(
+      content::RenderFrameHost* render_frame_host,
+      const content::GlobalRequestID& request_id,
+      const blink::mojom::ResourceLoadInfo& resource_load_info) override;
+
+ protected:
+  explicit HttpErrorTabHelper(content::WebContents* web_contents);
+
+ private:
+  friend class content::WebContentsUserData<HttpErrorTabHelper>;
+
+  WEB_CONTENTS_USER_DATA_KEY_DECL();
+
+  SEQUENCE_CHECKER(sequence_checker_);
+};
+
+#endif  // CHROME_BROWSER_TPCD_HTTP_ERROR_OBSERVER_HTTP_ERROR_TAB_HELPER_H_
diff --git a/chrome/browser/tpcd/http_error_observer/http_error_tab_helper_browsertest.cc b/chrome/browser/tpcd/http_error_observer/http_error_tab_helper_browsertest.cc
new file mode 100644
index 0000000..273d812a
--- /dev/null
+++ b/chrome/browser/tpcd/http_error_observer/http_error_tab_helper_browsertest.cc
@@ -0,0 +1,283 @@
+// Copyright 2023 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/tpcd/http_error_observer/http_error_tab_helper.h"
+
+#include "base/test/metrics/histogram_tester.h"
+#include "chrome/browser/content_settings/cookie_settings_factory.h"
+#include "chrome/browser/profiles/profile.h"
+#include "chrome/browser/ui/browser.h"
+#include "chrome/browser/ui/browser_list.h"
+#include "chrome/browser/ui/tabs/tab_strip_model.h"
+#include "chrome/browser/ui/ui_features.h"
+#include "chrome/test/base/in_process_browser_test.h"
+#include "chrome/test/base/ui_test_utils.h"
+#include "components/content_settings/core/browser/cookie_settings.h"
+#include "components/content_settings/core/common/pref_names.h"
+#include "components/prefs/pref_service.h"
+#include "components/ukm/test_ukm_recorder.h"
+#include "content/public/common/content_paths.h"
+#include "content/public/test/browser_test.h"
+#include "content/public/test/browser_test_utils.h"
+#include "content/public/test/content_browser_test_utils.h"
+#include "content/public/test/test_navigation_observer.h"
+#include "net/cookies/cookie_util.h"
+#include "net/dns/mock_host_resolver.h"
+#include "net/test/embedded_test_server/controllable_http_response.h"
+#include "net/test/embedded_test_server/default_handlers.h"
+#include "third_party/blink/public/mojom/loader/resource_load_info.mojom.h"
+
+constexpr char kHostA[] = "a.test";
+constexpr char kHostB[] = "b.test";
+
+// Handles Favicon requests so they don't produce a 404 and augment http error
+// metrics during a test
+std::unique_ptr<net::test_server::HttpResponse> HandleFaviconRequest(
+    const net::test_server::HttpRequest& request) {
+  if (request.relative_url != "/favicon.ico") {
+    return nullptr;
+  }
+  // The response doesn't have to be a valid favicon to avoid logging a
+  // console error. Any 200 response will do.
+  return std::make_unique<net::test_server::BasicHttpResponse>();
+}
+
+class HTTPErrProcBrowserTest : public InProcessBrowserTest {
+ public:
+  HTTPErrProcBrowserTest()
+      : https_server_(net::EmbeddedTestServer::TYPE_HTTPS) {}
+
+  net::EmbeddedTestServer* https_server() { return &https_server_; }
+
+  void SetUpOnMainThread() override {
+    host_resolver()->AddRule("*", "127.0.0.1");
+    https_server_.SetSSLConfig(net::EmbeddedTestServer::CERT_TEST_NAMES);
+    https_server_.AddDefaultHandlers(GetChromeTestDataDir());
+    https_server_.RegisterRequestHandler(
+        base::BindRepeating(&HandleFaviconRequest));
+    ASSERT_TRUE(https_server_.Start());
+  }
+
+  // Sets third party cookie blocking in settings
+  void SetThirdPartyCookieBlocking(bool enabled) {
+    browser()->profile()->GetPrefs()->SetInteger(
+        prefs::kCookieControlsMode,
+        static_cast<int>(
+            enabled ? content_settings::CookieControlsMode::kBlockThirdParty
+                    : content_settings::CookieControlsMode::kOff));
+    scoped_refptr<content_settings::CookieSettings> settings =
+        CookieSettingsFactory::GetForProfile(browser()->profile());
+    ASSERT_EQ(settings->ShouldBlockThirdPartyCookies(), enabled);
+  }
+
+  // Checks that the HTTP Error has been properly recorded in the metrics
+  void CheckServerErrBreakageMetrics(
+      ukm::TestAutoSetUkmRecorder& ukm_recorder,
+      size_t size,
+      size_t index,
+      bool blocked,
+      bool settings_blocked,
+      const base::Location& location = FROM_HERE) {
+    auto entries = ukm_recorder.GetEntries(
+        "ThirdPartyCookies.BreakageIndicator",
+        {"BreakageIndicatorType", "TPCBlocked", "TPCBlockedInSettings"});
+    EXPECT_EQ(entries.size(), size)
+        << "(expected at " << location.ToString() << ")";
+    EXPECT_EQ(
+        entries.at(index).metrics.at("BreakageIndicatorType"),
+        static_cast<int>(net::cookie_util::BreakageIndicatorType::HTTP_ERROR))
+        << "(expected at " << location.ToString() << ")";
+    EXPECT_EQ(entries.at(index).metrics.at("TPCBlocked"), blocked)
+        << "(expected at " << location.ToString() << ")";
+    EXPECT_EQ(entries.at(index).metrics.at("TPCBlockedInSettings"),
+              settings_blocked)
+        << "(expected at " << location.ToString() << ")";
+  }
+
+  // Navigates to a page with an iframe, then navigates the iframe to the given
+  // GURL. Can also set TPC blocking cookie.
+  void NavigateToURLAndIFrame(base::StringPiece host, const GURL iframe_url) {
+    ASSERT_TRUE(ui_test_utils::NavigateToURL(
+        browser(), https_server()->GetURL(host, "/iframe.html")));
+    ASSERT_TRUE(NavigateIframeToURL(
+        browser()->tab_strip_model()->GetActiveWebContents(), "test",
+        iframe_url));
+  }
+
+ private:
+  net::EmbeddedTestServer https_server_;
+};
+
+// Verify that Metric only registers HTTP errors
+IN_PROC_BROWSER_TEST_F(HTTPErrProcBrowserTest, NoErr) {
+  ukm::TestAutoSetUkmRecorder ukm_recorder;
+
+  SetThirdPartyCookieBlocking(false);
+  NavigateToURLAndIFrame(
+      /*host=*/kHostA,
+      /*iframe_url=*/https_server()->GetURL(kHostA, "/title1.html"));
+  EXPECT_EQ(ukm_recorder
+                .GetEntries("ThirdPartyCookies.BreakageIndicator",
+                            {"BreakageIndicatorType", "TPCBlocked",
+                             "TPCBlockedInSettings"})
+                .size(),
+            0u);
+}
+
+// Check that the ThirdPartyCookieBreakageIndicator UKM is sent on HTTP Error
+// without cookies blocked
+IN_PROC_BROWSER_TEST_F(HTTPErrProcBrowserTest, WithCookiesWithErr) {
+  ukm::TestAutoSetUkmRecorder ukm_recorder;
+
+  SetThirdPartyCookieBlocking(false);
+  NavigateToURLAndIFrame(
+      /*host=*/kHostA,
+      /*iframe_url=*/https_server()->GetURL(kHostB, "/page404.html"));
+  CheckServerErrBreakageMetrics(
+      /*ukm_recorder=*/ukm_recorder,
+      /*size=*/1,
+      /*index=*/0,
+      /*blocked=*/false,
+      /*settings_blocked=*/false);
+}
+
+// Check that the ThirdPartyCookieBreakageIndicator UKM is sent on HTTP Error
+// with cookies blocked in settings
+IN_PROC_BROWSER_TEST_F(HTTPErrProcBrowserTest, TPCBlockedInSettings4xxErr) {
+  ukm::TestAutoSetUkmRecorder ukm_recorder;
+
+  SetThirdPartyCookieBlocking(true);
+  NavigateToURLAndIFrame(
+      /*host=*/kHostA,
+      /*iframe_url=*/https_server()->GetURL(kHostB, "/page404.html"));
+  CheckServerErrBreakageMetrics(
+      /*ukm_recorder=*/ukm_recorder,
+      /*size=*/1,
+      /*index=*/0,
+      /*blocked=*/false,
+      /*settings_blocked=*/true);
+}
+
+// Check that the ThirdPartyCookieBreakageIndicator UKM works with image
+// subresources
+IN_PROC_BROWSER_TEST_F(HTTPErrProcBrowserTest, TPCBlockedImageErr) {
+  ukm::TestAutoSetUkmRecorder ukm_recorder;
+
+  SetThirdPartyCookieBlocking(true);
+  ASSERT_TRUE(ui_test_utils::NavigateToURL(
+      browser(), https_server()->GetURL(kHostA, "/no_real_image.html")));
+  CheckServerErrBreakageMetrics(
+      /*ukm_recorder=*/ukm_recorder,
+      /*size=*/1,
+      /*index=*/0,
+      /*blocked=*/false,
+      /*settings_blocked=*/true);
+}
+
+// Check that the ThirdPartyCookieBreakageIndicator UKM works with fetches
+IN_PROC_BROWSER_TEST_F(HTTPErrProcBrowserTest, TPCBlockedFetchErr) {
+  ukm::TestAutoSetUkmRecorder ukm_recorder;
+
+  SetThirdPartyCookieBlocking(true);
+  ASSERT_TRUE(ui_test_utils::NavigateToURL(
+      browser(), https_server()->GetURL(kHostA, "/no_real_fetch.html")));
+  CheckServerErrBreakageMetrics(
+      /*ukm_recorder=*/ukm_recorder,
+      /*size=*/1,
+      /*index=*/0,
+      /*blocked=*/false,
+      /*settings_blocked=*/true);
+}
+
+// Check that ThirdPartyCookieBreakageIndicator UKM works with subresource loads
+// within iframes
+IN_PROC_BROWSER_TEST_F(HTTPErrProcBrowserTest, TPCBlockedIframeErr) {
+  ukm::TestAutoSetUkmRecorder ukm_recorder;
+
+  SetThirdPartyCookieBlocking(true);
+  content::SetCookie(browser()->profile(),
+                     https_server()->GetURL(kHostB, "/no_real_image.html"),
+                     "thirdparty=1;SameSite=None;Secure");
+  NavigateToURLAndIFrame(
+      /*host=*/kHostA,
+      /*iframe_url=*/https_server()->GetURL(kHostB, "/no_real_image.html"));
+  CheckServerErrBreakageMetrics(
+      /*ukm_recorder=*/ukm_recorder,
+      /*size=*/1,
+      /*index=*/0,
+      /*blocked=*/true,
+      /*settings_blocked=*/true);
+}
+
+// Check that the ThirdPartyCookieBreakageIndicator UKM is sent on Server Error.
+IN_PROC_BROWSER_TEST_F(HTTPErrProcBrowserTest, TPCBlocked5xxErr) {
+  ukm::TestAutoSetUkmRecorder ukm_recorder;
+
+  SetThirdPartyCookieBlocking(true);
+  content::SetCookie(
+      browser()->profile(),
+      https_server()->GetURL(kHostB, "/echo-cookie-with-status?status=500"),
+      "thirdparty=1;SameSite=None;Secure");
+  NavigateToURLAndIFrame(
+      /*host=*/kHostA,
+      /*iframe_url=*/
+      https_server()->GetURL(kHostB, "/echo-cookie-with-status?status=500"));
+  CheckServerErrBreakageMetrics(
+      /*ukm_recorder=*/ukm_recorder,
+      /*size=*/1,
+      /*index=*/0,
+      /*blocked=*/true,
+      /*settings_blocked=*/true);
+}
+
+// Verify that ThirdPartyCookieBreakageIndicator UKM has correct value
+// when TPC are blocked in settings and by site
+IN_PROC_BROWSER_TEST_F(HTTPErrProcBrowserTest, TPCBlocked4xxErr) {
+  ukm::TestAutoSetUkmRecorder ukm_recorder;
+
+  SetThirdPartyCookieBlocking(true);
+  content::SetCookie(browser()->profile(),
+                     https_server()->GetURL(kHostB, "/not-real.html"),
+                     "thirdparty=1;SameSite=None;Secure");
+  NavigateToURLAndIFrame(
+      /*host=*/kHostA,
+      /*iframe_url=*/https_server()->GetURL(kHostB, "/not-real.html"));
+
+  CheckServerErrBreakageMetrics(
+      /*ukm_recorder=*/ukm_recorder,
+      /*size=*/1,
+      /*index=*/0,
+      /*blocked=*/true,
+      /*settings_blocked=*/true);
+}
+
+// Check that multiple entries are entered correctly
+IN_PROC_BROWSER_TEST_F(HTTPErrProcBrowserTest, MultiErrs) {
+  ukm::TestAutoSetUkmRecorder ukm_recorder;
+
+  SetThirdPartyCookieBlocking(true);
+  content::SetCookie(browser()->profile(),
+                     https_server()->GetURL(kHostB, "/page404.html"),
+                     "thirdparty=1;SameSite=None;Secure");
+  NavigateToURLAndIFrame(
+      /*host=*/kHostA,
+      /*iframe_url=*/https_server()->GetURL(kHostB, "/page404.html"));
+  CheckServerErrBreakageMetrics(
+      /*ukm_recorder=*/ukm_recorder,
+      /*size=*/1,
+      /*index=*/0,
+      /*blocked=*/true,
+      /*settings_blocked=*/true);
+
+  SetThirdPartyCookieBlocking(false);
+  NavigateToURLAndIFrame(
+      /*host=*/kHostA,
+      /*iframe_url=*/https_server()->GetURL(kHostA, "/not-real.html"));
+  CheckServerErrBreakageMetrics(
+      /*ukm_recorder=*/ukm_recorder,
+      /*size=*/2,
+      /*index=*/1,
+      /*blocked=*/false,
+      /*settings_blocked=*/false);
+}
diff --git a/chrome/browser/ui/tab_helpers.cc b/chrome/browser/ui/tab_helpers.cc
index 20d0576..31cdb67 100644
--- a/chrome/browser/ui/tab_helpers.cc
+++ b/chrome/browser/ui/tab_helpers.cc
@@ -83,6 +83,7 @@
 #include "chrome/browser/sync/sessions/sync_sessions_web_contents_router_factory.h"
 #include "chrome/browser/tab_contents/navigation_metrics_recorder.h"
 #include "chrome/browser/tpcd/heuristics/opener_heuristic_tab_helper.h"
+#include "chrome/browser/tpcd/http_error_observer/http_error_tab_helper.h"
 #include "chrome/browser/tpcd/metadata/devtools_observer.h"
 #include "chrome/browser/translate/chrome_translate_client.h"
 #include "chrome/browser/trusted_vault/trusted_vault_encryption_keys_tab_helper.h"
@@ -499,6 +500,7 @@
   StorageAccessAPITabHelper::CreateForWebContents(
       web_contents, StorageAccessAPIServiceFactory::GetForBrowserContext(
                         web_contents->GetBrowserContext()));
+  HttpErrorTabHelper::CreateForWebContents(web_contents);
   sync_sessions::SyncSessionsRouterTabHelper::CreateForWebContents(
       web_contents,
       sync_sessions::SyncSessionsWebContentsRouterFactory::GetForProfile(
diff --git a/chrome/test/BUILD.gn b/chrome/test/BUILD.gn
index 86fec30..35b4407 100644
--- a/chrome/test/BUILD.gn
+++ b/chrome/test/BUILD.gn
@@ -1638,6 +1638,7 @@
       "//chrome/browser/storage_access_api",
       "//chrome/browser/top_level_storage_access_api:permissions",
       "//chrome/browser/tpcd/heuristics:browser_tests",
+      "//chrome/browser/tpcd/http_error_observer:browser_tests",
       "//chrome/browser/ui/color:color_headers",
       "//chrome/browser/ui/side_panel:side_panel_enums",
       "//chrome/browser/ui/tabs:tab_enums",
diff --git a/chrome/test/data/no_real_fetch.html b/chrome/test/data/no_real_fetch.html
new file mode 100644
index 0000000..334da09
--- /dev/null
+++ b/chrome/test/data/no_real_fetch.html
@@ -0,0 +1,8 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>Test base with non-existent image file</title>
+<body>
+  <script>
+    fetch('notARealPage.html')
+  </script>
+</body>
\ No newline at end of file
diff --git a/chrome/test/data/no_real_image.html b/chrome/test/data/no_real_image.html
new file mode 100644
index 0000000..70dd3c8
--- /dev/null
+++ b/chrome/test/data/no_real_image.html
@@ -0,0 +1,7 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>Test base with non-existent image file</title>
+<body>
+  <h1>Hello have a look at this "picture"!</h1>
+  <img src="not_a_real_image.png">
+</body>
\ No newline at end of file
diff --git a/net/cookies/cookie_util.h b/net/cookies/cookie_util.h
index 8fc03dc..e2e9ea3e 100644
--- a/net/cookies/cookie_util.h
+++ b/net/cookies/cookie_util.h
@@ -56,7 +56,8 @@
 // at the end.
 enum class BreakageIndicatorType {
   USER_RELOAD = 0,
-  kMaxValue = USER_RELOAD,
+  HTTP_ERROR = 1,
+  kMaxValue = HTTP_ERROR,
 };
 // Helper to fire telemetry indicating if a given request for storage was
 // allowed or not by the provided |result|.
diff --git a/third_party/blink/public/mojom/loader/resource_load_info.mojom b/third_party/blink/public/mojom/loader/resource_load_info.mojom
index ce58a63..4eadb05 100644
--- a/third_party/blink/public/mojom/loader/resource_load_info.mojom
+++ b/third_party/blink/public/mojom/loader/resource_load_info.mojom
@@ -131,4 +131,7 @@
   // The list of redirects that led to this resource load. Empty if there were
   // no redirects.
   array<RedirectInfo> redirect_info_chain;
+
+  // The HTTP Status Code
+  int32 http_status_code;
 };
diff --git a/third_party/blink/renderer/platform/exported/resource_load_info_notifier_wrapper.cc b/third_party/blink/renderer/platform/exported/resource_load_info_notifier_wrapper.cc
index e584d9b..677b60c 100644
--- a/third_party/blink/renderer/platform/exported/resource_load_info_notifier_wrapper.cc
+++ b/third_party/blink/renderer/platform/exported/resource_load_info_notifier_wrapper.cc
@@ -125,6 +125,10 @@
       network_utils::AlwaysAccessNetwork(response_head->headers);
   resource_load_info_->network_info->remote_endpoint =
       response_head->remote_endpoint;
+  if (response_head->headers) {
+    resource_load_info_->http_status_code =
+        response_head->headers->response_code();
+  }
 
   if (task_runner_->BelongsToCurrentThread()) {
     if (weak_wrapper_resource_load_info_notifier_) {
diff --git a/tools/metrics/histograms/enums.xml b/tools/metrics/histograms/enums.xml
index a4ff7ba..2763e9c 100644
--- a/tools/metrics/histograms/enums.xml
+++ b/tools/metrics/histograms/enums.xml
@@ -13998,6 +13998,7 @@
 
 <enum name="BreakageIndicatorType">
   <int value="0" label="User Reload"/>
+  <int value="1" label="HTTP Error"/>
 </enum>
 
 <enum name="BreakoutBoxUsage">