Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(common): execute checks when image is already loaded #55444

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
Expand Up @@ -659,6 +659,8 @@ export class NgOptimizedImage implements OnInit, OnChanges, OnDestroy {

const removeLoadListenerFn = this.renderer.listen(img, 'load', callback);
const removeErrorListenerFn = this.renderer.listen(img, 'error', callback);

callOnLoadIfImageIsLoaded(img, callback);
}

/** @nodoc */
Expand Down Expand Up @@ -976,7 +978,7 @@ function assertNoImageDistortion(
img: HTMLImageElement,
renderer: Renderer2,
) {
const removeLoadListenerFn = renderer.listen(img, 'load', () => {
const callback = () => {
removeLoadListenerFn();
removeErrorListenerFn();
const computedStyle = window.getComputedStyle(img);
Expand Down Expand Up @@ -1069,7 +1071,9 @@ function assertNoImageDistortion(
);
}
}
});
};

const removeLoadListenerFn = renderer.listen(img, 'load', callback);

// We only listen to the `error` event to remove the `load` event listener because it will not be
// fired if the image fails to load. This is done to prevent memory leaks in development mode
Expand All @@ -1079,6 +1083,8 @@ function assertNoImageDistortion(
removeLoadListenerFn();
removeErrorListenerFn();
});

callOnLoadIfImageIsLoaded(img, callback);
}

/**
Expand Down Expand Up @@ -1124,7 +1130,7 @@ function assertNonZeroRenderedHeight(
img: HTMLImageElement,
renderer: Renderer2,
) {
const removeLoadListenerFn = renderer.listen(img, 'load', () => {
const callback = () => {
removeLoadListenerFn();
removeErrorListenerFn();
const renderedHeight = img.clientHeight;
Expand All @@ -1140,13 +1146,17 @@ function assertNonZeroRenderedHeight(
),
);
}
});
};

const removeLoadListenerFn = renderer.listen(img, 'load', callback);

// See comments in the `assertNoImageDistortion`.
const removeErrorListenerFn = renderer.listen(img, 'error', () => {
removeLoadListenerFn();
removeErrorListenerFn();
});

callOnLoadIfImageIsLoaded(img, callback);
}

/**
Expand Down Expand Up @@ -1245,6 +1255,21 @@ function assertNoLoaderParamsWithoutLoader(dir: NgOptimizedImage, imageLoader: I
}
}

function callOnLoadIfImageIsLoaded(img: HTMLImageElement, callback: VoidFunction): void {
// Note that the image may already be loaded from the browser cache before the
// `load` event fires, and the `load` event will not fire if it's already `complete`.
// In Safari, there is a known behavior where the `complete` property of an
// `HTMLImageElement` may sometimes return `true` even when the image is not fully loaded.
// Checking both `img.complete` and `img.naturalWidth` is the most reliable way to
// determine if an image has been fully loaded, especially in browsers where the
// `complete` property may return `true` prematurely.
// Mozilla employs the same check internally in its codebase, verifying
// both `complete` and `naturalWidth` when setting up a `load` event listener.
if (img.complete && img.naturalWidth) {
callback();
}
}

function round(input: number): number | string {
return Number.isInteger(input) ? input : input.toFixed(2);
}
Expand Down