[A11y] UKM for non-trivial canvas fallbacks

UKM approval doc:
https://docs.google.com/document/d/1DgYoGRSS--kCJyf5BsG8FzlvKInBJDT8yukrTJOq72k/edit?pli=1&tab=t.0

Bug: 371512570
Change-Id: Ia51d0754d036a3258f7fb2185a23fd98b7ce43ff
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/5904351
Reviewed-by: Sun Yueru <yrsun@chromium.org>
Commit-Queue: Aaron Leventhal <aleventhal@chromium.org>
Cr-Commit-Position: refs/heads/main@{#1368967}
diff --git a/third_party/blink/renderer/modules/accessibility/BUILD.gn b/third_party/blink/renderer/modules/accessibility/BUILD.gn
index 01a34ce4..2e763c7 100644
--- a/third_party/blink/renderer/modules/accessibility/BUILD.gn
+++ b/third_party/blink/renderer/modules/accessibility/BUILD.gn
@@ -50,6 +50,7 @@
 
   deps = [
     "//build:chromeos_buildflags",
+    "//services/metrics/public/cpp:ukm_builders",
     "//third_party/blink/public/strings:strings_grit",
     "//third_party/blink/renderer/modules/media_controls:media_controls",
     "//third_party/blink/renderer/modules/permissions:permissions",
diff --git a/third_party/blink/renderer/modules/accessibility/DEPS b/third_party/blink/renderer/modules/accessibility/DEPS
index 26b977ea..39caa2e 100644
--- a/third_party/blink/renderer/modules/accessibility/DEPS
+++ b/third_party/blink/renderer/modules/accessibility/DEPS
@@ -1,6 +1,7 @@
 include_rules = [
     "-third_party/blink/renderer/modules",
     "+base/containers/fixed_flat_set.h",
+    "+services/metrics/public/cpp",
     "+third_party/blink/renderer/modules/accessibility",
     "+third_party/blink/renderer/modules/media_controls",
     "+third_party/blink/renderer/modules/modules_export.h",
diff --git a/third_party/blink/renderer/modules/accessibility/ax_object_cache_impl.cc b/third_party/blink/renderer/modules/accessibility/ax_object_cache_impl.cc
index 93cef4d..d608057 100644
--- a/third_party/blink/renderer/modules/accessibility/ax_object_cache_impl.cc
+++ b/third_party/blink/renderer/modules/accessibility/ax_object_cache_impl.cc
@@ -37,6 +37,8 @@
 #include "base/metrics/histogram_macros.h"
 #include "base/ranges/algorithm.h"
 #include "mojo/public/cpp/bindings/pending_remote.h"
+#include "services/metrics/public/cpp/ukm_builders.h"
+#include "services/metrics/public/cpp/ukm_recorder.h"
 #include "third_party/abseil-cpp/absl/cleanup/cleanup.h"
 #include "third_party/blink/public/mojom/render_accessibility.mojom-blink.h"
 #include "third_party/blink/public/platform/task_type.h"
@@ -5386,6 +5388,36 @@
   }
 }
 
+void AXObjectCacheImpl::MaybeSendCanvasHasNonTrivialFallbackUKM(
+    const AXObject* ax_canvas) {
+  if (!ax_canvas->ChildCountIncludingIgnored()) {
+    // Canvas does not have fallback.
+    return;
+  }
+
+  if (ax_canvas->ChildCountIncludingIgnored() == 1 &&
+      ui::IsText(ax_canvas->FirstChildIncludingIgnored()->RoleValue())) {
+    // Ignore a fallback if it's just a single piece of text, as we are
+    // looking for advanced uses of canvas fallbacks.
+    return;
+  }
+
+  HTMLCanvasElement* canvas = To<HTMLCanvasElement>(ax_canvas->GetNode());
+  if (!canvas->HasPlacedElements()) {
+    // If it has placed elements, then the descendents are not a fallback.
+    return;
+  }
+
+  has_emitted_canvas_fallback_ukm_ = true;  // Stop checking.
+
+  ukm::UkmRecorder* ukm_recorder = GetDocument().UkmRecorder();
+  DCHECK(ukm_recorder);
+  ukm::builders::Accessibility_CanvasHasNonTrivialFallback(
+      GetDocument().UkmSourceID())
+      .SetSeen(true)
+      .Record(ukm_recorder);
+}
+
 void AXObjectCacheImpl::GetUpdatesAndEventsForSerialization(
     std::vector<ui::AXTreeUpdate>& updates,
     std::vector<ui::AXEvent>& events,
@@ -5463,6 +5495,12 @@
       // node from changed_bounds_ids_ to avoid sending it in
       // SerializeLocationChanges() later.
       changed_bounds_ids_.erase(id);
+
+      // Record advanced uses of canvas fallbacks.
+      if (!has_emitted_canvas_fallback_ukm_ &&
+          node_data.role == ax::mojom::blink::Role::kCanvas) {
+        MaybeSendCanvasHasNonTrivialFallbackUKM(ObjectFromAXID(node_data.id));
+      }
     }
 
     DCHECK(already_serialized_ids.Contains(obj->AXObjectID()))
diff --git a/third_party/blink/renderer/modules/accessibility/ax_object_cache_impl.h b/third_party/blink/renderer/modules/accessibility/ax_object_cache_impl.h
index 661ab77..99157b0 100644
--- a/third_party/blink/renderer/modules/accessibility/ax_object_cache_impl.h
+++ b/third_party/blink/renderer/modules/accessibility/ax_object_cache_impl.h
@@ -1126,6 +1126,10 @@
   // `processing_deferred_events_` for more details.
   void NotifyParentChildrenChanged(AXObject* parent);
 
+  void MaybeSendCanvasHasNonTrivialFallbackUKM(const AXObject* canvas);
+
+  void IncrementGenerationalCacheId() { ++generational_cache_id_; }
+
   // Queued callbacks.
   TreeUpdateCallbackQueue tree_update_callback_queue_main_;
   TreeUpdateCallbackQueue tree_update_callback_queue_popup_;
@@ -1291,7 +1295,7 @@
   // Whether or not the load event was sent in a previous serialization.
   bool load_sent_ = false;
 
-  void IncrementGenerationalCacheId() { ++generational_cache_id_; }
+  bool has_emitted_canvas_fallback_ukm_ = false;
 
   // Used to determine if a previously computed attribute is from the same
   // serialization update.
diff --git a/tools/metrics/ukm/ukm.xml b/tools/metrics/ukm/ukm.xml
index 0850d4d..7fcd7591 100644
--- a/tools/metrics/ukm/ukm.xml
+++ b/tools/metrics/ukm/ukm.xml
@@ -260,6 +260,21 @@
   </metric>
 </event>
 
+<event name="Accessibility.CanvasHasNonTrivialFallback">
+  <owner>aleventhal@chromium.org</owner>
+  <owner>aleventhal@google.com</owner>
+  <summary>
+    Recorded once for any page that had a canvas element with non-trivial
+    fallback content beyond plain text. Only recorded if a11y is turned on (~10%
+    of users).
+  </summary>
+  <metric name="Seen" enum="Boolean">
+    <summary>
+      Only ever set to true, thus serving as a counter.
+    </summary>
+  </metric>
+</event>
+
 <event name="Accessibility.ImageDescriptions">
   <owner>dtseng@chromium.org</owner>
   <owner>nektar@chromium.org</owner>