Pausing may be initiated by conscientious script or by the browser in order to intervene on the user's behalf. If initiated by script, it can be unpaused by script. If initiated by the browser as part of an intervention, it can only be unpaused by the browser.
var frameElement = document.getElementById("hungryFrameID"); await frameElement.pause(); console.log("Frame is paused");
When you're ready to unpause the frame:
await frameElement.unpause(); console.log("Frame is unpaused");
To detect if a frame is paused:
if (frameElement.paused) console.log("Rendering is paused");
Existing loads will continue but new resource requests (fetches) will queue instead of starting. When unpaused, the queued requests will start.
The frame will stop its rendering pipeline and will continue to show the results of the last paint. The browser may, if necessary, rerender the frame (e.g., because the frame is resized or the frame buffers were dropped while offscreen and it scrolled back into view). The rAF event will not fire on rerender (because script is also paused).
Any running script task is completed and future tasks for the given frame on the event queue (e.g., events [including WebSocket] and timers) remain queued until the document is unpaused. UI events are discarded instead of queued since there can be so many of them. rAF events will also be discarded.
- Pausing frames that violate policies (e.g., TransferSizePolicy). This provides a gentle, yet firm response to misbehaving frames.
- Pausing resource-intensive frames that the user isn't currently paying attention to. For instance, a carousel of frames, where some of the frames are offscreen.
- As a mechanism for browsers to automatically intervene on resource-intensive frames.
The user is likely to become confused by the frame being paused, as it is no longer interactive. It is therefore advisable that if you pause a visible frame that you (the web developer) make it visually obvious to the user that it's not interactive (e.g., overlay a semi-transparent play button over the paused frame that unpauses when clicked).
Each frame has two paused booleans,
When a frame is paused by API,
apiPaused is set to true for that frame (and its descendant frames). When a frame is paused via UA,
uaPaused is set to true for that frame (and its descendant frames). A frame is paused if either
uaPaused is true.
Pause is an asynchronous operation and returns a promise. It's async because there are multiple frames that need to coordinate. The promise resolves once all of the frame tree has updated and applied its paused options.
unpause is called on a frame via API,
apiPaused for the frame and its descendants is set to false. When a frame is unpaused via UA,
uaPaused is set to false for that frame and its descendants. A frame is paused if both
uaPaused are now false.
This means that the API can clear its own pausing options but not that of the UA and vice-versa.
Unpause is an asynchronous operation and returns a promise that resolves after all of the frame tree has updated and applied its paused options.
apiPaused || uaPaused for the given frame. It returns synchronously. If a
unpause operation is in progress, the result will depend on how far along the operation is.
Paused frames, and parents of paused frames, may want to know when a frame has been paused. This is TBD.
Cross-Origin Information Leaked
The effects of pausing may be observable to the parent. For instance, if a child frame's is paused, the parent might notice that loads become quicker (suggesting the child was loading resources). Or, script might become more responsive in the parent after the child frame is paused, suggesting the child was busy processing. These are things that could be discovered other ways, such as by unloading the frame.
The embedding page now has the ability to freeze the rendered output of a cross-origin frame at any point. Perhaps the cross-origin frame wants something to be displayed momentarily but now it's made permanent. Might this be used for nefarious purposes by the embedder?