Low chrome Memory Corruption 🔧 Commit mapped

Overview

Low
Severity
—
CVSS
No
Exploited ITW
Fixed
Fix Status
ImpactMemory corruption in Core
DescriptionMemory corruption in Core
ComponentCore
Sub-componentInput
Bug ClassMemory Corruption
Tracker553326035
Fix commit28b80f77efc0 (chromium/src) +237/-29
CISA KEVNot listed
CreditedGoogle
Disclosed2026-10-20

Changed Functions

FunctionChangeNotes
if
content/browser/renderer_host/render_frame_host_impl.cc
modified

Files Changed

  • content/browser/renderer_host/render_frame_host_impl.h
  • content/browser/renderer_host/unbounded_surface_window.cc
  • components/input/render_widget_host_view_input.cc
  • content/browser/renderer_host/render_frame_host_impl.cc
  • content/browser/site_per_process_hit_test_browsertest.cc
From 28b80f77efc01e6aa4faebce5b12f962e989b7e7 Mon Sep 17 00:00:00 2001
From: Aman Verma <[email protected]>
Date: Sun, 20 Sep 2026 08:13:34 -0700
Subject: [PATCH] [input] Act on clicked point for OOPIF context menu actions

OOPIF context menu actions (CopyImageAt, SaveImageAt, etc.) arrive in
root coordinates and used TransformRootPointToViewCoordSpace(), which
silently returns untransformed root coordinates on mapping failure. An
embedder could trigger this by hiding or moving the iframe while the
menu is open, redirecting the action to unintended elements inside the
cross-origin frame.

Record the clicked coordinates reported by the renderer when the menu is
shown in RenderFrameHostImpl and execute the action at that point.
Unmatched points are mapped via hit-test data and dropped on failure.

Also fix silent fallbacks in TransformPointToTargetCoordSpace() and
UnboundedSurfaceWindow::RouteWebPointerEvent() to drop on failure.

Fixed: 553326035
Change-Id: I8a97822d5861bfda8e966a41775da48618e8a0ab
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/8419501
Reviewed-by: Ken Buchanan <[email protected]>
Auto-Submit: Aman Verma <[email protected]>
Commit-Queue: Aman Verma <[email protected]>
Cr-Commit-Position: refs/heads/main@{#1701483}
---
diff --git a/components/input/render_widget_host_view_input.cc b/components/input/render_widget_host_view_input.cc
index 3e497ce..4ebd4783 100644
--- a/components/input/render_widget_host_view_input.cc
+++ b/components/input/render_widget_host_view_input.cc
@@ -352,8 +352,10 @@
   // HitTestQuery requires |point_in_pixels| to be in the coordinate space of
   // the root surface. See crbug.com/41460959 for context.
   gfx::Transform transform_root_to_original;
-  query->GetTransformToTarget(original_view->GetFrameSinkId(),
-                              &transform_root_to_original);
+  if (!query->GetTransformToTarget(original_view->GetFrameSinkId(),
+                                   &transform_root_to_original)) {
+    return false;
+  }
   const std::optional<gfx::PointF> point_in_pixels =
       transform_root_to_original.InverseMapPoint(
           gfx::ConvertPointToPixels(point, device_scale_factor));
diff --git a/content/browser/renderer_host/render_frame_host_impl.cc b/content/browser/renderer_host/render_frame_host_impl.cc
index 895a8475..e8f9247 100644
--- a/content/browser/renderer_host/render_frame_host_impl.cc
+++ b/content/browser/renderer_host/render_frame_host_impl.cc
@@ -352,6 +352,7 @@
 #include "ui/base/window_open_disposition.h"
 #include "ui/display/screen.h"
 #include "ui/events/event_constants.h"
+#include "ui/gfx/geometry/point_conversions.h"
 #include "ui/gfx/geometry/rect_conversions.h"
 #include "url/gurl.h"
 #include "url/origin.h"
@@ -3695,17 +3696,42 @@
   GetMojomFrameInRenderer()->SetWantErrorMessageStackTrace();
 }
 
+std::optional<gfx::Point>
+RenderFrameHostImpl::TransformRootPointForContextMenuAction(
+    const gfx::Point& root_point) {
+  // Actions from this frame's menu act on the clicked point directly, so
+  // moving or hiding the frame cannot steer the action.
+  if (context_menu_location_ &&
+      context_menu_location_->root_point == root_point) {
+    return context_menu_location_->local_point;
+  }
+
+  RenderWidgetHostViewBase* view = GetView();
+  if (!view) {
+    return std::nullopt;
+  }
+  RenderWidgetHostViewBase* root_view = view->GetRootView();
+  gfx::PointF point_in_view;
+  if (!root_view || !root_view->TransformPointToCoordSpaceForView(
+                        gfx::PointF(root_point), view, &point_in_view)) {
+    return std::nullopt;
+  }
+  return gfx::ToRoundedPoint(point_in_view);
+}
+
 void RenderFrameHostImpl::ExecuteMediaPlayerActionAtLocation(
     const gfx::Point& location,
     const blink::mojom::MediaPlayerAction& action) {
+  std::optional<gfx::Point> point_in_view =
+      TransformRootPointForContextMenuAction(location);
+  if (!point_in_view) {
+    return;
+  }
   auto media_player_action = blink::mojom::MediaPlayerAction::New();
   media_player_action->type = action.type;
   media_player_action->enable = action.enable;
-  gfx::PointF point_in_view = GetView()->TransformRootPointToViewCoordSpace(
-      gfx::PointF(location.x(), location.y()));
   GetAssociatedLocalFrame()->MediaPlayerActionAt(
-      gfx::Point(point_in_view.x(), point_in_view.y()),
-      std::move(media_player_action));
+      *point_in_view, std::move(media_player_action));
 }
 
 void RenderFrameHostImpl::RequestVideoFrameAtWithBoundsHint(
@@ -3713,11 +3739,14 @@
     const gfx::Size& max_size,
     int max_area,
     base::OnceCallback<void(const SkBitmap&, const gfx::Rect&)> callback) {
-  gfx::PointF point_in_view = GetView()->TransformRootPointToViewCoordSpace(
-      gfx::PointF(location.x(), location.y()));
+  std::optional<gfx::Point> point_in_view =
+      TransformRootPointForContextMenuAction(location);
+  if (!point_in_view) {
+    std::move(callback).Run(SkBitmap(), gfx::Rect());
+    return;
+  }
   GetAssociatedLocalFrame()->RequestVideoFrameAtWithBoundsHint(
-      gfx::Point(point_in_view.x(), point_in_view.y()), max_size, max_area,
-      std::move(callback));
+      *point_in_view, max_size, max_area, std::move(callback));
 }
 
 bool RenderFrameHostImpl::CreateNetworkServiceDefaultFactory(
@@ -3960,23 +3989,30 @@
 void RenderFrameHostImpl::ExecutePluginActionAtLocalLocation(
     const gfx::Point& location,
     blink::mojom::PluginActionType plugin_action) {
-  gfx::Point local_location = gfx::ToFlooredPoint(
-      GetView()->TransformRootPointToViewCoordSpace(gfx::PointF(location)));
-  GetAssociatedLocalFrame()->PluginActionAt(local_location, plugin_action);
+  std::optional<gfx::Point> point_in_view =
+      TransformRootPointForContextMenuAction(location);
+  if (!point_in_view) {
+    return;
+  }
+  GetAssociatedLocalFrame()->PluginActionAt(*point_in_view, plugin_action);
 }
 
 void RenderFrameHostImpl::CopyImageAt(int x, int y) {
-  gfx::PointF point_in_view =
-      GetView()->TransformRootPointToViewCoordSpace(gfx::PointF(x, y));
-  GetAssociatedLocalFrame()->CopyImageAt(
-      gfx::Point(point_in_view.x(), point_in_view.y()));
+  std::optional<gfx::Point> point_in_view =
+      TransformRootPointForContextMenuAction(gfx::Point(x, y));
+  if (!point_in_view) {
+    return;
+  }
+  GetAssociatedLocalFrame()->CopyImageAt(*point_in_view);
 }
 
 void RenderFrameHostImpl::SaveImageAt(int x, int y) {
-  gfx::PointF point_in_view =
-      GetView()->TransformRootPointToViewCoordSpace(gfx::PointF(x, y));
-  GetAssociatedLocalFrame()->SaveImageAt(
-      gfx::Point(point_in_view.x(), point_in_view.y()));
+  std::optional<gfx::Point> point_in_view =
+      TransformRootPointForContextMenuAction(gfx::Point(x, y));
+  if (!point_in_view) {
+    return;
+  }
+  GetAssociatedLocalFrame()->SaveImageAt(*point_in_view);
 }
 
 RenderViewHost* RenderFrameHostImpl::GetRenderViewHost() const {
@@ -10079,12 +10115,22 @@
 
   // It is necessary to transform the coordinates to account for nested
   // RenderWidgetHosts, such as with out-of-process iframes.
-  gfx::Point original_point(validated_params.x, validated_params.y);
-  gfx::Point transformed_point =
-      static_cast<RenderWidgetHostViewBase*>(GetView())
-          ->TransformPointToRootCoordSpace(original_point);
-  validated_params.x = transformed_point.x();
-  validated_params.y = transformed_point.y();
+  RenderWidgetHostViewBase* view = GetView();
+  if (!view) {
+    return;
+  }
+  RenderWidgetHostViewBase* root_view = view->GetRootView();
+  const gfx::Point original_point(validated_params.x, validated_params.y);
+  gfx::PointF transformed_point_f;
+  // If hit-test data is unavailable, show the menu at the renderer's point
+  // rather than suppressing it, since most menu items do not use position.
+  if (root_view &&
+      view->TransformPointToCoordSpaceForView(
+          gfx::PointF(original_point), root_view, &transformed_point_f)) {
+    const gfx::Point root_point = gfx::ToRoundedPoint(transformed_point_f);
+    validated_params.x = root_point.x();
+    validated_params.y = root_point.y();
+  }
 
   if (validated_params.selection_start_offset < 0) {
     bad_message::ReceivedBadMessage(
@@ -10095,6 +10141,11 @@
   validated_params.form_field_dom_node_id =
       GlobalDOMNodeId{GetWeakDocumentPtr(), validated_params.field_renderer_id};
 
+  // Cache coordinates so actions from this menu act where the user clicked.
+  context_menu_location_ = {
+      .root_point = gfx::Point(validated_params.x, validated_params.y),
+      .local_point = original_point};
+
   delegate_->ShowContextMenu(*this, std::move(context_menu_client),
                              validated_params);
 }
diff --git a/content/browser/renderer_host/render_frame_host_impl.h b/content/browser/renderer_host/render_frame_host_impl.h
index 18b3066..0e567b8 100644
--- a/content/browser/renderer_host/render_frame_host_impl.h
+++ b/content/browser/renderer_host/render_frame_host_impl.h
@@ -207,6 +207,7 @@
 #include "ui/accessibility/platform/ax_unique_id.h"
 #include "ui/base/clipboard/clipboard_metadata.h"
 #include "ui/base/page_transition_types.h"
+#include "ui/gfx/geometry/point.h"
 #include "ui/gfx/geometry/rect.h"
 #include "url/gurl.h"
 
@@ -3487,6 +3488,11 @@
   void OnSaveImageFromDataURL(const std::string& url_str);
   bool ValidateOutermostMainFrameWindowChange(std::string_view method_name);
 
+  // Maps `root_point` into this frame's view coordinates for actions like
+  // CopyImageAt(). Returns nullopt if the point cannot be mapped.
+  std::optional<gfx::Point> TransformRootPointForContextMenuAction(
+      const gfx::Point& root_point);
+
   // Computes the IsolationInfo for both navigations and subresources.
   //
   // For navigations, |frame_origin| is the origin being navigated to. For
@@ -5533,6 +5539,18 @@
   // True if this rfh was created via a window creation with user activation.
   bool opener_had_user_gesture_ = false;
 
+  // Caches context menu coordinates so actions from that menu (e.g.
+  // CopyImageAt) act on `local_point` even if the frame moves or hides.
+  struct ContextMenuLocation {
+    // Menu location in root coordinates (or `local_point` if unmapped).
+    gfx::Point root_point;
+    // Click location in this frame's view coordinates.
+    gfx::Point local_point;
+  };
+
+  // Cached location from the last ShowContextMenu().
+  std::optional<ContextMenuLocation> context_menu_location_;
+
   // WeakPtrFactories are the last members, to ensure they are destroyed before
   // all other fields of `this`.
   base::WeakPtrFactory<RenderFrameHostImpl> weak_ptr_factory_{this};
diff --git a/content/browser/renderer_host/unbounded_surface_window.cc b/content/browser/renderer_host/unbounded_surface_window.cc
index a520a2b8..4d78d8b 100644
--- a/content/browser/renderer_host/unbounded_surface_window.cc
+++ b/content/browser/renderer_host/unbounded_surface_window.cc
@@ -43,8 +43,11 @@
   gfx::Point root_origin = root_view->GetViewBounds().origin();
   gfx::PointF root_point =
       screen_point - gfx::Vector2dF(root_origin.x(), root_origin.y());
-  gfx::PointF parent_local_point =
-      parent_view->TransformRootPointToViewCoordSpace(root_point);
+  gfx::PointF parent_local_point;
+  if (!root_view->TransformPointToCoordSpaceForView(root_point, parent_view,
+                                                    &parent_local_point)) {
+    return;
+  }
   web_event.SetPositionInWidget(parent_local_point.x(), parent_local_point.y());
 
   if constexpr (std::is_same_v<EventType, blink::WebMouseEvent>) {
diff --git a/content/browser/site_per_process_hit_test_browsertest.cc b/content/browser/site_per_process_hit_test_browsertest.cc
index f0e60c6..5ce47145 100644
--- a/content/browser/site_per_process_hit_test_browsertest.cc
+++ b/content/browser/site_per_process_hit_test_browsertest.cc
@@ -50,6 +50,7 @@
 #include "mojo/public/cpp/test_support/test_utils.h"
 #include "third_party/blink/public/common/switches.h"
 #include "third_party/blink/public/common/web_preferences/web_preferences.h"
+#include "third_party/blink/public/mojom/frame/media_player_action.mojom.h"
 #include "third_party/blink/public/mojom/frame/user_activation_update_types.mojom.h"
 #include "third_party/blink/public/mojom/input/input_handler.mojom-test-utils.h"
 #include "third_party/blink/public/mojom/widget/platform_widget.mojom-test-utils.h"
@@ -7589,4 +7590,137 @@
 }
 #endif  // USE_AURA
 
+namespace {
+
+void LoopVideoAt(RenderFrameHostImpl* rfh, const gfx::Point& root_point) {
+  rfh->ExecuteMediaPlayerActionAtLocation(
+      root_point, blink::mojom::MediaPlayerAction(
+                      blink::mojom::MediaPlayerActionType::kLoop, true));
+}
+
+// Returns the ID of the looping video in `rfh`, or empty string if none.
+// Reading this flushes pending LocalFrame messages since EvalJs shares the
+// pipe.
+std::string LoopingVideoIn(RenderFrameHostImpl* rfh) {
+  return EvalJs(rfh,
+                "[...document.querySelectorAll('video')]"
+                "    .filter(v => v.loop).map(v => v.id).join()")
+      .ExtractString();
+}
+
+void StopLoopingVideosIn(RenderFrameHostImpl* rfh) {
+  ASSERT_TRUE(ExecJs(
+      rfh, "document.querySelectorAll('video').forEach(v => v.loop = false)"));
+}
+
+}  // namespace
+
+// Ensures context menu actions in an OOPIF target the original clicked point,
+// even if the frame is moved or hidden while the menu is open
+// (crbug.com/553326035).
+IN_PROC_BROWSER_TEST_F(SitePerProcessHitTestBrowserTest,
+                       ContextMenuActionUsesPointClickedInOopif) {
+  GURL main_url(embedded_test_server()->GetURL(
+      "a.com", "/frame_tree/page_with_positioned_frame.html"));
+  ASSERT_TRUE(NavigateToURL(shell(), main_url));
+
+  FrameTreeNode* root = web_contents()->GetPrimaryFrameTree().root();
+  ASSERT_EQ(1U, root->child_count());
+  RenderFrameHostImpl* child_rfh = root->child_at(0)->current_frame_host();
+  WaitForHitTestData(child_rfh);
+
+  // Two videos allow verifying whether actions hit the original target
+  // (#corner) or the shifted position (#rest).
+  ASSERT_TRUE(ExecJs(child_rfh, R"(
+      document.body.style.margin = '0';
+      document.body.innerHTML = `
+        <style>
+          video { position: absolute; }
+          #corner { left: 0; top: 0; width: 20px; height: 20px; }
+          #rest { left: 20px; top: 20px; width: 80px; height: 80px; }
+        </style>
+        <video id="corner"></video>
+        <video id="rest"></video>
+      `;
+  )"));
+
+  RenderWidgetHostViewBase* root_view = static_cast<RenderWidgetHostViewBase*>(
+      root->current_frame_host()->GetRenderWidgetHost()->GetView());
+  RenderWidgetHostViewBase* child_view = static_cast<RenderWidgetHostViewBase*>(
+      child_rfh->GetRenderWidgetHost()->GetView());
+  ASSERT_TRUE(root_view);
+  ASSERT_TRUE(child_view);
+
+  // Keeps the shell from opening a real context menu window.
+  ContextMenuObserverDelegate context_menu_delegate;
+  web_contents()->SetDelegate(&context_menu_delegate);
+
+  constexpr gfx::Point kLocalPoint(10, 10);
+  gfx::PointF root_point_f;
+  ASSERT_TRUE(child_view->TransformPointToCoordSpaceForView(
+      gfx::PointF(kLocalPoint), root_view, &root_point_f));
+  const gfx::Point root_point = gfx::ToRoundedPoint(root_point_f);
+  ASSERT_NE(kLocalPoint, root_point);
+
+  blink::UntrustworthyContextMenuParams params;
+  params.x = kLocalPoint.x();
+  params.y = kLocalPoint.y();
+  child_rfh->ShowContextMenu(mojo::NullAssociatedRemote(), params);
+  EXPECT_EQ(root_point, gfx::Point(context_menu_delegate.getParams().x,
+                                   context_menu_delegate.getParams().y));
+
+  LoopVideoAt(child_rfh, root_point);
+  EXPECT_EQ("corner", LoopingVideoIn(child_rfh));
+  StopLoopingVideosIn(child_rfh);
+
+  // Move the frame so `root_point` maps to #rest; action still hits #corner.
+  {
+    HitTestRegionObserver observer(root_view->GetRootFrameSinkId());
+    observer.WaitForHitTestData();
+    ASSERT_TRUE(ExecJs(web_contents(),
+                       "const f = document.querySelector('iframe');"
+                       "f.style.left = '25px';"
+                       "f.style.top = '25px';"));
+    MainThreadFrameObserver frame_observer(
+        root->current_frame_host()->GetRenderWidgetHost());
+    frame_observer.Wait();
+    observer.WaitForHitTestDataChange();
+
+    gfx::PointF moved_local;
+    ASSERT_TRUE(root_view->TransformPointToCoordSpaceForView(
+        gfx::PointF(root_point), child_view, &moved_local));
+    ASSERT_NE(kLocalPoint, gfx::ToRoundedPoint(moved_local));
+
+    LoopVideoAt(child_rfh, root_point);
+    EXPECT_EQ("corner", LoopingVideoIn(child_rfh));
+    StopLoopingVideosIn(child_rfh);
+  }
+
+  // Hide the frame; action still hits #corner.
+  {
+    HitTestRegionObserver observer(root_view->GetRootFrameSinkId());
+    observer.WaitForHitTestData();
+    ASSERT_TRUE(ExecJs(
+        web_contents(),
+        "document.querySelector('iframe').style.visibility = 'hidden';"));
+    MainThreadFrameObserver frame_observer(
+        root->current_frame_host()->GetRenderWidgetHost());
+    frame_observer.Wait();
+    observer.WaitForHitTestDataChange();
+
... (file diff truncated)
Loading diff…

Regression Test / PoC

shipped with the fix
diff --git a/content/browser/site_per_process_hit_test_browsertest.cc b/content/browser/site_per_process_hit_test_browsertest.cc
index f0e60c6..5ce47145 100644
--- a/content/browser/site_per_process_hit_test_browsertest.cc
+++ b/content/browser/site_per_process_hit_test_browsertest.cc
@@ -50,6 +50,7 @@
 #include "mojo/public/cpp/test_support/test_utils.h"
 #include "third_party/blink/public/common/switches.h"
 #include "third_party/blink/public/common/web_preferences/web_preferences.h"
+#include "third_party/blink/public/mojom/frame/media_player_action.mojom.h"
 #include "third_party/blink/public/mojom/frame/user_activation_update_types.mojom.h"
 #include "third_party/blink/public/mojom/input/input_handler.mojom-test-utils.h"
 #include "third_party/blink/public/mojom/widget/platform_widget.mojom-test-utils.h"
@@ -7589,4 +7590,137 @@
 }
 #endif  // USE_AURA
 
+namespace {
+
+void LoopVideoAt(RenderFrameHostImpl* rfh, const gfx::Point& root_point) {
+  rfh->ExecuteMediaPlayerActionAtLocation(
+      root_point, blink::mojom::MediaPlayerAction(
+                      blink::mojom::MediaPlayerActionType::kLoop, true));
+}
+
+// Returns the ID of the looping video in `rfh`, or empty string if none.
+// Reading this flushes pending LocalFrame messages since EvalJs shares the
+// pipe.
+std::string LoopingVideoIn(RenderFrameHostImpl* rfh) {
+  return EvalJs(rfh,
+                "[...document.querySelectorAll('video')]"
+                "    .filter(v => v.loop).map(v => v.id).join()")
+      .ExtractString();
+}
+
+void StopLoopingVideosIn(RenderFrameHostImpl* rfh) {
+  ASSERT_TRUE(ExecJs(
+      rfh, "document.querySelectorAll('video').forEach(v => v.loop = false)"));
+}
+
+}  // namespace
+
+// Ensures context menu actions in an OOPIF target the original clicked point,
+// even if the frame is moved or hidden while the menu is open
+// (crbug.com/553326035).
+IN_PROC_BROWSER_TEST_F(SitePerProcessHitTestBrowserTest,
+                       ContextMenuActionUsesPointClickedInOopif) {
+  GURL main_url(embedded_test_server()->GetURL(
+      "a.com", "/frame_tree/page_with_positioned_frame.html"));
+  ASSERT_TRUE(NavigateToURL(shell(), main_url));
+
+  FrameTreeNode* root = web_contents()->GetPrimaryFrameTree().root();
+  ASSERT_EQ(1U, root->child_count());
+  RenderFrameHostImpl* child_rfh = root->child_at(0)->current_frame_host();
+  WaitForHitTestData(child_rfh);
+
+  // Two videos allow verifying whether actions hit the original target
+  // (#corner) or the shifted position (#rest).
+  ASSERT_TRUE(ExecJs(child_rfh, R"(
+      document.body.style.margin = '0';
+      document.body.innerHTML = `
+        <style>
+          video { position: absolute; }
+          #corner { left: 0; top: 0; width: 20px; height: 20px; }
+          #rest { left: 20px; top: 20px; width: 80px; height: 80px; }
+        </style>
+        <video id="corner"></video>
+        <video id="rest"></video>
+      `;
+  )"));
+
+  RenderWidgetHostViewBase* root_view = static_cast<RenderWidgetHostViewBase*>(
+      root->current_frame_host()->GetRenderWidgetHost()->GetView());
+  RenderWidgetHostViewBase* child_view = static_cast<RenderWidgetHostViewBase*>(
+      child_rfh->GetRenderWidgetHost()->GetView());
+  ASSERT_TRUE(root_view);
+  ASSERT_TRUE(child_view);
+
+  // Keeps the shell from opening a real context menu window.
+  ContextMenuObserverDelegate context_menu_delegate;
+  web_contents()->SetDelegate(&context_menu_delegate);
+
+  constexpr gfx::Point kLocalPoint(10, 10);
+  gfx::PointF root_point_f;
+  ASSERT_TRUE(child_view->TransformPointToCoordSpaceForView(
+      gfx::PointF(kLocalPoint), root_view, &root_point_f));
+  const gfx::Point root_point = gfx::ToRoundedPoint(root_point_f);
+  ASSERT_NE(kLocalPoint, root_point);
+
+  blink::UntrustworthyContextMenuParams params;
+  params.x = kLocalPoint.x();
+  params.y = kLocalPoint.y();
+  child_rfh->ShowContextMenu(mojo::NullAssociatedRemote(), params);
+  EXPECT_EQ(root_point, gfx::Point(context_menu_delegate.getParams().x,
+                                   context_menu_delegate.getParams().y));
+
+  LoopVideoAt(child_rfh, root_point);
+  EXPECT_EQ("corner", LoopingVideoIn(child_rfh));
+  StopLoopingVideosIn(child_rfh);
+
+  // Move the frame so `root_point` maps to #rest; action still hits #corner.
+  {
+    HitTestRegionObserver observer(root_view->GetRootFrameSinkId());
+    observer.WaitForHitTestData();
+    ASSERT_TRUE(ExecJs(web_contents(),
+                       "const f = document.querySelector('iframe');"
+                       "f.style.left = '25px';"
+                       "f.style.top = '25px';"));
+    MainThreadFrameObserver frame_observer(
+        root->current_frame_host()->GetRenderWidgetHost());
+    frame_observer.Wait();
+    observer.WaitForHitTestDataChange();
+
+    gfx::PointF moved_local;
+    ASSERT_TRUE(root_view->TransformPointToCoordSpaceForView(
+        gfx::PointF(root_point), child_view, &moved_local));
+    ASSERT_NE(kLocalPoint, gfx::ToRoundedPoint(moved_local));
+
+    LoopVideoAt(child_rfh, root_point);
+    EXPECT_EQ("corner", LoopingVideoIn(child_rfh));
+    StopLoopingVideosIn(child_rfh);
+  }
+
+  // Hide the frame; action still hits #corner.
+  {
+    HitTestRegionObserver observer(root_view->GetRootFrameSinkId());
+    observer.WaitForHitTestData();
+    ASSERT_TRUE(ExecJs(
+        web_contents(),
+        "document.querySelector('iframe').style.visibility = 'hidden';"));
+    MainThreadFrameObserver frame_observer(
+        root->current_frame_host()->GetRenderWidgetHost());
+    frame_observer.Wait();
+    observer.WaitForHitTestDataChange();
+
+    gfx::PointF unused;
+    ASSERT_FALSE(root_view->TransformPointToCoordSpaceForView(
+        gfx::PointF(root_point), child_view, &unused));
+
+    LoopVideoAt(child_rfh, root_point);
+    EXPECT_EQ("corner", LoopingVideoIn(child_rfh));
+    StopLoopingVideosIn(child_rfh);
+
+    // Points not from this menu cannot be mapped for a hidden frame and are
+    // dropped.
+    LoopVideoAt(child_rfh, root_point + gfx::Vector2d(1, 1));
+    EXPECT_EQ("", LoopingVideoIn(child_rfh));
+  }
+}
+
 }  // namespace content
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.