Skip to content

Conversation

@mookums
Copy link
Contributor

@mookums mookums commented Aug 25, 2025

This implements fetch in Zig, allowing us to also gain access to the ReadableStream API.


pub fn _get(self: *const Headers, header: []const u8, page: *Page) !?[]const u8 {
const arena = page.arena;
const key = try std.ascii.allocLowerString(arena, header);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can use the call_arena here, as you don't need the key to outlive this function call.

const key = try std.ascii.allocLowerString(arena, header);

const value = (self.headers.getEntry(key) orelse return null).value_ptr.*;
return try arena.dupe(u8, value);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You don't need to dupe this value at all, the string will get copied by v8. you can return self.headers.get(key) directly.

done: bool,

pub fn get_value(self: *const ReadableStreamReadResult, page: *Page) !?[]const u8 {
return if (self.value) |value| try page.arena.dupe(u8, value) else null;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Probably don't need to dupe this:

return self.value;

switch (stream.state) {
.readable => {
if (stream.queue.items.len > 0) {
const data = self.stream.queue.orderedRemove(0);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is O(N), right? I have no clue what these streams are used for, so we know how big the queues (a) typically are and (b) can grow to?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes it is. Will circle back on this, mainly was waiting for us to go to 0.15.1 because a lot of the FIFO stuff was removed. Would be nice if we had our own nice Queue impl (or maybe I just use the PriorityQueue and always return .eq 🤷)

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@mookums Can you address this change now?

},
.closed => |_| {
if (stream.queue.items.len > 0) {
const data = try page.arena.dupe(u8, self.stream.queue.orderedRemove(0));
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If the value is dupe'd using the page.arena when being inserted, I don't think you'll ever need to dupe it again (certainly not with the same arena).

@krichprollsch
Copy link
Member

krichprollsch commented Aug 27, 2025

I tried to fetch http://127.0.0.1:1234/campfire-commerce/ and I get a response.json is not a function error.
We will have to implement https://developer.mozilla.org/en-US/docs/Web/API/Response/json

@mookums mookums force-pushed the fetch branch 3 times, most recently from 8c8ba1e to 47aa966 Compare September 5, 2025 05:24
@krichprollsch
Copy link
Member

I'm testing the branch and comparing results w/ main.
It works quite well 👏

I found a difference fetching https://fr.pinterest.com/
I have the following error on this branch I don't have with main:

INFO  js : function call error . . . . . . . . . . . . . . .  [+1095ms]
      name = browser.html.window.Window._fetch
      err = InvalidArgument
      args =
        1: https://fr.pinterest.com/_/_/storage_report/ (string)
        2: {"method":"POST","headers":{"Content-Type":"application/json"},"body":"{\"storage-report\":{\"get\":[\"theme\"],\"set\":[],\"remove\":[]}}"} (object)
      stack =
        fetch:1
        <anonymous>:1
        d:18
        <anonymous>:18
        <anonymous>:18
        d:1
        a:1
        <anonymous>:1
        Promise:1
        <anonymous>:1
        v:1
        <anonymous>:1
        r:1
        <anonymous>:1
        d:18
        <anonymous>:18
        <anonymous>:18
        d:1
        a:1
        <anonymous>:1
        Promise:1
        <anonymous>:1
        b:1
        w:1
        value:1
        p:1
        237193:1
        n:1
        264788:1
        n:1

self.body,
.{},
) catch |e| {
log.warn(.browser, "invalid json", .{ .err = e, .source = "Request" });
Copy link
Member

@krichprollsch krichprollsch Sep 5, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You should create and use a .fetch domain for all fetch's logs

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should be a debug or info. It's almost certainly a valid JS error that we can't do anything about. warn+ should be for things we can action.

@krichprollsch
Copy link
Member

Same with https://jobs.lightpanda.io/

INFO  js : function call error . . . . . . . . . . . . . . .  [+799ms]
      name = browser.html.window.Window._fetch
      err = InvalidArgument
      args =
        1: /api/v3/getStatsigResults (string)
        2: {"method":"POST","credentials":"same-origin","headers":{"notion-client-version":"23.13.0.4890","notion-audit-log-platform":"web","x-notion-active-user-header":"","Content-Type":"application/json"},"body":"{\"deviceId\":\"9928347c-20cc-4366-aa1f-c00d3080f870
\",\"device\":{\"clientVersion\":\"23.13.0.4890\",\"isElectron\":false,\"isMobileNative\":false,\"isMobileBeta\":false,\"isMobileBrowser\":false,\"isBrowser\":true,\"isMobile\":false,\"isDesktopBrowser\":true,\"isTablet\":false,\"os\":\"unknown\",\"browserName\":\"und
efined\"},\"stableID\":\"d9ec7133-17de-4ba0-a206-c8ad22c49127\",\"locale\":\"en-US\",\"shouldStringifyInitialValues\":true,\"clientSdkApiKey\":\"client-Tgza5wNFa8dVt9BdeUfG6Vkm29bHxX10MhoztTMzLBB\"}"} (object)
      stack =
        m:54
        <anonymous>:54

@mookums
Copy link
Contributor Author

mookums commented Sep 8, 2025

Both of those resolved with 4413f68.

self.body,
.{},
) catch |e| {
log.warn(.browser, "invalid json", .{ .err = e, .source = "Request" });
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should be a debug or info. It's almost certainly a valid JS error that we can't do anything about. warn+ should be for things we can action.

@mookums mookums marked this pull request as ready for review September 8, 2025 14:56
@mookums
Copy link
Contributor Author

mookums commented Sep 8, 2025

Ready for review by whoever, just don't merge yet as I'm going to migrate the tests to the new htmlRunner.

@krichprollsch
Copy link
Member

We have a hug memory regression:

Peak resident set size: 50120
https://github.com/lightpanda-io/browser/actions/runs/17554979612/job/49962587005?pr=972

@mookums
Copy link
Contributor Author

mookums commented Sep 9, 2025

We have a hug memory regression:

Peak resident set size: 50120
https://github.com/lightpanda-io/browser/actions/runs/17554979612/job/49962587005?pr=972

Fixed I believe, was a Persistent PromiseResolver not being deinitalized.


const resolver = Env.PromiseResolver{
.js_context = page.main_context,
.resolver = v8.PromiseResolver.init(page.main_context.v8_context),
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No other part of the WebAPI that needs to dig this deep into the JS implementation. The fetch code does this 10 times. Surely there's a way to clean this up and make it easier and less v8-specific to create promises

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I changed it so all instances of this pattern now use the Env.PersistentPromiseResolver which should make it less v8 specific to use these.

};

// Add destructor callback for FetchContext.
try page.main_context.destructor_callbacks.append(arena, Env.DestructorCallback.init(fetch_ctx));
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This isn't ideal. Really reaching into an implementation details. Do we really need to reject the promise on page shutdown?

The other issue, that we talked about, is that if you don't cancel the transfer, then when the page does it, the errorCallback is called, and at that point, the context is aborted. But I think we can make this safer. (Although, I notice now that the errorCallback doesn't seem to be doing anything)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Changed this so it only rejects the promise if we have an error that isn't error.Abort which is what we throw when the page is closed (I think). This allows us to just ditch using the destructor callbacks entirely.

karlseguin added a commit that referenced this pull request Sep 16, 2025
This defaults to Auto, which means it runs when the call stack reaches 0.
It appears that both Node and Deno set this to explicit.

I don't really understand why Auto doesn't work. It says the call stack is the
C++/C callstack, and I don't see what would block the current code from reaching
a depth of 0. Still, we already have explicit calls to performMicrotasksCheckpoint
which ties it holistically with our scheduler, so having it be explicit like
this should...well make it more explicit

This broke a test, but since the tests are being redone in the [fetch PR](#972) I simply removed the offending one.
@mookums mookums force-pushed the fetch branch 3 times, most recently from bd4dd38 to 2a7a8bc Compare September 16, 2025 18:36
@krichprollsch krichprollsch merged commit 2d24e3c into main Sep 18, 2025
10 checks passed
@krichprollsch krichprollsch deleted the fetch branch September 18, 2025 07:29
@github-actions github-actions bot locked and limited conversation to collaborators Sep 18, 2025
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants