Merge pull request #1616 from lightpanda-io/URL_createObjectURL

Add URL.createObjectURL and URL.revokeObjectURL
This commit is contained in:
Karl Seguin
2026-02-21 07:02:01 +08:00
committed by GitHub
3 changed files with 73 additions and 0 deletions

View File

@@ -39,6 +39,7 @@ const ScriptManager = @import("ScriptManager.zig");
const Parser = @import("parser/Parser.zig"); const Parser = @import("parser/Parser.zig");
const URL = @import("URL.zig"); const URL = @import("URL.zig");
const Blob = @import("webapi/Blob.zig");
const Node = @import("webapi/Node.zig"); const Node = @import("webapi/Node.zig");
const Event = @import("webapi/Event.zig"); const Event = @import("webapi/Event.zig");
const EventTarget = @import("webapi/EventTarget.zig"); const EventTarget = @import("webapi/EventTarget.zig");
@@ -133,6 +134,9 @@ _element_namespace_uris: Element.NamespaceUriLookup = .empty,
/// ``` /// ```
_element_attr_listeners: GlobalEventHandlersLookup = .empty, _element_attr_listeners: GlobalEventHandlersLookup = .empty,
// Blob URL registry for URL.createObjectURL/revokeObjectURL
_blob_urls: std.StringHashMapUnmanaged(*Blob) = .{},
/// `load` events that'll be fired before window's `load` event. /// `load` events that'll be fired before window's `load` event.
/// A call to `documentIsComplete` (which calls `_documentIsComplete`) resets it. /// A call to `documentIsComplete` (which calls `_documentIsComplete`) resets it.
_to_load: std.ArrayList(*Element) = .{}, _to_load: std.ArrayList(*Element) = .{},

View File

@@ -656,3 +656,45 @@
testing.expectEqual("?a=%7E&b=%7E&c=foo", url.search); testing.expectEqual("?a=%7E&b=%7E&c=foo", url.search);
} }
</script> </script>
<script id=objectURL>
{
// Test createObjectURL with Blob
const blob = new Blob(['<html><body>Hello</body></html>'], { type: 'text/html' });
const url = URL.createObjectURL(blob);
testing.expectEqual('string', typeof url);
testing.expectEqual(true, url.startsWith('blob:'));
testing.expectEqual(true, url.includes('http'));
}
{
// Test revokeObjectURL
const blob = new Blob(['test'], { type: 'text/plain' });
const url = URL.createObjectURL(blob);
testing.expectEqual(true, url.startsWith('blob:'));
URL.revokeObjectURL(url);
URL.revokeObjectURL(url); // Second revoke should be safe
}
{
// Test with non-blob URL (should be safe/silent)
URL.revokeObjectURL('http://example.com/notblob');
URL.revokeObjectURL('');
testing.expectEqual(true, true);
}
{
// Test uniqueness
const blob1 = new Blob(['test1']);
const blob2 = new Blob(['test2']);
const url1 = URL.createObjectURL(blob1);
const url2 = URL.createObjectURL(blob2);
testing.expectEqual(false, url1 === url2);
testing.expectEqual(true, url1.startsWith('blob:'));
testing.expectEqual(true, url2.startsWith('blob:'));
}
</script>

View File

@@ -22,6 +22,7 @@ const js = @import("../js/js.zig");
const U = @import("../URL.zig"); const U = @import("../URL.zig");
const Page = @import("../Page.zig"); const Page = @import("../Page.zig");
const URLSearchParams = @import("net/URLSearchParams.zig"); const URLSearchParams = @import("net/URLSearchParams.zig");
const Blob = @import("Blob.zig");
const Allocator = std.mem.Allocator; const Allocator = std.mem.Allocator;
@@ -228,6 +229,30 @@ pub fn canParse(url: []const u8, base_: ?[]const u8) bool {
return U.isCompleteHTTPUrl(url); return U.isCompleteHTTPUrl(url);
} }
pub fn createObjectURL(blob: *Blob, page: *Page) ![]const u8 {
var uuid_buf: [36]u8 = undefined;
@import("../../id.zig").uuidv4(&uuid_buf);
const origin = (try page.getOrigin(page.call_arena)) orelse "null";
const blob_url = try std.fmt.allocPrint(
page.arena,
"blob:{s}/{s}",
.{ origin, uuid_buf },
);
try page._blob_urls.put(page.arena, blob_url, blob);
return blob_url;
}
pub fn revokeObjectURL(url: []const u8, page: *Page) void {
// Per spec: silently ignore non-blob URLs
if (!std.mem.startsWith(u8, url, "blob:")) {
return;
}
// Remove from registry (no-op if not found)
_ = page._blob_urls.remove(url);
}
pub const JsApi = struct { pub const JsApi = struct {
pub const bridge = js.Bridge(URL); pub const bridge = js.Bridge(URL);
@@ -239,6 +264,8 @@ pub const JsApi = struct {
pub const constructor = bridge.constructor(URL.init, .{}); pub const constructor = bridge.constructor(URL.init, .{});
pub const canParse = bridge.function(URL.canParse, .{ .static = true }); pub const canParse = bridge.function(URL.canParse, .{ .static = true });
pub const createObjectURL = bridge.function(URL.createObjectURL, .{ .static = true });
pub const revokeObjectURL = bridge.function(URL.revokeObjectURL, .{ .static = true });
pub const toString = bridge.function(URL.toString, .{}); pub const toString = bridge.function(URL.toString, .{});
pub const toJSON = bridge.function(URL.toString, .{}); pub const toJSON = bridge.function(URL.toString, .{});
pub const href = bridge.accessor(URL.toString, URL.setHref, .{}); pub const href = bridge.accessor(URL.toString, URL.setHref, .{});