Engine 1.20.2 cherrypicks (#20446)

* Update 1.20.2 engine to use Dart 2.9.1

* Use a single mask view to clip iOS platform view (#20050)

* Moved to RMSE for image comparison to account for slight variations in golden image tests (#19658)

Moved to RMSE for image comparison to account for slight variations in golden image production.  (also fixed a flakey test)

Co-authored-by: Chris Yang <ychris@google.com>
Co-authored-by: gaaclarke <30870216+gaaclarke@users.noreply.github.com>
diff --git a/DEPS b/DEPS
index dc297a6..09e6e35 100644
--- a/DEPS
+++ b/DEPS
@@ -34,7 +34,7 @@
   # Dart is: https://github.com/dart-lang/sdk/blob/master/DEPS.
   # You can use //tools/dart/create_updated_flutter_deps.py to produce
   # updated revision list of existing dependencies.
-  'dart_revision': '6eb17654b6501e2617c67854ed113ab550d2b3c7',
+  'dart_revision': 'e940ff7819053ed8a4c04a4dfcda7df12e969331',
 
   # WARNING: DO NOT EDIT MANUALLY
   # The lines between blank lines above and below are generated by a script. See create_updated_flutter_deps.py
diff --git a/shell/platform/darwin/ios/framework/Source/FlutterPlatformViews.mm b/shell/platform/darwin/ios/framework/Source/FlutterPlatformViews.mm
index abf854f..e49e0fa 100644
--- a/shell/platform/darwin/ios/framework/Source/FlutterPlatformViews.mm
+++ b/shell/platform/darwin/ios/framework/Source/FlutterPlatformViews.mm
@@ -167,7 +167,11 @@
 
   touch_interceptors_[viewId] =
       fml::scoped_nsobject<FlutterTouchInterceptingView>([touch_interceptor retain]);
-  root_views_[viewId] = fml::scoped_nsobject<UIView>([touch_interceptor retain]);
+
+  ChildClippingView* clipping_view =
+      [[[ChildClippingView alloc] initWithFrame:CGRectZero] autorelease];
+  [clipping_view addSubview:touch_interceptor];
+  root_views_[viewId] = fml::scoped_nsobject<UIView>([clipping_view retain]);
 
   result(nil);
 }
@@ -317,83 +321,60 @@
   return clipCount;
 }
 
-UIView* FlutterPlatformViewsController::ReconstructClipViewsChain(int number_of_clips,
-                                                                  UIView* platform_view,
-                                                                  UIView* head_clip_view) {
-  NSInteger indexInFlutterView = -1;
-  if (head_clip_view.superview) {
-    // TODO(cyanglaz): potentially cache the index of oldPlatformViewRoot to make this a O(1).
-    // https://github.com/flutter/flutter/issues/35023
-    indexInFlutterView = [flutter_view_.get().subviews indexOfObject:head_clip_view];
-    [head_clip_view removeFromSuperview];
-  }
-  UIView* head = platform_view;
-  int clipIndex = 0;
-  // Re-use as much existing clip views as needed.
-  while (head != head_clip_view && clipIndex < number_of_clips) {
-    head = head.superview;
-    clipIndex++;
-  }
-  // If there were not enough existing clip views, add more.
-  while (clipIndex < number_of_clips) {
-    ChildClippingView* clippingView =
-        [[[ChildClippingView alloc] initWithFrame:flutter_view_.get().bounds] autorelease];
-    [clippingView addSubview:head];
-    head = clippingView;
-    clipIndex++;
-  }
-  [head removeFromSuperview];
-
-  if (indexInFlutterView > -1) {
-    // The chain was previously attached; attach it to the same position.
-    [flutter_view_.get() insertSubview:head atIndex:indexInFlutterView];
-  }
-  return head;
-}
-
 void FlutterPlatformViewsController::ApplyMutators(const MutatorsStack& mutators_stack,
                                                    UIView* embedded_view) {
   FML_DCHECK(CATransform3DEqualToTransform(embedded_view.layer.transform, CATransform3DIdentity));
-  UIView* head = embedded_view;
-  ResetAnchor(head.layer);
+  ResetAnchor(embedded_view.layer);
+  ChildClippingView* clipView = (ChildClippingView*)embedded_view.superview;
 
-  std::vector<std::shared_ptr<Mutator>>::const_reverse_iterator iter = mutators_stack.Bottom();
-  while (iter != mutators_stack.Top()) {
-    switch ((*iter)->GetType()) {
-      case transform: {
-        CATransform3D transform = GetCATransform3DFromSkMatrix((*iter)->GetMatrix());
-        head.layer.transform = CATransform3DConcat(head.layer.transform, transform);
-        break;
-      }
-      case clip_rect:
-      case clip_rrect:
-      case clip_path: {
-        ChildClippingView* clipView = (ChildClippingView*)head.superview;
-        clipView.layer.transform = CATransform3DIdentity;
-        [clipView setClip:(*iter)->GetType()
-                     rect:(*iter)->GetRect()
-                    rrect:(*iter)->GetRRect()
-                     path:(*iter)->GetPath()];
-        ResetAnchor(clipView.layer);
-        head = clipView;
-        break;
-      }
-      case opacity:
-        embedded_view.alpha = (*iter)->GetAlphaFloat() * embedded_view.alpha;
-        break;
-    }
-    ++iter;
-  }
-  // Reverse scale based on screen scale.
-  //
   // The UIKit frame is set based on the logical resolution instead of physical.
   // (https://developer.apple.com/library/archive/documentation/DeviceInformation/Reference/iOSDeviceCompatibility/Displays/Displays.html).
   // However, flow is based on the physical resolution. For example, 1000 pixels in flow equals
   // 500 points in UIKit. And until this point, we did all the calculation based on the flow
   // resolution. So we need to scale down to match UIKit's logical resolution.
   CGFloat screenScale = [UIScreen mainScreen].scale;
-  head.layer.transform = CATransform3DConcat(
-      head.layer.transform, CATransform3DMakeScale(1 / screenScale, 1 / screenScale, 1));
+  CATransform3D finalTransform = CATransform3DMakeScale(1 / screenScale, 1 / screenScale, 1);
+
+  // Mask view needs to be full screen because we might draw platform view pixels outside of the
+  // `ChildClippingView`. Since the mask view's frame will be based on the `clipView`'s coordinate
+  // system, we need to convert the flutter_view's frame to the clipView's coordinate system. The
+  // mask view is not displayed on the screen.
+  CGRect maskViewFrame = [flutter_view_ convertRect:flutter_view_.get().frame toView:clipView];
+  FlutterClippingMaskView* maskView =
+      [[[FlutterClippingMaskView alloc] initWithFrame:maskViewFrame] autorelease];
+  auto iter = mutators_stack.Begin();
+  while (iter != mutators_stack.End()) {
+    switch ((*iter)->GetType()) {
+      case transform: {
+        CATransform3D transform = GetCATransform3DFromSkMatrix((*iter)->GetMatrix());
+        finalTransform = CATransform3DConcat(transform, finalTransform);
+        break;
+      }
+      case clip_rect:
+        [maskView clipRect:(*iter)->GetRect() matrix:finalTransform];
+        break;
+      case clip_rrect:
+        [maskView clipRRect:(*iter)->GetRRect() matrix:finalTransform];
+        break;
+      case clip_path:
+        [maskView clipPath:(*iter)->GetPath() matrix:finalTransform];
+        break;
+      case opacity:
+        embedded_view.alpha = (*iter)->GetAlphaFloat() * embedded_view.alpha;
+        break;
+    }
+    ++iter;
+  }
+  // Reverse the offset of the clipView.
+  // The clipView's frame includes the final translate of the final transform matrix.
+  // So we need to revese this translate so the platform view can layout at the correct offset.
+  //
+  // Note that we don't apply this transform matrix the clippings because clippings happen on the
+  // mask view, whose origin is alwasy (0,0) to the flutter_view.
+  CATransform3D reverseTranslate =
+      CATransform3DMakeTranslation(-clipView.frame.origin.x, -clipView.frame.origin.y, 0);
+  embedded_view.layer.transform = CATransform3DConcat(finalTransform, reverseTranslate);
+  clipView.maskView = maskView;
 }
 
 void FlutterPlatformViewsController::CompositeWithParams(int view_id,
@@ -406,17 +387,15 @@
   touchInterceptor.alpha = 1;
 
   const MutatorsStack& mutatorStack = params.mutatorsStack();
-  int currentClippingCount = CountClips(mutatorStack);
-  int previousClippingCount = clip_count_[view_id];
-  if (currentClippingCount != previousClippingCount) {
-    clip_count_[view_id] = currentClippingCount;
-    // If we have a different clipping count in this frame, we need to reconstruct the
-    // ClippingChildView chain to prepare for `ApplyMutators`.
-    UIView* oldPlatformViewRoot = root_views_[view_id].get();
-    UIView* newPlatformViewRoot =
-        ReconstructClipViewsChain(currentClippingCount, touchInterceptor, oldPlatformViewRoot);
-    root_views_[view_id] = fml::scoped_nsobject<UIView>([newPlatformViewRoot retain]);
-  }
+  UIView* clippingView = root_views_[view_id].get();
+  // The frame of the clipping view should be the final bounding rect.
+  // Because the translate matrix in the Mutator Stack also includes the offset,
+  // when we apply the transforms matrix in |ApplyMutators|, we need
+  // to remember to do a reverse translate.
+  const SkRect& rect = params.finalBoundingRect();
+  CGFloat screenScale = [UIScreen mainScreen].scale;
+  clippingView.frame = CGRectMake(rect.x() / screenScale, rect.y() / screenScale,
+                                  rect.width() / screenScale, rect.height() / screenScale);
   ApplyMutators(mutatorStack, touchInterceptor);
 }
 
diff --git a/shell/platform/darwin/ios/framework/Source/FlutterPlatformViewsTest.mm b/shell/platform/darwin/ios/framework/Source/FlutterPlatformViewsTest.mm
index e2a0088..0e8397e 100644
--- a/shell/platform/darwin/ios/framework/Source/FlutterPlatformViewsTest.mm
+++ b/shell/platform/darwin/ios/framework/Source/FlutterPlatformViewsTest.mm
@@ -14,6 +14,7 @@
 FLUTTER_ASSERT_NOT_ARC
 @class FlutterPlatformViewsTestMockPlatformView;
 static FlutterPlatformViewsTestMockPlatformView* gMockPlatformView = nil;
+const float kFloatCompareEpsilon = 0.001;
 
 @interface FlutterPlatformViewsTestMockPlatformView : UIView
 @end
@@ -143,4 +144,385 @@
   flutterPlatformViewsController->Reset();
 }
 
