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
- Break the cycle by resolving the back-reference lazily: inject the provider/resolver and call get(...) inside methods, not in the constructor/deps array
- Extract the functionality both sides need into a third component that both depend on instead of on each other
- 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
- Inject one direction of a mutual relationship; obtain the other lazily via the resolver inside methods
- Use the eventBus for back-notification instead of constructor injection
- Extract shared logic into a third component both sides depend on
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
- GfxBlockElementError
- NoSurfaceModelError
- DatabaseBlockError
- EdgelessExportError
- Failed to read image size
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)