High chrome Memory Corruption 🔧 Commit mapped

Overview

High
Severity
—
CVSS
No
Exploited ITW
Fixed
Fix Status
ImpactMemory corruption in Paint
DescriptionMemory corruption in Paint
ComponentPaint
Sub-componentHiC
Bug ClassMemory Corruption
Tracker572598342
Fix commit2ec1ba2211a0 (chromium/src) +124/-33
CISA KEVNot listed
CreditedArni Hardarson (Neonix Security)
Disclosed2026-10-20

Changed Functions

FunctionChangeNotes
for
third_party/blink/renderer/core/dom/element.cc
modified
TEST_P
third_party/blink/renderer/core/html/canvas/html_canvas_element_test.cc
modified
GCOnFocusChangeObserver
third_party/blink/renderer/core/html/canvas/html_canvas_element_test.cc
modified

Files Changed

  • third_party/blink/renderer/core/html/html_install_element.h
  • third_party/blink/renderer/core/dom/element.h
  • third_party/blink/renderer/core/html/forms/html_select_element.cc
  • third_party/blink/renderer/core/html/media/html_video_element.cc
  • third_party/blink/renderer/core/dom/element.cc
  • third_party/blink/renderer/core/html/forms/html_select_element.h
  • third_party/blink/renderer/core/html/canvas/html_canvas_element_test.cc
  • third_party/blink/renderer/core/html/media/html_video_element.h
  • third_party/blink/renderer/core/html/forms/html_input_element.cc
  • third_party/blink/renderer/core/html/forms/html_text_area_element.cc
  • third_party/blink/renderer/core/html/html_frame_owner_element.h
  • third_party/blink/renderer/core/html/html_frame_owner_element.cc
  • third_party/blink/renderer/core/html/forms/html_input_element.h
  • third_party/blink/renderer/core/html/html_install_element.cc
  • third_party/blink/renderer/core/html/forms/html_text_area_element.h
From 2ec1ba2211a0c11af30296fc755a83d5bf7a7684 Mon Sep 17 00:00:00 2001
From: Stefan Zager <[email protected]>
Date: Tue, 13 Oct 2026 19:33:19 -0700
Subject: [PATCH] [HiC] Invalidate iframe contents correctly on top layer removal

This adds the `CanvasSubtreePropagation` enum to allow forcibly
invalidating an entire subtree as necessary. A heavy hammer, but should
probably be the default fix for under-invalidation bugs unless and until
we can figure out a more fine-grained fix.

Fixed: 572598342
Change-Id: Iceb8dd3f0001d8afc46377ff374f75426549fc6b
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/8560060
Commit-Queue: Stefan Zager <[email protected]>
Reviewed-by: Joey Arhar <[email protected]>
Cr-Commit-Position: refs/heads/main@{#1718252}
---
diff --git a/third_party/blink/renderer/core/dom/element.cc b/third_party/blink/renderer/core/dom/element.cc
index 7eb579c..4dde3c9b 100644
--- a/third_party/blink/renderer/core/dom/element.cc
+++ b/third_party/blink/renderer/core/dom/element.cc
@@ -4513,8 +4513,11 @@
 }
 #endif
 
-void Element::SetIsInCanvasSubtree(bool value) {
-  if (value == IsInCanvasSubtree()) {
+void Element::SetIsInCanvasSubtree(
+    bool value,
+    CanvasSubtreePropagation propagation_behavior) {
+  if (propagation_behavior == CanvasSubtreePropagation::kOnlyIfChanged &&
+      value == IsInCanvasSubtree()) {
 #if DCHECK_IS_ON()
     if (!GetDocument().IsSlotAssignmentRecalcForbidden()) {
       VerifySubtreeIsInCanvas(value);
@@ -4525,7 +4528,7 @@
 
   SetElementFlag(ElementFlags::kIsInCanvasSubtree, value);
   // Note that this call leads to HTMLFrameOwnerElement updating iframes.
-  DidChangeIsInCanvasSubtree();
+  DidChangeIsInCanvasSubtree(propagation_behavior);
   if (!value && IsA<HTMLCanvasElement>(*this)) {
     // Nested canvas element must reset the value to true.
     value = true;
@@ -4540,7 +4543,7 @@
   //    this visits active fallback content).
   if (ShadowRoot* shadow_root = GetShadowRoot()) {
     for (Element& child : ElementTraversal::ChildrenOf(*shadow_root)) {
-      child.SetIsInCanvasSubtree(value);
+      child.SetIsInCanvasSubtree(value, propagation_behavior);
     }
   } else if (auto* slot = ToHTMLSlotElementIfSupportsAssignmentOrNull(*this)) {
     const HeapVector<Member<Node>> assigned_nodes =
@@ -4548,22 +4551,22 @@
     for (Node* node : assigned_nodes) {
       if (auto* child = DynamicTo<Element>(node);
           child && child->AssignedSlotWithoutRecalc() == slot) {
-        child->SetIsInCanvasSubtree(value);
+        child->SetIsInCanvasSubtree(value, propagation_behavior);
       }
     }
     // Fallback content
     for (Element& child : ElementTraversal::ChildrenOf(*this)) {
-      child.SetIsInCanvasSubtree(value &&
-                                 slot->AssignedNodesNoRecalc().empty());
+      child.SetIsInCanvasSubtree(value && slot->AssignedNodesNoRecalc().empty(),
+                                 propagation_behavior);
     }
   } else {
     for (Element& child : ElementTraversal::ChildrenOf(*this)) {
-      child.SetIsInCanvasSubtree(value);
+      child.SetIsInCanvasSubtree(value, propagation_behavior);
     }
   }
   if (const NodeRareData* rare_data = RareData()) {
     for (PseudoElement* pseudo_element : rare_data->GetPseudoElements()) {
-      pseudo_element->SetIsInCanvasSubtree(value);
+      pseudo_element->SetIsInCanvasSubtree(value, propagation_behavior);
     }
   }
 }
@@ -4578,7 +4581,7 @@
   return false;
 }
 
-void Element::DidChangeIsInCanvasSubtree() {
+void Element::DidChangeIsInCanvasSubtree(CanvasSubtreePropagation) {
   if (auto* layout_object = GetLayoutObject()) {
     layout_object->SetNeedsPaintPropertyUpdate();
     layout_object->SetSubtreeShouldDoFullPaintInvalidation();
@@ -12087,7 +12090,10 @@
     // A popover inside a canvas subtree does not render with privacy
     // protections when in the top layer. So treat transitioning between top
     // layer states as equivalent to moving in or out of the canvas subtree.
-    DidChangeIsInCanvasSubtree();
+    // Moving into or out of the top layer does not by itself imply a change in
+    // IsInCanvasSubtree state, so kAlways is used to force invalidation.
+    SetIsInCanvasSubtree(IsInCanvasSubtree(),
+                         CanvasSubtreePropagation::kAlways);
   }
 
   bool user_action_stops_at_top_layer =
diff --git a/third_party/blink/renderer/core/dom/element.h b/third_party/blink/renderer/core/dom/element.h
index 3f61c16d..813a0bdbd 100644
--- a/third_party/blink/renderer/core/dom/element.h
+++ b/third_party/blink/renderer/core/dom/element.h
@@ -1170,8 +1170,16 @@
     SetElementFlag(ElementFlags::kStyleAffectedByEmpty);
   }
 
+  enum class CanvasSubtreePropagation { kOnlyIfChanged, kAlways };
+
   // Recursively sets the IsInCanvasSubtree bit for the element and its subtree.
-  void SetIsInCanvasSubtree(bool value);
+  // If propagation_behavior is kOnlyIfChanged, the recursion stops when it
+  // reaches a Node where `value` is the same as the Node's current state. If
+  // the behavior is kAlways, the recursion forcibly continues all the way down
+  // the tree regardless of the previous value.
+  void SetIsInCanvasSubtree(bool value,
+                            CanvasSubtreePropagation propagation_behavior =
+                                CanvasSubtreePropagation::kOnlyIfChanged);
   // True if this element has a flat-tree DOM ancestor <canvas> (including
   // across local frame boundaries).
   // Warning: This is a pure DOM check; it is true regardless of
@@ -1183,8 +1191,10 @@
   }
   // Like `IsInCanvasSubtree()`, but includes <canvas> elements.
   bool IsCanvasOrInCanvasSubtree() const;
-  // Called when `IsInCanvasSubtree()` changes.
-  virtual void DidChangeIsInCanvasSubtree();
+  // Called when `IsInCanvasSubtree()` changes, or when content should be
+  // invalidated *as if* `IsInCanvasSubtree()` changed (e.g., when showing
+  // or hiding a popover).
+  virtual void DidChangeIsInCanvasSubtree(CanvasSubtreePropagation);
 #if DCHECK_IS_ON()
   void VerifySubtreeIsInCanvas(bool value);
 #endif
diff --git a/third_party/blink/renderer/core/html/forms/html_input_element.cc b/third_party/blink/renderer/core/html/forms/html_input_element.cc
index 1b57117e..7bf0187 100644
--- a/third_party/blink/renderer/core/html/forms/html_input_element.cc
+++ b/third_party/blink/renderer/core/html/forms/html_input_element.cc
@@ -1351,8 +1351,9 @@
   input_type_view_->UpdateView();
 }
 
-void HTMLInputElement::DidChangeIsInCanvasSubtree() {
-  TextControlElement::DidChangeIsInCanvasSubtree();
+void HTMLInputElement::DidChangeIsInCanvasSubtree(
+    CanvasSubtreePropagation propagation_behavior) {
+  TextControlElement::DidChangeIsInCanvasSubtree(propagation_behavior);
   if (IsInCanvasSubtree()) {
     // Hide suggested values when under canvas, to prevent leaking this
     // information to javascript.
diff --git a/third_party/blink/renderer/core/html/forms/html_input_element.h b/third_party/blink/renderer/core/html/forms/html_input_element.h
index 80db469..401f172 100644
--- a/third_party/blink/renderer/core/html/forms/html_input_element.h
+++ b/third_party/blink/renderer/core/html/forms/html_input_element.h
@@ -545,7 +545,7 @@
 
   void MaybeReportPiiMetrics();
 
-  void DidChangeIsInCanvasSubtree() final;
+  void DidChangeIsInCanvasSubtree(CanvasSubtreePropagation) final;
 
   AtomicString name_;
   // The value string in |value| value mode.
diff --git a/third_party/blink/renderer/core/html/forms/html_select_element.cc b/third_party/blink/renderer/core/html/forms/html_select_element.cc
index 0d924ab8..ee76b80a4 100644
--- a/third_party/blink/renderer/core/html/forms/html_select_element.cc
+++ b/third_party/blink/renderer/core/html/forms/html_select_element.cc
@@ -904,8 +904,10 @@
   return -1;
 }
 
-void HTMLSelectElement::DidChangeIsInCanvasSubtree() {
-  HTMLFormControlElementWithState::DidChangeIsInCanvasSubtree();
+void HTMLSelectElement::DidChangeIsInCanvasSubtree(
+    CanvasSubtreePropagation propagation_behavior) {
+  HTMLFormControlElementWithState::DidChangeIsInCanvasSubtree(
+      propagation_behavior);
   if (IsInCanvasSubtree()) {
     // Hide suggested values when under canvas, to prevent leaking this
     // information to javascript.
diff --git a/third_party/blink/renderer/core/html/forms/html_select_element.h b/third_party/blink/renderer/core/html/forms/html_select_element.h
index 9330525..cd3f45d 100644
--- a/third_party/blink/renderer/core/html/forms/html_select_element.h
+++ b/third_party/blink/renderer/core/html/forms/html_select_element.h
@@ -540,7 +540,7 @@
   // Helper to update the select descendants' mutation observer.
   void UpdateMutationObserver();
 
-  void DidChangeIsInCanvasSubtree() final;
+  void DidChangeIsInCanvasSubtree(CanvasSubtreePropagation) final;
 
   // last_on_change_option_ is the currently selected option. It provides faster
   // access to the currently selected option than iterating through each option
diff --git a/third_party/blink/renderer/core/html/forms/html_text_area_element.cc b/third_party/blink/renderer/core/html/forms/html_text_area_element.cc
index 3504f79..0c153d7 100644
--- a/third_party/blink/renderer/core/html/forms/html_text_area_element.cc
+++ b/third_party/blink/renderer/core/html/forms/html_text_area_element.cc
@@ -742,8 +742,9 @@
       StyleChangeReasonForTracing::Create(style_change_reason::kControlValue));
 }
 
-void HTMLTextAreaElement::DidChangeIsInCanvasSubtree() {
-  TextControlElement::DidChangeIsInCanvasSubtree();
+void HTMLTextAreaElement::DidChangeIsInCanvasSubtree(
+    CanvasSubtreePropagation propagation_behavior) {
+  TextControlElement::DidChangeIsInCanvasSubtree(propagation_behavior);
   if (IsInCanvasSubtree()) {
     // Hide suggested values when under canvas, to prevent leaking this
     // information to javascript.
diff --git a/third_party/blink/renderer/core/html/forms/html_text_area_element.h b/third_party/blink/renderer/core/html/forms/html_text_area_element.h
index c309eb5f..0752635 100644
--- a/third_party/blink/renderer/core/html/forms/html_text_area_element.h
+++ b/third_party/blink/renderer/core/html/forms/html_text_area_element.h
@@ -174,7 +174,7 @@
   bool TooLong(const String*, NeedsToCheckDirtyFlag) const;
   bool TooShort(const String*, NeedsToCheckDirtyFlag) const;
 
-  void DidChangeIsInCanvasSubtree() final;
+  void DidChangeIsInCanvasSubtree(CanvasSubtreePropagation) final;
 
   unsigned rows_;
   unsigned cols_;
diff --git a/third_party/blink/renderer/core/html/html_frame_owner_element.cc b/third_party/blink/renderer/core/html/html_frame_owner_element.cc
index 6d83a091..ad9fe20f 100644
--- a/third_party/blink/renderer/core/html/html_frame_owner_element.cc
+++ b/third_party/blink/renderer/core/html/html_frame_owner_element.cc
@@ -217,11 +217,12 @@
   return result;
 }
 
-void HTMLFrameOwnerElement::DidChangeIsInCanvasSubtree() {
-  HTMLElement::DidChangeIsInCanvasSubtree();
+void HTMLFrameOwnerElement::DidChangeIsInCanvasSubtree(
+    CanvasSubtreePropagation propagation_behavior) {
+  HTMLElement::DidChangeIsInCanvasSubtree(propagation_behavior);
   if (Document* inner_document = contentDocument()) {
     if (Element* root = inner_document->documentElement()) {
-      root->SetIsInCanvasSubtree(IsInCanvasSubtree());
+      root->SetIsInCanvasSubtree(IsInCanvasSubtree(), propagation_behavior);
       if (auto* layout_view = inner_document->GetLayoutView()) {
         layout_view->SetNeedsPaintPropertyUpdate();
         layout_view->SetSubtreeShouldDoFullPaintInvalidation();
diff --git a/third_party/blink/renderer/core/html/html_frame_owner_element.h b/third_party/blink/renderer/core/html/html_frame_owner_element.h
index 1b04e48..c89ea2c 100644
--- a/third_party/blink/renderer/core/html/html_frame_owner_element.h
+++ b/third_party/blink/renderer/core/html/html_frame_owner_element.h
@@ -93,7 +93,7 @@
   // Node overrides:
   Node::InsertionNotificationRequest InsertedInto(
       ContainerNode& insertion_point) override;
-  void DidChangeIsInCanvasSubtree() override;
+  void DidChangeIsInCanvasSubtree(CanvasSubtreePropagation) override;
   void RemovedFrom(ContainerNode& insertion_point) override;
   // Element overrides:
   void DidRecalcStyle(const StyleRecalcChange) override;
diff --git a/third_party/blink/renderer/core/html/html_install_element.cc b/third_party/blink/renderer/core/html/html_install_element.cc
index ae0ff83..1479b936 100644
--- a/third_party/blink/renderer/core/html/html_install_element.cc
+++ b/third_party/blink/renderer/core/html/html_install_element.cc
@@ -161,8 +161,9 @@
          HTMLElement::IsURLAttribute(attr);
 }
 
-void HTMLInstallElement::DidChangeIsInCanvasSubtree() {
-  HTMLCapabilityElementBase::DidChangeIsInCanvasSubtree();
+void HTMLInstallElement::DidChangeIsInCanvasSubtree(
+    CanvasSubtreePropagation propagation_behavior) {
+  HTMLCapabilityElementBase::DidChangeIsInCanvasSubtree(propagation_behavior);
   UpdateAppearance();
 }
 
diff --git a/third_party/blink/renderer/core/html/html_install_element.h b/third_party/blink/renderer/core/html/html_install_element.h
index 3063b33..dfd6270 100644
--- a/third_party/blink/renderer/core/html/html_install_element.h
+++ b/third_party/blink/renderer/core/html/html_install_element.h
@@ -58,7 +58,7 @@
 
   // HTMLElement:
   bool IsURLAttribute(const Attribute&) const override;
-  void DidChangeIsInCanvasSubtree() override;
+  void DidChangeIsInCanvasSubtree(CanvasSubtreePropagation) override;
 
   // HTMLCapabilityElementBase:
   void UpdateAppearance() override;
diff --git a/third_party/blink/renderer/core/html/media/html_video_element.cc b/third_party/blink/renderer/core/html/media/html_video_element.cc
index 07d37b8..13d5500 100644
--- a/third_party/blink/renderer/core/html/media/html_video_element.cc
+++ b/third_party/blink/renderer/core/html/media/html_video_element.cc
@@ -898,8 +898,9 @@
   return 0;
 }
 
-void HTMLVideoElement::DidChangeIsInCanvasSubtree() {
-  HTMLMediaElement::DidChangeIsInCanvasSubtree();
+void HTMLVideoElement::DidChangeIsInCanvasSubtree(
+    CanvasSubtreePropagation propagation_behavior) {
+  HTMLMediaElement::DidChangeIsInCanvasSubtree(propagation_behavior);
   if (IsInCanvasSubtree()) {
     UpdateLayoutObject();
     if (auto* wmp = GetWebMediaPlayer()) {
diff --git a/third_party/blink/renderer/core/html/media/html_video_element.h b/third_party/blink/renderer/core/html/media/html_video_element.h
index 9f5dd5d5..9163038 100644
--- a/third_party/blink/renderer/core/html/media/html_video_element.h
+++ b/third_party/blink/renderer/core/html/media/html_video_element.h
@@ -252,7 +252,7 @@
   void RequestVisibility(RequestVisibilityCallback request_visibility_cb) final;
 
   void DidMoveToNewDocument(Document& old_document) override;
-  void DidChangeIsInCanvasSubtree() override;
+  void DidChangeIsInCanvasSubtree(CanvasSubtreePropagation) override;
 
   void UpdatePictureInPictureAvailability();
   void UpdateVideoFrameAvailability() override;
diff --git a/third_party/blink/renderer/core/html/canvas/html_canvas_element_test.cc b/third_party/blink/renderer/core/html/canvas/html_canvas_element_test.cc
index c94d465..614d273b 100644
--- a/third_party/blink/renderer/core/html/canvas/html_canvas_element_test.cc
+++ b/third_party/blink/renderer/core/html/canvas/html_canvas_element_test.cc
@@ -829,6 +829,74 @@
   EXPECT_TRUE(host->GetPseudoElement(kPseudoIdAfter)->IsInCanvasSubtree());
 }
 
+TEST_P(HTMLCanvasElementTest, TopLayerRemovalInvalidatesSubtreeAcrossFrames) {
+  SetBodyInnerHTML(R"HTML(
+    <style>
+      [popover] {
+        display: block !important;
+      }
+    </style>
+    <canvas id="canvas" content="drawable">
+      <div id="drawable" drawable>
+        <div id="popover" popover="manual">
+          <div id="child"></div>
+          <iframe id="iframe"></iframe>
+        </div>
+      </div>
+    </canvas>
+  )HTML");
+  SetChildFrameHTML(R"HTML(
+    <div id="inner_target">Hello</div>
+  )HTML");
+  UpdateAllLifecyclePhasesForTest();
+
+  auto* popover =
+      To<HTMLElement>(GetDocument().getElementById(AtomicString("popover")));
+  auto* child = GetDocument().getElementById(AtomicString("child"));
+  auto* iframe = GetDocument().getElementById(AtomicString("iframe"));
+  auto* inner_target =
+      ChildDocument().getElementById(AtomicString("inner_target"));
+
+  popover->ShowPopoverInternal(nullptr, nullptr);
+  UpdateAllLifecyclePhasesForTest();
+  ASSERT_TRUE(popover->IsInTopLayer());
+
+  EXPECT_FALSE(
+      popover->GetLayoutObject()->SubtreeShouldDoFullPaintInvalidation());
+  EXPECT_FALSE(
+      child->GetLayoutObject()->SubtreeShouldDoFullPaintInvalidation());
+  EXPECT_FALSE(child->GetLayoutObject()->NeedsPaintPropertyUpdate());
+  EXPECT_FALSE(
+      iframe->GetLayoutObject()->SubtreeShouldDoFullPaintInvalidation());
+  EXPECT_FALSE(iframe->GetLayoutObject()->NeedsPaintPropertyUpdate());
+  EXPECT_FALSE(
+      ChildDocument().GetLayoutView()->SubtreeShouldDoFullPaintInvalidation());
+  EXPECT_FALSE(ChildDocument().GetLayoutView()->NeedsPaintPropertyUpdate());
+  EXPECT_FALSE(
+      inner_target->GetLayoutObject()->SubtreeShouldDoFullPaintInvalidation());
+  EXPECT_FALSE(inner_target->GetLayoutObject()->NeedsPaintPropertyUpdate());
+
+  popover->HidePopoverInternal(
+      /*invoker=*/nullptr, HidePopoverFocusBehavior::kNone,
+      HidePopoverTransitionBehavior::kNoEventsNoWaiting,
+      /*exception_state=*/nullptr);
+  GetDocument().UpdateStyleAndLayoutTree();
+  ASSERT_FALSE(popover->IsInTopLayer());
+
+  EXPECT_TRUE(popover->GetLayoutObject()->ShouldDoFullPaintInvalidation());
+  EXPECT_TRUE(popover->GetLayoutObject()->NeedsPaintPropertyUpdate());
+  EXPECT_TRUE(child->GetLayoutObject()->ShouldDoFullPaintInvalidation());
+  EXPECT_TRUE(child->GetLayoutObject()->NeedsPaintPropertyUpdate());
+  EXPECT_TRUE(iframe->GetLayoutObject()->ShouldDoFullPaintInvalidation());
+  EXPECT_TRUE(iframe->GetLayoutObject()->NeedsPaintPropertyUpdate());
+  EXPECT_TRUE(
+      ChildDocument().GetLayoutView()->SubtreeShouldDoFullPaintInvalidation());
+  EXPECT_TRUE(ChildDocument().GetLayoutView()->NeedsPaintPropertyUpdate());
+  EXPECT_TRUE(
+      inner_target->GetLayoutObject()->SubtreeShouldDoFullPaintInvalidation());
+  EXPECT_TRUE(inner_target->GetLayoutObject()->NeedsPaintPropertyUpdate());
+}
+
 TEST_P(HTMLCanvasElementTest, SlotAssignmentDuringSetIsInCanvasSubtree) {
   class GCOnFocusChangeObserver final
       : public GarbageCollected<GCOnFocusChangeObserver>,
Loading diff…

Regression Test / PoC

shipped with the fix
diff --git a/third_party/blink/renderer/core/html/canvas/html_canvas_element_test.cc b/third_party/blink/renderer/core/html/canvas/html_canvas_element_test.cc
index c94d465..614d273b 100644
--- a/third_party/blink/renderer/core/html/canvas/html_canvas_element_test.cc
+++ b/third_party/blink/renderer/core/html/canvas/html_canvas_element_test.cc
@@ -829,6 +829,74 @@
   EXPECT_TRUE(host->GetPseudoElement(kPseudoIdAfter)->IsInCanvasSubtree());
 }
 
+TEST_P(HTMLCanvasElementTest, TopLayerRemovalInvalidatesSubtreeAcrossFrames) {
+  SetBodyInnerHTML(R"HTML(
+    <style>
+      [popover] {
+        display: block !important;
+      }
+    </style>
+    <canvas id="canvas" content="drawable">
+      <div id="drawable" drawable>
+        <div id="popover" popover="manual">
+          <div id="child"></div>
+          <iframe id="iframe"></iframe>
+        </div>
+      </div>
+    </canvas>
+  )HTML");
+  SetChildFrameHTML(R"HTML(
+    <div id="inner_target">Hello</div>
+  )HTML");
+  UpdateAllLifecyclePhasesForTest();
+
+  auto* popover =
+      To<HTMLElement>(GetDocument().getElementById(AtomicString("popover")));
+  auto* child = GetDocument().getElementById(AtomicString("child"));
+  auto* iframe = GetDocument().getElementById(AtomicString("iframe"));
+  auto* inner_target =
+      ChildDocument().getElementById(AtomicString("inner_target"));
+
+  popover->ShowPopoverInternal(nullptr, nullptr);
+  UpdateAllLifecyclePhasesForTest();
+  ASSERT_TRUE(popover->IsInTopLayer());
+
+  EXPECT_FALSE(
+      popover->GetLayoutObject()->SubtreeShouldDoFullPaintInvalidation());
+  EXPECT_FALSE(
+      child->GetLayoutObject()->SubtreeShouldDoFullPaintInvalidation());
+  EXPECT_FALSE(child->GetLayoutObject()->NeedsPaintPropertyUpdate());
+  EXPECT_FALSE(
+      iframe->GetLayoutObject()->SubtreeShouldDoFullPaintInvalidation());
+  EXPECT_FALSE(iframe->GetLayoutObject()->NeedsPaintPropertyUpdate());
+  EXPECT_FALSE(
+      ChildDocument().GetLayoutView()->SubtreeShouldDoFullPaintInvalidation());
+  EXPECT_FALSE(ChildDocument().GetLayoutView()->NeedsPaintPropertyUpdate());
+  EXPECT_FALSE(
+      inner_target->GetLayoutObject()->SubtreeShouldDoFullPaintInvalidation());
+  EXPECT_FALSE(inner_target->GetLayoutObject()->NeedsPaintPropertyUpdate());
+
+  popover->HidePopoverInternal(
+      /*invoker=*/nullptr, HidePopoverFocusBehavior::kNone,
+      HidePopoverTransitionBehavior::kNoEventsNoWaiting,
+      /*exception_state=*/nullptr);
+  GetDocument().UpdateStyleAndLayoutTree();
+  ASSERT_FALSE(popover->IsInTopLayer());
+
+  EXPECT_TRUE(popover->GetLayoutObject()->ShouldDoFullPaintInvalidation());
+  EXPECT_TRUE(popover->GetLayoutObject()->NeedsPaintPropertyUpdate());
+  EXPECT_TRUE(child->GetLayoutObject()->ShouldDoFullPaintInvalidation());
+  EXPECT_TRUE(child->GetLayoutObject()->NeedsPaintPropertyUpdate());
+  EXPECT_TRUE(iframe->GetLayoutObject()->ShouldDoFullPaintInvalidation());
+  EXPECT_TRUE(iframe->GetLayoutObject()->NeedsPaintPropertyUpdate());
+  EXPECT_TRUE(
+      ChildDocument().GetLayoutView()->SubtreeShouldDoFullPaintInvalidation());
+  EXPECT_TRUE(ChildDocument().GetLayoutView()->NeedsPaintPropertyUpdate());
+  EXPECT_TRUE(
+      inner_target->GetLayoutObject()->SubtreeShouldDoFullPaintInvalidation());
+  EXPECT_TRUE(inner_target->GetLayoutObject()->NeedsPaintPropertyUpdate());
+}
+
 TEST_P(HTMLCanvasElementTest, SlotAssignmentDuringSetIsInCanvasSubtree) {
   class GCOnFocusChangeObserver final
       : public GarbageCollected<GCOnFocusChangeObserver>,
Loading diff…

Original Bug Report

The reporter's bug is still restricted on the tracker. Chrome de-restricts security bugs ~30–90 days after the fix ships; a later run will backfill it here.