toeverything/AFFiNE · critical

Unable to export content to canvas

Error message

Unable to export content to canvas

What it means

Before running a factory, Resolver.track checks whether the requested identifier (name+variant) already appears on the current resolution stack; if so, the container throws CircularDependencyError with the full cycle printed as [A] -> [B] -> ... -> [A]. This detects constructor-level dependency cycles at resolve time instead of leaking half-built instances or overflowing the JS stack.

Solutions

  1. Break the cycle by resolving the back-reference lazily: inject the provider/resolver and call get(...) inside methods, not in the constructor/deps array
  2. Extract the functionality both sides need into a third component that both depend on instead of on each other
  3. Use the printed dependency stack to find the exact pair, remove one edge; for notification-style coupling use eventBus instead of direct injection

Example fix

// before
fw.service(Editor, [Document]);
fw.service(Document, [Editor]); // CircularDependencyError: [Editor] -> [Document] -> [Editor]

// after
fw.service(Document, []);
fw.service(Editor, [Document]); // one direction only; use eventBus for the way back
Defensive patterns

Strategy: try-catch

Validate before calling

null

Type guard

null

Try / catch

import { CircularDependencyError } from '.../framework/core/error';

try {
  provider.get(Editor);
} catch (err) {
  if (err instanceof CircularDependencyError) {
    // err.dependencyStack prints the exact cycle; break one edge in the graph
  } else {
    throw err;
  }
}

Prevention

When it happens

Trigger: framework.service(A, [B]) and framework.service(B, [A]); a component that (directly or through a variant) injects itself; cycles that cross scopes and are resolved with sameScope chains.

Common situations: Two services that naturally reference each other (editor <-> document, auth <-> router); a refactor that adds a dependency and accidentally closes a loop; entity hierarchies with parent back-references injected via constructor.

Related errors


AI-assisted analysis of toeverything/AFFiNE@b4c8548c09 (2026-08-18). Data as JSON: /api/errors/d6178f25afaac292. Report an issue: GitHub.

Appendix: source

Thrown at blocksuite/affine/blocks/root/src/edgeless/clipboard/clipboard.ts:427

  private get crud() {
    return this.std.get(EdgelessCRUDIdentifier);
  }

  private get toolManager() {
    return this.gfx.tool;
  }

  private _checkCanContinueToCanvas(
    host: EditorHost,
    pathName: string,
    editorMode: boolean
  ) {
    if (
      location.pathname !== pathName ||
      isInsidePageEditor(host) !== editorMode
    ) {
      throw new Error('Unable to export content to canvas');
    }
  }

  private async _edgelessToCanvas(
    bound: IBound,
    nodes?: GfxBlockElementModel[],
    canvasElements: GfxPrimitiveElementModel[] = [],
    {
      background,
      padding = IMAGE_PADDING,
      dpr = window.devicePixelRatio || 1,
    }: CanvasExportOptions = {}
  ): Promise<HTMLCanvasElement | undefined> {
    const host = this.std.host;
    const rootModel = this.doc.root;
    if (!rootModel) return;

    const html2canvas = (await import('html2canvas')).default;

View on GitHub (pinned to b4c8548c09)