Skip to content

[web] Share core dart:ui primitives between CanvasKit and Skwasm #175630

@harryterkelsen

Description

@harryterkelsen

The most immediate source of code duplication between the CanvasKit and Skwasm backends is in the implementation of the core dart:ui primitives, such as Canvas, Paint, and Path. For nearly every primitive, there is a CanvasKit-specific version (Ck...) and a Skwasm-specific version (Skwasm...).

Current State & Evidence of Duplication

Both backends implement the same interfaces (e.g., LayerCanvas, which implements ui.Canvas), but do so in parallel, leading to nearly identical code. The methods in these classes contain the same high-level logic: validate arguments, convert Flutter types to a native representation, make the native call, and manage the lifecycle of the native object.

This pattern is repeated for dozens of methods. A clear example is drawRect:

lib/src/engine/canvaskit/canvas.dart (CkCanvas)

@override
void drawRect(ui.Rect rect, ui.Paint paint) {
  assert(rectIsValid(rect));
  final skPaint = (paint as CkPaint).toSkPaint();
  skCanvas.drawRect(toSkRect(rect), skPaint);
  skPaint.delete();
}

lib/src/engine/skwasm/skwasm_impl/canvas.dart (SkwasmCanvas)

@override
void drawRect(ui.Rect rect, ui.Paint paint) {
  final paintHandle = (paint as SkwasmPaint).toRawPaint();
  withStackScope((StackScope s) {
    canvasDrawRect(_handle, s.convertRectToNative(rect), paintHandle);
  });
  paintDispose(paintHandle);
}

The logic is identical: get a native paint object, convert the Rect, make the native call, and dispose of the paint object. Only the final, backend-specific API call is different.

Duplication Leads to Bugs and Inconsistent Behavior

A more critical issue with this parallel implementation is that bug fixes and edge-case handling applied to one backend are often not ported to the other. This leads to a divergence in behavior and hard-to-diagnose, backend-specific bugs.

A perfect historical example of this was a crash that occurred when creating an ImageFilter.blur with sigma values of zero. This was fixed in CanvasKit, but because the logic was not shared, the bug persisted in the Skwasm backend until the fix was manually ported. Unifying the implementations would have prevented this discrepancy.

Proposed Unification

To eliminate this duplication, the common logic should be extracted into a shared base class or a composition layer. The backend-specific implementations (Ck... and Skwasm...) would then inherit this shared logic and would only be responsible for providing the final, backend-specific native call.

This ensures that validation, type conversion, lifecycle management, and bug fixes are implemented once and applied to both backends simultaneously, improving correctness and consistency across the entire web platform.

Metadata

Metadata

Assignees

No one assigned

    Labels

    P2Important issues not at the top of the work liste: web_canvaskitCanvasKit (a.k.a. Skia-on-WebGL) rendering backend for Webe: web_skwasmSkwasm rendering backend for webplatform-webWeb applications specificallyteam-webOwned by Web platform teamtriaged-webTriaged by Web platform team

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions