Skip to content

Commit af4066d

Browse files
authored
Merge pull request #946 from lightpanda-io/request_interception
Request Interception
2 parents 4de4e75 + f5ec742 commit af4066d

File tree

11 files changed

+552
-242
lines changed

11 files changed

+552
-242
lines changed

src/browser/ScriptManager.zig

Lines changed: 15 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -24,8 +24,8 @@ const parser = @import("netsurf.zig");
2424
const Env = @import("env.zig").Env;
2525
const Page = @import("page.zig").Page;
2626
const DataURI = @import("DataURI.zig");
27+
const Http = @import("../http/Http.zig");
2728
const Browser = @import("browser.zig").Browser;
28-
const HttpClient = @import("../http/Client.zig");
2929
const URL = @import("../url.zig").URL;
3030

3131
const Allocator = std.mem.Allocator;
@@ -57,7 +57,7 @@ deferreds: OrderList,
5757

5858
shutdown: bool = false,
5959

60-
client: *HttpClient,
60+
client: *Http.Client,
6161
allocator: Allocator,
6262
buffer_pool: BufferPool,
6363
script_pool: std.heap.MemoryPool(PendingScript),
@@ -229,7 +229,7 @@ pub fn addFromElement(self: *ScriptManager, element: *parser.Element) !void {
229229

230230
errdefer pending_script.deinit();
231231

232-
var headers = try HttpClient.Headers.init();
232+
var headers = try Http.Headers.init();
233233
try page.requestCookie(.{}).headersForRequest(page.arena, remote_url.?, &headers);
234234

235235
try self.client.request(.{
@@ -238,6 +238,7 @@ pub fn addFromElement(self: *ScriptManager, element: *parser.Element) !void {
238238
.method = .GET,
239239
.headers = headers,
240240
.cookie_jar = page.cookie_jar,
241+
.resource_type = .script,
241242
.start_callback = if (log.enabled(.http, .debug)) startCallback else null,
242243
.header_done_callback = headerCallback,
243244
.data_callback = dataCallback,
@@ -296,7 +297,7 @@ pub fn blockingGet(self: *ScriptManager, url: [:0]const u8) !BlockingResult {
296297
.buffer_pool = &self.buffer_pool,
297298
};
298299

299-
var headers = try HttpClient.Headers.init();
300+
var headers = try Http.Headers.init();
300301
try self.page.requestCookie(.{}).headersForRequest(self.page.arena, url, &headers);
301302

302303
var client = self.client;
@@ -306,6 +307,7 @@ pub fn blockingGet(self: *ScriptManager, url: [:0]const u8) !BlockingResult {
306307
.headers = headers,
307308
.cookie_jar = self.page.cookie_jar,
308309
.ctx = &blocking,
310+
.resource_type = .script,
309311
.start_callback = if (log.enabled(.http, .debug)) Blocking.startCallback else null,
310312
.header_done_callback = Blocking.headerCallback,
311313
.data_callback = Blocking.dataCallback,
@@ -423,15 +425,15 @@ fn getList(self: *ScriptManager, script: *const Script) *OrderList {
423425
return &self.scripts;
424426
}
425427

426-
fn startCallback(transfer: *HttpClient.Transfer) !void {
428+
fn startCallback(transfer: *Http.Transfer) !void {
427429
const script: *PendingScript = @alignCast(@ptrCast(transfer.ctx));
428430
script.startCallback(transfer) catch |err| {
429431
log.err(.http, "SM.startCallback", .{ .err = err, .transfer = transfer });
430432
return err;
431433
};
432434
}
433435

434-
fn headerCallback(transfer: *HttpClient.Transfer) !void {
436+
fn headerCallback(transfer: *Http.Transfer) !void {
435437
const script: *PendingScript = @alignCast(@ptrCast(transfer.ctx));
436438
script.headerCallback(transfer) catch |err| {
437439
log.err(.http, "SM.headerCallback", .{
@@ -443,7 +445,7 @@ fn headerCallback(transfer: *HttpClient.Transfer) !void {
443445
};
444446
}
445447

446-
fn dataCallback(transfer: *HttpClient.Transfer, data: []const u8) !void {
448+
fn dataCallback(transfer: *Http.Transfer, data: []const u8) !void {
447449
const script: *PendingScript = @alignCast(@ptrCast(transfer.ctx));
448450
script.dataCallback(transfer, data) catch |err| {
449451
log.err(.http, "SM.dataCallback", .{ .err = err, .transfer = transfer, .len = data.len });
@@ -488,12 +490,12 @@ const PendingScript = struct {
488490
}
489491
}
490492

491-
fn startCallback(self: *PendingScript, transfer: *HttpClient.Transfer) !void {
493+
fn startCallback(self: *PendingScript, transfer: *Http.Transfer) !void {
492494
_ = self;
493495
log.debug(.http, "script fetch start", .{ .req = transfer });
494496
}
495497

496-
fn headerCallback(self: *PendingScript, transfer: *HttpClient.Transfer) !void {
498+
fn headerCallback(self: *PendingScript, transfer: *Http.Transfer) !void {
497499
const header = &transfer.response_header.?;
498500
log.debug(.http, "script header", .{
499501
.req = transfer,
@@ -513,7 +515,7 @@ const PendingScript = struct {
513515
self.script.source = .{ .remote = self.manager.buffer_pool.get() };
514516
}
515517

516-
fn dataCallback(self: *PendingScript, transfer: *HttpClient.Transfer, data: []const u8) !void {
518+
fn dataCallback(self: *PendingScript, transfer: *Http.Transfer, data: []const u8) !void {
517519
_ = transfer;
518520
// too verbose
519521
// log.debug(.http, "script data chunk", .{
@@ -766,11 +768,11 @@ const Blocking = struct {
766768
done: BlockingResult,
767769
};
768770

769-
fn startCallback(transfer: *HttpClient.Transfer) !void {
771+
fn startCallback(transfer: *Http.Transfer) !void {
770772
log.debug(.http, "script fetch start", .{ .req = transfer, .blocking = true });
771773
}
772774

773-
fn headerCallback(transfer: *HttpClient.Transfer) !void {
775+
fn headerCallback(transfer: *Http.Transfer) !void {
774776
const header = &transfer.response_header.?;
775777
log.debug(.http, "script header", .{
776778
.req = transfer,
@@ -787,7 +789,7 @@ const Blocking = struct {
787789
self.buffer = self.buffer_pool.get();
788790
}
789791

790-
fn dataCallback(transfer: *HttpClient.Transfer, data: []const u8) !void {
792+
fn dataCallback(transfer: *Http.Transfer, data: []const u8) !void {
791793
// too verbose
792794
// log.debug(.http, "script data chunk", .{
793795
// .req = transfer,

src/browser/page.zig

Lines changed: 56 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ const Renderer = @import("renderer.zig").Renderer;
3030
const Window = @import("html/window.zig").Window;
3131
const Walker = @import("dom/walker.zig").WalkerDepthFirst;
3232
const Scheduler = @import("Scheduler.zig");
33-
const HttpClient = @import("../http/Client.zig");
33+
const Http = @import("../http/Http.zig");
3434
const ScriptManager = @import("ScriptManager.zig");
3535
const HTMLDocument = @import("html/document.zig").HTMLDocument;
3636

@@ -87,13 +87,23 @@ pub const Page = struct {
8787
polyfill_loader: polyfill.Loader = .{},
8888

8989
scheduler: Scheduler,
90-
http_client: *HttpClient,
90+
http_client: *Http.Client,
9191
script_manager: ScriptManager,
9292

9393
mode: Mode,
9494

9595
load_state: LoadState = .parsing,
9696

97+
// Page.wait balances waiting for resources / tasks and producing an output.
98+
// Up until a timeout, Page.wait will always wait for inflight or pending
99+
// HTTP requests, via the Http.Client.active counter. However, intercepted
100+
// requests (via CDP, but it could be anything), aren't considered "active"
101+
// connection. So it's possible that we have intercepted requests (which are
102+
// pending on some driver to continue/abort) while Http.Client.active == 0.
103+
// This boolean exists to supplment Http.Client.active and inform Page.wait
104+
// of pending connections.
105+
request_intercepted: bool = false,
106+
97107
const Mode = union(enum) {
98108
pre: void,
99109
err: anyerror,
@@ -275,16 +285,26 @@ pub const Page = struct {
275285
while (true) {
276286
SW: switch (self.mode) {
277287
.pre, .raw => {
288+
if (self.request_intercepted) {
289+
// the page request was intercepted.
290+
291+
// there shouldn't be any active requests;
292+
std.debug.assert(http_client.active == 0);
293+
294+
// nothing we can do for this, need to kick the can up
295+
// the chain and wait for activity (e.g. a CDP message)
296+
// to unblock this.
297+
return;
298+
}
299+
278300
// The main page hasn't started/finished navigating.
279301
// There's no JS to run, and no reason to run the scheduler.
280-
281302
if (http_client.active == 0) {
282303
// haven't started navigating, I guess.
283304
return;
284305
}
285306

286307
// There should only be 1 active http transfer, the main page
287-
std.debug.assert(http_client.active == 1);
288308
try http_client.tick(ms_remaining);
289309
},
290310
.html, .parsed => {
@@ -330,20 +350,35 @@ pub const Page = struct {
330350

331351
_ = try scheduler.runLowPriority();
332352

333-
// We'll block here, waiting for network IO. We know
334-
// when the next timeout is scheduled, and we know how long
335-
// the caller wants to wait for, so we can pick a good wait
336-
// duration
337-
const ms_to_wait = @min(ms_remaining, ms_to_next_task orelse 1000);
353+
const request_intercepted = self.request_intercepted;
354+
355+
// We want to prioritize processing intercepted requests
356+
// because, the sooner they get unblocked, the sooner we
357+
// can start the HTTP request. But we still want to advanced
358+
// existing HTTP requests, if possible. So, if we have
359+
// intercepted requests, we'll still look at existing HTTP
360+
// requests, but we won't block waiting for more data.
361+
const ms_to_wait =
362+
if (request_intercepted) 0
363+
364+
// But if we have no intercepted requests, we'll wait
365+
// for as long as we can for data to our existing
366+
// inflight requests
367+
else @min(ms_remaining, ms_to_next_task orelse 1000);
368+
338369
try http_client.tick(ms_to_wait);
339370

340-
if (try_catch.hasCaught()) {
341-
const msg = (try try_catch.err(self.arena)) orelse "unknown";
342-
log.warn(.user_script, "page wait", .{ .err = msg, .src = "data" });
343-
return error.JsError;
371+
if (request_intercepted) {
372+
// Again, proritizing intercepted requests. Exit this
373+
// loop so that our caller can hopefully resolve them
374+
// (i.e. continue or abort them);
375+
return;
344376
}
345377
},
346-
.err => |err| return err,
378+
.err => |err| {
379+
self.mode = .{ .raw_done = @errorName(err) };
380+
return err;
381+
},
347382
.raw_done => return,
348383
}
349384

@@ -362,7 +397,7 @@ pub const Page = struct {
362397
std.debug.print("\nactive requests: {d}\n", .{self.http_client.active});
363398
var n_ = self.http_client.handles.in_use.first;
364399
while (n_) |n| {
365-
const transfer = HttpClient.Transfer.fromEasy(n.data.conn.easy) catch |err| {
400+
const transfer = Http.Transfer.fromEasy(n.data.conn.easy) catch |err| {
366401
std.debug.print(" - failed to load transfer: {any}\n", .{err});
367402
break;
368403
};
@@ -435,7 +470,7 @@ pub const Page = struct {
435470
is_http: bool = true,
436471
is_navigation: bool = false,
437472
};
438-
pub fn requestCookie(self: *const Page, opts: RequestCookieOpts) HttpClient.RequestCookie {
473+
pub fn requestCookie(self: *const Page, opts: RequestCookieOpts) Http.Client.RequestCookie {
439474
return .{
440475
.jar = self.cookie_jar,
441476
.origin = &self.url.uri,
@@ -473,7 +508,7 @@ pub const Page = struct {
473508
const owned_url = try self.arena.dupeZ(u8, request_url);
474509
self.url = try URL.parse(owned_url, null);
475510

476-
var headers = try HttpClient.Headers.init();
511+
var headers = try Http.Headers.init();
477512
if (opts.header) |hdr| try headers.add(hdr);
478513
try self.requestCookie(.{ .is_navigation = true }).headersForRequest(self.arena, owned_url, &headers);
479514

@@ -484,6 +519,7 @@ pub const Page = struct {
484519
.headers = headers,
485520
.body = opts.body,
486521
.cookie_jar = self.cookie_jar,
522+
.resource_type = .document,
487523
.header_done_callback = pageHeaderDoneCallback,
488524
.data_callback = pageDataCallback,
489525
.done_callback = pageDoneCallback,
@@ -563,7 +599,7 @@ pub const Page = struct {
563599
);
564600
}
565601

566-
fn pageHeaderDoneCallback(transfer: *HttpClient.Transfer) !void {
602+
fn pageHeaderDoneCallback(transfer: *Http.Transfer) !void {
567603
var self: *Page = @alignCast(@ptrCast(transfer.ctx));
568604

569605
// would be different than self.url in the case of a redirect
@@ -578,7 +614,7 @@ pub const Page = struct {
578614
});
579615
}
580616

581-
fn pageDataCallback(transfer: *HttpClient.Transfer, data: []const u8) !void {
617+
fn pageDataCallback(transfer: *Http.Transfer, data: []const u8) !void {
582618
var self: *Page = @alignCast(@ptrCast(transfer.ctx));
583619

584620
if (self.mode == .pre) {
@@ -1002,7 +1038,7 @@ pub const NavigateReason = enum {
10021038
pub const NavigateOpts = struct {
10031039
cdp_id: ?i64 = null,
10041040
reason: NavigateReason = .address_bar,
1005-
method: HttpClient.Method = .GET,
1041+
method: Http.Method = .GET,
10061042
body: ?[]const u8 = null,
10071043
header: ?[:0]const u8 = null,
10081044
};

src/browser/xhr/xhr.zig

Lines changed: 13 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ const URL = @import("../../url.zig").URL;
3030
const Mime = @import("../mime.zig").Mime;
3131
const parser = @import("../netsurf.zig");
3232
const Page = @import("../page.zig").Page;
33-
const HttpClient = @import("../../http/Client.zig");
33+
const Http = @import("../../http/Http.zig");
3434
const CookieJar = @import("../storage/storage.zig").CookieJar;
3535

3636
// XHR interfaces
@@ -80,12 +80,12 @@ const XMLHttpRequestBodyInit = union(enum) {
8080
pub const XMLHttpRequest = struct {
8181
proto: XMLHttpRequestEventTarget = XMLHttpRequestEventTarget{},
8282
arena: Allocator,
83-
transfer: ?*HttpClient.Transfer = null,
83+
transfer: ?*Http.Transfer = null,
8484
err: ?anyerror = null,
8585
last_dispatch: i64 = 0,
8686
send_flag: bool = false,
8787

88-
method: HttpClient.Method,
88+
method: Http.Method,
8989
state: State,
9090
url: ?[:0]const u8 = null,
9191

@@ -320,7 +320,7 @@ pub const XMLHttpRequest = struct {
320320
}
321321

322322
const methods = [_]struct {
323-
tag: HttpClient.Method,
323+
tag: Http.Method,
324324
name: []const u8,
325325
}{
326326
.{ .tag = .DELETE, .name = "DELETE" },
@@ -330,7 +330,7 @@ pub const XMLHttpRequest = struct {
330330
.{ .tag = .POST, .name = "POST" },
331331
.{ .tag = .PUT, .name = "PUT" },
332332
};
333-
pub fn validMethod(m: []const u8) DOMError!HttpClient.Method {
333+
pub fn validMethod(m: []const u8) DOMError!Http.Method {
334334
for (methods) |method| {
335335
if (std.ascii.eqlIgnoreCase(method.name, m)) {
336336
return method.tag;
@@ -370,7 +370,7 @@ pub const XMLHttpRequest = struct {
370370
}
371371
}
372372

373-
var headers = try HttpClient.Headers.init();
373+
var headers = try Http.Headers.init();
374374
for (self.headers.items) |hdr| {
375375
try headers.add(hdr);
376376
}
@@ -383,6 +383,7 @@ pub const XMLHttpRequest = struct {
383383
.headers = headers,
384384
.body = self.request_body,
385385
.cookie_jar = page.cookie_jar,
386+
.resource_type = .xhr,
386387
.start_callback = httpStartCallback,
387388
.header_callback = httpHeaderCallback,
388389
.header_done_callback = httpHeaderDoneCallback,
@@ -392,18 +393,19 @@ pub const XMLHttpRequest = struct {
392393
});
393394
}
394395

395-
fn httpStartCallback(transfer: *HttpClient.Transfer) !void {
396+
fn httpStartCallback(transfer: *Http.Transfer) !void {
396397
const self: *XMLHttpRequest = @alignCast(@ptrCast(transfer.ctx));
397398
log.debug(.http, "request start", .{ .method = self.method, .url = self.url, .source = "xhr" });
398399
self.transfer = transfer;
399400
}
400401

401-
fn httpHeaderCallback(transfer: *HttpClient.Transfer, header: []const u8) !void {
402+
fn httpHeaderCallback(transfer: *Http.Transfer, header: Http.Header) !void {
402403
const self: *XMLHttpRequest = @alignCast(@ptrCast(transfer.ctx));
403-
try self.response_headers.append(self.arena, try self.arena.dupe(u8, header));
404+
const joined = try std.fmt.allocPrint(self.arena, "{s}: {s}", .{ header.name, header.value });
405+
try self.response_headers.append(self.arena, joined);
404406
}
405407

406-
fn httpHeaderDoneCallback(transfer: *HttpClient.Transfer) !void {
408+
fn httpHeaderDoneCallback(transfer: *Http.Transfer) !void {
407409
const self: *XMLHttpRequest = @alignCast(@ptrCast(transfer.ctx));
408410

409411
const header = &transfer.response_header.?;
@@ -433,7 +435,7 @@ pub const XMLHttpRequest = struct {
433435
self.dispatchEvt("readystatechange");
434436
}
435437

436-
fn httpDataCallback(transfer: *HttpClient.Transfer, data: []const u8) !void {
438+
fn httpDataCallback(transfer: *Http.Transfer, data: []const u8) !void {
437439
const self: *XMLHttpRequest = @alignCast(@ptrCast(transfer.ctx));
438440
try self.response_bytes.appendSlice(self.arena, data);
439441

0 commit comments

Comments
 (0)