Skip to content

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

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

Closed
Closed
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -708,6 +708,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 @@ -1025,7 +1027,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 @@ -1118,7 +1120,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 @@ -1128,6 +1132,8 @@ function assertNoImageDistortion(
removeLoadListenerFn();
removeErrorListenerFn();
});

callOnLoadIfImageIsLoaded(img, callback);
}

/**
Expand Down Expand Up @@ -1173,7 +1179,7 @@ function assertNonZeroRenderedHeight(
img: HTMLImageElement,
renderer: Renderer2,
) {
const removeLoadListenerFn = renderer.listen(img, 'load', () => {
const callback = () => {
removeLoadListenerFn();
removeErrorListenerFn();
const renderedHeight = img.clientHeight;
Expand All @@ -1189,13 +1195,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 @@ -1338,6 +1348,22 @@ function assertPlaceholderDimensions(dir: NgOptimizedImage, imgElement: HTMLImag
}
}

function callOnLoadIfImageIsLoaded(img: HTMLImageElement, callback: VoidFunction): void {
// https://html.spec.whatwg.org/multipage/embedded-content.html#dom-img-complete
// The spec defines that `complete` is truthy once its request state is fully available.
// The image may already be available if it’s loaded from the browser cache.
// In that case, the `load` event will not fire at all, meaning that all setup
// callbacks listening for the `load` event will not be invoked.
// 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.
if (img.complete && img.naturalWidth) {
callback();
}
Comment on lines +1362 to +1364
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@atcastle could you please help review this change when you get a chance?

}

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