Skip to content

Text measurement keeps stale canvas/document context after setEnv() switches document #10930

@antonshevelov

Description

@antonshevelov

Before submitting

  • I agree to follow this project's Code of Conduct
  • I searched existing issues and discussions

Version

7.2.0

Environment

Chrome, Safari, Firefox

Link To Reproduction

No response

Steps To Reproduce

  1. Create an iframe-based preview/editor.
  2. Point Fabric to the iframe environment with fabric.setEnv({ ...fabric.getEnv(), window, document }).
  3. Create/render a fabric.Textbox and trigger text measurement.
  4. Destroy that iframe and create a new one.
  5. Call fabric.setEnv() again with the new iframe window/document.
  6. Render or measure the same text again.

In our app this reproduces consistently after:

  1. opening the editor,
  2. viewing banner text,
  3. leaving the editor,
  4. opening the editor again.

Expected Behavior

After fabric.setEnv() switches to a new document, Fabric should recreate the internal text measuring canvas/context for the current document.

Text metrics should stay correct after iframe recreation or editor reopen.

Actual Behavior

Fabric keeps using the original cached measuring context created for the first document/window.

After the iframe is destroyed and recreated, text measurement still uses the stale context from the old document. This causes incorrect text metrics, and text can visually collapse / "stick together" after reopening the editor.

Additional Context

This looks related to the module-level measuringContext cache used by text measurement.

The context is created once and then reused forever, even after fabric.setEnv() changes the active document. In iframe-based apps this means the measuring context may belong to a destroyed iframe window/document.

A fix like this resolves the issue on our side:

let measuringContext: CanvasRenderingContext2D | null;
let measuringContextDocument: Document | null;

function getMeasuringContext() {
  const fabricDocument = getFabricDocument();

  if (!measuringContext || measuringContextDocument !== fabricDocument) {
    const canvas = createCanvasElementFor({ width: 0, height: 0 });
    measuringContext = canvas.getContext('2d');
    measuringContextDocument = fabricDocument;
  }

  return measuringContext;
}

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions