webapi: documents report the navigation response's content type

Fixes 9 failing files under /dom/nodes/Document-contentType/contentType/
(bmp, css, gif, jpg, mimeheader_01, png, txt, xml, and keeps the rest
green): a document created from a non-HTML response (the synthesized
image/text/raw documents) reported the text/html default from
document.contentType instead of the response's MIME type.

The frame records the response's MIME essence (type/subtype, lowercased,
parameters stripped) when the first chunk arrives, and applies it to
the new document's _content_type override once the navigation commits.
text/html responses keep the default.

The remaining file in the directory (contenttype_javascripturi) needs
iframes to navigate to javascript: URLs and document their string
result, which is a different mechanism.

Coverage: /dom/nodes/Document-contentType/: 14/15 files passing (9
newly passing, 1 subtest each).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Francis Bouvier
2026-07-11 23:09:29 +02:00
committed by Karl Seguin
parent 888d6fd52f
commit bf56adeb16

View File

@@ -236,6 +236,10 @@ _factory: *Factory,
_load_state: LoadState = .waiting,
// The navigation response's MIME essence, recorded when the first chunk
// arrives and applied to the new document (document.contentType) once the
// navigation commits. null means the text/html default.
_pending_content_type: ?[]const u8 = null,
_parse_state: ParseState = .pre,
/// `frameErrorCallback` swallows the failure into a placeholder page;
@@ -1427,6 +1431,19 @@ fn frameDataCallback(transfer: *HttpClient.Transfer, data: []const u8) !void {
});
}
// Record the response's MIME essence so the resulting document
// reports it (document.contentType); text/html stays the default.
self._pending_content_type = null;
if (transfer.contentType()) |ct| {
const end = std.mem.indexOfScalar(u8, ct, ';') orelse ct.len;
const essence = std.mem.trim(u8, ct[0..end], " \t");
if (essence.len > 0 and !std.ascii.eqlIgnoreCase(essence, "text/html")) {
const lower = try self.arena.dupe(u8, essence);
_ = std.ascii.lowerString(lower, essence);
self._pending_content_type = lower;
}
}
switch (mime.content_type) {
.text_html => {
// Normalize and store the charset using encoding_rs canonical names
@@ -1501,6 +1518,11 @@ fn frameDoneCallback(ctx: *anyopaque) !void {
//We need to handle different navigation types differently.
try self._session.navigation.commitNavigation(self);
if (self._pending_content_type) |content_type| {
self.document._content_type = content_type;
self._pending_content_type = null;
}
defer if (comptime IS_DEBUG) {
log.debug(.frame, "frame load complete", .{
.url = self.url,