WPT: Create function to test BFCache not restored reason (#38038)

Added a function `assertBFCacheNotRestoredReasonsIfApplicable` that
uses the `notRestoredReasons` API. Unlike the helper functions in
performance timing API folder, this one only checks the main frame's
reasons, which should be good (and concise) enough for most of the
BFCache feature WPTs.

If the API is not available, the function will return straightaway
instead report assertion failure.

This CL also modifies back-forward-cache-open-connection.window.js
as an example usage of the helper function.

Bug: 1408436
Change-Id: Ia6215ae88bc295b9c2d8c164aab78780ded3606c
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/4177970
Reviewed-by: Fergal Daly <fergal@chromium.org>
Reviewed-by: Yuzu Saijo <yuzus@chromium.org>
Commit-Queue: Mingyu Lei <leimy@chromium.org>
Cr-Commit-Position: refs/heads/main@{#1098432}

Co-authored-by: Mingyu Lei <leimy@chromium.org>
diff --git a/IndexedDB/back-forward-cache-open-connection.window.js b/IndexedDB/back-forward-cache-open-connection.window.js
index 397eada..10c8482 100644
--- a/IndexedDB/back-forward-cache-open-connection.window.js
+++ b/IndexedDB/back-forward-cache-open-connection.window.js
@@ -26,6 +26,9 @@
   // Create an IndexedDB database with higher version.
   await createIndexedDBForTesting(rc2, 'test_idb_2', 2);
   await rc2.historyBack();
-  // The previous page receiving versionchange event should be evicted.
-  await assert_not_bfcached(rc1);
+  // The previous page receiving versionchange event should be evicted with the
+  // correct reason.
+  // `kIgnoreEventAndEvict` will be reported as "Internal error".
+  // See `NotRestoredReasonToReportString()`.
+  await assert_not_bfcached(rc1, ['Internal error']);
 });
diff --git a/html/browsers/browsing-the-web/back-forward-cache/resources/rc-helper.js b/html/browsers/browsing-the-web/back-forward-cache/resources/rc-helper.js
index ef0da2d..7b9e83a 100644
--- a/html/browsers/browsing-the-web/back-forward-cache/resources/rc-helper.js
+++ b/html/browsers/browsing-the-web/back-forward-cache/resources/rc-helper.js
@@ -25,12 +25,62 @@
   assert_implements_optional(beforeBFCache == true, 'BFCache not supported.');
 }
 
-// If the value in window is undefined, this means that the page was reloaded,
-// i.e., the page was not restored from BFCache.
+// A helper function to assert that the page is not restored from BFCache by
+// checking whether the `beforeBFCache` value from `window` is undefined
+// due to page reload.
+// This function also takes an optional `notRestoredReasons` list which
+// indicates the set of expected reasons that make the page not restored.
+// If the reasons list is undefined, the check will be skipped. Otherwise
+// this check will use the `notRestoredReasons` API, to obtain the reasons
+// in a tree structure, and flatten the reasons before making the order-
+// insensitive comparison.
+// If the API is not available, the function will terminate instead of marking
+// the assertion failed.
 // Call `prepareForBFCache()` before navigating away to call this function.
-async function assert_not_bfcached(remoteContextHelper) {
+async function assert_not_bfcached(
+    remoteContextHelper, notRestoredReasons) {
   var beforeBFCache = await getBeforeBFCache(remoteContextHelper);
   assert_equals(beforeBFCache, undefined);
+
+  // The reason is optional, so skip the remaining test if the
+  // `notRestoredReasons` is not set.
+  if (notRestoredReasons === undefined) {
+    return;
+  }
+
+  let isFeatureEnabled = await remoteContextHelper.executeScript(() => {
+    return 'notRestoredReasons' in performance.getEntriesByType('navigation')[0];
+  });
+
+  // Return if the `notRestoredReasons` API is not available.
+  if (!isFeatureEnabled) {
+    return;
+  }
+
+  let result = await remoteContextHelper.executeScript(() => {
+    return performance.getEntriesByType('navigation')[0].notRestoredReasons;
+  });
+
+  let expectedNotRestoredReasonsSet = new Set(notRestoredReasons);
+  let notRestoredReasonsSet = new Set();
+
+  // Flatten the reasons from the main frame and all the child frames.
+  const collectReason = (node) => {
+    for (let reason of node.reasons) {
+      notRestoredReasonsSet.add(reason);
+    }
+    for (let child of node.children) {
+      collectReason(child);
+    }
+  }
+  collectReason(result);
+
+  assert_equals(notRestoredReasonsSet.length,
+      expectedNotRestoredReasonsSet.length);
+
+  for (let reason of expectedNotRestoredReasonsSet) {
+    assert_true(notRestoredReasonsSet.has(reason));
+  }
 }
 
 // A helper function that combines the steps of setting window property,