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() {
this.#forwardIsPlaying(true);
this.#sendMessageToIframe({ type: "play" });
}
pause() {
this.#forwardIsPlaying(false);
this.#sendMessageToIframe({ type: "pause" });
}
#connectToChildren() {
@ -83,14 +83,23 @@ class OutfitLayer extends HTMLElement {
const iframe = this.querySelector("iframe");
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("error", () => this.#setStatus("error"));
this.#setStatus(image.complete ? "loaded" : "loading");
} else if (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));
this.iframe.addEventListener("error", () => this.#setStatus("error"));
this.#setStatus("loading");
} else {
throw new Error(`<outfit-layer> must contain an <img> or <iframe> tag`);
}
@ -134,15 +143,13 @@ class OutfitLayer extends HTMLElement {
}
}
#forwardIsPlaying(isPlaying) {
if (this.iframe == null) {
#sendMessageToIframe(message) {
if (this.iframe?.contentWindow == null) {
return;
}
this.iframe.contentWindow.postMessage(
{ type: isPlaying ? "play" : "pause" },
"*", // The frame is sandboxed (origin == null), so send to Any origin.
);
// The frame is sandboxed (origin == null), so send to Any origin.
this.iframe.contentWindow.postMessage(message, "*");
}
}

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