+- (void)testChildClippingViewHitTests {
+  ChildClippingView* childClippingView =
+      [[[ChildClippingView alloc] initWithFrame:CGRectMake(0, 0, 500, 500)] autorelease];
+  UIView* childView = [[[UIView alloc] initWithFrame:CGRectMake(100, 100, 100, 100)] autorelease];
+  [childClippingView addSubview:childView];
+
+  XCTAssertFalse([childClippingView pointInside:CGPointMake(50, 50) withEvent:nil]);
+  XCTAssertFalse([childClippingView pointInside:CGPointMake(99, 100) withEvent:nil]);
+  XCTAssertFalse([childClippingView pointInside:CGPointMake(100, 99) withEvent:nil]);
+  XCTAssertFalse([childClippingView pointInside:CGPointMake(201, 200) withEvent:nil]);
+  XCTAssertFalse([childClippingView pointInside:CGPointMake(200, 201) withEvent:nil]);
+  XCTAssertFalse([childClippingView pointInside:CGPointMake(99, 200) withEvent:nil]);
+  XCTAssertFalse([childClippingView pointInside:CGPointMake(200, 299) withEvent:nil]);
+
+  XCTAssertTrue([childClippingView pointInside:CGPointMake(150, 150) withEvent:nil]);
+  XCTAssertTrue([childClippingView pointInside:CGPointMake(100, 100) withEvent:nil]);
+  XCTAssertTrue([childClippingView pointInside:CGPointMake(199, 100) withEvent:nil]);
+  XCTAssertTrue([childClippingView pointInside:CGPointMake(100, 199) withEvent:nil]);
+  XCTAssertTrue([childClippingView pointInside:CGPointMake(199, 199) withEvent:nil]);
+}
+
+- (void)testCompositePlatformView {
+  flutter::FlutterPlatformViewsTestMockPlatformViewDelegate mock_delegate;
+  auto thread_task_runner = CreateNewThread("FlutterPlatformViewsTest");
+  flutter::TaskRunners runners(/*label=*/self.name.UTF8String,
+                               /*platform=*/thread_task_runner,
+                               /*raster=*/thread_task_runner,
+                               /*ui=*/thread_task_runner,
+                               /*io=*/thread_task_runner);
+  auto platform_view = std::make_unique<flutter::PlatformViewIOS>(
+      /*delegate=*/mock_delegate,
+      /*rendering_api=*/flutter::IOSRenderingAPI::kSoftware,
+      /*task_runners=*/runners);
+
+  auto flutterPlatformViewsController = std::make_unique<flutter::FlutterPlatformViewsController>();
+
+  FlutterPlatformViewsTestMockFlutterPlatformFactory* factory =
+      [[FlutterPlatformViewsTestMockFlutterPlatformFactory new] autorelease];
+  flutterPlatformViewsController->RegisterViewFactory(
+      factory, @"MockFlutterPlatformView",
+      FlutterPlatformViewGestureRecognizersBlockingPolicyEager);
+  FlutterResult result = ^(id result) {
+  };
+  flutterPlatformViewsController->OnMethodCall(
+      [FlutterMethodCall
+          methodCallWithMethodName:@"create"
+                         arguments:@{@"id" : @2, @"viewType" : @"MockFlutterPlatformView"}],
+      result);
+
+  XCTAssertNotNil(gMockPlatformView);
+
+  UIView* mockFlutterView = [[[UIView alloc] initWithFrame:CGRectMake(0, 0, 500, 500)] autorelease];
+  flutterPlatformViewsController->SetFlutterView(mockFlutterView);
+  // Create embedded view params
+  flutter::MutatorsStack stack;
+  // Layer tree always pushes a screen scale factor to the stack
+  SkMatrix screenScaleMatrix =
+      SkMatrix::MakeScale([UIScreen mainScreen].scale, [UIScreen mainScreen].scale);
+  stack.PushTransform(screenScaleMatrix);
+  // Push a translate matrix
+  SkMatrix translateMatrix = SkMatrix::MakeTrans(100, 100);
+  stack.PushTransform(translateMatrix);
+  SkMatrix finalMatrix;
+  finalMatrix.setConcat(screenScaleMatrix, translateMatrix);
+
+  auto embeddedViewParams =
+      std::make_unique<flutter::EmbeddedViewParams>(finalMatrix, SkSize::Make(300, 300), stack);
+
+  flutterPlatformViewsController->PrerollCompositeEmbeddedView(2, std::move(embeddedViewParams));
+  flutterPlatformViewsController->CompositeEmbeddedView(2);
+  CGRect platformViewRectInFlutterView = [gMockPlatformView convertRect:gMockPlatformView.bounds
+                                                                 toView:mockFlutterView];
+  XCTAssertTrue(CGRectEqualToRect(platformViewRectInFlutterView, CGRectMake(100, 100, 300, 300)));
+  flutterPlatformViewsController->Reset();
+}
+
+- (void)testChildClippingViewShouldBeTheBoundingRectOfPlatformView {
+  flutter::FlutterPlatformViewsTestMockPlatformViewDelegate mock_delegate;
+  auto thread_task_runner = CreateNewThread("FlutterPlatformViewsTest");
+  flutter::TaskRunners runners(/*label=*/self.name.UTF8String,
+                               /*platform=*/thread_task_runner,
+                               /*raster=*/thread_task_runner,
+                               /*ui=*/thread_task_runner,
+                               /*io=*/thread_task_runner);
+  auto platform_view = std::make_unique<flutter::PlatformViewIOS>(
+      /*delegate=*/mock_delegate,
+      /*rendering_api=*/flutter::IOSRenderingAPI::kSoftware,
+      /*task_runners=*/runners);
+
+  auto flutterPlatformViewsController = std::make_unique<flutter::FlutterPlatformViewsController>();
+
+  FlutterPlatformViewsTestMockFlutterPlatformFactory* factory =
+      [[FlutterPlatformViewsTestMockFlutterPlatformFactory new] autorelease];
+  flutterPlatformViewsController->RegisterViewFactory(
+      factory, @"MockFlutterPlatformView",
+      FlutterPlatformViewGestureRecognizersBlockingPolicyEager);
+  FlutterResult result = ^(id result) {
+  };
+  flutterPlatformViewsController->OnMethodCall(
+      [FlutterMethodCall
+          methodCallWithMethodName:@"create"
+                         arguments:@{@"id" : @2, @"viewType" : @"MockFlutterPlatformView"}],
+      result);
+
+  XCTAssertNotNil(gMockPlatformView);
+
+  UIView* mockFlutterView = [[[UIView alloc] initWithFrame:CGRectMake(0, 0, 500, 500)] autorelease];
+  flutterPlatformViewsController->SetFlutterView(mockFlutterView);
+  // Create embedded view params
+  flutter::MutatorsStack stack;
+  // Layer tree always pushes a screen scale factor to the stack
+  SkMatrix screenScaleMatrix =
+      SkMatrix::MakeScale([UIScreen mainScreen].scale, [UIScreen mainScreen].scale);
+  stack.PushTransform(screenScaleMatrix);
+  // Push a rotate matrix
+  SkMatrix rotateMatrix;
+  rotateMatrix.setRotate(10);
+  stack.PushTransform(rotateMatrix);
+  SkMatrix finalMatrix;
+  finalMatrix.setConcat(screenScaleMatrix, rotateMatrix);
+
+  auto embeddedViewParams =
+      std::make_unique<flutter::EmbeddedViewParams>(finalMatrix, SkSize::Make(300, 300), stack);
+
+  flutterPlatformViewsController->PrerollCompositeEmbeddedView(2, std::move(embeddedViewParams));
+  flutterPlatformViewsController->CompositeEmbeddedView(2);
+  CGRect platformViewRectInFlutterView = [gMockPlatformView convertRect:gMockPlatformView.bounds
+                                                                 toView:mockFlutterView];
+  XCTAssertTrue([gMockPlatformView.superview.superview isKindOfClass:ChildClippingView.class]);
+  ChildClippingView* childClippingView = (ChildClippingView*)gMockPlatformView.superview.superview;
+  // The childclippingview's frame is set based on flow, but the platform view's frame is set based
+  // on quartz. Although they should be the same, but we should tolerate small floating point
+  // errors.
+  XCTAssertLessThan(fabs(platformViewRectInFlutterView.origin.x - childClippingView.frame.origin.x),
+                    kFloatCompareEpsilon);
+  XCTAssertLessThan(fabs(platformViewRectInFlutterView.origin.y - childClippingView.frame.origin.y),
+                    kFloatCompareEpsilon);
+  XCTAssertLessThan(
+      fabs(platformViewRectInFlutterView.size.width - childClippingView.frame.size.width),
+      kFloatCompareEpsilon);
+  XCTAssertLessThan(
+      fabs(platformViewRectInFlutterView.size.height - childClippingView.frame.size.height),
+      kFloatCompareEpsilon);
+
+  flutterPlatformViewsController->Reset();
+}
+
+- (void)testClipRect {
+  flutter::FlutterPlatformViewsTestMockPlatformViewDelegate mock_delegate;
+  auto thread_task_runner = CreateNewThread("FlutterPlatformViewsTest");
+  flutter::TaskRunners runners(/*label=*/self.name.UTF8String,
+                               /*platform=*/thread_task_runner,
+                               /*raster=*/thread_task_runner,
+                               /*ui=*/thread_task_runner,
+                               /*io=*/thread_task_runner);
+  auto platform_view = std::make_unique<flutter::PlatformViewIOS>(
+      /*delegate=*/mock_delegate,
+      /*rendering_api=*/flutter::IOSRenderingAPI::kSoftware,
+      /*task_runners=*/runners);
+
+  auto flutterPlatformViewsController = std::make_unique<flutter::FlutterPlatformViewsController>();
+
+  FlutterPlatformViewsTestMockFlutterPlatformFactory* factory =
+      [[FlutterPlatformViewsTestMockFlutterPlatformFactory new] autorelease];
+  flutterPlatformViewsController->RegisterViewFactory(
+      factory, @"MockFlutterPlatformView",
+      FlutterPlatformViewGestureRecognizersBlockingPolicyEager);
+  FlutterResult result = ^(id result) {
+  };
+  flutterPlatformViewsController->OnMethodCall(
+      [FlutterMethodCall
+          methodCallWithMethodName:@"create"
+                         arguments:@{@"id" : @2, @"viewType" : @"MockFlutterPlatformView"}],
+      result);
+
+  XCTAssertNotNil(gMockPlatformView);
+
+  UIView* mockFlutterView = [[[UIView alloc] initWithFrame:CGRectMake(0, 0, 10, 10)] autorelease];
+  flutterPlatformViewsController->SetFlutterView(mockFlutterView);
+  // Create embedded view params
+  flutter::MutatorsStack stack;
+  // Layer tree always pushes a screen scale factor to the stack
+  SkMatrix screenScaleMatrix =
+      SkMatrix::MakeScale([UIScreen mainScreen].scale, [UIScreen mainScreen].scale);
+  stack.PushTransform(screenScaleMatrix);
+  // Push a clip rect
+  SkRect rect = SkRect::MakeXYWH(2, 2, 3, 3);
+  stack.PushClipRect(rect);
+
+  auto embeddedViewParams =
+      std::make_unique<flutter::EmbeddedViewParams>(screenScaleMatrix, SkSize::Make(10, 10), stack);
+
+  flutterPlatformViewsController->PrerollCompositeEmbeddedView(2, std::move(embeddedViewParams));
+  flutterPlatformViewsController->CompositeEmbeddedView(2);
+  gMockPlatformView.backgroundColor = UIColor.redColor;
+  XCTAssertTrue([gMockPlatformView.superview.superview isKindOfClass:ChildClippingView.class]);
+  ChildClippingView* childClippingView = (ChildClippingView*)gMockPlatformView.superview.superview;
+  [mockFlutterView addSubview:childClippingView];
+
+  [mockFlutterView setNeedsLayout];
+  [mockFlutterView layoutIfNeeded];
+
+  for (int i = 0; i < 10; i++) {
+    for (int j = 0; j < 10; j++) {
+      CGPoint point = CGPointMake(i, j);
+      int alpha = [self alphaOfPoint:CGPointMake(i, j) onView:mockFlutterView];
+      // Edges of the clipping might have a semi transparent pixel, we only check the pixels that
+      // are fully inside the clipped area.
+      CGRect insideClipping = CGRectMake(3, 3, 1, 1);
+      if (CGRectContainsPoint(insideClipping, point)) {
+        XCTAssertEqual(alpha, 255);
+      } else {
+        XCTAssertLessThan(alpha, 255);
+      }
+    }
+  }
+  flutterPlatformViewsController->Reset();
+}
+
+- (void)testClipRRect {
+  flutter::FlutterPlatformViewsTestMockPlatformViewDelegate mock_delegate;
+  auto thread_task_runner = CreateNewThread("FlutterPlatformViewsTest");
+  flutter::TaskRunners runners(/*label=*/self.name.UTF8String,
+                               /*platform=*/thread_task_runner,
+                               /*raster=*/thread_task_runner,
+                               /*ui=*/thread_task_runner,
+                               /*io=*/thread_task_runner);
+  auto platform_view = std::make_unique<flutter::PlatformViewIOS>(
+      /*delegate=*/mock_delegate,
+      /*rendering_api=*/flutter::IOSRenderingAPI::kSoftware,
+      /*task_runners=*/runners);
+
+  auto flutterPlatformViewsController = std::make_unique<flutter::FlutterPlatformViewsController>();
+
+  FlutterPlatformViewsTestMockFlutterPlatformFactory* factory =
+      [[FlutterPlatformViewsTestMockFlutterPlatformFactory new] autorelease];
+  flutterPlatformViewsController->RegisterViewFactory(
+      factory, @"MockFlutterPlatformView",
+      FlutterPlatformViewGestureRecognizersBlockingPolicyEager);
+  FlutterResult result = ^(id result) {
+  };
+  flutterPlatformViewsController->OnMethodCall(
+      [FlutterMethodCall
+          methodCallWithMethodName:@"create"
+                         arguments:@{@"id" : @2, @"viewType" : @"MockFlutterPlatformView"}],
+      result);
+
+  XCTAssertNotNil(gMockPlatformView);
+
+  UIView* mockFlutterView = [[[UIView alloc] initWithFrame:CGRectMake(0, 0, 10, 10)] autorelease];
+  flutterPlatformViewsController->SetFlutterView(mockFlutterView);
+  // Create embedded view params
+  flutter::MutatorsStack stack;
+  // Layer tree always pushes a screen scale factor to the stack
+  SkMatrix screenScaleMatrix =
+      SkMatrix::MakeScale([UIScreen mainScreen].scale, [UIScreen mainScreen].scale);
+  stack.PushTransform(screenScaleMatrix);
+  // Push a clip rrect
+  SkRRect rrect = SkRRect::MakeRectXY(SkRect::MakeXYWH(2, 2, 6, 6), 1, 1);
+  stack.PushClipRRect(rrect);
+
+  auto embeddedViewParams =
+      std::make_unique<flutter::EmbeddedViewParams>(screenScaleMatrix, SkSize::Make(10, 10), stack);
+
+  flutterPlatformViewsController->PrerollCompositeEmbeddedView(2, std::move(embeddedViewParams));
+  flutterPlatformViewsController->CompositeEmbeddedView(2);
+  gMockPlatformView.backgroundColor = UIColor.redColor;
+  XCTAssertTrue([gMockPlatformView.superview.superview isKindOfClass:ChildClippingView.class]);
+  ChildClippingView* childClippingView = (ChildClippingView*)gMockPlatformView.superview.superview;
+  [mockFlutterView addSubview:childClippingView];
+
+  [mockFlutterView setNeedsLayout];
+  [mockFlutterView layoutIfNeeded];
+
+  for (int i = 0; i < 10; i++) {
+    for (int j = 0; j < 10; j++) {
+      CGPoint point = CGPointMake(i, j);
+      int alpha = [self alphaOfPoint:CGPointMake(i, j) onView:mockFlutterView];
+      // Edges of the clipping might have a semi transparent pixel, we only check the pixels that
+      // are fully inside the clipped area.
+      CGRect insideClipping = CGRectMake(3, 3, 4, 4);
+      if (CGRectContainsPoint(insideClipping, point)) {
+        XCTAssertEqual(alpha, 255);
+      } else {
+        XCTAssertLessThan(alpha, 255);
+      }
+    }
+  }
+  flutterPlatformViewsController->Reset();
+}
+
+- (void)testClipPath {
+  flutter::FlutterPlatformViewsTestMockPlatformViewDelegate mock_delegate;
+  auto thread_task_runner = CreateNewThread("FlutterPlatformViewsTest");
+  flutter::TaskRunners runners(/*label=*/self.name.UTF8String,
+                               /*platform=*/thread_task_runner,
+                               /*raster=*/thread_task_runner,
+                               /*ui=*/thread_task_runner,
+                               /*io=*/thread_task_runner);
+  auto platform_view = std::make_unique<flutter::PlatformViewIOS>(
+      /*delegate=*/mock_delegate,
+      /*rendering_api=*/flutter::IOSRenderingAPI::kSoftware,
+      /*task_runners=*/runners);
+
+  auto flutterPlatformViewsController = std::make_unique<flutter::FlutterPlatformViewsController>();
+
+  FlutterPlatformViewsTestMockFlutterPlatformFactory* factory =
+      [[FlutterPlatformViewsTestMockFlutterPlatformFactory new] autorelease];
+  flutterPlatformViewsController->RegisterViewFactory(
+      factory, @"MockFlutterPlatformView",
+      FlutterPlatformViewGestureRecognizersBlockingPolicyEager);
+  FlutterResult result = ^(id result) {
+  };
+  flutterPlatformViewsController->OnMethodCall(
+      [FlutterMethodCall
+          methodCallWithMethodName:@"create"
+                         arguments:@{@"id" : @2, @"viewType" : @"MockFlutterPlatformView"}],
+      result);
+
+  XCTAssertNotNil(gMockPlatformView);
+
+  UIView* mockFlutterView = [[[UIView alloc] initWithFrame:CGRectMake(0, 0, 10, 10)] autorelease];
+  flutterPlatformViewsController->SetFlutterView(mockFlutterView);
+  // Create embedded view params
+  flutter::MutatorsStack stack;
+  // Layer tree always pushes a screen scale factor to the stack
+  SkMatrix screenScaleMatrix =
+      SkMatrix::MakeScale([UIScreen mainScreen].scale, [UIScreen mainScreen].scale);
+  stack.PushTransform(screenScaleMatrix);
+  // Push a clip path
+  SkPath path;
+  path.addRoundRect(SkRect::MakeXYWH(2, 2, 6, 6), 1, 1);
+  stack.PushClipPath(path);
+
+  auto embeddedViewParams =
+      std::make_unique<flutter::EmbeddedViewParams>(screenScaleMatrix, SkSize::Make(10, 10), stack);
+
+  flutterPlatformViewsController->PrerollCompositeEmbeddedView(2, std::move(embeddedViewParams));
+  flutterPlatformViewsController->CompositeEmbeddedView(2);
+  gMockPlatformView.backgroundColor = UIColor.redColor;
+  XCTAssertTrue([gMockPlatformView.superview.superview isKindOfClass:ChildClippingView.class]);
+  ChildClippingView* childClippingView = (ChildClippingView*)gMockPlatformView.superview.superview;
+  [mockFlutterView addSubview:childClippingView];
+
+  [mockFlutterView setNeedsLayout];
+  [mockFlutterView layoutIfNeeded];
+
+  for (int i = 0; i < 10; i++) {
+    for (int j = 0; j < 10; j++) {
+      CGPoint point = CGPointMake(i, j);
+      int alpha = [self alphaOfPoint:CGPointMake(i, j) onView:mockFlutterView];
+      // Edges of the clipping might have a semi transparent pixel, we only check the pixels that
+      // are fully inside the clipped area.
+      CGRect insideClipping = CGRectMake(3, 3, 4, 4);
+      if (CGRectContainsPoint(insideClipping, point)) {
+        XCTAssertEqual(alpha, 255);
+      } else {
+        XCTAssertLessThan(alpha, 255);
+      }
+    }
+  }
+  flutterPlatformViewsController->Reset();
+}
+
+- (int)alphaOfPoint:(CGPoint)point onView:(UIView*)view {
+  unsigned char pixel[4] = {0};
+
+  CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
+
+  // Draw the pixel on `point` in the context.
+  CGContextRef context = CGBitmapContextCreate(
+      pixel, 1, 1, 8, 4, colorSpace, kCGBitmapAlphaInfoMask & kCGImageAlphaPremultipliedLast);
+  CGContextTranslateCTM(context, -point.x, -point.y);
+  [view.layer renderInContext:context];
+
+  CGContextRelease(context);
+  CGColorSpaceRelease(colorSpace);
+  // Get the alpha from the pixel that we just rendered.
+  return pixel[3];
+}
+
 @end
