Potentially improve loading state in new item page preview

I thiiiink I've seen the status of a movie `<outfit-layer>` sometimes
be `loading` even when it's clearly already loaded and running. I
haven't been able to track down where and how that happens exactly, so
this is me acting on a hunch: that maybe the
I-would-have-thought-very-unlikely event that the iframe finishes
loading before the `<outfit-layer>` connects with its children maybe
happens more often than one might think!

In this change, we set up the iframe to receive `requestStatus`
messages, which it responds to with the status immediately. And we send
one of these when the `<outfit-layer>` first discovers the iframe.

Fingers crossed!
This commit is contained in:
Emi Matchu 2024-07-08 16:44:22 -07:00
parent b03fee8c7e
commit 20202b5cd9
2 changed files with 43 additions and 22 deletions

View file

@ -71,11 +71,11 @@ class OutfitLayer extends HTMLElement {
} }
play() { play() {
this.#forwardIsPlaying(true); this.#sendMessageToIframe({ type: "play" });
} }
pause() { pause() {
this.#forwardIsPlaying(false); this.#sendMessageToIframe({ type: "pause" });
} }
#connectToChildren() { #connectToChildren() {
@ -83,14 +83,23 @@ class OutfitLayer extends HTMLElement {
const iframe = this.querySelector("iframe"); const iframe = this.querySelector("iframe");
if (image) { if (image) {
// Initialize status based on the image's current `complete` attribute,
// then wait for load/error events to update it further if needed.
this.#setStatus(image.complete ? "loaded" : "loading");
image.addEventListener("load", () => this.#setStatus("loaded")); image.addEventListener("load", () => this.#setStatus("loaded"));
image.addEventListener("error", () => this.#setStatus("error")); image.addEventListener("error", () => this.#setStatus("error"));
this.#setStatus(image.complete ? "loaded" : "loading");
} else if (iframe) { } else if (iframe) {
this.iframe = iframe; this.iframe = iframe;
// Initialize status to `loading`, and asynchronously request a status
// message from the iframe if it managed to load before this triggers
// (impressive, but I think I've seen it happen!). Then, wait for
// messages or error events from the iframe to update status further if
// needed.
this.#setStatus("loading");
this.#sendMessageToIframe({ type: "requestStatus" });
window.addEventListener("message", (m) => this.#onMessage(m)); window.addEventListener("message", (m) => this.#onMessage(m));
this.iframe.addEventListener("error", () => this.#setStatus("error")); this.iframe.addEventListener("error", () => this.#setStatus("error"));
this.#setStatus("loading");
} else { } else {
throw new Error(`<outfit-layer> must contain an <img> or <iframe> tag`); throw new Error(`<outfit-layer> must contain an <img> or <iframe> tag`);
} }
@ -134,15 +143,13 @@ class OutfitLayer extends HTMLElement {
} }
} }
#forwardIsPlaying(isPlaying) { #sendMessageToIframe(message) {
if (this.iframe == null) { if (this.iframe?.contentWindow == null) {
return; return;
} }
this.iframe.contentWindow.postMessage( // The frame is sandboxed (origin == null), so send to Any origin.
{ type: isPlaying ? "play" : "pause" }, this.iframe.contentWindow.postMessage(message, "*");
"*", // The frame is sandboxed (origin == null), so send to Any origin.
);
} }
} }

View file

@ -290,6 +290,28 @@ function hasAnimations(createjsNode) {
); );
} }
function sendStatus() {
if (loadingStatus === "loading") {
sendMessage({ type: "status", status: "loading" });
} else if (loadingStatus === "loaded") {
sendMessage({
type: "status",
status: "loaded",
hasAnimations: hasAnimations(movieClip),
});
} else if (loadingStatus === "error") {
sendMessage({ type: "status", status: "error" });
} else {
throw new Error(
`unexpected loadingStatus ${JSON.stringify(loadingStatus)}`,
);
}
}
function sendMessage(message) {
parent.postMessage(message, document.location.origin);
}
window.addEventListener("resize", () => { window.addEventListener("resize", () => {
updateCanvasDimensions(); updateCanvasDimensions();
@ -310,6 +332,8 @@ window.addEventListener("message", ({ data }) => {
play(); play();
} else if (data.type === "pause") { } else if (data.type === "pause") {
pause(); pause();
} else if (data.type === "requestStatus") {
sendStatus();
} else { } else {
throw new Error(`unexpected message: ${JSON.stringify(data)}`); throw new Error(`unexpected message: ${JSON.stringify(data)}`);
} }
@ -317,23 +341,13 @@ window.addEventListener("message", ({ data }) => {
startMovie() startMovie()
.then(() => { .then(() => {
parent.postMessage( sendStatus();
{
type: "status",
status: "loaded",
hasAnimations: hasAnimations(movieClip),
},
document.location.origin,
);
}) })
.catch((error) => { .catch((error) => {
console.error(logPrefix, error); console.error(logPrefix, error);
loadingStatus = "error"; loadingStatus = "error";
parent.postMessage( sendStatus();
{ type: "status", status: "error" },
document.location.origin,
);
// If loading the movie fails, show the fallback image instead, by moving // If loading the movie fails, show the fallback image instead, by moving
// it out of the canvas content and into the body. // it out of the canvas content and into the body.