Add RSuperellipse support to Web (global cache) (#171489)

This PR adds `RSuperellipse` support to Web, so that these ops will
actually draw `RSuperellipse`s instead of falling back to `RRect`s,
except for platform views.
* (I was told in some earlier comments that RSuperellipses should fall
back to RRects for platform views but I can't remember where for now. If
reviewers think otherwise I can implement them right away or make this a
TODO in the future.)

The `RSuperellipse`s are drawn after being converted to paths. The
algorithm is nothing new, but already used in
`round_superellipse_param.cc`.

For performance optimization, `RSuperellipse`s that have uniform radii
will have their paths cached in a global cache after offset
normalization.

This PR does not add any new public APIs. (Although, I'm planning to
also implement this to the main `dart:ui` in the future to support
non-Impeller-nor-Web platforms, which will need
`RSuperellipse.toPathOffset` public.)

Fixes https://github.com/flutter/flutter/issues/163718

## Pre-launch Checklist

- [ ] I read the [Contributor Guide] and followed the process outlined
there for submitting PRs.
- [ ] I read the [Tree Hygiene] wiki page, which explains my
responsibilities.
- [ ] I read and followed the [Flutter Style Guide], including [Features
we expect every widget to implement].
- [ ] I signed the [CLA].
- [ ] I listed at least one issue that this PR fixes in the description
above.
- [ ] I updated/added relevant documentation (doc comments with `///`).
- [ ] I added new tests to check the change I am making, or this PR is
[test-exempt].
- [ ] I followed the [breaking change policy] and added [Data Driven
Fixes] where supported.
- [ ] 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/blob/main/docs/contributing/Tree-hygiene.md#overview
[Tree Hygiene]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md
[test-exempt]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#tests
[Flutter Style Guide]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md
[Features we expect every widget to implement]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md#features-we-expect-every-widget-to-implement
[CLA]: https://cla.developers.google.com/
[flutter/tests]: https://github.com/flutter/tests
[breaking change policy]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#handling-breaking-changes
[Discord]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Chat.md
[Data Driven Fixes]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Data-driven-Fixes.md
diff --git a/dev/benchmarks/macrobenchmarks/lib/src/web/bench_draw_rrect_rsuperellipse.dart b/dev/benchmarks/macrobenchmarks/lib/src/web/bench_draw_rrect_rsuperellipse.dart
new file mode 100644
index 0000000..8080ca9
--- /dev/null
+++ b/dev/benchmarks/macrobenchmarks/lib/src/web/bench_draw_rrect_rsuperellipse.dart
@@ -0,0 +1,86 @@
+// Copyright 2014 The Flutter Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+import 'dart:ui';
+
+import 'recorder.dart';
+
+typedef _Draw = void Function(Canvas canvas, int key, Rect rect, Radius radius, Paint paint);
+
+/// Repeatedly paints a grid of rounded rectangles or rounded superellipses.
+///
+/// Measures the performance of the `drawRSuperellipse` operation compared with `drawRRect`.
+class BenchDrawRRectRSuperellipse extends SceneBuilderRecorder {
+  /// A variant of the benchmark that draws rounded rectangles.
+  ///
+  /// This variant is used as a comparison benchmark for `drawRSuperellipse`.
+  BenchDrawRRectRSuperellipse.drawRRect() : _draw = _drawRRect, super(name: drawRRectName);
+
+  /// A variant of the benchmark that draws rounded superellipses.
+  BenchDrawRRectRSuperellipse.drawRSuperellipse() : super(name: drawRSuperellipseName) {
+    _draw = _drawRSuperellipse;
+  }
+
+  static const String drawRRectName = 'draw_rrect';
+  static const String drawRSuperellipseName = 'draw_rsuperellipse';
+
+  /// Number of rows in the grid.
+  static const int kRows = 25;
+
+  /// Number of columns in the grid.
+  static const int kColumns = 40;
+
+  late final _Draw _draw;
+
+  static void _drawRRect(Canvas canvas, int key, Rect rect, Radius radius, Paint paint) {
+    canvas.drawRRect(RRect.fromRectAndRadius(rect, radius), paint);
+  }
+
+  static void _drawRSuperellipse(Canvas canvas, int key, Rect rect, Radius radius, Paint paint) {
+    canvas.drawRSuperellipse(RSuperellipse.fromRectAndRadius(rect, radius), paint);
+  }
+
+  /// Counter used to offset the rendered rsuperellipse to make them wobble.
+  ///
+  /// The wobbling is there so a human could visually verify that the benchmark
+  /// is correctly pumping frames.
+  double wobbleCounter = 0;
+
+  static final Paint _paint = Paint()..color = const Color.fromARGB(255, 255, 0, 0);
+
+  @override
+  void onDrawFrame(SceneBuilder sceneBuilder) {
+    final PictureRecorder pictureRecorder = PictureRecorder();
+    final Canvas canvas = Canvas(pictureRecorder);
+    final Size viewSize = view.physicalSize;
+
+    final Size cellSize = Size(viewSize.width / kColumns, viewSize.height / kRows);
+    final Size rectSize = cellSize * 0.8;
+    final double maxRadius = rectSize.shortestSide / 2;
+
+    for (int row = 0; row < kRows; row++) {
+      canvas.save();
+      for (int col = 0; col < kColumns; col++) {
+        final double radius = maxRadius / kColumns * col;
+        _draw(
+          canvas,
+          row * kColumns + col,
+          Offset((wobbleCounter - 5).abs(), 0) & rectSize,
+          Radius.circular(radius),
+          _paint,
+        );
+        canvas.translate(cellSize.width, 0);
+      }
+      canvas.restore();
+      canvas.translate(0, cellSize.height);
+    }
+
+    wobbleCounter += 1;
+    wobbleCounter = wobbleCounter % 10;
+    final Picture picture = pictureRecorder.endRecording();
+    sceneBuilder.pushOffset(0.0, 0.0);
+    sceneBuilder.addPicture(Offset.zero, picture);
+    sceneBuilder.pop();
+  }
+}
diff --git a/dev/benchmarks/macrobenchmarks/lib/web_benchmarks.dart b/dev/benchmarks/macrobenchmarks/lib/web_benchmarks.dart
index f85960f..03e5209 100644
--- a/dev/benchmarks/macrobenchmarks/lib/web_benchmarks.dart
+++ b/dev/benchmarks/macrobenchmarks/lib/web_benchmarks.dart
@@ -17,6 +17,7 @@
 import 'src/web/bench_clipped_out_pictures.dart';
 import 'src/web/bench_default_target_platform.dart';
 import 'src/web/bench_draw_rect.dart';
+import 'src/web/bench_draw_rrect_rsuperellipse.dart';
 import 'src/web/bench_dynamic_clip_on_static_picture.dart';
 import 'src/web/bench_harness.dart';
 import 'src/web/bench_image_decoding.dart';
@@ -56,6 +57,9 @@
   BenchClippedOutPictures.benchmarkName: () => BenchClippedOutPictures(),
   BenchDrawRect.benchmarkName: () => BenchDrawRect.staticPaint(),
   BenchDrawRect.variablePaintBenchmarkName: () => BenchDrawRect.variablePaint(),
+  BenchDrawRRectRSuperellipse.drawRRectName: () => BenchDrawRRectRSuperellipse.drawRRect(),
+  BenchDrawRRectRSuperellipse.drawRSuperellipseName: () =>
+      BenchDrawRRectRSuperellipse.drawRSuperellipse(),
   BenchPathRecording.benchmarkName: () => BenchPathRecording(),
   BenchTextOutOfPictureBounds.benchmarkName: () => BenchTextOutOfPictureBounds(),
   BenchSimpleLazyTextScroll.benchmarkName: () => BenchSimpleLazyTextScroll(),
diff --git a/engine/src/flutter/ci/licenses_golden/licenses_flutter b/engine/src/flutter/ci/licenses_golden/licenses_flutter
index 0e9526d..be8fc56 100644
--- a/engine/src/flutter/ci/licenses_golden/licenses_flutter
+++ b/engine/src/flutter/ci/licenses_golden/licenses_flutter
@@ -52630,6 +52630,7 @@
 ORIGIN: ../../../flutter/lib/web_ui/lib/platform_dispatcher.dart + ../../../flutter/LICENSE
 ORIGIN: ../../../flutter/lib/web_ui/lib/platform_isolate.dart + ../../../flutter/LICENSE
 ORIGIN: ../../../flutter/lib/web_ui/lib/pointer.dart + ../../../flutter/LICENSE
+ORIGIN: ../../../flutter/lib/web_ui/lib/rsuperellipse_param.dart + ../../../flutter/LICENSE
 ORIGIN: ../../../flutter/lib/web_ui/lib/semantics.dart + ../../../flutter/LICENSE
 ORIGIN: ../../../flutter/lib/web_ui/lib/src/engine.dart + ../../../flutter/LICENSE
 ORIGIN: ../../../flutter/lib/web_ui/lib/src/engine/alarm_clock.dart + ../../../flutter/LICENSE
@@ -55704,6 +55705,7 @@
 FILE: ../../../flutter/lib/web_ui/lib/platform_dispatcher.dart
 FILE: ../../../flutter/lib/web_ui/lib/platform_isolate.dart
 FILE: ../../../flutter/lib/web_ui/lib/pointer.dart
+FILE: ../../../flutter/lib/web_ui/lib/rsuperellipse_param.dart
 FILE: ../../../flutter/lib/web_ui/lib/semantics.dart
 FILE: ../../../flutter/lib/web_ui/lib/src/engine.dart
 FILE: ../../../flutter/lib/web_ui/lib/src/engine/alarm_clock.dart
diff --git a/engine/src/flutter/impeller/geometry/round_superellipse_unittests.cc b/engine/src/flutter/impeller/geometry/round_superellipse_unittests.cc
index 274a450..3752bc5 100644
--- a/engine/src/flutter/impeller/geometry/round_superellipse_unittests.cc
+++ b/engine/src/flutter/impeller/geometry/round_superellipse_unittests.cc
@@ -698,9 +698,9 @@
 #undef CHECK_POINT_AND_MIRRORS
 }
 
-TEST(RoundSuperellipseTest, SlimDiagnalContains) {
-  // This shape has large radii on one diagnal and tiny radii on the other,
-  // resulting in a almond-like shape placed diagnally (NW to SE).
+TEST(RoundSuperellipseTest, SlimDiagonalContains) {
+  // This shape has large radii on one diagonal and tiny radii on the other,
+  // resulting in a almond-like shape placed diagonally (NW to SE).
   Rect bounds = Rect::MakeLTRB(-50.0f, -50.0f, 50.0f, 50.0f);
   auto rr = RoundSuperellipse::MakeRectRadii(
       bounds, {
@@ -721,17 +721,17 @@
   CHECK_POINT_WITH_OFFSET(rr, Point(49.70, 49.70), Point(0.02, 0.02));
 
 // Checks two points symmetrical to the origin.
-#define CHECK_DIAGNAL_POINTS(p)                         \
+#define CHECK_DIAGONAL_POINTS(p)                        \
   CHECK_POINT_WITH_OFFSET(rr, (p), Point(0.02, -0.02)); \
   CHECK_POINT_WITH_OFFSET(rr, (p) * Point(-1, -1), Point(-0.02, 0.02));
 
   // A few other points along the edge
-  CHECK_DIAGNAL_POINTS(Point(-40.0, -49.59));
-  CHECK_DIAGNAL_POINTS(Point(-20.0, -45.64));
-  CHECK_DIAGNAL_POINTS(Point(0.0, -37.01));
-  CHECK_DIAGNAL_POINTS(Point(20.0, -21.96));
-  CHECK_DIAGNAL_POINTS(Point(21.05, -20.92));
-  CHECK_DIAGNAL_POINTS(Point(40.0, 5.68));
+  CHECK_DIAGONAL_POINTS(Point(-40.0, -49.59));
+  CHECK_DIAGONAL_POINTS(Point(-20.0, -45.64));
+  CHECK_DIAGONAL_POINTS(Point(0.0, -37.01));
+  CHECK_DIAGONAL_POINTS(Point(20.0, -21.96));
+  CHECK_DIAGONAL_POINTS(Point(21.05, -20.92));
+  CHECK_DIAGONAL_POINTS(Point(40.0, 5.68));
 #undef CHECK_POINT_AND_MIRRORS
 }
 
diff --git a/engine/src/flutter/lib/web_ui/lib/geometry.dart b/engine/src/flutter/lib/web_ui/lib/geometry.dart
index 9f5e694..ea4140e 100644
--- a/engine/src/flutter/lib/web_ui/lib/geometry.dart
+++ b/engine/src/flutter/lib/web_ui/lib/geometry.dart
@@ -441,6 +441,7 @@
     required double brRadiusY,
     required double blRadiusX,
     required double blRadiusY,
+    required bool uniformRadii,
   });
 
   final double left;
@@ -460,6 +461,19 @@
   final double blRadiusY;
   Radius get blRadius => Radius.elliptical(blRadiusX, blRadiusY);
 
+  // Whether the radii of the four corners are guaranteed to be the same.
+  //
+  // This is a Web-only flag for optimization.
+  //
+  // Returns `true` only if the object was constructed in a way that guarantees
+  // all radii are equal (e.g., from `.fromRectAndRadius`), saving the cost of
+  // checking the corner radii fields individually.
+  //
+  // A `false` return value does not mean the radii are necessarily different.
+  // It simply means there's no guarantee, and they must be compared manually
+  // if uniformity needs to be determined.
+  bool get _uniformRadii => false;
+
   T shift(Offset offset) {
     return _create(
       left: left + offset.dx,
@@ -474,6 +488,7 @@
       blRadiusY: blRadiusY,
       brRadiusX: brRadiusX,
       brRadiusY: brRadiusY,
+      uniformRadii: _uniformRadii,
     );
   }
 
@@ -491,6 +506,7 @@
       blRadiusY: math.max(0, blRadiusY + delta),
       brRadiusX: math.max(0, brRadiusX + delta),
       brRadiusY: math.max(0, brRadiusY + delta),
+      uniformRadii: _uniformRadii,
     );
   }
 
@@ -614,6 +630,7 @@
         blRadiusY: blRadiusY * scale,
         brRadiusX: brRadiusX * scale,
         brRadiusY: brRadiusY * scale,
+        uniformRadii: _uniformRadii,
       );
     }
 
@@ -630,6 +647,7 @@
       blRadiusY: blRadiusY,
       brRadiusX: brRadiusX,
       brRadiusY: brRadiusY,
+      uniformRadii: _uniformRadii,
     );
   }
 
@@ -651,6 +669,7 @@
         brRadiusY: math.max(0, brRadiusY * k),
         blRadiusX: math.max(0, blRadiusX * k),
         blRadiusY: math.max(0, blRadiusY * k),
+        uniformRadii: _uniformRadii,
       );
     } else {
       return _create(
@@ -666,6 +685,7 @@
         brRadiusY: math.max(0, _lerpDouble(brRadiusY, b.brRadiusY, t)),
         blRadiusX: math.max(0, _lerpDouble(blRadiusX, b.blRadiusX, t)),
         blRadiusY: math.max(0, _lerpDouble(blRadiusY, b.blRadiusY, t)),
+        uniformRadii: _uniformRadii,
       );
     }
   }
@@ -876,6 +896,7 @@
     required double brRadiusY,
     required double blRadiusX,
     required double blRadiusY,
+    required bool uniformRadii, // Not used. See `get _uniformRadii`.
   }) => RRect._raw(
     top: top,
     left: left,
@@ -891,6 +912,10 @@
     brRadiusY: brRadiusY,
   );
 
+  // RRect doesn't need uniformRadii for optimization for now.
+  @override
+  bool get _uniformRadii => false;
+
   static const RRect zero = RRect._raw();
 
   bool contains(Offset point) {
@@ -976,6 +1001,7 @@
         blRadiusY: radiusY,
         brRadiusX: radiusX,
         brRadiusY: radiusY,
+        uniformRadii: true,
       );
 
   RSuperellipse.fromLTRBR(double left, double top, double right, double bottom, Radius radius)
@@ -992,6 +1018,7 @@
         blRadiusY: radius.y,
         brRadiusX: radius.x,
         brRadiusY: radius.y,
+        uniformRadii: true,
       );
 
   RSuperellipse.fromRectXY(Rect rect, double radiusX, double radiusY)
@@ -1008,6 +1035,7 @@
         blRadiusY: radiusY,
         brRadiusX: radiusX,
         brRadiusY: radiusY,
+        uniformRadii: true,
       );
 
   RSuperellipse.fromRectAndRadius(Rect rect, Radius radius)
@@ -1024,6 +1052,7 @@
         blRadiusY: radius.y,
         brRadiusX: radius.x,
         brRadiusY: radius.y,
+        uniformRadii: true,
       );
 
   RSuperellipse.fromLTRBAndCorners(
@@ -1048,6 +1077,13 @@
          blRadiusY: bottomLeft.y,
          brRadiusX: bottomRight.x,
          brRadiusY: bottomRight.y,
+         uniformRadii:
+             topLeft.x == topRight.x &&
+             topLeft.y == topRight.y &&
+             topLeft.x == bottomLeft.x &&
+             topLeft.y == bottomLeft.y &&
+             topLeft.x == bottomRight.x &&
+             topLeft.y == bottomRight.y,
        );
 
   RSuperellipse.fromRectAndCorners(
@@ -1069,6 +1105,13 @@
          blRadiusY: bottomLeft.y,
          brRadiusX: bottomRight.x,
          brRadiusY: bottomRight.y,
+         uniformRadii:
+             topLeft.x == topRight.x &&
+             topLeft.y == topRight.y &&
+             topLeft.x == bottomLeft.x &&
+             topLeft.y == bottomLeft.y &&
+             topLeft.x == bottomRight.x &&
+             topLeft.y == bottomRight.y,
        );
 
   const RSuperellipse._raw({
@@ -1084,7 +1127,8 @@
     super.brRadiusY = 0.0,
     super.blRadiusX = 0.0,
     super.blRadiusY = 0.0,
-  });
+    bool uniformRadii = false,
+  }) : _uniformRadii = uniformRadii;
 
   @override
   RSuperellipse _create({
@@ -1100,6 +1144,7 @@
     required double brRadiusY,
     required double blRadiusX,
     required double blRadiusY,
+    required bool uniformRadii,
   }) => RSuperellipse._raw(
     top: top,
     left: left,
@@ -1113,8 +1158,34 @@
     blRadiusY: blRadiusY,
     brRadiusX: brRadiusX,
     brRadiusY: brRadiusY,
+    uniformRadii: uniformRadii,
   );
 
+  @override
+  final bool _uniformRadii;
+
+  /// (Web only) Returns a [Path] for this shape and an [Offset] for its
+  /// placement.
+  ///
+  /// To correctly position the shape, the caller is required to apply the
+  /// returned `offset` to the `path`.
+  ///
+  /// The returned path's coordinate system is relative to the `offset`. The
+  /// caller should not make any assumptions about the path's origin or whether
+  /// the offset is zero, as the implementation may use different strategies for
+  /// efficiency.
+  ///
+  /// For example, to draw the shape, first translate the canvas by the `offset`
+  /// and then draw the `path`. To add it to another path, provide both the
+  /// `path` and the `offset` to the `addPath` method.
+  (Path, Offset) toPathOffset() {
+    if (_uniformRadii) {
+      return (_RSuperellipseCache.instance.get(width, height, tlRadius), center);
+    } else {
+      return (_RSuperellipsePathBuilder.exact(this).path, Offset.zero);
+    }
+  }
+
   // Approximates a rounded superellipse with a round rectangle to the
   // best practical accuracy.
   //
@@ -1143,12 +1214,8 @@
   static const RSuperellipse zero = RSuperellipse._raw();
 
   bool contains(Offset point) {
-    // Web doesn't support RSuperellipse, but falls back to RRect in all use
-    // cases. Therefore this `contains` is implemented as RRect. Once Web
-    // supports RSuperellipse this method should be changed to the correct shape.
-    // TODO(dkwingsmt): Properly implement the shape on Web instead of
-    // falling back to RRect.  https://github.com/flutter/flutter/issues/163718
-    return toApproximateRRect().contains(point);
+    final (Path path, Offset offset) = toPathOffset();
+    return path.contains(point - offset);
   }
 
   static RSuperellipse? lerp(RSuperellipse? a, RSuperellipse? b, double t) {
@@ -1166,6 +1233,7 @@
     return _toString(className: 'RSuperellipse');
   }
 }
+
 // Modeled after Skia's SkRSXform.
 
 class RSTransform {
diff --git a/engine/src/flutter/lib/web_ui/lib/rsuperellipse_param.dart b/engine/src/flutter/lib/web_ui/lib/rsuperellipse_param.dart
new file mode 100644
index 0000000..d376e95
--- /dev/null
+++ b/engine/src/flutter/lib/web_ui/lib/rsuperellipse_param.dart
@@ -0,0 +1,561 @@
+// Copyright 2013 The Flutter Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+part of ui;
+
+// The logic in this file mirrors the implementation in
+// `round_superellipse_param.cc`, which has detailed comments.
+
+// A helper class for composing affine transformations.
+//
+// This is used to build and combine transforms because `dart:ui` does not
+// provide a direct API for matrix composition.
+extension type _Transform(Offset Function(Offset) apply) {
+  static _Transform makeComposite(_Transform second, _Transform first) {
+    return _Transform((Offset p) => second.apply(first.apply(p)));
+  }
+
+  static _Transform makeTranslate(Offset offset) {
+    return _Transform((Offset p) => Offset(p.dx + offset.dx, p.dy + offset.dy));
+  }
+
+  static _Transform makeScale(Offset scale) {
+    return _Transform((Offset p) => Offset(p.dx * scale.dx, p.dy * scale.dy));
+  }
+
+  static final _Transform kFlip = _Transform((Offset p) {
+    return Offset(p.dy, p.dx);
+  });
+}
+
+// The Path class extended with a few utility methods.
+extension type _RSuperellipsePath(Path path) {
+  void cubicToPoints(Offset p2, Offset p3, Offset p4) {
+    path.cubicTo(p2.dx, p2.dy, p3.dx, p3.dy, p4.dx, p4.dy);
+  }
+
+  void lineToPoint(Offset p) {
+    path.lineTo(p.dx, p.dy);
+  }
+}
+
+// An octant of an RSuperellipse, used in _RSuperellipseQuadrant.
+class _RSuperellipseOctant {
+  factory _RSuperellipseOctant(Offset center, double a, double radius) {
+    if (radius <= 0) {
+      return _RSuperellipseOctant.square(offset: center, seA: a);
+    }
+
+    final double ratio = a * 2 / radius;
+    final double g = kGapFactor * radius;
+
+    final (double n, double xJOverA) = _computeNAndXj(ratio);
+    final double xJ = xJOverA * a;
+    final double yJ = math.pow(1 - math.pow(xJOverA, n), 1 / n) * a;
+    final double maxTheta = math.asin(math.pow(xJOverA, n / 2));
+
+    final double tanPhiJ = math.pow(xJ / yJ, n - 1) as double;
+    final double d = (xJ - tanPhiJ * yJ) / (1 - tanPhiJ);
+    final double R = (a - d - g) * math.sqrt(2);
+
+    final Offset pointM = Offset(a - g, a - g);
+    final Offset pointJ = Offset(xJ, yJ);
+    final Offset circleCenter = radius == 0 ? pointM : _findCircleCenter(pointJ, pointM, R);
+    final double circleMaxAngle = radius == 0
+        ? 0
+        : _angleTo(pointM - circleCenter, pointJ - circleCenter);
+
+    return _RSuperellipseOctant._raw(
+      offset: center,
+      seA: a,
+      seN: n,
+      seMaxTheta: maxTheta,
+      circleStart: pointJ,
+      circleCenter: circleCenter,
+      circleMaxAngle: circleMaxAngle,
+    );
+  }
+
+  const _RSuperellipseOctant.square({required Offset offset, required double seA})
+    : this._raw(
+        offset: offset,
+        seA: seA,
+        seN: 0,
+        seMaxTheta: 0,
+        circleStart: Offset.zero,
+        circleCenter: Offset.zero,
+        circleMaxAngle: 0,
+      );
+
+  const _RSuperellipseOctant._raw({
+    required this.offset,
+    required this.seA,
+    required this.seN,
+    required this.seMaxTheta,
+    required this.circleStart,
+    required this.circleCenter,
+    required this.circleMaxAngle,
+  });
+
+  final Offset offset;
+  final double seA;
+  final double seN;
+  final double seMaxTheta;
+  final Offset circleStart;
+  final Offset circleCenter;
+  final double circleMaxAngle;
+
+  void addToPath(
+    _RSuperellipsePath path,
+    _Transform externalTransform, {
+    required bool reverse,
+    required bool flip,
+  }) {
+    _Transform transform = _Transform.makeComposite(
+      externalTransform,
+      _Transform.makeTranslate(offset),
+    );
+    if (flip) {
+      transform = _Transform.makeComposite(transform, _Transform.kFlip);
+    }
+
+    final List<Offset> circlePoints = _circularArcPoints();
+    final List<Offset> sePoints = _superellipseArcPoints();
+
+    if (!reverse) {
+      path.cubicToPoints(
+        transform.apply(sePoints[1]),
+        transform.apply(sePoints[2]),
+        transform.apply(sePoints[3]),
+      );
+      path.cubicToPoints(
+        transform.apply(circlePoints[1]),
+        transform.apply(circlePoints[2]),
+        transform.apply(circlePoints[3]),
+      );
+    } else {
+      path.cubicToPoints(
+        transform.apply(circlePoints[2]),
+        transform.apply(circlePoints[1]),
+        transform.apply(circlePoints[0]),
+      );
+      path.cubicToPoints(
+        transform.apply(sePoints[2]),
+        transform.apply(sePoints[1]),
+        transform.apply(sePoints[0]),
+      );
+    }
+  }
+
+  List<Offset> _superellipseArcPoints() {
+    final Offset start = Offset(0, seA);
+    final Offset end = circleStart;
+    const Offset startTangent = Offset(1, 0);
+    final Offset circleStartVector = circleStart - circleCenter;
+    final Offset endTangent =
+        Offset(-circleStartVector.dy, circleStartVector.dx) / circleStartVector.distance;
+
+    final (double startFactor, double endFactor) = _superellipseBezierFactors(seN);
+    return <Offset>[
+      start,
+      start + startTangent * startFactor * seA,
+      end + endTangent * endFactor * seA,
+      end,
+    ];
+  }
+
+  List<Offset> _circularArcPoints() {
+    final Offset startVector = circleStart - circleCenter;
+    final Offset endVector = _rotate(startVector, -circleMaxAngle);
+    final Offset circleEnd = circleCenter + endVector;
+    final Offset startTangent = Offset(startVector.dy, -startVector.dx) / startVector.distance;
+    final Offset endTangent = Offset(-endVector.dy, endVector.dx) / endVector.distance;
+    final double bezierFactor = math.tan(circleMaxAngle / 4) * 4 / 3;
+    final double radius = startVector.distance;
+
+    return <Offset>[
+      circleStart,
+      circleStart + startTangent * bezierFactor * radius,
+      circleEnd + endTangent * bezierFactor * radius,
+      circleEnd,
+    ];
+  }
+
+  static Offset _rotate(Offset p, double radians) {
+    final double cosine = math.cos(radians);
+    final double sine = math.sin(radians);
+    return Offset(p.dx * cosine - p.dy * sine, p.dx * sine + p.dy * cosine);
+  }
+
+  static (double, double) _superellipseBezierFactors(double n) {
+    const List<(double, double)> kPrecomputedVariables = [
+      /*n= 2.0*/ (0.01339448, 0.05994973),
+      /*n= 3.0*/ (0.13664115, 0.13592082),
+      /*n= 4.0*/ (0.24545546, 0.14099516),
+      /*n= 5.0*/ (0.32353151, 0.12808021),
+      /*n= 6.0*/ (0.39093068, 0.11726264),
+      /*n= 7.0*/ (0.44847800, 0.10808278),
+      /*n= 8.0*/ (0.49817452, 0.10026175),
+      /*n= 9.0*/ (0.54105583, 0.09344429),
+      /*n=10.0*/ (0.57812578, 0.08748984),
+      /*n=11.0*/ (0.61050961, 0.08224722),
+      /*n=12.0*/ (0.63903989, 0.07759639),
+      /*n=13.0*/ (0.66416338, 0.07346530),
+      /*n=14.0*/ (0.68675338, 0.06974996),
+      /*n=15.0*/ (0.70678034, 0.06529512),
+    ];
+    final int kNumRecords = kPrecomputedVariables.length;
+    const double kStep = 1.00;
+    const double kMinN = 2.00;
+    final double kMaxN = kMinN + (kNumRecords - 1) * kStep;
+
+    if (n >= kMaxN) {
+      return (
+        1.07 - math.exp(1.307649835) * math.pow(n, -0.8568516731),
+        -0.01 + math.exp(-0.9287690322) * math.pow(n, -0.6120901398),
+      );
+    }
+
+    final double steps = ((n - kMinN) / kStep).clamp(0, kNumRecords - 1);
+    final int left = steps.floor().clamp(0, kNumRecords - 2);
+    final double frac = steps - left;
+
+    return (
+      (1 - frac) * kPrecomputedVariables[left].$1 + frac * kPrecomputedVariables[left + 1].$1,
+      (1 - frac) * kPrecomputedVariables[left].$2 + frac * kPrecomputedVariables[left + 1].$2,
+    );
+  }
+
+  static Offset _findCircleCenter(Offset a, Offset b, double r) {
+    final Offset aToB = b - a;
+    final Offset m = (a + b) / 2;
+    final Offset cToM = Offset(-aToB.dy, aToB.dx);
+    final double distanceAm = aToB.distance / 2;
+    final double distanceCm = math.sqrt(r * r - distanceAm * distanceAm);
+    return m - cToM / cToM.distance * distanceCm;
+  }
+
+  static (double, double) _computeNAndXj(double ratio) {
+    const List<List<double>> kPrecomputedVariables = [
+      /*ratio=2.00*/ [2.00000000, 1.13276676],
+      /*ratio=2.10*/ [2.18349805, 1.20311921],
+      /*ratio=2.20*/ [2.33888662, 1.28698796],
+      /*ratio=2.30*/ [2.48660575, 1.36351941],
+      /*ratio=2.40*/ [2.62226596, 1.44717976],
+      /*ratio=2.50*/ [2.75148990, 1.53385819],
+      /*ratio=3.00*/ [3.36298265, 1.98288283],
+      /*ratio=3.50*/ [4.08649929, 2.23811846],
+      /*ratio=4.00*/ [4.85481134, 2.47563463],
+      /*ratio=4.50*/ [5.62945551, 2.72948597],
+      /*ratio=5.00*/ [6.43023796, 2.98020421],
+    ];
+
+    const double kMinRatio = 2.00;
+    const double kFirstStepInverse = 10; // = 1 / 0.10
+    const double kFirstMaxRatio = 2.50;
+    const double kFirstNumRecords = 6;
+    const double kSecondStepInverse = 2; // = 1 / 0.50
+    const double kSecondMaxRatio = 5.00;
+    const double kThirdNSlope = 1.559599389;
+    const double kThirdFactorXjSlope = 0.522807185;
+    final int kNumRecords = kPrecomputedVariables.length;
+
+    if (ratio > kSecondMaxRatio) {
+      final double n =
+          kThirdNSlope * (ratio - kSecondMaxRatio) + kPrecomputedVariables[kNumRecords - 1][0];
+      final double factorXj =
+          kThirdFactorXjSlope * (ratio - kSecondMaxRatio) +
+          kPrecomputedVariables[kNumRecords - 1][1];
+      return (n, 1 - 1 / factorXj);
+    }
+    ratio = ratio.clamp(kMinRatio, kSecondMaxRatio);
+    final double steps;
+    if (ratio < kFirstMaxRatio) {
+      steps = (ratio - kMinRatio) * kFirstStepInverse;
+    } else {
+      steps = (ratio - kFirstMaxRatio) * kSecondStepInverse + kFirstNumRecords - 1;
+    }
+
+    final int left = steps.floor().clamp(0, kNumRecords - 2);
+    final double frac = steps - left;
+
+    final double n =
+        (1 - frac) * kPrecomputedVariables[left][0] + frac * kPrecomputedVariables[left + 1][0];
+    final double factorXj =
+        (1 - frac) * kPrecomputedVariables[left][1] + frac * kPrecomputedVariables[left + 1][1];
+    return (n, 1 - 1 / factorXj);
+  }
+
+  static const double kGapFactor = 0.29289321881; // 1-cos(pi/4)
+
+  static double _angleTo(Offset a, Offset b) {
+    return math.atan2(a.dx * b.dy - a.dy * b.dx, a.dx * b.dx + a.dy * b.dy);
+  }
+}
+
+// A quadrant of an RSuperellipse, used in _RSuperellipsePathBuilder.
+class _RSuperellipseQuadrant {
+  factory _RSuperellipseQuadrant(Offset center, Offset corner, Radius inRadii, Size sign) {
+    final Offset cornerVector = corner - center;
+    final Size radii = Size(inRadii.x.abs(), inRadii.y.abs());
+
+    final double normRadius = radii.shortestSide;
+    final Size forwardScale = normRadius == 0 ? const Size(1, 1) : radii / normRadius;
+    final Size normHalfSize = Size(
+      cornerVector.dx.abs() / forwardScale.width,
+      cornerVector.dy.abs() / forwardScale.height,
+    );
+    final Offset signedScale = _replaceNaNWith(
+      Offset(cornerVector.dx / normHalfSize.width, cornerVector.dy / normHalfSize.height),
+      sign,
+    );
+
+    final double c = normHalfSize.width - normHalfSize.height;
+
+    return _RSuperellipseQuadrant._raw(
+      offset: center,
+      signedScale: signedScale,
+      sign: sign,
+      top: _RSuperellipseOctant(Offset(0, -c), normHalfSize.width, normRadius),
+      right: _RSuperellipseOctant(Offset(c, 0), normHalfSize.height, normRadius),
+    );
+  }
+
+  const _RSuperellipseQuadrant._raw({
+    required this.offset,
+    required this.signedScale,
+    required this.sign,
+    required this.top,
+    required this.right,
+  });
+
+  final Offset offset;
+  final Offset signedScale;
+  final Size sign;
+  final _RSuperellipseOctant top;
+  final _RSuperellipseOctant right;
+
+  void addToPath(
+    _RSuperellipsePath path, {
+    required bool reverse,
+    Size extraScale = const Size(1, 1),
+  }) {
+    final _Transform transform = _Transform.makeComposite(
+      _Transform.makeTranslate(offset),
+      _Transform.makeScale(signedScale.scale(extraScale.width, extraScale.height)),
+    );
+    if (top.seN < 2 || right.seN < 2) {
+      if (!reverse) {
+        final _Transform transformOctant = _Transform.makeComposite(
+          transform,
+          _Transform.makeTranslate(right.offset),
+        );
+        path.lineToPoint(transformOctant.apply(Offset(right.seA, right.seA)));
+        path.lineToPoint(transformOctant.apply(Offset(right.seA, 0)));
+      } else {
+        final _Transform transformOctant = _Transform.makeComposite(
+          transform,
+          _Transform.makeTranslate(top.offset),
+        );
+        path.lineToPoint(transformOctant.apply(Offset(top.seA, top.seA)));
+        path.lineToPoint(transformOctant.apply(Offset(0, top.seA)));
+      }
+      return;
+    }
+    if (!reverse) {
+      top.addToPath(path, transform, reverse: false, flip: false);
+      right.addToPath(path, transform, reverse: true, flip: true);
+    } else {
+      right.addToPath(path, transform, reverse: false, flip: true);
+      top.addToPath(path, transform, reverse: true, flip: false);
+    }
+  }
+
+  static Offset _replaceNaNWith(Offset p, Size sign) {
+    return Offset(p.dx.isFinite ? p.dx : sign.width, p.dy.isFinite ? p.dy : sign.height);
+  }
+}
+
+// A class that can build a path for a `RSuperellipse`.
+//
+// Used in `_RSuperellipsePathCache`.
+class _RSuperellipsePathBuilder {
+  // Build a path for a translated version of the provided RSuperellipse, so
+  // that the center of the bound is placed at the origin. The target RSuperellipse
+  // must also have a uniform radius.
+  _RSuperellipsePathBuilder.normalized(double width, double height, double radiusX, double radiusY)
+    : path = Path() {
+    final _RSuperellipsePath p = _RSuperellipsePath(path);
+    final _RSuperellipseQuadrant bottomRight = _RSuperellipseQuadrant(
+      Offset.zero,
+      Offset(width / 2, height / 2),
+      Radius.elliptical(radiusX, radiusY),
+      const Size(1, 1),
+    );
+    final Offset start = Offset(0, height / 2);
+    path.moveTo(start.dx, start.dy);
+    bottomRight.addToPath(p, reverse: false);
+    bottomRight.addToPath(p, reverse: true, extraScale: const Size(1, -1));
+    bottomRight.addToPath(p, reverse: false, extraScale: const Size(-1, -1));
+    bottomRight.addToPath(p, reverse: true, extraScale: const Size(-1, 1));
+    path.lineTo(start.dx, start.dy);
+    path.close();
+  }
+
+  // Build a path for an RSuperellipse with arbitrary position and radii.
+  _RSuperellipsePathBuilder.exact(RSuperellipse r) : path = Path() {
+    final _RSuperellipsePath p = _RSuperellipsePath(path);
+    final Offset start = Offset((r.left + r.right) / 2, r.top);
+    path.moveTo(start.dx, start.dy);
+
+    final double topSplit = _split(r.left, r.right, r.tlRadiusX, r.trRadiusX);
+    final double rightSplit = _split(r.top, r.bottom, r.trRadiusY, r.brRadiusY);
+    final double bottomSplit = _split(r.left, r.right, r.blRadiusX, r.brRadiusX);
+    final double leftSplit = _split(r.top, r.bottom, r.tlRadiusY, r.blRadiusY);
+    _RSuperellipseQuadrant(
+      Offset(topSplit, rightSplit),
+      Offset(r.right, r.top),
+      r.trRadius,
+      const Size(1, -1),
+    ).addToPath(p, reverse: false);
+    _RSuperellipseQuadrant(
+      Offset(bottomSplit, rightSplit),
+      Offset(r.right, r.bottom),
+      r.brRadius,
+      const Size(1, 1),
+    ).addToPath(p, reverse: true);
+    _RSuperellipseQuadrant(
+      Offset(bottomSplit, leftSplit),
+      Offset(r.left, r.bottom),
+      r.blRadius,
+      const Size(-1, 1),
+    ).addToPath(p, reverse: false);
+    _RSuperellipseQuadrant(
+      Offset(topSplit, leftSplit),
+      Offset(r.left, r.top),
+      r.tlRadius,
+      const Size(-1, -1),
+    ).addToPath(p, reverse: true);
+
+    path.lineTo(start.dx, start.dy);
+    path.close();
+  }
+
+  final Path path;
+
+  static double _split(double left, double right, double ratioLeft, double ratioRight) {
+    if (ratioLeft == 0 && ratioRight == 0) {
+      return (left + right) / 2;
+    }
+    return (left * ratioRight + right * ratioLeft) / (ratioLeft + ratioRight);
+  }
+}
+
+// The cache key for a `RSuperellipse` with uniform radii to be used in
+// `_RSuperellipseCache`.
+//
+// To handle floating-point precision issues and make it usable as a Map key,
+// we multiply each float by a factor and round it to an integer.
+class _RSuperellipseCacheKey {
+  _RSuperellipseCacheKey(double width, double height, double radiusX, double radiusY)
+    : _widthInt = (width * kPrecisionFactor).round(),
+      _heightInt = (height * kPrecisionFactor).round(),
+      _radiusXInt = (radiusX * kPrecisionFactor).round(),
+      _radiusYInt = (radiusY * kPrecisionFactor).round();
+
+  final int _widthInt;
+  final int _heightInt;
+  final int _radiusXInt;
+  final int _radiusYInt;
+
+  double get width => _widthInt / kPrecisionFactor;
+  double get height => _heightInt / kPrecisionFactor;
+  double get radiusX => _radiusXInt / kPrecisionFactor;
+  double get radiusY => _radiusYInt / kPrecisionFactor;
+
+  @override
+  bool operator ==(Object other) {
+    if (identical(this, other)) {
+      return true;
+    }
+    return other is _RSuperellipseCacheKey &&
+        _widthInt == other._widthInt &&
+        _heightInt == other._heightInt &&
+        _radiusXInt == other._radiusXInt &&
+        _radiusYInt == other._radiusYInt;
+  }
+
+  @override
+  int get hashCode => Object.hash(_widthInt, _heightInt, _radiusXInt, _radiusYInt);
+
+  @override
+  String toString() {
+    return '_RSuperellipseCacheKey('
+        'width: ${_widthInt / kPrecisionFactor},'
+        'height: ${_heightInt / kPrecisionFactor},'
+        'radiusX: ${_radiusXInt / kPrecisionFactor},'
+        'radiusY: ${_radiusYInt / kPrecisionFactor})';
+  }
+
+  static const double kPrecisionFactor = 100;
+}
+
+/// An LRU (Least Recently Used) cache that maps from normalized RSuperellipse
+/// to its path.
+class _RSuperellipseCache {
+  _RSuperellipseCache._({required this.capacity}) : assert(capacity > 0);
+
+  static final _RSuperellipseCache instance = _RSuperellipseCache._(capacity: kCapacity);
+
+  // A rough estimate by that a typical screen should hardly contain more than
+  // 20 RSuperellipses.
+  static const int kCapacity = 50;
+
+  final int capacity;
+
+  final Map<_RSuperellipseCacheKey, Path> _cache = <_RSuperellipseCacheKey, Path>{};
+
+  /// Retrieves a Path from the cache.
+  ///
+  /// If the path is found, it is moved to the most-recently-used position.
+  /// Otherwise, a new path is built and inserted.
+  Path get(double width, double height, Radius radius) {
+    final key = _RSuperellipseCacheKey(width, height, radius.x, radius.y);
+
+    // Remove the key and re-insert it to mark it as most recently used.
+    final Path? path = _cache.remove(key);
+    if (path != null) {
+      _cache[key] = path;
+      return path;
+    } else {
+      final Path newPath = _buildPath(key);
+      _cache[key] = newPath;
+      _checkCacheSize();
+      return newPath;
+    }
+  }
+
+  Path _buildPath(_RSuperellipseCacheKey key) {
+    return _RSuperellipsePathBuilder.normalized(
+      key.width,
+      key.height,
+      key.radiusX,
+      key.radiusY,
+    ).path;
+  }
+
+  /// Evicts all entries from the cache.
+  void clear() {
+    _cache.clear();
+  }
+
+  /// Remove entries from the cache until it is at or below the desired size.
+  void _checkCacheSize() {
+    while (_cache.length > capacity) {
+      // .keys.first gives the least recently used key in a `LinkedHashMap`.
+      _cache.remove(_cache.keys.first);
+    }
+    assert(_cache.length <= capacity);
+  }
+}
diff --git a/engine/src/flutter/lib/web_ui/lib/src/engine/canvaskit/canvas.dart b/engine/src/flutter/lib/web_ui/lib/src/engine/canvaskit/canvas.dart
index 7b3a99c..b52283e 100644
--- a/engine/src/flutter/lib/web_ui/lib/src/engine/canvaskit/canvas.dart
+++ b/engine/src/flutter/lib/web_ui/lib/src/engine/canvaskit/canvas.dart
@@ -5,6 +5,7 @@
 import 'dart:math' as math;
 import 'dart:typed_data';
 
+import 'package:ui/src/engine.dart' show LazyPath;
 import 'package:ui/ui.dart' as ui;
 
 import '../color_filter.dart';
@@ -53,13 +54,14 @@
   }
 
   void clipRSuperellipse(ui.RSuperellipse rsuperellipse, bool doAntiAlias) {
-    // TODO(dkwingsmt): Properly implement RSuperellipse on Web instead of falling
-    // back to RRect.  https://github.com/flutter/flutter/issues/163718
-    skCanvas.clipRRect(
-      toSkRRect(rsuperellipse.toApproximateRRect()),
+    final (ui.Path path, ui.Offset offset) = rsuperellipse.toPathOffset();
+    translate(offset.dx, offset.dy);
+    skCanvas.clipPath(
+      ((path as LazyPath).builtPath as CkPath).skiaObject,
       _clipOpIntersect,
       doAntiAlias,
     );
+    translate(-offset.dx, -offset.dy);
   }
 
   void clipRect(ui.Rect rect, ui.ClipOp clipOp, bool doAntiAlias) {
@@ -228,6 +230,15 @@
     skPaint.delete();
   }
 
+  void drawRSuperellipse(ui.RSuperellipse rsuperellipse, CkPaint paint) {
+    final skPaint = paint.toSkPaint();
+    final (ui.Path path, ui.Offset offset) = rsuperellipse.toPathOffset();
+    translate(offset.dx, offset.dy);
+    skCanvas.drawPath(((path as LazyPath).builtPath as CkPath).skiaObject, skPaint);
+    translate(-offset.dx, -offset.dy);
+    skPaint.delete();
+  }
+
   void drawRect(ui.Rect rect, CkPaint paint) {
     final skPaint = paint.toSkPaint();
     skCanvas.drawRect(toSkRect(rect), skPaint);
diff --git a/engine/src/flutter/lib/web_ui/lib/src/engine/canvaskit/canvaskit_canvas.dart b/engine/src/flutter/lib/web_ui/lib/src/engine/canvaskit/canvaskit_canvas.dart
index e78dcf8..8ca9e39 100644
--- a/engine/src/flutter/lib/web_ui/lib/src/engine/canvaskit/canvaskit_canvas.dart
+++ b/engine/src/flutter/lib/web_ui/lib/src/engine/canvaskit/canvaskit_canvas.dart
@@ -121,10 +121,7 @@
 
   @override
   void clipRSuperellipse(ui.RSuperellipse rsuperellipse, {bool doAntiAlias = true}) {
-    assert(rsuperellipseIsValid(rsuperellipse));
-    // TODO(dkwingsmt): Properly implement RSuperellipse on Web instead of falling
-    // back to RRect.  https://github.com/flutter/flutter/issues/163718
-    _clipRRect(rsuperellipse.toApproximateRRect(), doAntiAlias);
+    _canvas.clipRSuperellipse(rsuperellipse, doAntiAlias);
   }
 
   @override
@@ -198,10 +195,7 @@
 
   @override
   void drawRSuperellipse(ui.RSuperellipse rsuperellipse, ui.Paint paint) {
-    assert(rsuperellipseIsValid(rsuperellipse));
-    // TODO(dkwingsmt): Properly implement RSuperellipse on Web instead of falling
-    // back to RRect.  https://github.com/flutter/flutter/issues/163718
-    _drawRRect(rsuperellipse.toApproximateRRect(), paint);
+    _canvas.drawRSuperellipse(rsuperellipse, paint as CkPaint);
   }
 
   @override
diff --git a/engine/src/flutter/lib/web_ui/lib/src/engine/canvaskit/embedded_views.dart b/engine/src/flutter/lib/web_ui/lib/src/engine/canvaskit/embedded_views.dart
index 7fbcc26..2d3c551 100644
--- a/engine/src/flutter/lib/web_ui/lib/src/engine/canvaskit/embedded_views.dart
+++ b/engine/src/flutter/lib/web_ui/lib/src/engine/canvaskit/embedded_views.dart
@@ -815,6 +815,12 @@
     _mutators.add(Mutator.clipRRect(rrect));
   }
 
+  void pushClipRSuperellipse(ui.RSuperellipse rsuperellipse) {
+    // RSuperellipse ops in PlatformView are approximated by RRect because they
+    // are expensive.
+    pushClipRRect(rsuperellipse.toApproximateRRect());
+  }
+
   void pushClipPath(ui.Path path) {
     _mutators.add(Mutator.clipPath(path));
   }
diff --git a/engine/src/flutter/lib/web_ui/lib/src/engine/canvaskit/layer.dart b/engine/src/flutter/lib/web_ui/lib/src/engine/canvaskit/layer.dart
index c593657..f5d8c5e 100644
--- a/engine/src/flutter/lib/web_ui/lib/src/engine/canvaskit/layer.dart
+++ b/engine/src/flutter/lib/web_ui/lib/src/engine/canvaskit/layer.dart
@@ -112,7 +112,7 @@
   }
 }
 
