webapi: DOMParser capture owning frame for correct URL

The URL assigned to a document parsed via DOMParser should come from the Frame
which created the DOMParser.
This commit is contained in:
Karl Seguin committed 2026-08-20 12:39:16 +08:00
1 parent 46de01944e
commit 28c5778fde
3 files changed
+61 -9

No files matched your search

+12
View File
@@ -535,3 +535,15 @@
testing.expectEqual('svg', svg.documentElement.localName);
}
</script>
<script id=invalidType>
{
let err = null;
try {
new DOMParser().parseFromString('', 'text/foo');
} catch (e) {
err = e;
}
testing.expectEqual(true, err instanceof TypeError);
}
</script>
@@ -0,0 +1,40 @@
<!DOCTYPE html>
<head></head>
<body>
<script src="../testing.js"></script>
<!--
The document DOMParser.parseFromString produces takes its URL from the
DOMParser's own realm (the frame it was constructed in), not from the realm
whose parseFromString is invoked.
-->
<iframe id=if_dp src="support/cross_realm_collection.html"></iframe>
<script id=document_url_follows_the_parser_realm>
testing.onload(() => {
const iwin = document.getElementById('if_dp').contentWindow;
const child_url = iwin.document.URL;
testing.expectEqual(true, child_url !== document.URL);
for (const type of ['text/html', 'text/xml']) {
const local = new DOMParser();
const remote = new iwin.DOMParser();
let doc = local.parseFromString('<a/>', type);
testing.expectEqual(document.URL, doc.URL);
testing.expectEqual(document.URL, doc.baseURI);
doc = remote.parseFromString('<a/>', type);
testing.expectEqual(child_url, doc.URL);
testing.expectEqual(child_url, doc.documentURI);
testing.expectEqual(child_url, doc.baseURI);
// method from the other realm, parser from this one (and vice versa)
doc = iwin.DOMParser.prototype.parseFromString.call(local, '<a/>', type);
testing.expectEqual(document.URL, doc.URL);
doc = DOMParser.prototype.parseFromString.call(remote, '<a/>', type);
testing.expectEqual(child_url, doc.URL);
}
});
</script>
+9 -9
View File
@@ -24,25 +24,24 @@ const Frame = @import("../Frame.zig");
const Parser = @import("../parser/Parser.zig");
const Node = @import("Node.zig");
const HTMLDocument = @import("HTMLDocument.zig");
const Document = @import("Document.zig");
const HTMLDocument = @import("HTMLDocument.zig");
const DOMParser = @This();
// Padding to avoid zero-size struct, which causes identity_map pointer collisions.
_pad: bool = false,
_frame: *Frame,
pub fn init() DOMParser {
return .{};
pub fn init(frame: *Frame) !*DOMParser {
return frame._factory.create(DOMParser{ ._frame = frame });
}
pub fn parseFromString(
_: *const DOMParser,
self: *const DOMParser,
html: []const u8,
mime_type: []const u8,
frame: *Frame,
) !*Document {
const target_mime = std.meta.stringToEnum(SupportedType, mime_type) orelse return error.NotSupported;
const frame = self._frame;
const target_mime = std.meta.stringToEnum(SupportedType, mime_type) orelse return error.TypeError;
switch (target_mime) {
.@"text/html" => {
@@ -64,6 +63,7 @@ pub fn parseFromString(
const doc = try frame._factory.document(HTMLDocument{
._proto = undefined,
});
doc.asDocument()._url = frame.url;
var normalized = std.mem.trim(u8, html, &std.ascii.whitespace);
if (normalized.len == 0) {
@@ -86,6 +86,7 @@ pub fn parseFromString(
else => {
const xml_doc = (try Frame.parse.xmlDocument(frame, html)) orelse try parserErrorDocument(frame);
const doc = xml_doc.asDocument();
doc._url = frame.url;
doc._content_type = @tagName(target_mime);
return doc;
},
@@ -121,7 +122,6 @@ pub const JsApi = struct {
pub const name = "DOMParser";
pub const prototype_chain = bridge.prototypeChain();
pub var class_id: bridge.ClassId = undefined;
pub const empty_with_no_proto = true;
};
pub const constructor = bridge.constructor(DOMParser.init, .{});