mirror of
https://github.com/lightpanda-io/browser.git
synced 2026-03-22 04:34:44 +00:00
Merge pull request #1877 from lightpanda-io/xhr_and_fetch_blob_urls
Support blob urls in XHR and Fetch
This commit is contained in:
@@ -408,16 +408,9 @@ pub fn isSameOrigin(self: *const Page, url: [:0]const u8) !bool {
|
|||||||
return std.mem.startsWith(u8, url, current_origin);
|
return std.mem.startsWith(u8, url, current_origin);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Look up a blob URL in this page's registry, walking up the parent chain.
|
/// Look up a blob URL in this page's registry.
|
||||||
pub fn lookupBlobUrl(self: *Page, url: []const u8) ?*Blob {
|
pub fn lookupBlobUrl(self: *Page, url: []const u8) ?*Blob {
|
||||||
var current: ?*Page = self;
|
return self._blob_urls.get(url);
|
||||||
while (current) |page| {
|
|
||||||
if (page._blob_urls.get(url)) |blob| {
|
|
||||||
return blob;
|
|
||||||
}
|
|
||||||
current = page.parent;
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn navigate(self: *Page, request_url: [:0]const u8, opts: NavigateOpts) !void {
|
pub fn navigate(self: *Page, request_url: [:0]const u8, opts: NavigateOpts) !void {
|
||||||
@@ -458,7 +451,14 @@ pub fn navigate(self: *Page, request_url: [:0]const u8, opts: NavigateOpts) !voi
|
|||||||
|
|
||||||
// Content injection
|
// Content injection
|
||||||
if (is_blob) {
|
if (is_blob) {
|
||||||
const blob = self.lookupBlobUrl(request_url) orelse {
|
// For navigation, walk up the parent chain to find blob URLs
|
||||||
|
// (e.g., parent creates blob URL and sets iframe.src to it)
|
||||||
|
const blob = blk: {
|
||||||
|
var current: ?*Page = self.parent;
|
||||||
|
while (current) |page| {
|
||||||
|
if (page._blob_urls.get(request_url)) |b| break :blk b;
|
||||||
|
current = page.parent;
|
||||||
|
}
|
||||||
log.warn(.js, "invalid blob", .{ .url = request_url });
|
log.warn(.js, "invalid blob", .{ .url = request_url });
|
||||||
return error.BlobNotFound;
|
return error.BlobNotFound;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -203,3 +203,25 @@
|
|||||||
testing.expectEqual(true, response.body !== null);
|
testing.expectEqual(true, response.body !== null);
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
<script id=fetch_blob_url>
|
||||||
|
testing.async(async (restore) => {
|
||||||
|
// Create a blob and get its URL
|
||||||
|
const blob = new Blob(['Hello from blob!'], { type: 'text/plain' });
|
||||||
|
const blobUrl = URL.createObjectURL(blob);
|
||||||
|
|
||||||
|
const response = await fetch(blobUrl);
|
||||||
|
restore();
|
||||||
|
|
||||||
|
testing.expectEqual(200, response.status);
|
||||||
|
testing.expectEqual(true, response.ok);
|
||||||
|
testing.expectEqual(blobUrl, response.url);
|
||||||
|
testing.expectEqual('text/plain', response.headers.get('Content-Type'));
|
||||||
|
|
||||||
|
const text = await response.text();
|
||||||
|
testing.expectEqual('Hello from blob!', text);
|
||||||
|
|
||||||
|
// Clean up
|
||||||
|
URL.revokeObjectURL(blobUrl);
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|||||||
@@ -283,3 +283,26 @@
|
|||||||
testing.expectEqual(XMLHttpRequest.UNSENT, req.readyState);
|
testing.expectEqual(XMLHttpRequest.UNSENT, req.readyState);
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
<script id=xhr_blob_url>
|
||||||
|
testing.async(async (restore) => {
|
||||||
|
// Create a blob and get its URL
|
||||||
|
const blob = new Blob(['Hello from blob!'], { type: 'text/plain' });
|
||||||
|
const blobUrl = URL.createObjectURL(blob);
|
||||||
|
|
||||||
|
const req = new XMLHttpRequest();
|
||||||
|
await new Promise((resolve) => {
|
||||||
|
req.onload = resolve;
|
||||||
|
req.open('GET', blobUrl);
|
||||||
|
req.send();
|
||||||
|
});
|
||||||
|
|
||||||
|
restore();
|
||||||
|
testing.expectEqual(200, req.status);
|
||||||
|
testing.expectEqual('Hello from blob!', req.responseText);
|
||||||
|
testing.expectEqual(blobUrl, req.responseURL);
|
||||||
|
|
||||||
|
// Clean up
|
||||||
|
URL.revokeObjectURL(blobUrl);
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|||||||
@@ -25,6 +25,7 @@ const js = @import("../../js/js.zig");
|
|||||||
const Page = @import("../../Page.zig");
|
const Page = @import("../../Page.zig");
|
||||||
const URL = @import("../../URL.zig");
|
const URL = @import("../../URL.zig");
|
||||||
|
|
||||||
|
const Blob = @import("../Blob.zig");
|
||||||
const Request = @import("Request.zig");
|
const Request = @import("Request.zig");
|
||||||
const Response = @import("Response.zig");
|
const Response = @import("Response.zig");
|
||||||
|
|
||||||
@@ -44,11 +45,15 @@ pub const InitOpts = Request.InitOpts;
|
|||||||
|
|
||||||
pub fn init(input: Input, options: ?InitOpts, page: *Page) !js.Promise {
|
pub fn init(input: Input, options: ?InitOpts, page: *Page) !js.Promise {
|
||||||
const request = try Request.init(input, options, page);
|
const request = try Request.init(input, options, page);
|
||||||
|
const resolver = page.js.local.?.createPromiseResolver();
|
||||||
|
|
||||||
|
if (std.mem.startsWith(u8, request._url, "blob:")) {
|
||||||
|
return handleBlobUrl(request._url, resolver, page);
|
||||||
|
}
|
||||||
|
|
||||||
const response = try Response.init(null, .{ .status = 0 }, page);
|
const response = try Response.init(null, .{ .status = 0 }, page);
|
||||||
errdefer response.deinit(true, page._session);
|
errdefer response.deinit(true, page._session);
|
||||||
|
|
||||||
const resolver = page.js.local.?.createPromiseResolver();
|
|
||||||
|
|
||||||
const fetch = try response._arena.create(Fetch);
|
const fetch = try response._arena.create(Fetch);
|
||||||
fetch.* = .{
|
fetch.* = .{
|
||||||
._page = page,
|
._page = page,
|
||||||
@@ -90,6 +95,26 @@ pub fn init(input: Input, options: ?InitOpts, page: *Page) !js.Promise {
|
|||||||
return resolver.promise();
|
return resolver.promise();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn handleBlobUrl(url: []const u8, resolver: js.PromiseResolver, page: *Page) !js.Promise {
|
||||||
|
const blob: *Blob = page.lookupBlobUrl(url) orelse {
|
||||||
|
resolver.rejectError("fetch blob error", .{ .type_error = "BlobNotFound" });
|
||||||
|
return resolver.promise();
|
||||||
|
};
|
||||||
|
|
||||||
|
const response = try Response.init(null, .{ .status = 200 }, page);
|
||||||
|
response._body = try response._arena.dupe(u8, blob._slice);
|
||||||
|
response._url = try response._arena.dupeZ(u8, url);
|
||||||
|
response._type = .basic;
|
||||||
|
|
||||||
|
if (blob._mime.len > 0) {
|
||||||
|
try response._headers.append("Content-Type", blob._mime, page);
|
||||||
|
}
|
||||||
|
|
||||||
|
const js_val = try page.js.local.?.zigValueToJs(response, .{});
|
||||||
|
resolver.resolve("fetch blob done", js_val);
|
||||||
|
return resolver.promise();
|
||||||
|
}
|
||||||
|
|
||||||
fn httpStartCallback(transfer: *HttpClient.Transfer) !void {
|
fn httpStartCallback(transfer: *HttpClient.Transfer) !void {
|
||||||
const self: *Fetch = @ptrCast(@alignCast(transfer.ctx));
|
const self: *Fetch = @ptrCast(@alignCast(transfer.ctx));
|
||||||
if (comptime IS_DEBUG) {
|
if (comptime IS_DEBUG) {
|
||||||
|
|||||||
@@ -29,6 +29,7 @@ const Page = @import("../../Page.zig");
|
|||||||
const Session = @import("../../Session.zig");
|
const Session = @import("../../Session.zig");
|
||||||
|
|
||||||
const Node = @import("../Node.zig");
|
const Node = @import("../Node.zig");
|
||||||
|
const Blob = @import("../Blob.zig");
|
||||||
const Event = @import("../Event.zig");
|
const Event = @import("../Event.zig");
|
||||||
const Headers = @import("Headers.zig");
|
const Headers = @import("Headers.zig");
|
||||||
const EventTarget = @import("../EventTarget.zig");
|
const EventTarget = @import("../EventTarget.zig");
|
||||||
@@ -211,6 +212,11 @@ pub fn send(self: *XMLHttpRequest, body_: ?[]const u8) !void {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const page = self._page;
|
const page = self._page;
|
||||||
|
|
||||||
|
if (std.mem.startsWith(u8, self._url, "blob:")) {
|
||||||
|
return self.handleBlobUrl(page);
|
||||||
|
}
|
||||||
|
|
||||||
const http_client = page._session.browser.http_client;
|
const http_client = page._session.browser.http_client;
|
||||||
var headers = try http_client.newHeaders();
|
var headers = try http_client.newHeaders();
|
||||||
|
|
||||||
@@ -242,6 +248,39 @@ pub fn send(self: *XMLHttpRequest, body_: ?[]const u8) !void {
|
|||||||
|
|
||||||
page.js.strongRef(self);
|
page.js.strongRef(self);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn handleBlobUrl(self: *XMLHttpRequest, page: *Page) !void {
|
||||||
|
const blob = page.lookupBlobUrl(self._url) orelse {
|
||||||
|
self.handleError(error.BlobNotFound);
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
|
self._response_status = 200;
|
||||||
|
self._response_url = self._url;
|
||||||
|
|
||||||
|
try self._response_data.appendSlice(self._arena, blob._slice);
|
||||||
|
self._response_len = blob._slice.len;
|
||||||
|
|
||||||
|
try self.stateChanged(.headers_received, page);
|
||||||
|
try self._proto.dispatch(.load_start, .{ .loaded = 0, .total = self._response_len orelse 0 }, page);
|
||||||
|
try self.stateChanged(.loading, page);
|
||||||
|
try self._proto.dispatch(.progress, .{
|
||||||
|
.total = self._response_len orelse 0,
|
||||||
|
.loaded = self._response_data.items.len,
|
||||||
|
}, page);
|
||||||
|
try self.stateChanged(.done, page);
|
||||||
|
|
||||||
|
const loaded = self._response_data.items.len;
|
||||||
|
try self._proto.dispatch(.load, .{
|
||||||
|
.total = loaded,
|
||||||
|
.loaded = loaded,
|
||||||
|
}, page);
|
||||||
|
try self._proto.dispatch(.load_end, .{
|
||||||
|
.total = loaded,
|
||||||
|
.loaded = loaded,
|
||||||
|
}, page);
|
||||||
|
}
|
||||||
|
|
||||||
pub fn getReadyState(self: *const XMLHttpRequest) u32 {
|
pub fn getReadyState(self: *const XMLHttpRequest) u32 {
|
||||||
return @intFromEnum(self._ready_state);
|
return @intFromEnum(self._ready_state);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user