-/// A layer that clips its child layers by a given [RRect].
+/// A layer that clips its child layers by a given [RSuperellipse].
 class ClipRSuperellipseEngineLayer extends ContainerLayer
     implements ui.ClipRSuperellipseEngineLayer {
   ClipRSuperellipseEngineLayer(this.clipRSuperellipse, this.clipBehavior)
diff --git a/engine/src/flutter/lib/web_ui/lib/src/engine/canvaskit/layer_visitor.dart b/engine/src/flutter/lib/web_ui/lib/src/engine/canvaskit/layer_visitor.dart
index f1e14b2..e12f536 100644
--- a/engine/src/flutter/lib/web_ui/lib/src/engine/canvaskit/layer_visitor.dart
+++ b/engine/src/flutter/lib/web_ui/lib/src/engine/canvaskit/layer_visitor.dart
@@ -112,9 +112,7 @@
 
   @override
   void visitClipRSuperellipse(ClipRSuperellipseEngineLayer clipRSuperellipse) {
-    // TODO(dkwingsmt): Properly implement RSuperellipse on Web instead of falling
-    // back to RRect.  https://github.com/flutter/flutter/issues/163718
-    mutatorsStack.pushClipRRect(clipRSuperellipse.clipRSuperellipse.toApproximateRRect());
+    mutatorsStack.pushClipRSuperellipse(clipRSuperellipse.clipRSuperellipse);
     final ui.Rect childPaintBounds = prerollChildren(clipRSuperellipse);
     if (childPaintBounds.overlaps(clipRSuperellipse.clipRSuperellipse.outerRect)) {
       clipRSuperellipse.paintBounds = childPaintBounds.intersect(
@@ -575,12 +573,9 @@
   @override
   void visitClipRSuperellipse(ClipRSuperellipseEngineLayer clipRSuperellipse) {
     assert(clipRSuperellipse.needsPainting);
-
     nWayCanvas.save();
-    // TODO(dkwingsmt): Properly implement RSuperellipse on Web instead of falling
-    // back to RRect.  https://github.com/flutter/flutter/issues/163718
-    nWayCanvas.clipRRect(
-      clipRSuperellipse.clipRSuperellipse.toApproximateRRect(),
+    nWayCanvas.clipRSuperellipse(
+      clipRSuperellipse.clipRSuperellipse,
       clipRSuperellipse.clipBehavior != ui.Clip.hardEdge,
     );
     if (clipRSuperellipse.clipBehavior == ui.Clip.antiAliasWithSaveLayer) {
diff --git a/engine/src/flutter/lib/web_ui/lib/src/engine/canvaskit/n_way_canvas.dart b/engine/src/flutter/lib/web_ui/lib/src/engine/canvaskit/n_way_canvas.dart
index d494ba1..7488601 100644
--- a/engine/src/flutter/lib/web_ui/lib/src/engine/canvaskit/n_way_canvas.dart
+++ b/engine/src/flutter/lib/web_ui/lib/src/engine/canvaskit/n_way_canvas.dart
@@ -96,4 +96,11 @@
       _canvases[i].clipRRect(rrect, doAntiAlias);
     }
   }
+
+  /// Calls [clipRSuperellipse] on all canvases.
+  void clipRSuperellipse(ui.RSuperellipse rsuperellipse, bool doAntiAlias) {
+    // RSuperellipse ops in PlatformView are approximated by RRect because they
+    // are expensive.
+    return clipRRect(rsuperellipse.toApproximateRRect(), doAntiAlias);
+  }
 }
diff --git a/engine/src/flutter/lib/web_ui/lib/src/engine/canvaskit/path.dart b/engine/src/flutter/lib/web_ui/lib/src/engine/canvaskit/path.dart
index 014126a..e5fbc5d 100644
--- a/engine/src/flutter/lib/web_ui/lib/src/engine/canvaskit/path.dart
+++ b/engine/src/flutter/lib/web_ui/lib/src/engine/canvaskit/path.dart
@@ -114,9 +114,8 @@
 
   @override
   void addRSuperellipse(ui.RSuperellipse rsuperellipse) {
-    // TODO(dkwingsmt): Properly implement RSuperellipse on Web instead of falling
-    // back to RRect.  https://github.com/flutter/flutter/issues/163718
-    addRRect(rsuperellipse.toApproximateRRect());
+    final (ui.Path path, ui.Offset offset) = rsuperellipse.toPathOffset();
+    addPath((path as LazyPath).builtPath, offset);
   }
 
   @override
diff --git a/engine/src/flutter/lib/web_ui/lib/src/engine/layers.dart b/engine/src/flutter/lib/web_ui/lib/src/engine/layers.dart
index e83a642..d940772 100644
--- a/engine/src/flutter/lib/web_ui/lib/src/engine/layers.dart
+++ b/engine/src/flutter/lib/web_ui/lib/src/engine/layers.dart
@@ -324,8 +324,8 @@
 
   @override
   PlatformViewStyling createPlatformViewStyling() {
-    // TODO(dkwingsmt): Properly implement RSuperellipse on Web instead of falling
-    // back to RRect.  https://github.com/flutter/flutter/issues/163718
+    // RSuperellipse ops in PlatformView are approximated by RRect because they
+    // are expensive.
     return PlatformViewStyling(clip: PlatformViewRRectClip(rsuperellipse.toApproximateRRect()));
   }
 
@@ -338,7 +338,7 @@
   @override
   Map<String, Object> get debugJsonDescription {
     return <String, Object>{
-      'type': 'clipRSuperEllipse',
+      'type': 'clipRSuperellipse',
       'rsuperellipse': {
         'left': rsuperellipse.left,
         'top': rsuperellipse.top,
diff --git a/engine/src/flutter/lib/web_ui/lib/src/engine/skwasm/skwasm_impl/canvas.dart b/engine/src/flutter/lib/web_ui/lib/src/engine/skwasm/skwasm_impl/canvas.dart
index 8e494e0..9da4ca6 100644
--- a/engine/src/flutter/lib/web_ui/lib/src/engine/skwasm/skwasm_impl/canvas.dart
+++ b/engine/src/flutter/lib/web_ui/lib/src/engine/skwasm/skwasm_impl/canvas.dart
@@ -136,9 +136,10 @@
 
   @override
   void clipRSuperellipse(ui.RSuperellipse rsuperellipse, {bool doAntiAlias = true}) {
-    // TODO(dkwingsmt): Properly implement RSuperellipse on Web instead of falling
-    // back to RRect.  https://github.com/flutter/flutter/issues/163718
-    clipRRect(rsuperellipse.toApproximateRRect(), doAntiAlias: doAntiAlias);
+    final (ui.Path path, ui.Offset offset) = rsuperellipse.toPathOffset();
+    translate(offset.dx, offset.dy);
+    clipPath(path, doAntiAlias: doAntiAlias);
+    translate(-offset.dx, -offset.dy);
   }
 
   @override
@@ -184,9 +185,10 @@
 
   @override
   void drawRSuperellipse(ui.RSuperellipse rsuperellipse, ui.Paint paint) {
-    // TODO(dkwingsmt): Properly implement RSuperellipse on Web instead of falling
-    // back to RRect.  https://github.com/flutter/flutter/issues/163718
-    drawRRect(rsuperellipse.toApproximateRRect(), paint);
+    final (ui.Path path, ui.Offset offset) = rsuperellipse.toPathOffset();
+    translate(offset.dx, offset.dy);
+    drawPath(path, paint);
+    translate(-offset.dx, -offset.dy);
   }
 
   @override
diff --git a/engine/src/flutter/lib/web_ui/lib/src/engine/skwasm/skwasm_impl/path.dart b/engine/src/flutter/lib/web_ui/lib/src/engine/skwasm/skwasm_impl/path.dart
index 5323e5d..0486f45 100644
--- a/engine/src/flutter/lib/web_ui/lib/src/engine/skwasm/skwasm_impl/path.dart
+++ b/engine/src/flutter/lib/web_ui/lib/src/engine/skwasm/skwasm_impl/path.dart
@@ -174,9 +174,8 @@
 
   @override
   void addRSuperellipse(ui.RSuperellipse rsuperellipse) {
-    // TODO(dkwingsmt): Properly implement RSuperellipse on Web instead of falling
-    // back to RRect.  https://github.com/flutter/flutter/issues/163718
-    addRRect(rsuperellipse.toApproximateRRect());
+    final (ui.Path path, ui.Offset offset) = rsuperellipse.toPathOffset();
+    addPath((path as LazyPath).builtPath, offset);
   }
 
   @override
diff --git a/engine/src/flutter/lib/web_ui/lib/ui.dart b/engine/src/flutter/lib/web_ui/lib/ui.dart
index bd53947..3d4addf 100644
--- a/engine/src/flutter/lib/web_ui/lib/ui.dart
+++ b/engine/src/flutter/lib/web_ui/lib/ui.dart
@@ -30,6 +30,7 @@
 part 'platform_dispatcher.dart';
 part 'platform_isolate.dart';
 part 'pointer.dart';
+part 'rsuperellipse_param.dart';
 part 'semantics.dart';
 part 'text.dart';
 part 'tile_mode.dart';
diff --git a/engine/src/flutter/lib/web_ui/test/canvaskit/rsuperellipse_contains_test.dart b/engine/src/flutter/lib/web_ui/test/canvaskit/rsuperellipse_contains_test.dart
new file mode 100644
index 0000000..27cfd6a
--- /dev/null
+++ b/engine/src/flutter/lib/web_ui/test/canvaskit/rsuperellipse_contains_test.dart
@@ -0,0 +1,168 @@
+// Copyright 2013 The Flutter Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+import 'package:test/bootstrap/browser.dart';
+import 'package:test/test.dart';
+
+import 'package:ui/src/engine.dart';
+import 'package:ui/ui.dart';
+
+import 'common.dart';
+
+// Tests for `RSuperellipse.contains` are placed in the canvaskit folder instead
+// of the engine folder because `RSuperellipse.contains` is implemented with
+// paths, which needs a renderer.
+//
+// Some of the numbers slightly deviate from round_superellipse_unittests
+// because it uses Bezier path approximation.
+
+void main() {
+  internalBootstrapBrowserTest(() => testMain);
+}
+
+void testMain() {
+  group('RSuperellipse.contains', () {
+    setUpCanvasKitTest();
+
+    tearDown(() {
+      CanvasKitRenderer.instance.debugClear();
+    });
+
+    test('RSuperellipse.contains is correct with no corners', () {
+      // RSuperellipse of bounds with no corners contains corners just barely.
+      const RSuperellipse rse = RSuperellipse.fromLTRBXY(-50, -50, 50, 50, 0, 0);
+
+      expect(rse.contains(const Offset(-50, -50)), isTrue);
+      // Rectangles have half-in, half-out containment so we need
+      // to be careful about testing containment of right/bottom corners.
+      expect(rse.contains(const Offset(-50, 49.99)), isTrue);
+      expect(rse.contains(const Offset(49.99, -50)), isTrue);
+      expect(rse.contains(const Offset(49.99, 49.99)), isTrue);
+      expect(rse.contains(const Offset(-50.01, -50)), isFalse);
+      expect(rse.contains(const Offset(-50, -50.01)), isFalse);
+      expect(rse.contains(const Offset(-50.01, 50)), isFalse);
+      expect(rse.contains(const Offset(-50, 50.01)), isFalse);
+      expect(rse.contains(const Offset(50.01, -50)), isFalse);
+      expect(rse.contains(const Offset(50, -50.01)), isFalse);
+      expect(rse.contains(const Offset(50.01, 50)), isFalse);
+      expect(rse.contains(const Offset(50, 50.01)), isFalse);
+    });
+
+    test('RSuperellipse.contains is correct with tiny corners', () {
+      // RSuperellipse of bounds with even the tiniest corners does not contain corners.
+      const RSuperellipse rse = RSuperellipse.fromLTRBXY(-50, -50, 50, 50, 0.01, 0.01);
+
+      expect(rse.contains(const Offset(-50, -50)), isFalse);
+      expect(rse.contains(const Offset(-50, 50)), isFalse);
+      expect(rse.contains(const Offset(50, -50)), isFalse);
+      expect(rse.contains(const Offset(50, 50)), isFalse);
+    });
+
+    test('RSuperellipse.contains is correct with uniform corners', () {
+      const RSuperellipse rse = RSuperellipse.fromLTRBXY(-50, -50, 50, 50, 5.0, 5.0);
+
+      void checkPointAndMirrors(Offset p) {
+        checkPointWithOffset(rse, Offset(p.dx, p.dy), const Offset(0.02, 0.02));
+        checkPointWithOffset(rse, Offset(p.dx, -p.dy), const Offset(0.02, -0.02));
+        checkPointWithOffset(rse, Offset(-p.dx, p.dy), const Offset(-0.02, 0.02));
+        checkPointWithOffset(rse, Offset(-p.dx, -p.dy), const Offset(-0.02, -0.02));
+      }
+
+      checkPointAndMirrors(const Offset(0, 49.995)); // Top
+      checkPointAndMirrors(const Offset(44.15, 49.95)); // Top curve start
+      checkPointAndMirrors(const Offset(45.72, 49.87)); // Top joint
+      checkPointAndMirrors(const Offset(48.53, 48.53)); // Circular arc mid
+      checkPointAndMirrors(const Offset(49.87, 45.72)); // Right joint
+      checkPointAndMirrors(const Offset(49.95, 44.15)); // Right curve start
+      checkPointAndMirrors(const Offset(49.995, 0)); // Right
+    });
+
+    test('RSuperellipse.contains is correct with uniform elliptical corners', () {
+      const RSuperellipse rse = RSuperellipse.fromLTRBXY(-50, -50, 50, 50, 5.0, 10.0);
+
+      void checkPointAndMirrors(Offset p) {
+        checkPointWithOffset(rse, Offset(p.dx, p.dy), const Offset(0.02, 0.02));
+        checkPointWithOffset(rse, Offset(p.dx, -p.dy), const Offset(0.02, -0.02));
+        checkPointWithOffset(rse, Offset(-p.dx, p.dy), const Offset(-0.02, 0.02));
+        checkPointWithOffset(rse, Offset(-p.dx, -p.dy), const Offset(-0.02, -0.02));
+      }
+
+      checkPointAndMirrors(const Offset(0, 49.995)); // Top
+      checkPointAndMirrors(const Offset(43.90, 49.911)); // Top curve start
+      checkPointAndMirrors(const Offset(45.69, 49.75)); // Top joint
+      checkPointAndMirrors(const Offset(48.51, 47.07)); // Circular arc mid
+      checkPointAndMirrors(const Offset(49.87, 41.44)); // Right joint
+      checkPointAndMirrors(const Offset(49.95, 38.49)); // Right curve start
+      checkPointAndMirrors(const Offset(49.995, 0)); // Right
+    });
+
+    test('RSuperellipse.contains is correct with uniform corners and unequal height and width', () {
+      // The bounds is not centered at the origin and has unequal height and width.
+      const RSuperellipse rse = RSuperellipse.fromLTRBXY(0, 0, 50, 100, 23.0, 30.0);
+
+      final Offset center = rse.outerRect.center;
+      void checkPointAndMirrors(Offset globalPoint) {
+        final Offset p = globalPoint - center;
+        checkPointWithOffset(rse, Offset(p.dx, p.dy) + center, const Offset(0.02, 0.02));
+        checkPointWithOffset(rse, Offset(p.dx, -p.dy) + center, const Offset(0.02, -0.02));
+        checkPointWithOffset(rse, Offset(-p.dx, p.dy) + center, const Offset(-0.02, 0.02));
+        checkPointWithOffset(rse, Offset(-p.dx, -p.dy) + center, const Offset(-0.02, -0.02));
+      }
+
+      checkPointAndMirrors(const Offset(24.99, 99.99)); // Bottom mid-edge
+      checkPointAndMirrors(const Offset(29.99, 99.64));
+      checkPointAndMirrors(const Offset(34.99, 98.06));
+      checkPointAndMirrors(const Offset(39.99, 94.73));
+      checkPointAndMirrors(const Offset(44.13, 89.99));
+      checkPointAndMirrors(const Offset(48.46, 79.99));
+      checkPointAndMirrors(const Offset(49.68, 69.99));
+      checkPointAndMirrors(const Offset(49.97, 59.99));
+      checkPointAndMirrors(const Offset(49.99, 49.99)); // Right mid-edge
+    });
+
+    test('RSuperellipse.contains is correct for a slim diagonal shape', () {
+      // This shape has large radii on one diagonal and tiny radii on the other,
+      // resulting in a almond-like shape placed diagonally (NW to SE).
+      final RSuperellipse rse = RSuperellipse.fromLTRBAndCorners(
+        -50,
+        -50,
+        50,
+        50,
+        topLeft: const Radius.circular(1.0),
+        topRight: const Radius.circular(99.0),
+        bottomLeft: const Radius.circular(99.0),
+        bottomRight: const Radius.circular(1.0),
+      );
+
+      expect(rse.contains(Offset.zero), isTrue);
+      expect(rse.contains(const Offset(-49.999, -49.999)), isFalse);
+      expect(rse.contains(const Offset(-49.999, 49.999)), isFalse);
+      expect(rse.contains(const Offset(49.999, 49.999)), isFalse);
+      expect(rse.contains(const Offset(49.999, -49.999)), isFalse);
+
+      // The pointy ends at the NE and SW corners
+      checkPointWithOffset(rse, const Offset(-49.70, -49.70), const Offset(-0.02, -0.02));
+      checkPointWithOffset(rse, const Offset(49.70, 49.70), const Offset(0.02, 0.02));
+
+      // Checks two points symmetrical to the origin.
+      void checkDiagonalPoints(Offset p) {
+        checkPointWithOffset(rse, p, const Offset(0.02, -0.02));
+        checkPointWithOffset(rse, Offset(-p.dx, -p.dy), const Offset(-0.02, 0.02));
+      }
+
+      // A few other points along the edge
+      checkDiagonalPoints(const Offset(-40.0, -49.59));
+      checkDiagonalPoints(const Offset(-20.0, -45.64));
+      checkDiagonalPoints(const Offset(0.0, -37.01));
+      checkDiagonalPoints(const Offset(20.0, -21.96));
+      checkDiagonalPoints(const Offset(21.05, -20.92));
+      checkDiagonalPoints(const Offset(40.0, 5.68));
+    });
+  });
+}
+
+void checkPointWithOffset(RSuperellipse rse, Offset inPoint, Offset outwardOffset) {
+  expect(rse.contains(inPoint), isTrue);
+  expect(rse.contains(inPoint + outwardOffset), isFalse);
+}
diff --git a/engine/src/flutter/testing/dart/geometry_test.dart b/engine/src/flutter/testing/dart/geometry_test.dart
index e9c7943..2c0cf50 100644
--- a/engine/src/flutter/testing/dart/geometry_test.dart
+++ b/engine/src/flutter/testing/dart/geometry_test.dart
@@ -664,9 +664,9 @@
     checkPointAndMirrors(const Offset(49.99, 49.99)); // Right mid-edge
   });
 
-  test('RSuperellipse.contains is correct for a slim diagnal shape', () {
-    // This shape has large radii on one diagnal and tiny radii on the other,
-    // resulting in a almond-like shape placed diagnally (NW to SE).
+  test('RSuperellipse.contains is correct for a slim diagonal shape', () {
+    // This shape has large radii on one diagonal and tiny radii on the other,
+    // resulting in a almond-like shape placed diagonally (NW to SE).
     final RSuperellipse rse = RSuperellipse.fromLTRBAndCorners(
       -50,
       -50,
@@ -689,18 +689,18 @@
     checkPointWithOffset(rse, const Offset(49.70, 49.70), const Offset(0.02, 0.02));
 
     // Checks two points symmetrical to the origin.
-    void checkDiagnalPoints(Offset p) {
+    void checkDiagonalPoints(Offset p) {
       checkPointWithOffset(rse, p, const Offset(0.02, -0.02));
       checkPointWithOffset(rse, Offset(-p.dx, -p.dy), const Offset(-0.02, 0.02));
     }
 
     // A few other points along the edge
-    checkDiagnalPoints(const Offset(-40.0, -49.59));
-    checkDiagnalPoints(const Offset(-20.0, -45.64));
-    checkDiagnalPoints(const Offset(0.0, -37.01));
-    checkDiagnalPoints(const Offset(20.0, -21.96));
-    checkDiagnalPoints(const Offset(21.05, -20.92));
-    checkDiagnalPoints(const Offset(40.0, 5.68));
+    checkDiagonalPoints(const Offset(-40.0, -49.59));
+    checkDiagonalPoints(const Offset(-20.0, -45.64));
+    checkDiagonalPoints(const Offset(0.0, -37.01));
+    checkDiagonalPoints(const Offset(20.0, -21.96));
+    checkDiagonalPoints(const Offset(21.05, -20.92));
+    checkDiagonalPoints(const Offset(40.0, 5.68));
   });
 
   test('RSuperellipse.contains is correct for points outside of a sharp corner', () {
diff --git a/engine/src/flutter/testing/dart/path_test.dart b/engine/src/flutter/testing/dart/path_test.dart
index 8ae3c83..299ccd3 100644
--- a/engine/src/flutter/testing/dart/path_test.dart
+++ b/engine/src/flutter/testing/dart/path_test.dart
@@ -256,7 +256,7 @@
     );
   });
 
-  test('RSuperellipse path is correct for a slim diagnal shape', () {
+  test('RSuperellipse path is correct for a slim diagonal shape', () {
     // This test mirrors a similar test from "geometry_test.dart" and serves as
     // a smoke test.
     final RSuperellipse rsuperellipse = RSuperellipse.fromLTRBAndCorners(
@@ -284,18 +284,18 @@
     checkPointWithOffset(path, const Offset(49.70, 49.70), const Offset(0.02, 0.02));
 
     // Checks two points symmetrical to the origin.
-    void checkDiagnalPoints(Offset p) {
+    void checkDiagonalPoints(Offset p) {
       checkPointWithOffset(path, p, const Offset(0.02, -0.02));
       checkPointWithOffset(path, Offset(-p.dx, -p.dy), const Offset(-0.02, 0.02));
     }
 
     // A few other points along the edge
-    checkDiagnalPoints(const Offset(-40.0, -49.59));
-    checkDiagnalPoints(const Offset(-20.0, -45.64));
-    checkDiagnalPoints(const Offset(0.0, -37.01));
-    checkDiagnalPoints(const Offset(20.0, -21.96));
-    checkDiagnalPoints(const Offset(21.05, -20.92));
-    checkDiagnalPoints(const Offset(40.0, 5.68));
+    checkDiagonalPoints(const Offset(-40.0, -49.59));
+    checkDiagonalPoints(const Offset(-20.0, -45.64));
+    checkDiagonalPoints(const Offset(0.0, -37.01));
+    checkDiagonalPoints(const Offset(20.0, -21.96));
+    checkDiagonalPoints(const Offset(21.05, -20.92));
+    checkDiagonalPoints(const Offset(40.0, 5.68));
   });
 }