[canvaskit] Fix backdrop filter with platform views as children (#51442)

There is a bug in CanvasKit where a backdrop filter with a platform view
as a child will cause the backdrop filter to apply multiple times. This
change fixes it by only applying the backdrop filter once.

The bug case is more likely to happen since
https://github.com/flutter/engine/pull/47317 landed because when we
encounter an invisible platform view, we always create a new SkPicture
instead of continuing to use the same SkPicture.

BEFORE:

![canvaskit_backdropfilter_with_platformview_BAD](https://github.com/flutter/engine/assets/1961493/c1fa766f-ebf7-4468-9ea7-8ab0d51314c7)

AFTER:

![canvaskit_backdropfilter_with_platformview](https://github.com/flutter/engine/assets/1961493/57d27d62-ee49-43a2-af3f-e532aaba0ffb)


## Pre-launch Checklist

- [x] I read the [Contributor Guide] and followed the process outlined
there for submitting PRs.
- [x] I read the [Tree Hygiene] wiki page, which explains my
responsibilities.
- [x] I read and followed the [Flutter Style Guide] and the [C++,
Objective-C, Java style guides].
- [x] I listed at least one issue that this PR fixes in the description
above.
- [x] I added new tests to check the change I am making or feature I am
adding, or the PR is [test-exempt]. See [testing the engine] for
instructions on writing and running engine tests.
- [x] I updated/added relevant documentation (doc comments with `///`).
- [x] I signed the [CLA].
- [x] All existing and new tests are passing.

If you need help, consider asking for advice on the #hackers-new channel
on [Discord].

<!-- Links -->
[Contributor Guide]:
https://github.com/flutter/flutter/wiki/Tree-hygiene#overview
[Tree Hygiene]: https://github.com/flutter/flutter/wiki/Tree-hygiene
[test-exempt]:
https://github.com/flutter/flutter/wiki/Tree-hygiene#tests
[Flutter Style Guide]:
https://github.com/flutter/flutter/wiki/Style-guide-for-Flutter-repo
[C++, Objective-C, Java style guides]:
https://github.com/flutter/engine/blob/main/CONTRIBUTING.md#style
[testing the engine]:
https://github.com/flutter/flutter/wiki/Testing-the-engine
[CLA]: https://cla.developers.google.com/
[flutter/tests]: https://github.com/flutter/tests
[breaking change policy]:
https://github.com/flutter/flutter/wiki/Tree-hygiene#handling-breaking-changes
[Discord]: https://github.com/flutter/flutter/wiki/Chat
diff --git a/lib/web_ui/lib/src/engine/canvaskit/layer.dart b/lib/web_ui/lib/src/engine/canvaskit/layer.dart
index f54d7ac..143ff86 100644
--- a/lib/web_ui/lib/src/engine/canvaskit/layer.dart
+++ b/lib/web_ui/lib/src/engine/canvaskit/layer.dart
@@ -178,11 +178,16 @@
   @override
   void paint(PaintContext paintContext) {
     final CkPaint paint = CkPaint()..blendMode = _blendMode;
-    paintContext.internalNodesCanvas
-        .saveLayerWithFilter(paintBounds, _filter, paint);
+
+    // Only apply the backdrop filter to the current canvas. If we apply the
+    // backdrop filter to every canvas (i.e. by applying it to the
+    // [internalNodesCanvas]), then later when we compose the canvases into a
+    // single canvas, the backdrop filter will be applied multiple times.
+    final CkCanvas currentCanvas = paintContext.leafNodesCanvas!;
+    currentCanvas.saveLayerWithFilter(paintBounds, _filter, paint);
     paint.dispose();
     paintChildren(paintContext);
-    paintContext.internalNodesCanvas.restore();
+    currentCanvas.restore();
   }
 
   // TODO(dnfield): dispose of the _filter
diff --git a/lib/web_ui/test/canvaskit/backdrop_filter_golden_test.dart b/lib/web_ui/test/canvaskit/backdrop_filter_golden_test.dart
index 34dccb7..d297e7b 100644
--- a/lib/web_ui/test/canvaskit/backdrop_filter_golden_test.dart
+++ b/lib/web_ui/test/canvaskit/backdrop_filter_golden_test.dart
@@ -6,6 +6,7 @@
 import 'package:test/test.dart';
 import 'package:ui/src/engine.dart';
 import 'package:ui/ui.dart' as ui;
+import 'package:ui/ui_web/src/ui_web.dart' as ui_web;
 
 import 'common.dart';
 
@@ -18,7 +19,15 @@
 void testMain() {
   group('BackdropFilter', () {
     setUpCanvasKitTest(withImplicitView: true);
-    EngineFlutterDisplay.instance.debugOverrideDevicePixelRatio(1.0);
+
+    setUp(() {
+      EngineFlutterDisplay.instance.debugOverrideDevicePixelRatio(1);
+    });
+
+    tearDown(() {
+      PlatformViewManager.instance.debugClear();
+      CanvasKitRenderer.instance.debugClear();
+    });
 
     test('blur renders to the edges', () async {
       // Make a checkerboard picture so we can see the blur.
@@ -76,6 +85,77 @@
           builder.build(),
           region: region);
     });
+
+    test('works with an invisible platform view inside', () async {
+      ui_web.platformViewRegistry.registerViewFactory(
+        'test-platform-view',
+        (int viewId) => createDomHTMLDivElement()..id = 'view-0',
+        isVisible: false,
+      );
+      await createPlatformView(0, 'test-platform-view');
+
+      // Make a checkerboard picture so we can see the blur.
+      final CkPictureRecorder recorder = CkPictureRecorder();
+      final CkCanvas canvas = recorder.beginRecording(region);
+      canvas.drawColor(const ui.Color(0xffffffff), ui.BlendMode.srcOver);
+      final double sideLength = region.width / 20;
+      final int rows = (region.height / sideLength).ceil();
+
+      for (int row = 0; row < rows; row++) {
+        for (int column = 0; column < 10; column++) {
+          final ui.Rect rect = ui.Rect.fromLTWH(
+            row.isEven
+                ? (column * 2) * sideLength
+                : (column * 2 + 1) * sideLength,
+            row * sideLength,
+            sideLength,
+            sideLength,
+          );
+          canvas.drawRect(rect, CkPaint()..color = const ui.Color(0xffff0000));
+        }
+      }
+      final CkPicture checkerboard = recorder.endRecording();
+
+      final LayerSceneBuilder builder = LayerSceneBuilder();
+      builder.pushOffset(0, 0);
+      builder.addPicture(ui.Offset.zero, checkerboard);
+      builder.pushBackdropFilter(ui.ImageFilter.blur(sigmaX: 10, sigmaY: 10));
+
+      // Draw a green rectangle, then an invisible platform view, then a blue
+      // rectangle. Both rectangles should not be blurred.
+      final CkPictureRecorder greenRectRecorder = CkPictureRecorder();
+      final CkCanvas greenRectCanvas = greenRectRecorder.beginRecording(region);
+      final CkPaint greenPaint = CkPaint()..color = const ui.Color(0xff00ff00);
+      greenRectCanvas.drawRect(
+          ui.Rect.fromCenter(
+              center: ui.Offset(region.width / 3, region.height / 2),
+              width: region.width / 6,
+              height: region.height / 6),
+          greenPaint);
+      final CkPicture greenRectPicture = greenRectRecorder.endRecording();
+
+      final CkPictureRecorder blueRectRecorder = CkPictureRecorder();
+      final CkCanvas blueRectCanvas = blueRectRecorder.beginRecording(region);
+      final CkPaint bluePaint = CkPaint()..color = const ui.Color(0xff0000ff);
+      blueRectCanvas.drawRect(
+          ui.Rect.fromCenter(
+              center: ui.Offset(2 * region.width / 3, region.height / 2),
+              width: region.width / 6,
+              height: region.height / 6),
+          bluePaint);
+      final CkPicture blueRectPicture = blueRectRecorder.endRecording();
+
+      builder.addPicture(ui.Offset.zero, greenRectPicture);
+      builder.addPlatformView(0, width: 10, height: 10);
+      builder.addPicture(ui.Offset.zero, blueRectPicture);
+
+      // Pop the backdrop filter layer.
+      builder.pop();
+
+      await matchSceneGolden(
+          'canvaskit_backdropfilter_with_platformview.png', builder.build(),
+          region: region);
+    });
     // TODO(hterkelsen): https://github.com/flutter/flutter/issues/71520
   }, skip: isSafari || isFirefox);
 }