Add debugging screen for SeedFiles.

Because we're moving the seed and other fields to the Seed Files, the
content will not be available from chrome://local-state for debugging.

This change adds the information stored in the Seed Files in the already
existing chrome://metrics-internals/#variations page.

To handle very large values, if the string is larger than 64 characters,
it will be trimmed and a button for copying the value into the clipboard
will be displayed.

The change is visible in the attachment in the bug
https://g-issues.chromium.org/issues/452071822

Bug: 452071822
Change-Id: I12887e615607323da58d16be4f2486b37e89118b
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/7100841
Reviewed-by: Alexei Svitkine <asvitkine@chromium.org>
Commit-Queue: Alexei Svitkine <asvitkine@chromium.org>
Cr-Commit-Position: refs/heads/main@{#1549170}
NOKEYCHECK=True
GitOrigin-RevId: 72f0a6bddb742f0cf2808b762f5dd503af1a2453
diff --git a/debug/app.html b/debug/app.html
index 8b1c4f7..c618c23 100644
--- a/debug/app.html
+++ b/debug/app.html
@@ -104,6 +104,16 @@
     <table>
       <tbody id="variations-summary-body"></tbody>
     </table>
+    <h2>Stored Latest Seed Info</h2>
+    <button id="fetch-stored-latest-seed-info" disabled>Refresh</button>
+    <table>
+      <tbody id="stored-latest-seed-info-body"></tbody>
+    </table>
+    <h2>Stored Safe Seed Info</h2>
+    <button id="fetch-stored-safe-seed-info" disabled>Refresh</button>
+    <table>
+      <tbody id="stored-safe-seed-info-body"></tbody>
+    </table>
   </div>
   <div slot="tab">Field Trials</div>
   <div slot="panel">
@@ -140,3 +150,12 @@
     <td><!-- Value --></td>
   </tr>
 </template>
+<template id="seed-info-row-template">
+  <tr>
+    <td><!-- Key --></td>
+    <td><!-- Value --></td>
+    <td><!-- Copy -->
+      <button>Copy</button>
+    </td>
+  </tr>
+</template>
\ No newline at end of file
diff --git a/debug/app.ts b/debug/app.ts
index eaf8e65..5271d6b 100644
--- a/debug/app.ts
+++ b/debug/app.ts
@@ -13,7 +13,7 @@
 import {loadTimeData} from 'chrome://resources/js/load_time_data.js';
 
 import {getTemplate} from './app.html.js';
-import type {KeyValue, Log, LogData, MetricsInternalsBrowserProxy} from './browser_proxy.js';
+import type {KeyValue, Log, LogData, MetricsInternalsBrowserProxy, SeedType} from './browser_proxy.js';
 import {MetricsInternalsBrowserProxyImpl} from './browser_proxy.js';
 import {getEventsPeekString, logEventToString, sizeToString, timestampToString, umaLogTypeToString} from './log_utils.js';
 
@@ -29,6 +29,12 @@
   events: [],
 };
 
+/**
+ * The maximum length of a value in the seed info table. Values longer than
+ * this will be truncated with an ellipsis.
+ */
+const VALUE_LENGTH_LIMIT = 64;
+
 export class MetricsInternalsAppElement extends CustomElement {
   static get is(): string {
     return 'metrics-internals-app';
@@ -73,6 +79,22 @@
     await this.updateVariationsSummary_();
     setInterval(() => this.updateVariationsSummary_(), 3000);
 
+    // Fetch stored latest and safe seeds info and set up a listener for the
+    // buttons for refreshing the data.
+    await this.updateStoredSeedInfo_('Latest');
+    const fetchStoredLatestSeedInfoButton =
+        this.getRequiredElement('#fetch-stored-latest-seed-info');
+    assert(fetchStoredLatestSeedInfoButton);
+    fetchStoredLatestSeedInfoButton.addEventListener(
+        'click', () => this.updateStoredSeedInfo_('Latest'));
+
+    await this.updateStoredSeedInfo_('Safe');
+    const fetchStoredSafeSeedInfoButton =
+        this.getRequiredElement('#fetch-stored-safe-seed-info');
+    assert(fetchStoredSafeSeedInfoButton);
+    fetchStoredSafeSeedInfoButton.addEventListener(
+        'click', () => this.updateStoredSeedInfo_('Safe'));
+
     // Fetch UMA summary data and set up a recurring timer.
     await this.updateUmaSummary_();
     setInterval(() => this.updateUmaSummary_(), 3000);
@@ -185,6 +207,41 @@
   }
 
   /**
+   * Fills the passed table element with the given seed info.
+   */
+  private updateSeedInfoTable_(tableBody: HTMLElement, summary: KeyValue[]):
+      void {
+    // Clear the table first.
+    tableBody.replaceChildren();
+
+    const template =
+        this.getRequiredElement<HTMLTemplateElement>('#seed-info-row-template');
+    for (const info of summary) {
+      const row = template.content.cloneNode(true) as HTMLElement;
+      const [key, value, copy] = row.querySelectorAll('td');
+
+      assert(key);
+      key.textContent = info.key;
+
+      assert(value);
+      if (info.value.length > VALUE_LENGTH_LIMIT) {
+        value.textContent = info.value.substring(0, VALUE_LENGTH_LIMIT) + '...';
+      } else {
+        value.textContent = info.value;
+      }
+
+      assert(copy);
+      const copyButton = copy.querySelector('button');
+      assert(copyButton);
+      copyButton.addEventListener('click', () => {
+        navigator.clipboard.writeText(info.value);
+      });
+
+      tableBody.appendChild(row);
+    }
+  }
+
+  /**
    * Fetches variations summary data and updates the view.
    */
   private async updateVariationsSummary_(): Promise<void> {
@@ -204,6 +261,23 @@
   }
 
   /**
+   * Fetches stored seed info and updates the view for the given seed type.
+   */
+  private async updateStoredSeedInfo_(seedType: SeedType): Promise<void> {
+    const seedTypeKey = seedType.toLowerCase();
+    const refreshButton =
+        this.getRequiredElement(`#fetch-stored-${seedTypeKey}-seed-info`);
+    assert(refreshButton);
+    refreshButton.setAttribute('disabled', '');
+    const storedSeedInfo: KeyValue[] =
+        await this.browserProxy_.fetchStoredSeedInfo(seedType);
+    const storedSeedInfoTableBody =
+        this.getRequiredElement(`#stored-${seedTypeKey}-seed-info-body`);
+    this.updateSeedInfoTable_(storedSeedInfoTableBody, storedSeedInfo);
+    refreshButton.removeAttribute('disabled');
+  }
+
+  /**
    * Fetches UMA summary data and updates the view.
    */
   private async updateUmaSummary_(): Promise<void> {
diff --git a/debug/browser_proxy.ts b/debug/browser_proxy.ts
index bc28c0d..bd928d9 100644
--- a/debug/browser_proxy.ts
+++ b/debug/browser_proxy.ts
@@ -109,6 +109,11 @@
   fetchVariationsSummary(): Promise<KeyValue[]>;
 
   /**
+   * Fetches the stored seed info.
+   */
+  fetchStoredSeedInfo(seedType: string): Promise<KeyValue[]>;
+
+  /**
    * Fetches a summary of UMA info.
    */
   fetchUmaSummary(): Promise<KeyValue[]>;
@@ -149,6 +154,8 @@
   restart(): Promise<void>;
 }
 
+export type SeedType = 'Latest'|'Safe';
+
 export class MetricsInternalsBrowserProxyImpl implements
     MetricsInternalsBrowserProxy {
   getUmaLogData(includeLogProtoData: boolean): Promise<string> {
@@ -159,6 +166,10 @@
     return sendWithPromise('fetchVariationsSummary');
   }
 
+  fetchStoredSeedInfo(seedType: SeedType): Promise<KeyValue[]> {
+    return sendWithPromise(`fetchStored${seedType}SeedInfo`);
+  }
+
   fetchUmaSummary(): Promise<KeyValue[]> {
     return sendWithPromise('fetchUmaSummary');
   }
diff --git a/debug/metrics_internals_utils.cc b/debug/metrics_internals_utils.cc
index b63ffa6..62fe238 100644
--- a/debug/metrics_internals_utils.cc
+++ b/debug/metrics_internals_utils.cc
@@ -7,6 +7,7 @@
 #include <string>
 #include <string_view>
 
+#include "base/base64.h"
 #include "base/strings/string_number_conversions.h"
 #include "build/branding_buildflags.h"
 #include "build/build_config.h"
@@ -125,6 +126,31 @@
   return dict;
 }
 
+void StoredSeedInfoToKeyValueList(
+    base::OnceCallback<void(base::ValueView)> done_callback,
+    variations::StoredSeedInfo stored_seed_info) {
+  base::Value::List list;
+  // We need to encode the seed data so it's valid utf-8.
+  list.Append(CreateKeyValueDict("Seed Data",
+                                 base::Base64Encode(stored_seed_info.data())));
+  list.Append(
+      CreateKeyValueDict("Seed Signature", stored_seed_info.signature()));
+  list.Append(CreateKeyValueDict(
+      "Seed Milestone", base::NumberToString(stored_seed_info.milestone())));
+  list.Append(CreateKeyValueDict(
+      "Seed Date", base::NumberToString(stored_seed_info.seed_date())));
+  list.Append(CreateKeyValueDict(
+      "Client Fetch Time",
+      base::NumberToString(stored_seed_info.client_fetch_time())));
+  list.Append(CreateKeyValueDict("Session Country Code",
+                                 stored_seed_info.session_country_code()));
+  list.Append(CreateKeyValueDict("Permanent Country Code",
+                                 stored_seed_info.permanent_country_code()));
+  list.Append(CreateKeyValueDict("Permanent Version",
+                                 stored_seed_info.permanent_version()));
+  std::move(done_callback).Run(std::move(list));
+}
+
 }  // namespace
 
 base::Value::List GetUmaSummary(MetricsService* metrics_service) {
@@ -175,4 +201,15 @@
   return list;
 }
 
+void GetStoredSeedInfo(
+    base::OnceCallback<void(base::ValueView)> done_callback,
+    metrics_services_manager::MetricsServicesManager* metrics_service_manager,
+    variations::VariationsSeedStore::SeedType seed_type) {
+  metrics_service_manager->GetVariationsService()
+      ->GetStoredSeedInfoForDebugging(
+          base::BindOnce(&StoredSeedInfoToKeyValueList,
+                         std::move(done_callback)),
+          seed_type);
+}
+
 }  // namespace metrics
diff --git a/debug/metrics_internals_utils.h b/debug/metrics_internals_utils.h
index 2759f22..e9270a6 100644
--- a/debug/metrics_internals_utils.h
+++ b/debug/metrics_internals_utils.h
@@ -8,6 +8,7 @@
 #include "base/values.h"
 #include "components/metrics/metrics_service.h"
 #include "components/metrics_services_manager/metrics_services_manager.h"
+#include "components/variations/variations_seed_store.h"
 
 namespace metrics {
 
@@ -16,6 +17,11 @@
 base::Value::List GetVariationsSummary(
     metrics_services_manager::MetricsServicesManager* metrics_service_manager);
 
+void GetStoredSeedInfo(
+    base::OnceCallback<void(base::ValueView)> done_callback,
+    metrics_services_manager::MetricsServicesManager* metrics_service_manager,
+    variations::VariationsSeedStore::SeedType seed_type);
+
 }  // namespace metrics
 
 #endif  // COMPONENTS_METRICS_DEBUG_METRICS_INTERNALS_UTILS_H_