microsoft / microsoft/pxt

postHostMessageAsync never times out — infinite loading when iframe host is slow to respond

Open
#11,280 0 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

Dominant language
TypeScript
Stars
2.3k
Forks
641
Avg merge
12h 4m
Merged PRs (30d)
57

Description

Describe the bug

When the editor is embedded as an iframe with allowParentController (controller mode), postHostMessageAsync in pxteditor/editorcontroller.ts creates a Promise that only resolves when the host posts back a pxthost message with a matching id. There is no timeout and no reject path anywhere in the codebase, so if the host is slow to respond or drops the message, the Promise stays pending forever.

Because workspace.initAsync() → syncAsync() → iframeworkspace.listAsync() calls postHostMessageAsync({ action: "workspacesync", response: true }), the entire bootstrap .then chain in webapp/src/app.tsx halts. The #loading overlay (defined statically in webapp/public/index.html) is only removed near the end of that chain, so users see an infinite loading spinner.

The existing fallback syncAsync().catch(() => switchToMemoryWorkspace(...)) at webapp/src/workspace.ts:~1692 is already wired to recover from a rejection, but never triggers because no rejection is ever produced.

To Reproduce

  1. Open any MakeCode editor embedded as an iframe in a host that implements the workspacesync / workspaceloaded protocol
  2. Open Chrome DevTools → Network tab → set throttling to Slow 4G
  3. Reload the page
  4. The loading spinner remains indefinitely; the editor never mounts
  5. Switch throttling back to No throttling → the editor eventually loads as the delayed host response arrives

Expected behavior

postHostMessageAsync should reject pending requests after a reasonable timeout (e.g. 30s), allowing the already-present switchToMemoryWorkspace fallback in syncAsync to take over so the editor at least boots with an in-memory workspace instead of hanging forever.

Verification of fix direction

Verified in-browser without redeploying by attaching a DevTools conditional breakpoint on the pendingRequests[env.id] = { resolve, reject } line with this
expression:

(function(){var id=env.id,a=env.action;setTimeout(function(){var r=pendingRequests[id];if(r){console.warn("[POSTHOST TIMEOUT]",a,id);delete
pendingRequests[id];r.reject(new Error("host no-response: "+a));}},15000);return false;})()                                                                  

Under Slow 4G reproduction, both workspacesync and workspaceloaded time out at 15s, the syncAsync catch triggers, memory workspace fallback kicks in, and the
editor renders normally. Confirms that adding a timeout alone is sufficient to unblock the UI.

Proposed fix (sketch)

Add a timeout in postHostMessageAsync when msg.response === true:

const HOST_MESSAGE_TIMEOUT_MS = 15000; // or configurable                                                                                                    
                                                                                                                                                             
if (msg.response) {
    pendingRequests[env.id] = { resolve, reject };                                                                                                           
    setTimeout(() => {                                                                                                                                       
        const req = pendingRequests[env.id];
        if (req) {                                                                                                                                           
            delete pendingRequests[env.id];                                                                                                                  
            pxt.warn(`postHostMessageAsync timeout: ${env.action} (${env.id})`);
            req.reject(new Error(`host no-response: ${env.action}`));                                                                                        
        }                                                                                                                                                    
    }, HOST_MESSAGE_TIMEOUT_MS);                                                                                                                             
}                                                                                                                                                            

Happy to open a PR if the direction is acceptable. Open questions for the maintainers:

  • Default timeout value (30s suggested to accommodate legitimately slow host workflows like OAuth)
  • Whether to expose it as a per-request opt-out (e.g. msg.timeout, 0 = no timeout to preserve current behavior for hosts that need it)
  • Whether to apply the same treatment to iframeDriver.sendRequest for symmetry

Desktop (please complete the following information):

  • OS: win11
  • Browser : chrome 147.0.7727.102
  • Version : pxt-core 12.3.5

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Read pxteditor/editorcontroller.ts to trace postHostMessageAsync and its pending request handling, then inspect the syncAsync fallback around webapp/src/workspace.ts:~1692. Check how workspace.initAsync flows through webapp/src/app.tsx and the loading overlay in webapp/public/index.html. Done means a missing host response rejects after the chosen timeout and the in-memory workspace fallback lets the editor boot.

Written by the indexing model from the issue text.

Assessment

Tech stack
typescript
Domain
frontend
Issue type
Bug
Difficulty
3/5
Estimated time
1-2 days
Activity status
Quiet
Clarity
Mostly clear
Newbie friendliness
70/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.