diff --git a/change/@fluentui-react-motion-2679d27b-fa54-468d-9a6e-88febcf5eab0.json b/change/@fluentui-react-motion-2679d27b-fa54-468d-9a6e-88febcf5eab0.json new file mode 100644 index 0000000000000..66240442af08f --- /dev/null +++ b/change/@fluentui-react-motion-2679d27b-fa54-468d-9a6e-88febcf5eab0.json @@ -0,0 +1,7 @@ +{ + "type": "patch", + "comment": "fix: avoid memory leak in Animation:finished", + "packageName": "@fluentui/react-motion", + "email": "olfedias@microsoft.com", + "dependentChangeType": "patch" +} diff --git a/packages/react-components/react-motion/library/src/factories/createMotionComponent.test.tsx b/packages/react-components/react-motion/library/src/factories/createMotionComponent.test.tsx index a2508262d2f20..7106dde042397 100644 --- a/packages/react-components/react-motion/library/src/factories/createMotionComponent.test.tsx +++ b/packages/react-components/react-motion/library/src/factories/createMotionComponent.test.tsx @@ -16,7 +16,9 @@ function createElementMock() { cancel: jest.fn(), persist: jest.fn(), finish: finishMock, - finished: Promise.resolve(), + set onfinish(fn: () => void) { + fn(); + }, })); const ElementMock = React.forwardRef<{ animate: () => void }, { onRender?: () => void }>((props, ref) => { React.useImperativeHandle(ref, () => ({ diff --git a/packages/react-components/react-motion/library/src/factories/createPresenceComponent.test.tsx b/packages/react-components/react-motion/library/src/factories/createPresenceComponent.test.tsx index ad2cedcacd631..77472e8c855ba 100644 --- a/packages/react-components/react-motion/library/src/factories/createPresenceComponent.test.tsx +++ b/packages/react-components/react-motion/library/src/factories/createPresenceComponent.test.tsx @@ -21,7 +21,10 @@ function createElementMock() { cancel: jest.fn(), persist: jest.fn(), finish: finishMock, - finished: Promise.resolve(), + + set onfinish(fn: () => void) { + fn(); + }, })); const ElementMock = React.forwardRef<{ animate: () => void }, { onRender?: () => void }>((props, ref) => { React.useImperativeHandle(ref, () => ({ diff --git a/packages/react-components/react-motion/library/src/hooks/useAnimateAtoms.ts b/packages/react-components/react-motion/library/src/hooks/useAnimateAtoms.ts index 66ea0c35e1b6b..cb0a03544d3a2 100644 --- a/packages/react-components/react-motion/library/src/hooks/useAnimateAtoms.ts +++ b/packages/react-components/react-motion/library/src/hooks/useAnimateAtoms.ts @@ -34,20 +34,22 @@ function useAnimateAtomsInSupportedEnvironment() { }); }, setMotionEndCallbacks(onfinish: () => void, oncancel: () => void) { - Promise.all(animations.map(animation => animation.finished)) + // Heads up! + // This could use "Animation:finished", but it's causing a memory leak in Chromium. + // See: https://issues.chromium.org/u/2/issues/383016426 + const promises = animations.map(animation => { + return new Promise((resolve, reject) => { + animation.onfinish = () => resolve(); + animation.oncancel = () => reject(); + }); + }); + + Promise.all(promises) .then(() => { onfinish(); }) - .catch((err: unknown) => { - const DOMException = element.ownerDocument.defaultView?.DOMException; - - // Ignores "DOMException: The user aborted a request" that appears if animations are cancelled - if (DOMException && err instanceof DOMException && err.name === 'AbortError') { - oncancel(); - return; - } - - throw err; + .catch(() => { + oncancel(); }); },