diff --git a/third_party/blink/renderer/core/input/event_handler.cc b/third_party/blink/renderer/core/input/event_handler.cc
index e0b10bae013e6..a66422a990d66 100644
--- a/third_party/blink/renderer/core/input/event_handler.cc
+++ b/third_party/blink/renderer/core/input/event_handler.cc
@@ -1713,7 +1713,7 @@ bool EventHandler::BestClickableNodeForHitTestResult(
   // adjustment only takes into account DOM nodes so a touch over a scrollbar
   // will be adjusted towards nearby nodes. This leads to things like textarea
   // scrollbars being untouchable.
-  if (result.GetScrollbar()) {
+  if (result.GetScrollbar() || result.IsOverResizer()) {
     target_node = nullptr;
     return false;
   }
diff --git a/third_party/blink/renderer/core/input/pointer_event_manager.cc b/third_party/blink/renderer/core/input/pointer_event_manager.cc
index f4542867a5520..3471419f1a0ee 100644
--- a/third_party/blink/renderer/core/input/pointer_event_manager.cc
+++ b/third_party/blink/renderer/core/input/pointer_event_manager.cc
@@ -30,10 +30,12 @@
 #include "third_party/blink/renderer/core/page/chrome_client.h"
 #include "third_party/blink/renderer/core/page/page.h"
 #include "third_party/blink/renderer/core/page/pointer_lock_controller.h"
+#include "third_party/blink/renderer/core/paint/paint_layer.h"
 #include "third_party/blink/renderer/core/scroll/scrollbar.h"
 #include "third_party/blink/renderer/core/timing/dom_window_performance.h"
 #include "third_party/blink/renderer/core/timing/event_timing.h"
 #include "third_party/blink/renderer/core/timing/window_performance.h"
+#include "third_party/blink/renderer/platform/geometry/layout_size.h"
 #include "third_party/blink/renderer/platform/instrumentation/use_counter.h"
 #include "third_party/blink/renderer/platform/runtime_enabled_features.h"
 
@@ -105,6 +107,8 @@ void PointerEventManager::Clear() {
   pointer_capture_target_.clear();
   pending_pointer_capture_target_.clear();
   dispatching_pointer_id_ = 0;
+  resize_scrollable_area_.Clear();
+  offset_from_resize_corner_ = LayoutSize();
 }
 
 void PointerEventManager::Trace(Visitor* visitor) const {
@@ -116,6 +120,7 @@ void PointerEventManager::Trace(Visitor* visitor) const {
   visitor->Trace(mouse_event_manager_);
   visitor->Trace(gesture_manager_);
   visitor->Trace(captured_scrollbar_);
+  visitor->Trace(resize_scrollable_area_);
 }
 
 PointerEventManager::PointerEventBoundaryEventDispatcher::
@@ -667,6 +672,9 @@ WebInputEventResult PointerEventManager::HandlePointerEvent(
   if (HandleScrollbarTouchDrag(event, pointer_event_target.scrollbar))
     return WebInputEventResult::kHandledSuppressed;
 
+  if (HandleResizerDrag(pointer_event, pointer_event_target))
+    return WebInputEventResult::kHandledSuppressed;
+
   // Any finger lifting is a user gesture only when it wasn't associated with a
   // scroll.
   // https://docs.google.com/document/d/1oF1T3O7_E4t1PYHV6gyCwHxOi3ystm0eSL5xZu7nvOg/edit#
@@ -744,6 +752,58 @@ bool PointerEventManager::HandleScrollbarTouchDrag(const WebPointerEvent& event,
   return handled;
 }
 
+bool PointerEventManager::HandleResizerDrag(
+    const WebPointerEvent& event,
+    const event_handling_util::PointerEventTarget& pointer_event_target) {
+  switch (event.GetType()) {
+    case WebPointerEvent::Type::kPointerDown: {
+      Node* node = pointer_event_target.target_element;
+      if (!node || !node->GetLayoutObject() ||
+          !node->GetLayoutObject()->EnclosingLayer())
+        return false;
+
+      PaintLayer* layer = node->GetLayoutObject()->EnclosingLayer();
+      if (!layer->GetScrollableArea())
+        return false;
+
+      gfx::Point p = frame_->View()->ConvertFromRootFrame(
+          gfx::ToFlooredPoint(event.PositionInWidget()));
+
+      if (layer->GetScrollableArea()->IsAbsolutePointInResizeControl(
+              p, kResizerForTouch)) {
+        resize_scrollable_area_ = layer->GetScrollableArea();
+        resize_scrollable_area_->SetInResizeMode(true);
+        frame_->GetPage()->GetChromeClient().SetTouchAction(frame_,
+                                                            TouchAction::kNone);
+        offset_from_resize_corner_ =
+            LayoutSize(resize_scrollable_area_->OffsetFromResizeCorner(p));
+        return true;
+      }
+      break;
+    }
+    case WebInputEvent::Type::kPointerMove: {
+      if (resize_scrollable_area_ && resize_scrollable_area_->InResizeMode()) {
+        gfx::Point pos = gfx::ToRoundedPoint(event.PositionInWidget());
+        resize_scrollable_area_->Resize(pos, offset_from_resize_corner_);
+        return true;
+      }
+      break;
+    }
+    case WebInputEvent::Type::kPointerUp: {
+      if (resize_scrollable_area_ && resize_scrollable_area_->InResizeMode()) {
+        resize_scrollable_area_->SetInResizeMode(false);
+        resize_scrollable_area_.Clear();
+        offset_from_resize_corner_ = LayoutSize();
+        return true;
+      }
+      break;
+    }
+    default:
+      return false;
+  }
+  return false;
+}
+
 WebInputEventResult PointerEventManager::CreateAndDispatchPointerEvent(
     Element* target,
     const AtomicString& mouse_event_name,
diff --git a/third_party/blink/renderer/core/input/pointer_event_manager.h b/third_party/blink/renderer/core/input/pointer_event_manager.h
index 5b42f4dafe3ca..d60ff7635b81b 100644
--- a/third_party/blink/renderer/core/input/pointer_event_manager.h
+++ b/third_party/blink/renderer/core/input/pointer_event_manager.h
@@ -9,6 +9,7 @@
 #include "third_party/blink/renderer/core/input/boundary_event_dispatcher.h"
 #include "third_party/blink/renderer/core/input/touch_event_manager.h"
 #include "third_party/blink/renderer/core/page/touch_adjustment.h"
+#include "third_party/blink/renderer/core/paint/paint_layer_scrollable_area.h"
 #include "third_party/blink/renderer/platform/heap/collection_support/heap_hash_map.h"
 #include "third_party/blink/renderer/platform/wtf/deque.h"
 
@@ -254,11 +255,17 @@ class CORE_EXPORT PointerEventManager final
 
   bool HandleScrollbarTouchDrag(const WebPointerEvent&, Scrollbar*);
 
+  bool HandleResizerDrag(const WebPointerEvent&,
+                         const event_handling_util::PointerEventTarget&);
+
   // NOTE: If adding a new field to this class please ensure that it is
   // cleared in |PointerEventManager::clear()|.
 
   const Member<LocalFrame> frame_;
 
+  WeakMember<PaintLayerScrollableArea> resize_scrollable_area_;
+  LayoutSize offset_from_resize_corner_;
+
   // Prevents firing mousedown, mousemove & mouseup in-between a canceled
   // pointerdown and next pointerup/pointercancel.
   // See "PREVENT MOUSE EVENT flag" in the spec:
diff --git a/third_party/blink/renderer/core/layout/hit_test_result.cc b/third_party/blink/renderer/core/layout/hit_test_result.cc
index 72f6ecec00d9d..191aef5c014b3 100644
--- a/third_party/blink/renderer/core/layout/hit_test_result.cc
+++ b/third_party/blink/renderer/core/layout/hit_test_result.cc
@@ -81,6 +81,7 @@ HitTestResult::HitTestResult(const HitTestResult& other)
       inner_url_element_(other.URLElement()),
       scrollbar_(other.GetScrollbar()),
       is_over_embedded_content_view_(other.IsOverEmbeddedContentView()),
+      is_over_resizer_(other.is_over_resizer_),
       canvas_region_id_(other.CanvasRegionId()) {
   // Only copy the NodeSet in case of list hit test.
   list_based_test_result_ =
@@ -127,6 +128,7 @@ void HitTestResult::PopulateFromCachedResult(const HitTestResult& other) {
   is_over_embedded_content_view_ = other.IsOverEmbeddedContentView();
   cacheable_ = other.cacheable_;
   canvas_region_id_ = other.CanvasRegionId();
+  is_over_resizer_ = other.IsOverResizer();
 
   // Only copy the NodeSet in case of list hit test.
   list_based_test_result_ =
@@ -593,6 +595,7 @@ void HitTestResult::Append(const HitTestResult& other) {
     inner_url_element_ = other.URLElement();
     is_over_embedded_content_view_ = other.IsOverEmbeddedContentView();
     canvas_region_id_ = other.CanvasRegionId();
+    is_over_resizer_ = other.IsOverResizer();
   }
 
   if (other.list_based_test_result_) {
diff --git a/third_party/blink/renderer/core/layout/hit_test_result.h b/third_party/blink/renderer/core/layout/hit_test_result.h
index 37e272fea5b2a..87374f025cb9b 100644
--- a/third_party/blink/renderer/core/layout/hit_test_result.h
+++ b/third_party/blink/renderer/core/layout/hit_test_result.h
@@ -151,6 +151,10 @@ class CORE_EXPORT HitTestResult {
   void SetIsOverEmbeddedContentView(bool b) {
     is_over_embedded_content_view_ = b;
   }
+  void SetIsOverResizer(bool is_over_resizer) {
+    is_over_resizer_ = is_over_resizer;
+  }
+  bool IsOverResizer() const { return is_over_resizer_; }
 
   bool IsSelected(const HitTestLocation& location) const;
   String Title(TextDirection&) const;
@@ -238,6 +242,11 @@ class CORE_EXPORT HitTestResult {
   // Returns true if we are over a EmbeddedContentView (and not in the
   // border/padding area of a LayoutEmbeddedContent for example).
   bool is_over_embedded_content_view_;
+  // This is true if the location is over the bottom right of a resizable
+  // object, where resize controls are located. See
+  // PaintLayerScrollableArea::IsAbsolutePointInResizeControl for how that is
+  // tested.
+  bool is_over_resizer_ = false;
 
   mutable Member<NodeSet> list_based_test_result_;
   String canvas_region_id_;
diff --git a/third_party/blink/renderer/core/paint/paint_layer_scrollable_area.cc b/third_party/blink/renderer/core/paint/paint_layer_scrollable_area.cc
index 7a721ca821460..93616f7720616 100644
--- a/third_party/blink/renderer/core/paint/paint_layer_scrollable_area.cc
+++ b/third_party/blink/renderer/core/paint/paint_layer_scrollable_area.cc
@@ -1963,8 +1963,10 @@ bool PaintLayerScrollableArea::HitTestOverflowControls(
   gfx::Rect resize_control_rect;
   if (GetLayoutBox()->CanResize()) {
     resize_control_rect = ResizerCornerRect(kResizerForPointer);
-    if (resize_control_rect.Contains(local_point))
+    if (resize_control_rect.Contains(local_point)) {
+      result.SetIsOverResizer(true);
       return true;
+    }
   }
   int resize_control_size = max(resize_control_rect.height(), 0);
 
diff --git a/third_party/blink/renderer/platform/graphics/paint/paint_chunker.cc b/third_party/blink/renderer/platform/graphics/paint/paint_chunker.cc
index ea6d0a6185da9..e7f2d800b8f69 100644
--- a/third_party/blink/renderer/platform/graphics/paint/paint_chunker.cc
+++ b/third_party/blink/renderer/platform/graphics/paint/paint_chunker.cc
@@ -277,7 +277,8 @@ void PaintChunker::CreateScrollHitTestChunk(
   auto& hit_test_data = chunk.EnsureHitTestData();
   hit_test_data.scroll_translation = scroll_translation;
   hit_test_data.scroll_hit_test_rect = rect;
-  if (id.type == DisplayItem::Type::kScrollbarHitTest) {
+  if (id.type == DisplayItem::Type::kScrollbarHitTest ||
+      id.type == DisplayItem::Type::kResizerScrollHitTest) {
     hit_test_data.touch_action_rects.push_back(
         TouchActionRect{rect, TouchAction::kNone});
   }
diff --git a/third_party/blink/renderer/platform/widget/input/input_handler_proxy.cc b/third_party/blink/renderer/platform/widget/input/input_handler_proxy.cc
index 4a56dd36ae360..aa93ea1211446 100644
--- a/third_party/blink/renderer/platform/widget/input/input_handler_proxy.cc
+++ b/third_party/blink/renderer/platform/widget/input/input_handler_proxy.cc
@@ -1315,6 +1315,15 @@ InputHandlerProxy::EventDisposition InputHandlerProxy::HandleTouchStart(
     }
   }
 
+  // main_thread_touch_sequence_start_disposition_ will indicate that touchmoves
+  // in the sequence should be sent to the main thread if the touchstart event
+  // was blocking. We may change |result| from DROP_EVENT if there is a touchend
+  // listener so we need to update main_thread_touch_sequence_start_disposition_
+  // here (before the check for a touchend listener) so that we send touchmoves
+  // only IF we send the (blocking) touchstart.
+  if (!(result == DID_HANDLE || result == DROP_EVENT))
+    main_thread_touch_sequence_start_disposition_ = result;
+
   // If |result| is still DROP_EVENT look at the touch end handler as we may
   // not want to discard the entire touch sequence. Note this code is
   // explicitly after the assignment of the |touch_result_| in
@@ -1374,8 +1383,20 @@ InputHandlerProxy::EventDisposition InputHandlerProxy::HandleTouchMove(
       touch_event.touch_start_or_first_touch_move) {
     bool is_touching_scrolling_layer;
     cc::TouchAction allowed_touch_action = cc::TouchAction::kAuto;
-    EventDisposition result = HitTestTouchEvent(
-        touch_event, &is_touching_scrolling_layer, &allowed_touch_action);
+    EventDisposition result;
+    bool is_main_thread_touch_sequence_with_blocking_start =
+        main_thread_touch_sequence_start_disposition_.has_value() &&
+        main_thread_touch_sequence_start_disposition_.value() == DID_NOT_HANDLE;
+    // If the touchmove occurs in a touch sequence that's being forwarded to
+    // the main thread, we can avoid the hit test since we want to also forward
+    // touchmoves in the sequence to the main thread.
+    if (is_main_thread_touch_sequence_with_blocking_start) {
+      touch_result_ = main_thread_touch_sequence_start_disposition_.value();
+      result = touch_result_.value();
+    } else {
+      result = HitTestTouchEvent(touch_event, &is_touching_scrolling_layer,
+                                 &allowed_touch_action);
+    }
     TRACE_EVENT_INSTANT2(
         "input", "Allowed TouchAction", TRACE_EVENT_SCOPE_THREAD, "TouchAction",
         cc::TouchActionToString(allowed_touch_action), "disposition", result);
@@ -1400,6 +1421,10 @@ InputHandlerProxy::EventDisposition InputHandlerProxy::HandleTouchEnd(
   }
   if (touch_event.touches_length == 1)
     touch_result_.reset();
+
+  if (main_thread_touch_sequence_start_disposition_.has_value())
+    main_thread_touch_sequence_start_disposition_.reset();
+
   return DID_NOT_HANDLE;
 }
 
diff --git a/third_party/blink/renderer/platform/widget/input/input_handler_proxy.h b/third_party/blink/renderer/platform/widget/input/input_handler_proxy.h
index 5ef5bc4a39da1..c86ef2e30b88f 100644
--- a/third_party/blink/renderer/platform/widget/input/input_handler_proxy.h
+++ b/third_party/blink/renderer/platform/widget/input/input_handler_proxy.h
@@ -342,6 +342,8 @@ class PLATFORM_EXPORT InputHandlerProxy : public cc::InputHandlerClient,
   bool gesture_pinch_in_progress_ = false;
   bool in_inertial_scrolling_ = false;
   bool scroll_sequence_ignored_;
+  absl::optional<EventDisposition>
+      main_thread_touch_sequence_start_disposition_;
 
   // Used to animate rubber-band/bounce over-scroll effect.
   std::unique_ptr<ElasticOverscrollController> elastic_overscroll_controller_;
diff --git a/third_party/blink/web_tests/TestExpectations b/third_party/blink/web_tests/TestExpectations
index db35bbc8ee021..464b826010974 100644
--- a/third_party/blink/web_tests/TestExpectations
+++ b/third_party/blink/web_tests/TestExpectations
@@ -6753,7 +6753,7 @@ crbug.com/1311128 external/wpt/paint-timing/fcp-only/fcp-document-opacity-text.h
 
 # Scroll Unification known issues (go/su-web-tests) for enabling in test:
 crbug.com/1311431 [ Mac ] fast/scroll-behavior/overscroll-behavior.html [ Failure Pass ]
-crbug.com/1248581 fast/scrolling/resize-corner-tracking-touch.html [ Failure Pass ]
+crbug.com/1248581 fast/scrolling/resize-iframe-corner-tracking-touch.html [ Failure Pass ]
 crbug.com/1312036 virtual/hidpi/fast/scrolling/scrollbars/dsf-ready/mouse-interactions-dsf-2.html [ Failure Pass ]
 
 # These tests fail on SwiftShader FEMU due to a change in log/exp implementation.
diff --git a/third_party/blink/web_tests/fast/scrolling/resize-corner-tracking-touch.html b/third_party/blink/web_tests/fast/scrolling/resize-corner-tracking-touch.html
index 9cfd024d55065..73231c2ae34b5 100644
--- a/third_party/blink/web_tests/fast/scrolling/resize-corner-tracking-touch.html
+++ b/third_party/blink/web_tests/fast/scrolling/resize-corner-tracking-touch.html
@@ -24,8 +24,6 @@ we have enough elements to scroll the page</div>
 <div id="div" style="width: 150px; height: 100px;"></div>
 <textarea id="textarea1" style="width: 150px; height: 100px;"></textarea>
 <br>
-<iframe id="iframe1" src="resources/resize-corner-tracking-touch-iframe.html"
-        style="resize:both; width: 200px; height: 200px"></iframe>
 <script type="text/javascript">
   async function touchDrag(target, offset, delta) {
     const rect = target.getBoundingClientRect();
@@ -74,7 +72,6 @@ we have enough elements to scroll the page</div>
   async function setupTest(scrollTop = 50) {
     resetStyle('div', 'width: 150px; height: 100px;');
     resetStyle('textarea1', 'width: 150px; height: 100px;');
-    resetStyle('iframe1', 'resize:both; width: 200px; height: 200px;');
 
     // Scroll the page first to test that resize works with a scrolled page.
     document.scrollingElement.scrollTop = scrollTop;
@@ -133,40 +130,5 @@ we have enough elements to scroll the page</div>
       assert_no_resize(target, oldBounds);
     }, 'Touchpad scroll should not resize the textarea.');
 
-    promise_test(async () => {
-      await setupTest(/* scrollTop */ 150);
-
-      const iframe = document.getElementById("iframe1");
-      const oldBounds = iframe.getBoundingClientRect();
-
-      await touchDrag(iframe, {x: -6, y: -7}, {dx: -20, dy: -10});
-      assert_resize(iframe, oldBounds);
-    }, 'Touch drag inside the resizer area of iframe resizes the iframe.');
-
-    promise_test(async () => {
-      await setupTest(/* scrollTop */ 150);
-
-      const iframe = document.getElementById("iframe1");
-      const oldBounds = iframe.getBoundingClientRect();
-
-      // Do not require an exact hit on the resizer due to touch fuzzing.
-      await touchDrag(iframe, {x: -20, y: -20}, {dx: -20, dy: -10});
-      assert_resize(iframe, oldBounds);
-    }, 'Touch drag a little off the resizer area of an iframe will resize ' +
-       ' the iframe.');
-
-    promise_test(async () => {
-      await setupTest(/* scrollTop */ 150);
-
-      const iframe = document.getElementById("iframe1");
-      const rect = iframe.getBoundingClientRect();
-      const target = iframe.contentDocument.getElementById("textarea2");
-      const oldBounds = target.getBoundingClientRect();
-
-      await touchDrag(target, {x: rect.left - 6, y: rect.top - 7},
-                      {dx: 20, dy: 10});
-      assert_resize(target, oldBounds);
-    }, 'Touch drag inside the resizer area of a textarea inside an iframe ' +
-       'will resize the textarea.');
 };
 </script>
diff --git a/third_party/blink/web_tests/fast/scrolling/resize-iframe-corner-tracking-touch.html b/third_party/blink/web_tests/fast/scrolling/resize-iframe-corner-tracking-touch.html
new file mode 100644
index 0000000000000..0e9943d64a909
--- /dev/null
+++ b/third_party/blink/web_tests/fast/scrolling/resize-iframe-corner-tracking-touch.html
@@ -0,0 +1,107 @@
+<!DOCTYPE html>
+<script src='../../resources/gesture-util.js'></script>
+<script src="../../resources/testharness.js"></script>
+<script src="../../resources/testharnessreport.js"></script>
+<style>
+  iframe {
+    border: blue 2px solid;
+  }
+
+</style>
+
+<iframe id="iframe1" src="resources/resize-corner-tracking-touch-iframe.html"
+        style="resize:both; width: 200px; height: 200px"></iframe>
+
+<script type="text/javascript">
+  async function touchDrag(target, offset, delta) {
+    const rect = target.getBoundingClientRect();
+    const x0 = rect.right + offset.x;
+    const y0 = rect.bottom + offset.y;
+    drag = {
+      start_x: x0,
+      start_y: y0,
+      end_x: x0 + delta.dx,
+      end_y: y0 + delta.dy
+    };
+    await touchDragTo(drag);
+
+    return waitForCompositorCommit();
+  }
+
+  async function touchpadScroll(target, offset, delta, device) {
+    var rect = target.getBoundingClientRect();
+    const precise_scroll_delta = true;
+    await smoothScrollWithXY(-delta.dx, -delta.dy,
+      rect.right + offset.x, rect.bottom + offset.y,
+      GestureSourceType.TOUCHPAD_INPUT, SPEED_INSTANT,
+      precise_scroll_delta);
+    return waitForCompositorCommit();
+  }
+
+  function assert_resize(target, oldBounds) {
+    const newBounds = target.getBoundingClientRect();
+    assert_true(newBounds.width != oldBounds.width &&
+      newBounds.height != oldBounds.height,
+      'Element failed to resize');
+  }
+
+  function assert_no_resize(target, oldBounds) {
+    const newBounds = target.getBoundingClientRect();
+    assert_true(newBounds.width == oldBounds.width &&
+      newBounds.height == oldBounds.height,
+      'Element unexpectedly resized');
+  }
+
+  function resetStyle(element_id, style) {
+    const element = document.getElementById(element_id);
+    element.style = style;
+  }
+
+  async function setupTest(scrollTop = 50) {
+    resetStyle('iframe1', 'resize:both; width: 200px; height: 200px;');
+
+    // Scroll the page first to test that resize works with a scrolled page.
+    document.scrollingElement.scrollTop = scrollTop;
+
+    return waitForCompositorCommit();
+  }
+
+  window.onload = () => {
+    promise_test(async () => {
+      await setupTest(/* scrollTop */ 150);
+
+      const iframe = document.getElementById("iframe1");
+      const oldBounds = iframe.getBoundingClientRect();
+
+
+      await touchDrag(iframe, {x: -6, y: -7}, {dx: -20, dy: -10});
+      assert_resize(iframe, oldBounds);
+    }, 'Touch drag inside the resizer area of iframe resizes the iframe.');
+
+    promise_test(async () => {
+      await setupTest(/* scrollTop */ 150);
+
+      const iframe = document.getElementById("iframe1");
+      const oldBounds = iframe.getBoundingClientRect();
+
+      // Do not require an exact hit on the resizer due to touch fuzzing.
+      await touchDrag(iframe, {x: -20, y: -20}, {dx: -20, dy: -10});
+      assert_resize(iframe, oldBounds);
+    }, 'Touch drag a little off the resizer area of an iframe will resize ' +
+    ' the iframe.');
+
+    promise_test(async () => {
+      await setupTest(/* scrollTop */ 150);
+
+      const iframe = document.getElementById("iframe1");
+      const rect = iframe.getBoundingClientRect();
+      const target = iframe.contentDocument.getElementById("textarea2");
+      const oldBounds = target.getBoundingClientRect();
+
+      await touchDrag(target, {x: rect.left - 6, y: rect.top - 7},
+        {dx: 20, dy: 10});
+      assert_resize(target, oldBounds);
+    }, 'Touch drag inside the resizer area of a textarea inside an iframe ' +
+    'will resize the textarea.');
+  };
+</script>