mirror of
https://github.com/lightpanda-io/browser.git
synced 2026-08-01 02:06:17 -04:00
Merge pull request #2843 from navidemad/fix-a10-stylesheet-deferred-flush
page: flush deferred transfers after a synchronous stylesheet fetch
This commit is contained in:
@@ -2048,6 +2048,20 @@ pub fn loadExternalStylesheet(self: *Frame, link: *Element.Html.Link, href: []co
|
||||
};
|
||||
|
||||
const http_client = &session.browser.http_client;
|
||||
|
||||
// `syncRequest` below registers a blocking request for this frame, which
|
||||
// makes the DeferringLayer hold back the completion callbacks of every
|
||||
// OTHER in-flight transfer for the frame (e.g. a `<script defer>` still
|
||||
// loading) so they don't run JS while we're on the parser stack. Those
|
||||
// deferred completions must be flushed once the sync fetch returns —
|
||||
// otherwise a `<script defer>` whose fetch finishes during this window is
|
||||
// left at `complete == false` forever, the deferred-script queue never
|
||||
// drains, and `documentIsLoaded` (readyState -> "interactive",
|
||||
// DOMContentLoaded, the load event) never fires. The blocking-`<script>`
|
||||
// path (ScriptManager.addFromElement) and the worker path already flush;
|
||||
// the external-stylesheet path must too.
|
||||
defer http_client.deferring_layer.flushFrame(self._frame_id);
|
||||
|
||||
var headers = try http_client.newHeaders();
|
||||
try headers.add("Accept: text/css,*/*;q=0.1");
|
||||
try self.headersForRequest(&headers);
|
||||
|
||||
38
src/browser/tests/css/deferred_script_then_stylesheet.html
Normal file
38
src/browser/tests/css/deferred_script_then_stylesheet.html
Normal file
@@ -0,0 +1,38 @@
|
||||
<!DOCTYPE html>
|
||||
<head>
|
||||
<script src="../testing.js"></script>
|
||||
<script id=deferred_script_drains_after_sync_stylesheet>
|
||||
// Runs during parsing, before the body's deferred script + stylesheet.
|
||||
// Crucially this is the LAST blocking <script> in the document: nothing
|
||||
// blocking follows the <link rel=stylesheet> below, so the only thing
|
||||
// that can flush the deferred script's completion is the stylesheet
|
||||
// fetch itself (the regression). A blocking <script> after the <link>
|
||||
// would call flushFrame on its own and hide the bug.
|
||||
testing.expectEqual('loading', document.readyState);
|
||||
testing.expectEqual(undefined, window.__deferRan);
|
||||
|
||||
testing.onload(() => {
|
||||
// Reaching load means the deferred-script queue drained: the deferred
|
||||
// script ran and the document advanced out of "loading".
|
||||
testing.expectEqual(true, window.__deferRan);
|
||||
testing.expectEqual('complete', document.readyState);
|
||||
});
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
<!--
|
||||
Regression: a `<script defer>` followed by an external
|
||||
`<link rel=stylesheet>` (external-stylesheet loading enabled).
|
||||
|
||||
Frame.loadExternalStylesheet fetches the sheet SYNCHRONOUSLY, registering
|
||||
a blocking request for the frame. While that blocking request is active,
|
||||
the DeferringLayer holds back the completion callbacks of every OTHER
|
||||
in-flight transfer for the frame — including the deferred script that
|
||||
started loading just above. Those deferred completions must be flushed
|
||||
once the sync fetch returns; otherwise the script stays at
|
||||
`complete == false`, the deferred-script queue never drains, and
|
||||
DOMContentLoaded / the load event / readyState -> "complete" never fire.
|
||||
-->
|
||||
<script defer src="/defer-marker.js"></script>
|
||||
<link rel="stylesheet" href="/styles/visibility.css">
|
||||
</body>
|
||||
@@ -288,3 +288,14 @@ test "WebApi: HTML.Link external stylesheet" {
|
||||
defer filter.deinit();
|
||||
try testing.htmlRunner("css/external_stylesheet.html", .{ .load_external_stylesheets = true });
|
||||
}
|
||||
|
||||
// Regression: a synchronous external-stylesheet fetch must not strand the
|
||||
// completion of an in-flight <script defer> (deferred by the blocking-request
|
||||
// window). Otherwise the deferred-script queue never drains and the document
|
||||
// is stuck at readyState "loading". See Frame.loadExternalStylesheet's
|
||||
// flushFrame call.
|
||||
test "WebApi: HTML.Link deferred script then external stylesheet" {
|
||||
const filter: testing.LogFilter = .init(&.{.http});
|
||||
defer filter.deinit();
|
||||
try testing.htmlRunner("css/deferred_script_then_stylesheet.html", .{ .load_external_stylesheets = true });
|
||||
}
|
||||
|
||||
@@ -715,6 +715,17 @@ fn testHTTPHandler(req: *std.http.Server.Request) !void {
|
||||
});
|
||||
}
|
||||
|
||||
if (std.mem.eql(u8, path, "/defer-marker.js")) {
|
||||
// A deferred script body. Used by the regression test for a
|
||||
// <script defer> whose completion is deferred by a later
|
||||
// <link rel=stylesheet>'s synchronous fetch — it must still execute.
|
||||
return req.respond("window.__deferRan = true;", .{
|
||||
.extra_headers = &.{
|
||||
.{ .name = "Content-Type", .value = "application/javascript" },
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
if (std.mem.eql(u8, path, "/xhr/500")) {
|
||||
return req.respond("Internal Server Error", .{
|
||||
.status = .internal_server_error,
|
||||
|
||||
Reference in New Issue
Block a user