diff --git a/shell/platform/darwin/ios/framework/Source/FlutterPlatformViews_Internal.h b/shell/platform/darwin/ios/framework/Source/FlutterPlatformViews_Internal.h
index 7a4724f..796d1e5 100644
--- a/shell/platform/darwin/ios/framework/Source/FlutterPlatformViews_Internal.h
+++ b/shell/platform/darwin/ios/framework/Source/FlutterPlatformViews_Internal.h
@@ -16,6 +16,33 @@
 #include "flutter/shell/platform/darwin/ios/ios_context.h"
 #include "third_party/skia/include/core/SkPictureRecorder.h"
 
+// A UIView that acts as a clipping mask for the |ChildClippingView|.
+//
+// On the [UIView drawRect:] method, this view performs a series of clipping operations and sets the
+// alpha channel to the final resulting area to be 1; it also sets the "clipped out" area's alpha
+// channel to be 0.
+//
+// When a UIView sets a |FlutterClippingMaskView| as its `maskView`, the alpha channel of the UIView
+// is replaced with the alpha channel of the |FlutterClippingMaskView|.
+@interface FlutterClippingMaskView : UIView
+
+// Adds a clip rect operation to the queue.
+//
+// The `clipSkRect` is transformed with the `matrix` before adding to the queue.
+- (void)clipRect:(const SkRect&)clipSkRect matrix:(const CATransform3D&)matrix;
+
+// Adds a clip rrect operation to the queue.
+//
+// The `clipSkRRect` is transformed with the `matrix` before adding to the queue.
+- (void)clipRRect:(const SkRRect&)clipSkRRect matrix:(const CATransform3D&)matrix;
+
+// Adds a clip path operation to the queue.
+//
+// The `path` is transformed with the `matrix` before adding to the queue.
+- (void)clipPath:(const SkPath&)path matrix:(const CATransform3D&)matrix;
+
+@end
+
 // A UIView that is used as the parent for embedded UIViews.
 //
 // This view has 2 roles:
