mirror of
https://github.com/lightpanda-io/browser.git
synced 2026-06-11 01:25:53 -04:00
fix: drop orphaned deferred contexts on frame teardown
A fetch() deferred behind a parser-blocking script keeps a DeferredContext in the DeferringLayer whose forward.ctx points at the Fetch struct, which lives in a page/session arena. If the transfer completes while deferred, it is deinited and unlinked from the frame owner, so abortTransfers -> abortOwner can't reach the now-orphaned context. It lingers in the active list, and when the page is torn down its Fetch arena is freed; a later flushFrame (e.g. the next page's parser-blocking script popping) replays the buffered header callback into the freed Fetch -> use-after-free. Add DeferringLayer.cancelFrame to drop these orphaned (terminal) contexts during Frame.abortTransfers. Non-terminal contexts still have a live transfer that cleans them up through its own callback path, so they are left alone.
This commit is contained in:
@@ -895,7 +895,10 @@ pub fn abortTransfers(self: *Frame) void {
|
||||
for (self.child_frames.items) |child| {
|
||||
child.abortTransfers();
|
||||
}
|
||||
self._session.browser.http_client.abortOwner(&self._http_owner);
|
||||
const http_client = &self._session.browser.http_client;
|
||||
http_client.abortOwner(&self._http_owner);
|
||||
// abortOwner misses deferred contexts whose transfer already completed.
|
||||
http_client.deferring_layer.cancelFrame(self._frame_id);
|
||||
}
|
||||
|
||||
pub fn documentIsLoaded(self: *Frame) void {
|
||||
|
||||
@@ -113,6 +113,25 @@ pub fn flushFrame(self: *DeferringLayer, frame_id: u32) void {
|
||||
}
|
||||
}
|
||||
|
||||
/// Drop orphaned deferred contexts for a frame that's going away. A `terminal`
|
||||
/// context's transfer already completed while deferred, so it's been deinited
|
||||
/// and unlinked from the owner — abortOwner can't reach it, yet it lingers in
|
||||
/// `active` pointing at a forward target (the Fetch) whose arena page teardown
|
||||
/// is about to free, and a later flushFrame would fire into it. Non-terminal
|
||||
/// contexts still have a live transfer that cleans them up itself.
|
||||
pub fn cancelFrame(self: *DeferringLayer, frame_id: u32) void {
|
||||
var node = self.active.first;
|
||||
while (node) |n| {
|
||||
node = n.next;
|
||||
const ctx: *DeferredContext = @fieldParentPtr("node", n);
|
||||
if (ctx.frame_id != frame_id or !ctx.terminal) {
|
||||
continue;
|
||||
}
|
||||
self.active.remove(n);
|
||||
ctx.deinit();
|
||||
}
|
||||
}
|
||||
|
||||
pub fn drainAll(self: *DeferringLayer) void {
|
||||
while (self.active.popFirst()) |node| {
|
||||
const ctx: *DeferredContext = @fieldParentPtr("node", node);
|
||||
|
||||
Reference in New Issue
Block a user