@@ -37,14 +64,6 @@
 // The parent view handles clipping to its subviews.
 @interface ChildClippingView : UIView
 
-// Performs the clipping based on the type.
-//
-// The `type` must be one of the 3: clip_rect, clip_rrect, clip_path.
-- (void)setClip:(flutter::MutatorType)type
-           rect:(const SkRect&)rect
-          rrect:(const SkRRect&)rrect
-           path:(const SkPath&)path;
-
 @end
 
 namespace flutter {
@@ -253,20 +272,6 @@
   // Traverse the `mutators_stack` and return the number of clip operations.
   int CountClips(const MutatorsStack& mutators_stack);
 
-  // Make sure that platform_view has exactly clip_count ChildClippingView ancestors.
-  //
-  // Existing ChildClippingViews are re-used. If there are currently more ChildClippingView
-  // ancestors than needed, the extra views are detached. If there are less ChildClippingView
-  // ancestors than needed, new ChildClippingViews will be added.
-  //
-  // If head_clip_view was attached as a subview to FlutterView, the head of the newly constructed
-  // ChildClippingViews chain is attached to FlutterView in the same position.
-  //
-  // Returns the new head of the clip views chain.
-  UIView* ReconstructClipViewsChain(int number_of_clips,
-                                    UIView* platform_view,
-                                    UIView* head_clip_view);
-
   // Applies the mutators in the mutators_stack to the UIView chain that was constructed by
   // `ReconstructClipViewsChain`
   //
diff --git a/shell/platform/darwin/ios/framework/Source/FlutterPlatformViews_Internal.mm b/shell/platform/darwin/ios/framework/Source/FlutterPlatformViews_Internal.mm
index 551535a..5e9ed80 100644
--- a/shell/platform/darwin/ios/framework/Source/FlutterPlatformViews_Internal.mm
+++ b/shell/platform/darwin/ios/framework/Source/FlutterPlatformViews_Internal.mm
@@ -53,32 +53,72 @@
 
 @implementation ChildClippingView
 
-+ (CGRect)getCGRectFromSkRect:(const SkRect&)clipSkRect {
-  return CGRectMake(clipSkRect.fLeft, clipSkRect.fTop, clipSkRect.fRight - clipSkRect.fLeft,
-                    clipSkRect.fBottom - clipSkRect.fTop);
+// The ChildClippingView's frame is the bounding rect of the platform view. we only want touches to
+// be hit tested and consumed by this view if they are inside the embedded platform view which could
+// be smaller the embedded platform view is rotated.
+- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent*)event {
+  for (UIView* view in self.subviews) {
+    if ([view pointInside:[self convertPoint:point toView:view] withEvent:event]) {
+      return YES;
+    }
+  }
+  return NO;
 }
 
-- (void)clipRect:(const SkRect&)clipSkRect {
-  CGRect clipRect = [ChildClippingView getCGRectFromSkRect:clipSkRect];
-  fml::CFRef<CGPathRef> pathRef(CGPathCreateWithRect(clipRect, nil));
-  CAShapeLayer* clip = [[[CAShapeLayer alloc] init] autorelease];
-  clip.path = pathRef;
-  self.layer.mask = clip;
+@end
+
+@interface FlutterClippingMaskView ()
+
+- (fml::CFRef<CGPathRef>)getTransformedPath:(CGPathRef)path matrix:(CATransform3D)matrix;
+- (CGRect)getCGRectFromSkRect:(const SkRect&)clipSkRect;
+
+@end
+
+@implementation FlutterClippingMaskView {
+  std::vector<fml::CFRef<CGPathRef>> paths_;
 }
 
-- (void)clipRRect:(const SkRRect&)clipSkRRect {
+- (instancetype)initWithFrame:(CGRect)frame {
+  if ([super initWithFrame:frame]) {
+    self.backgroundColor = UIColor.clearColor;
+  }
+  return self;
+}
+
+- (void)drawRect:(CGRect)rect {
+  CGContextRef context = UIGraphicsGetCurrentContext();
+  CGContextSaveGState(context);
+
+  // For mask view, only the alpha channel is used.
+  CGContextSetAlpha(context, 1);
+
+  for (size_t i = 0; i < paths_.size(); i++) {
+    CGContextAddPath(context, paths_.at(i));
+    CGContextClip(context);
+  }
+  CGContextFillRect(context, rect);
+  CGContextRestoreGState(context);
+}
+
+- (void)clipRect:(const SkRect&)clipSkRect matrix:(const CATransform3D&)matrix {
+  CGRect clipRect = [self getCGRectFromSkRect:clipSkRect];
+  CGPathRef path = CGPathCreateWithRect(clipRect, nil);
+  paths_.push_back([self getTransformedPath:path matrix:matrix]);
+}
+
+- (void)clipRRect:(const SkRRect&)clipSkRRect matrix:(const CATransform3D&)matrix {
   CGPathRef pathRef = nullptr;
   switch (clipSkRRect.getType()) {
     case SkRRect::kEmpty_Type: {
       break;
     }
     case SkRRect::kRect_Type: {
-      [self clipRect:clipSkRRect.rect()];
+      [self clipRect:clipSkRRect.rect() matrix:matrix];
       return;
     }
     case SkRRect::kOval_Type:
     case SkRRect::kSimple_Type: {
-      CGRect clipRect = [ChildClippingView getCGRectFromSkRect:clipSkRRect.rect()];
+      CGRect clipRect = [self getCGRectFromSkRect:clipSkRRect.rect()];
       pathRef = CGPathCreateWithRoundedRect(clipRect, clipSkRRect.getSimpleRadii().x(),
                                             clipSkRRect.getSimpleRadii().y(), nil);
       break;
@@ -129,23 +169,17 @@
   // TODO(cyanglaz): iOS does not seem to support hard edge on CAShapeLayer. It clearly stated that
   // the CAShaperLayer will be drawn antialiased. Need to figure out a way to do the hard edge
   // clipping on iOS.
-  CAShapeLayer* clip = [[[CAShapeLayer alloc] init] autorelease];
-  clip.path = pathRef;
-  self.layer.mask = clip;
-  CGPathRelease(pathRef);
+  paths_.push_back([self getTransformedPath:pathRef matrix:matrix]);
 }
 
-- (void)clipPath:(const SkPath&)path {
+- (void)clipPath:(const SkPath&)path matrix:(const CATransform3D&)matrix {
   if (!path.isValid()) {
     return;
   }
-  fml::CFRef<CGMutablePathRef> pathRef(CGPathCreateMutable());
   if (path.isEmpty()) {
-    CAShapeLayer* clip = [[[CAShapeLayer alloc] init] autorelease];
-    clip.path = pathRef;
-    self.layer.mask = clip;
     return;
   }
+  CGMutablePathRef pathRef = CGPathCreateMutable();
 
   // Loop through all verbs and translate them into CGPath
   SkPath::Iter iter(path, true);
@@ -197,42 +231,20 @@
     }
     verb = iter.next(pts);
   }
-
-  CAShapeLayer* clip = [[[CAShapeLayer alloc] init] autorelease];
-  clip.path = pathRef;
-  self.layer.mask = clip;
+  paths_.push_back([self getTransformedPath:pathRef matrix:matrix]);
 }
 
-- (void)setClip:(flutter::MutatorType)type
-           rect:(const SkRect&)rect
-          rrect:(const SkRRect&)rrect
-           path:(const SkPath&)path {
-  FML_CHECK(type == flutter::clip_rect || type == flutter::clip_rrect ||
-            type == flutter::clip_path);
-  switch (type) {
-    case flutter::clip_rect:
-      [self clipRect:rect];
-      break;
-    case flutter::clip_rrect:
-      [self clipRRect:rrect];
-      break;
-    case flutter::clip_path:
-      [self clipPath:path];
-      break;
-    default:
-      break;
-  }
+- (fml::CFRef<CGPathRef>)getTransformedPath:(CGPathRef)path matrix:(CATransform3D)matrix {
+  CGAffineTransform affine =
+      CGAffineTransformMake(matrix.m11, matrix.m12, matrix.m21, matrix.m22, matrix.m41, matrix.m42);
+  CGPathRef transformedPath = CGPathCreateCopyByTransformingPath(path, &affine);
+  CGPathRelease(path);
+  return fml::CFRef<CGPathRef>(transformedPath);
 }
 
-// The ChildClippingView is as big as the FlutterView, we only want touches to be hit tested and
-// consumed by this view if they are inside the smaller child view.
-- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent*)event {
-  for (UIView* view in self.subviews) {
-    if ([view pointInside:[self convertPoint:point toView:view] withEvent:event]) {
-      return YES;
-    }
-  }
-  return NO;
+- (CGRect)getCGRectFromSkRect:(const SkRect&)clipSkRect {
+  return CGRectMake(clipSkRect.fLeft, clipSkRect.fTop, clipSkRect.fRight - clipSkRect.fLeft,
+                    clipSkRect.fBottom - clipSkRect.fTop);
 }
 
 @end
diff --git a/testing/scenario_app/ios/Scenarios/ScenariosUITests/GoldenImage.m b/testing/scenario_app/ios/Scenarios/ScenariosUITests/GoldenImage.m
index e1b27c9..9961d1a 100644
--- a/testing/scenario_app/ios/Scenarios/ScenariosUITests/GoldenImage.m
+++ b/testing/scenario_app/ios/Scenarios/ScenariosUITests/GoldenImage.m
@@ -6,6 +6,8 @@
 #import <XCTest/XCTest.h>
 #include <sys/sysctl.h>
 
+static const double kRmseThreshold = 0.5;
+
 @interface GoldenImage ()
 
 @end
@@ -67,8 +69,24 @@
   CGContextDrawImage(contextB, CGRectMake(0, 0, widthA, heightA), imageRefB);
   CGContextRelease(contextB);
 
-  BOOL isSame = memcmp(rawA.mutableBytes, rawB.mutableBytes, size) == 0;
-  return isSame;
+  const char* apos = rawA.mutableBytes;
+  const char* bpos = rawB.mutableBytes;
+  double sum = 0.0;
+  for (size_t i = 0; i < size; ++i, ++apos, ++bpos) {
+    // Skip transparent pixels.
+    if (*apos == 0 && *bpos == 0 && i % 4 == 0) {
+      i += 3;
+      apos += 3;
+      bpos += 3;
+    } else {
+      double aval = *apos;
+      double bval = *bpos;
+      double diff = aval - bval;
+      sum += diff * diff;
+    }
+  }
+  double rmse = sqrt(sum / size);
+  return rmse <= kRmseThreshold;
 }
 
 NS_INLINE NSString* _platformName() {
diff --git a/testing/scenario_app/ios/Scenarios/ScenariosUITests/UnobstructedPlatformViewTests.m b/testing/scenario_app/ios/Scenarios/ScenariosUITests/UnobstructedPlatformViewTests.m
index 3752203..2d3db20 100644
--- a/testing/scenario_app/ios/Scenarios/ScenariosUITests/UnobstructedPlatformViewTests.m
+++ b/testing/scenario_app/ios/Scenarios/ScenariosUITests/UnobstructedPlatformViewTests.m
@@ -243,12 +243,8 @@
 
   XCUIElement* overlay = app.otherElements[@"platform_view[0].overlay[0]"];
   XCTAssertTrue(overlay.exists);
-  XCTAssertEqual(overlay.frame.origin.x, 75);
-  XCTAssertEqual(overlay.frame.origin.y, 85);
-  XCTAssertEqual(overlay.frame.size.width, 150);
-  XCTAssertEqual(overlay.frame.size.height, 190);
-
   XCTAssertFalse(app.otherElements[@"platform_view[0].overlay[1]"].exists);
+  XCTAssertTrue(CGRectContainsRect(platform_view.frame, overlay.frame));
 }
 
 @end
diff --git a/testing/scenario_app/ios/Scenarios/ScenariosUITests/golden_platform_view_clippath_iPhone 8_simulator.png b/testing/scenario_app/ios/Scenarios/ScenariosUITests/golden_platform_view_clippath_iPhone 8_simulator.png
index 9ec19ab..30072dc 100644
--- a/testing/scenario_app/ios/Scenarios/ScenariosUITests/golden_platform_view_clippath_iPhone 8_simulator.png
+++ b/testing/scenario_app/ios/Scenarios/ScenariosUITests/golden_platform_view_clippath_iPhone 8_simulator.png
Binary files differ
diff --git a/testing/scenario_app/ios/Scenarios/ScenariosUITests/golden_platform_view_cliprrect_iPhone 8_simulator.png b/testing/scenario_app/ios/Scenarios/ScenariosUITests/golden_platform_view_cliprrect_iPhone 8_simulator.png
index b193419..69ba03a 100644
--- a/testing/scenario_app/ios/Scenarios/ScenariosUITests/golden_platform_view_cliprrect_iPhone 8_simulator.png
+++ b/testing/scenario_app/ios/Scenarios/ScenariosUITests/golden_platform_view_cliprrect_iPhone 8_simulator.png
Binary files differ