mirror of
https://github.com/lightpanda-io/browser.git
synced 2025-10-29 23:23:28 +00:00
Switch XHR to new http client
get puppeteer/cdp.js working again make test are all passing
This commit is contained in:
@@ -38,6 +38,10 @@ page: *Page,
|
|||||||
// Only once this is true can deferred scripts be run
|
// Only once this is true can deferred scripts be run
|
||||||
static_scripts_done: bool,
|
static_scripts_done: bool,
|
||||||
|
|
||||||
|
// when async_count == 0 and static_script_done == true, the document is completed
|
||||||
|
// loading (i.e. page.documentIsComplete should be called).
|
||||||
|
async_count: usize,
|
||||||
|
|
||||||
// Normal scripts (non-deffered & non-async). These must be executed ni order
|
// Normal scripts (non-deffered & non-async). These must be executed ni order
|
||||||
scripts: OrderList,
|
scripts: OrderList,
|
||||||
|
|
||||||
@@ -58,6 +62,7 @@ pub fn init(app: *App, page: *Page) ScriptManager {
|
|||||||
.page = page,
|
.page = page,
|
||||||
.scripts = .{},
|
.scripts = .{},
|
||||||
.deferred = .{},
|
.deferred = .{},
|
||||||
|
.async_count = 0,
|
||||||
.allocator = allocator,
|
.allocator = allocator,
|
||||||
.client = app.http_client,
|
.client = app.http_client,
|
||||||
.static_scripts_done = false,
|
.static_scripts_done = false,
|
||||||
@@ -183,7 +188,7 @@ pub fn addFromElement(self: *ScriptManager, element: *parser.Element) !void {
|
|||||||
.ctx = pending_script,
|
.ctx = pending_script,
|
||||||
.method = .GET,
|
.method = .GET,
|
||||||
.start_callback = startCallback,
|
.start_callback = startCallback,
|
||||||
.header_callback = headerCallback,
|
.header_done_callback = headerCallback,
|
||||||
.data_callback = dataCallback,
|
.data_callback = dataCallback,
|
||||||
.done_callback = doneCallback,
|
.done_callback = doneCallback,
|
||||||
.error_callback = errorCallback,
|
.error_callback = errorCallback,
|
||||||
@@ -229,7 +234,31 @@ fn evaluate(self: *ScriptManager) void {
|
|||||||
pending_script.script.eval(page);
|
pending_script.script.eval(page);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// When all scripts (normal and deferred) are done loading, the document
|
||||||
|
// state changes (this ultimately triggers the DOMContentLoaded event)
|
||||||
page.documentIsLoaded();
|
page.documentIsLoaded();
|
||||||
|
|
||||||
|
if (self.async_count == 0) {
|
||||||
|
// if we're here, then its like `asyncDone`
|
||||||
|
// 1 - there are no async scripts pending
|
||||||
|
// 2 - we checkecked static_scripts_done == true above
|
||||||
|
// 3 - we drained self.scripts above
|
||||||
|
// 4 - we drained self.deferred above
|
||||||
|
page.documentIsComplete();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn asyncDone(self: *ScriptManager) void {
|
||||||
|
self.async_count -= 1;
|
||||||
|
if (
|
||||||
|
self.async_count == 0 and // there are no more async scripts
|
||||||
|
self.static_scripts_done and // and we've finished parsing the HTML to queue all <scripts>
|
||||||
|
self.scripts.first == null and // and there are no more <script src=> to wait for
|
||||||
|
self.deferred.first == null // and there are no more <script defer src=> to wait for
|
||||||
|
) {
|
||||||
|
// then the document is considered complete
|
||||||
|
self.page.documentIsComplete();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn getList(self: *ScriptManager, script: *const Script) ?*OrderList {
|
fn getList(self: *ScriptManager, script: *const Script) ?*OrderList {
|
||||||
@@ -334,10 +363,13 @@ const PendingScript = struct {
|
|||||||
|
|
||||||
fn doneCallback(self: *PendingScript, transfer: *http.Transfer) void {
|
fn doneCallback(self: *PendingScript, transfer: *http.Transfer) void {
|
||||||
log.debug(.http, "script fetch complete", .{ .req = transfer });
|
log.debug(.http, "script fetch complete", .{ .req = transfer });
|
||||||
|
|
||||||
|
const manager = self.manager;
|
||||||
if (self.script.is_async) {
|
if (self.script.is_async) {
|
||||||
// async script can be evaluated immediately
|
// async script can be evaluated immediately
|
||||||
defer self.deinit();
|
defer self.deinit();
|
||||||
self.script.eval(self.manager.page);
|
self.script.eval(self.manager.page);
|
||||||
|
manager.asyncDone();
|
||||||
} else {
|
} else {
|
||||||
self.complete = true;
|
self.complete = true;
|
||||||
self.manager.evaluate();
|
self.manager.evaluate();
|
||||||
@@ -348,6 +380,7 @@ const PendingScript = struct {
|
|||||||
log.warn(.http, "script fetch error", .{ .req = transfer, .err = err });
|
log.warn(.http, "script fetch error", .{ .req = transfer, .err = err });
|
||||||
self.deinit();
|
self.deinit();
|
||||||
}
|
}
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const Script = struct {
|
const Script = struct {
|
||||||
|
|||||||
@@ -96,12 +96,23 @@ pub const Mime = struct {
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
var attribute_value = value;
|
var attribute_value = value;
|
||||||
if (value[0] == '"' and value[value.len - 1] == '"') {
|
if (value[0] == '"') {
|
||||||
|
if (value.len < 3 or value[value.len - 1] != '"') {
|
||||||
|
return error.Invalid;
|
||||||
|
}
|
||||||
attribute_value = value[1 .. value.len - 1];
|
attribute_value = value[1 .. value.len - 1];
|
||||||
}
|
}
|
||||||
|
|
||||||
if (std.ascii.eqlIgnoreCase(attribute_value, "utf-8")) {
|
if (std.ascii.eqlIgnoreCase(attribute_value, "utf-8")) {
|
||||||
charset = "UTF-8";
|
charset = "UTF-8";
|
||||||
|
} else {
|
||||||
|
// we only care about null (which we default to UTF-8)
|
||||||
|
// or UTF-8. If this is actually set (i.e. not null)
|
||||||
|
// and isn't UTF-8, we'll just put a dummy value. If
|
||||||
|
// we want to capture the actual value, we'll need to
|
||||||
|
// dupe/allocate it. Since, for now, we don't need that
|
||||||
|
// we can avoid the allocation.
|
||||||
|
charset = "lightpanda:UNSUPPORTED";
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
@@ -271,7 +282,7 @@ pub const Mime = struct {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const testing = @import("../testing.zig");
|
const testing = @import("../testing.zig");
|
||||||
test "Mime: invalid " {
|
test "Mime: invalid" {
|
||||||
defer testing.reset();
|
defer testing.reset();
|
||||||
|
|
||||||
const invalids = [_][]const u8{
|
const invalids = [_][]const u8{
|
||||||
@@ -289,7 +300,6 @@ test "Mime: invalid " {
|
|||||||
"text/html; charset=\"\"",
|
"text/html; charset=\"\"",
|
||||||
"text/html; charset=\"",
|
"text/html; charset=\"",
|
||||||
"text/html; charset=\"\\",
|
"text/html; charset=\"\\",
|
||||||
"text/html; charset=\"\\a\"", // invalid to escape non special characters
|
|
||||||
};
|
};
|
||||||
|
|
||||||
for (invalids) |invalid| {
|
for (invalids) |invalid| {
|
||||||
@@ -351,19 +361,19 @@ test "Mime: parse charset" {
|
|||||||
|
|
||||||
try expect(.{
|
try expect(.{
|
||||||
.content_type = .{ .text_xml = {} },
|
.content_type = .{ .text_xml = {} },
|
||||||
.charset = "utf-8",
|
.charset = "UTF-8",
|
||||||
.params = "charset=utf-8",
|
.params = "charset=utf-8",
|
||||||
}, "text/xml; charset=utf-8");
|
}, "text/xml; charset=utf-8");
|
||||||
|
|
||||||
try expect(.{
|
try expect(.{
|
||||||
.content_type = .{ .text_xml = {} },
|
.content_type = .{ .text_xml = {} },
|
||||||
.charset = "utf-8",
|
.charset = "UTF-8",
|
||||||
.params = "charset=\"utf-8\"",
|
.params = "charset=\"utf-8\"",
|
||||||
}, "text/xml;charset=\"utf-8\"");
|
}, "text/xml;charset=\"utf-8\"");
|
||||||
|
|
||||||
try expect(.{
|
try expect(.{
|
||||||
.content_type = .{ .text_xml = {} },
|
.content_type = .{ .text_xml = {} },
|
||||||
.charset = "\\ \" ",
|
.charset = "lightpanda:UNSUPPORTED",
|
||||||
.params = "charset=\"\\\\ \\\" \"",
|
.params = "charset=\"\\\\ \\\" \"",
|
||||||
}, "text/xml;charset=\"\\\\ \\\" \" ");
|
}, "text/xml;charset=\"\\\\ \\\" \" ");
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -131,6 +131,7 @@ pub const Page = struct {
|
|||||||
.messageloop_node = .{ .func = messageLoopCallback },
|
.messageloop_node = .{ .func = messageLoopCallback },
|
||||||
.keydown_event_node = .{ .func = keydownCallback },
|
.keydown_event_node = .{ .func = keydownCallback },
|
||||||
.window_clicked_event_node = .{ .func = windowClicked },
|
.window_clicked_event_node = .{ .func = windowClicked },
|
||||||
|
// @newhttp
|
||||||
// .request_factory = browser.http_client.requestFactory(.{
|
// .request_factory = browser.http_client.requestFactory(.{
|
||||||
// .notification = browser.notification,
|
// .notification = browser.notification,
|
||||||
// }),
|
// }),
|
||||||
@@ -233,32 +234,32 @@ pub const Page = struct {
|
|||||||
pub fn wait(self: *Page, wait_sec: usize) !void {
|
pub fn wait(self: *Page, wait_sec: usize) !void {
|
||||||
switch (self.mode) {
|
switch (self.mode) {
|
||||||
.pre, .html, .raw, .parsed => {
|
.pre, .html, .raw, .parsed => {
|
||||||
// The HTML page was parsed. We're now either have JS scripts to
|
// The HTML page was parsed. We now either have JS scripts to
|
||||||
// download, or timeouts to execute, or both.
|
// download, or timeouts to execute, or both.
|
||||||
|
|
||||||
const cutoff = timestamp() + wait_sec;
|
const cutoff = timestamp() + wait_sec;
|
||||||
|
|
||||||
var loop = self.session.browser.app.loop;
|
|
||||||
|
|
||||||
var try_catch: Env.TryCatch = undefined;
|
var try_catch: Env.TryCatch = undefined;
|
||||||
try_catch.init(self.main_context);
|
try_catch.init(self.main_context);
|
||||||
defer try_catch.deinit();
|
defer try_catch.deinit();
|
||||||
|
|
||||||
|
var http_client = self.http_client;
|
||||||
|
var loop = self.session.browser.app.loop;
|
||||||
|
|
||||||
// @newhttp Not sure about the timing / the order / any of this.
|
// @newhttp Not sure about the timing / the order / any of this.
|
||||||
// I think I want to remove the loop. Implement our own timeouts
|
// I think I want to remove the loop. Implement our own timeouts
|
||||||
// and switch the CDP server to blocking. For now, just try this.`
|
// and switch the CDP server to blocking. For now, just try this.`
|
||||||
while (timestamp() < cutoff) {
|
while (timestamp() < cutoff) {
|
||||||
const active = try self.http_client.tick(10); // 10ms
|
const has_pending_timeouts = loop.hasPendingTimeout();
|
||||||
|
if (http_client.active > 0) {
|
||||||
|
try http_client.tick(10); // 10ms
|
||||||
|
} else if (self.loaded and self.loaded and !has_pending_timeouts) {
|
||||||
|
// we have no active HTTP requests, and no timeouts pending
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (active == 0) {
|
if (!has_pending_timeouts) {
|
||||||
if (!self.loaded) {
|
continue;
|
||||||
// We have no pending HTTP requests and we haven't
|
|
||||||
// triggered the load event. Trigger the load event.
|
|
||||||
self.documentIsComplete();
|
|
||||||
} else if (loop.hasPendingTimeout() == false) {
|
|
||||||
// we have no active HTTP requests, and no timeouts pending
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 10ms
|
// 10ms
|
||||||
@@ -308,11 +309,17 @@ pub const Page = struct {
|
|||||||
.ctx = self,
|
.ctx = self,
|
||||||
.url = owned_url,
|
.url = owned_url,
|
||||||
.method = opts.method,
|
.method = opts.method,
|
||||||
.header_callback = pageHeaderCallback,
|
.header_done_callback = pageHeaderCallback,
|
||||||
.data_callback = pageDataCallback,
|
.data_callback = pageDataCallback,
|
||||||
.done_callback = pageDoneCallback,
|
.done_callback = pageDoneCallback,
|
||||||
.error_callback = pageErrorCallback,
|
.error_callback = pageErrorCallback,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
self.session.browser.notification.dispatch(.page_navigate, &.{
|
||||||
|
.opts = opts,
|
||||||
|
.url = owned_url,
|
||||||
|
.timestamp = timestamp(),
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn documentIsLoaded(self: *Page) void {
|
pub fn documentIsLoaded(self: *Page) void {
|
||||||
@@ -321,11 +328,18 @@ pub const Page = struct {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
fn documentIsComplete(self: *Page) void {
|
pub fn documentIsComplete(self: *Page) void {
|
||||||
|
std.debug.assert(self.loaded == false);
|
||||||
|
|
||||||
self.loaded = true;
|
self.loaded = true;
|
||||||
self._documentIsComplete() catch |err| {
|
self._documentIsComplete() catch |err| {
|
||||||
log.err(.browser, "document is complete", .{ .err = err });
|
log.err(.browser, "document is complete", .{ .err = err });
|
||||||
};
|
};
|
||||||
|
|
||||||
|
self.session.browser.notification.dispatch(.page_navigated, &.{
|
||||||
|
.url = self.url.raw,
|
||||||
|
.timestamp = timestamp(),
|
||||||
|
});
|
||||||
}
|
}
|
||||||
fn _documentIsComplete(self: *Page) !void {
|
fn _documentIsComplete(self: *Page) !void {
|
||||||
try HTMLDocument.documentIsComplete(self.window.document, self);
|
try HTMLDocument.documentIsComplete(self.window.document, self);
|
||||||
|
|||||||
@@ -31,7 +31,6 @@ const Mime = @import("../mime.zig").Mime;
|
|||||||
const parser = @import("../netsurf.zig");
|
const parser = @import("../netsurf.zig");
|
||||||
const Page = @import("../page.zig").Page;
|
const Page = @import("../page.zig").Page;
|
||||||
const http = @import("../../http/client.zig");
|
const http = @import("../../http/client.zig");
|
||||||
const Loop = @import("../../runtime/loop.zig").Loop;
|
|
||||||
const CookieJar = @import("../storage/storage.zig").CookieJar;
|
const CookieJar = @import("../storage/storage.zig").CookieJar;
|
||||||
|
|
||||||
// XHR interfaces
|
// XHR interfaces
|
||||||
@@ -80,54 +79,29 @@ const XMLHttpRequestBodyInit = union(enum) {
|
|||||||
|
|
||||||
pub const XMLHttpRequest = struct {
|
pub const XMLHttpRequest = struct {
|
||||||
proto: XMLHttpRequestEventTarget = XMLHttpRequestEventTarget{},
|
proto: XMLHttpRequestEventTarget = XMLHttpRequestEventTarget{},
|
||||||
loop: *Loop,
|
|
||||||
arena: Allocator,
|
arena: Allocator,
|
||||||
request: ?*http.Request = null,
|
transfer: ?*http.Transfer = null,
|
||||||
|
cookie_jar: *CookieJar,
|
||||||
|
err: ?anyerror = null,
|
||||||
|
last_dispatch: i64 = 0,
|
||||||
|
send_flag: bool = false,
|
||||||
|
|
||||||
method: http.Method,
|
method: http.Method,
|
||||||
state: State,
|
state: State,
|
||||||
url: ?URL = null,
|
url: ?[:0]const u8 = null,
|
||||||
origin_url: *const URL,
|
|
||||||
|
|
||||||
// request headers
|
|
||||||
headers: Headers,
|
|
||||||
sync: bool = true,
|
sync: bool = true,
|
||||||
err: ?anyerror = null,
|
withCredentials: bool = false,
|
||||||
last_dispatch: i64 = 0,
|
headers: std.ArrayListUnmanaged([:0]const u8),
|
||||||
request_body: ?[]const u8 = null,
|
request_body: ?[]const u8 = null,
|
||||||
|
|
||||||
cookie_jar: *CookieJar,
|
response_status: u16 = 0,
|
||||||
// the URI of the page where this request is originating from
|
|
||||||
|
|
||||||
// TODO uncomment this field causes casting issue with
|
|
||||||
// XMLHttpRequestEventTarget. I think it's dueto an alignement issue, but
|
|
||||||
// not sure. see
|
|
||||||
// https://lightpanda.slack.com/archives/C05TRU6RBM1/p1707819010681019
|
|
||||||
// upload: ?XMLHttpRequestUpload = null,
|
|
||||||
|
|
||||||
// TODO uncomment this field causes casting issue with
|
|
||||||
// XMLHttpRequestEventTarget. I think it's dueto an alignement issue, but
|
|
||||||
// not sure. see
|
|
||||||
// https://lightpanda.slack.com/archives/C05TRU6RBM1/p1707819010681019
|
|
||||||
// timeout: u32 = 0,
|
|
||||||
|
|
||||||
withCredentials: bool = false,
|
|
||||||
// TODO: response readonly attribute any response;
|
|
||||||
response_bytes: std.ArrayListUnmanaged(u8) = .{},
|
response_bytes: std.ArrayListUnmanaged(u8) = .{},
|
||||||
response_type: ResponseType = .Empty,
|
response_type: ResponseType = .Empty,
|
||||||
response_headers: Headers,
|
response_headers: std.ArrayListUnmanaged([]const u8) = .{},
|
||||||
|
|
||||||
response_status: u16 = 0,
|
|
||||||
|
|
||||||
// TODO uncomment this field causes casting issue with
|
|
||||||
// XMLHttpRequestEventTarget. I think it's dueto an alignement issue, but
|
|
||||||
// not sure. see
|
|
||||||
// https://lightpanda.slack.com/archives/C05TRU6RBM1/p1707819010681019
|
|
||||||
// response_override_mime_type: ?[]const u8 = null,
|
|
||||||
|
|
||||||
response_mime: ?Mime = null,
|
response_mime: ?Mime = null,
|
||||||
response_obj: ?ResponseObj = null,
|
response_obj: ?ResponseObj = null,
|
||||||
send_flag: bool = false,
|
|
||||||
|
|
||||||
pub const prototype = *XMLHttpRequestEventTarget;
|
pub const prototype = *XMLHttpRequestEventTarget;
|
||||||
|
|
||||||
@@ -158,68 +132,6 @@ pub const XMLHttpRequest = struct {
|
|||||||
|
|
||||||
const JSONValue = std.json.Value;
|
const JSONValue = std.json.Value;
|
||||||
|
|
||||||
const Headers = struct {
|
|
||||||
list: List,
|
|
||||||
arena: Allocator,
|
|
||||||
|
|
||||||
const List = std.ArrayListUnmanaged(std.http.Header);
|
|
||||||
|
|
||||||
fn init(arena: Allocator) Headers {
|
|
||||||
return .{
|
|
||||||
.arena = arena,
|
|
||||||
.list = .{},
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
fn append(self: *Headers, k: []const u8, v: []const u8) !void {
|
|
||||||
// duplicate strings
|
|
||||||
const kk = try self.arena.dupe(u8, k);
|
|
||||||
const vv = try self.arena.dupe(u8, v);
|
|
||||||
try self.list.append(self.arena, .{ .name = kk, .value = vv });
|
|
||||||
}
|
|
||||||
|
|
||||||
fn reset(self: *Headers) void {
|
|
||||||
self.list.clearRetainingCapacity();
|
|
||||||
}
|
|
||||||
|
|
||||||
fn has(self: Headers, k: []const u8) bool {
|
|
||||||
for (self.list.items) |h| {
|
|
||||||
if (std.ascii.eqlIgnoreCase(k, h.name)) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
fn getFirstValue(self: Headers, k: []const u8) ?[]const u8 {
|
|
||||||
for (self.list.items) |h| {
|
|
||||||
if (std.ascii.eqlIgnoreCase(k, h.name)) {
|
|
||||||
return h.value;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
// replace any existing header with the same key
|
|
||||||
fn set(self: *Headers, k: []const u8, v: []const u8) !void {
|
|
||||||
for (self.list.items, 0..) |h, i| {
|
|
||||||
if (std.ascii.eqlIgnoreCase(k, h.name)) {
|
|
||||||
_ = self.list.swapRemove(i);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
self.append(k, v);
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO
|
|
||||||
fn sort(_: *Headers) void {}
|
|
||||||
|
|
||||||
fn all(self: Headers) []std.http.Header {
|
|
||||||
return self.list.items;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const Response = union(ResponseType) {
|
const Response = union(ResponseType) {
|
||||||
Empty: void,
|
Empty: void,
|
||||||
Text: []const u8,
|
Text: []const u8,
|
||||||
@@ -254,22 +166,18 @@ pub const XMLHttpRequest = struct {
|
|||||||
return .{
|
return .{
|
||||||
.url = null,
|
.url = null,
|
||||||
.arena = arena,
|
.arena = arena,
|
||||||
.loop = page.loop,
|
.headers = .{},
|
||||||
.headers = Headers.init(arena),
|
|
||||||
.response_headers = Headers.init(arena),
|
|
||||||
.method = undefined,
|
.method = undefined,
|
||||||
.state = .unsent,
|
.state = .unsent,
|
||||||
.origin_url = &page.url,
|
|
||||||
.cookie_jar = page.cookie_jar,
|
.cookie_jar = page.cookie_jar,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn destructor(self: *XMLHttpRequest) void {
|
pub fn destructor(self: *XMLHttpRequest) void {
|
||||||
// @newhttp
|
if (self.transfer) |transfer| {
|
||||||
// if (self.request) |req| {
|
transfer.abort();
|
||||||
// req.abort();
|
self.transfer = null;
|
||||||
self.request = null;
|
}
|
||||||
// }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn reset(self: *XMLHttpRequest) void {
|
pub fn reset(self: *XMLHttpRequest) void {
|
||||||
@@ -283,9 +191,8 @@ pub const XMLHttpRequest = struct {
|
|||||||
self.response_type = .Empty;
|
self.response_type = .Empty;
|
||||||
self.response_mime = null;
|
self.response_mime = null;
|
||||||
|
|
||||||
// TODO should we clearRetainingCapacity instead?
|
self.headers.clearRetainingCapacity();
|
||||||
self.headers.reset();
|
self.response_headers.clearRetainingCapacity();
|
||||||
self.response_headers.reset();
|
|
||||||
self.response_status = 0;
|
self.response_status = 0;
|
||||||
|
|
||||||
self.send_flag = false;
|
self.send_flag = false;
|
||||||
@@ -325,6 +232,7 @@ pub const XMLHttpRequest = struct {
|
|||||||
asyn: ?bool,
|
asyn: ?bool,
|
||||||
username: ?[]const u8,
|
username: ?[]const u8,
|
||||||
password: ?[]const u8,
|
password: ?[]const u8,
|
||||||
|
page: *Page,
|
||||||
) !void {
|
) !void {
|
||||||
_ = username;
|
_ = username;
|
||||||
_ = password;
|
_ = password;
|
||||||
@@ -335,9 +243,7 @@ pub const XMLHttpRequest = struct {
|
|||||||
self.reset();
|
self.reset();
|
||||||
|
|
||||||
self.method = try validMethod(method);
|
self.method = try validMethod(method);
|
||||||
const arena = self.arena;
|
self.url = try URL.stitch(page.arena, url, page.url.raw, .{ .null_terminated = true });
|
||||||
|
|
||||||
self.url = try self.origin_url.resolve(arena, url);
|
|
||||||
self.sync = if (asyn) |b| !b else false;
|
self.sync = if (asyn) |b| !b else false;
|
||||||
|
|
||||||
self.state = .opened;
|
self.state = .opened;
|
||||||
@@ -438,9 +344,18 @@ pub const XMLHttpRequest = struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn _setRequestHeader(self: *XMLHttpRequest, name: []const u8, value: []const u8) !void {
|
pub fn _setRequestHeader(self: *XMLHttpRequest, name: []const u8, value: []const u8) !void {
|
||||||
if (self.state != .opened) return DOMError.InvalidState;
|
if (self.state != .opened) {
|
||||||
if (self.send_flag) return DOMError.InvalidState;
|
return DOMError.InvalidState;
|
||||||
return try self.headers.append(name, value);
|
}
|
||||||
|
|
||||||
|
if (self.send_flag) {
|
||||||
|
return DOMError.InvalidState;
|
||||||
|
}
|
||||||
|
|
||||||
|
return self.headers.append(
|
||||||
|
self.arena,
|
||||||
|
try std.fmt.allocPrintZ(self.arena, "{s}: {s}", .{name, value}),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO body can be either a XMLHttpRequestBodyInit or a document
|
// TODO body can be either a XMLHttpRequestBodyInit or a document
|
||||||
@@ -455,113 +370,107 @@ pub const XMLHttpRequest = struct {
|
|||||||
self.request_body = try self.arena.dupe(u8, b);
|
self.request_body = try self.arena.dupe(u8, b);
|
||||||
}
|
}
|
||||||
|
|
||||||
// @newhttp
|
try page.http_client.request(.{
|
||||||
_ = page;
|
.ctx = self,
|
||||||
// try page.request_factory.initAsync(
|
.url = self.url.?,
|
||||||
// page.arena,
|
.method = self.method,
|
||||||
// self.method,
|
.start_callback = httpStartCallback,
|
||||||
// &self.url.?.uri,
|
.header_callback = httpHeaderCallback,
|
||||||
// self,
|
.header_done_callback = httpHeaderDoneCallback,
|
||||||
// onHttpRequestReady,
|
.data_callback = httpDataCallback,
|
||||||
// );
|
.done_callback = httpDoneCallback,
|
||||||
|
.error_callback = httpErrorCallback,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
fn onHttpRequestReady(ctx: *anyopaque, request: *http.Request) !void {
|
fn httpStartCallback(transfer: *http.Transfer) !void {
|
||||||
// on error, our caller will cleanup request
|
const self: *XMLHttpRequest = @alignCast(@ptrCast(transfer.ctx));
|
||||||
const self: *XMLHttpRequest = @alignCast(@ptrCast(ctx));
|
|
||||||
|
|
||||||
for (self.headers.list.items) |hdr| {
|
for (self.headers.items) |hdr| {
|
||||||
try request.addHeader(hdr.name, hdr.value, .{});
|
try transfer.addHeader(hdr);
|
||||||
}
|
}
|
||||||
|
|
||||||
{
|
// @newhttp
|
||||||
var arr: std.ArrayListUnmanaged(u8) = .{};
|
// {
|
||||||
try self.cookie_jar.forRequest(&self.url.?.uri, arr.writer(self.arena), .{
|
// var arr: std.ArrayListUnmanaged(u8) = .{};
|
||||||
.navigation = false,
|
// try self.cookie_jar.forRequest(&self.url.?.uri, arr.writer(self.arena), .{
|
||||||
.origin_uri = &self.origin_url.uri,
|
// .navigation = false,
|
||||||
.is_http = true,
|
// .origin_uri = &self.origin_url.uri,
|
||||||
});
|
// .is_http = true,
|
||||||
|
// });
|
||||||
|
|
||||||
if (arr.items.len > 0) {
|
// if (arr.items.len > 0) {
|
||||||
try request.addHeader("Cookie", arr.items, .{});
|
// try request.addHeader("Cookie", arr.items, .{});
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
|
|
||||||
// The body argument provides the request body, if any, and is ignored
|
|
||||||
// if the request method is GET or HEAD.
|
|
||||||
// https://xhr.spec.whatwg.org/#the-send()-method
|
|
||||||
// var used_body: ?XMLHttpRequestBodyInit = null;
|
|
||||||
if (self.request_body) |b| {
|
if (self.request_body) |b| {
|
||||||
if (self.method != .GET and self.method != .HEAD) {
|
if (self.method != .GET and self.method != .HEAD) {
|
||||||
request.body = b;
|
try transfer.setBody(b);
|
||||||
try request.addHeader("Content-Type", "text/plain; charset=UTF-8", .{});
|
try transfer.addHeader("Content-Type: text/plain; charset=UTF-8");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
self.transfer = transfer;
|
||||||
try request.sendAsync(self, .{});
|
|
||||||
self.request = request;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn onHttpResponse(self: *XMLHttpRequest, progress_: anyerror!http.Progress) !void {
|
fn httpHeaderCallback(transfer: *http.Transfer, header: []const u8) !void {
|
||||||
const progress = progress_ catch |err| {
|
const self: *XMLHttpRequest = @alignCast(@ptrCast(transfer.ctx));
|
||||||
// The request has been closed internally by the client, it isn't safe
|
try self.response_headers.append(self.arena, try self.arena.dupe(u8, header));
|
||||||
// for us to keep it around.
|
}
|
||||||
self.request = null;
|
|
||||||
self.onErr(err);
|
|
||||||
return err;
|
|
||||||
};
|
|
||||||
|
|
||||||
if (progress.first) {
|
fn httpHeaderDoneCallback(transfer: *http.Transfer) !void {
|
||||||
const header = progress.header;
|
const self: *XMLHttpRequest = @alignCast(@ptrCast(transfer.ctx));
|
||||||
log.debug(.http, "request header", .{
|
|
||||||
.source = "xhr",
|
|
||||||
.url = self.url,
|
|
||||||
.status = header.status,
|
|
||||||
});
|
|
||||||
for (header.headers.items) |hdr| {
|
|
||||||
try self.response_headers.append(hdr.name, hdr.value);
|
|
||||||
}
|
|
||||||
|
|
||||||
// extract a mime type from headers.
|
const header = &transfer.response_header.?;
|
||||||
if (header.get("content-type")) |ct| {
|
|
||||||
self.response_mime = Mime.parse(self.arena, ct) catch |e| {
|
|
||||||
return self.onErr(e);
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO handle override mime type
|
log.debug(.http, "request header", .{
|
||||||
self.state = .headers_received;
|
.source = "xhr",
|
||||||
self.dispatchEvt("readystatechange");
|
.url = self.url,
|
||||||
|
.status = header.status,
|
||||||
|
});
|
||||||
|
|
||||||
self.response_status = header.status;
|
if (header.contentType()) |ct| {
|
||||||
|
self.response_mime = Mime.parse(ct) catch |e| {
|
||||||
// TODO correct total
|
return self.onErr(e);
|
||||||
self.dispatchProgressEvent("loadstart", .{ .loaded = 0, .total = 0 });
|
};
|
||||||
|
|
||||||
self.state = .loading;
|
|
||||||
self.dispatchEvt("readystatechange");
|
|
||||||
|
|
||||||
try self.cookie_jar.populateFromResponse(self.request.?.request_uri, &header);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (progress.data) |data| {
|
// TODO handle override mime type
|
||||||
try self.response_bytes.appendSlice(self.arena, data);
|
self.state = .headers_received;
|
||||||
|
self.dispatchEvt("readystatechange");
|
||||||
|
|
||||||
|
self.response_status = header.status;
|
||||||
|
|
||||||
|
// TODO correct total
|
||||||
|
self.dispatchProgressEvent("loadstart", .{ .loaded = 0, .total = 0 });
|
||||||
|
|
||||||
|
self.state = .loading;
|
||||||
|
self.dispatchEvt("readystatechange");
|
||||||
|
|
||||||
|
// @newhttp
|
||||||
|
// try self.cookie_jar.populateFromResponse(self.request.?.request_uri, &header);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn httpDataCallback(transfer: *http.Transfer, data: []const u8) !void {
|
||||||
|
const self: *XMLHttpRequest = @alignCast(@ptrCast(transfer.ctx));
|
||||||
|
try self.response_bytes.appendSlice(self.arena, data);
|
||||||
|
|
||||||
|
const now = std.time.milliTimestamp();
|
||||||
|
if (now - self.last_dispatch < 50) {
|
||||||
|
// don't send this more than once every 50ms
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const loaded = self.response_bytes.items.len;
|
const loaded = self.response_bytes.items.len;
|
||||||
const now = std.time.milliTimestamp();
|
self.dispatchProgressEvent("progress", .{
|
||||||
if (now - self.last_dispatch > 50) {
|
.total = loaded, // TODO, this is wrong? Need the content-type
|
||||||
// don't send this more than once every 50ms
|
.loaded = loaded,
|
||||||
self.dispatchProgressEvent("progress", .{
|
});
|
||||||
.total = loaded,
|
self.last_dispatch = now;
|
||||||
.loaded = loaded,
|
}
|
||||||
});
|
|
||||||
self.last_dispatch = now;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (progress.done == false) {
|
fn httpDoneCallback(transfer: *http.Transfer) !void {
|
||||||
return;
|
const self: *XMLHttpRequest = @alignCast(@ptrCast(transfer.ctx));
|
||||||
}
|
|
||||||
|
|
||||||
log.info(.http, "request complete", .{
|
log.info(.http, "request complete", .{
|
||||||
.source = "xhr",
|
.source = "xhr",
|
||||||
@@ -569,20 +478,34 @@ pub const XMLHttpRequest = struct {
|
|||||||
.status = self.response_status,
|
.status = self.response_status,
|
||||||
});
|
});
|
||||||
|
|
||||||
// Not that the request is done, the http/client will free the request
|
// Not that the request is done, the http/client will free the transfer
|
||||||
// object. It isn't safe to keep it around.
|
// object. It isn't safe to keep it around.
|
||||||
self.request = null;
|
self.transfer = null;
|
||||||
|
|
||||||
self.state = .done;
|
self.state = .done;
|
||||||
self.send_flag = false;
|
self.send_flag = false;
|
||||||
self.dispatchEvt("readystatechange");
|
self.dispatchEvt("readystatechange");
|
||||||
|
|
||||||
|
const loaded = self.response_bytes.items.len;
|
||||||
|
|
||||||
// dispatch a progress event load.
|
// dispatch a progress event load.
|
||||||
self.dispatchProgressEvent("load", .{ .loaded = loaded, .total = loaded });
|
self.dispatchProgressEvent("load", .{ .loaded = loaded, .total = loaded });
|
||||||
// dispatch a progress event loadend.
|
// dispatch a progress event loadend.
|
||||||
self.dispatchProgressEvent("loadend", .{ .loaded = loaded, .total = loaded });
|
self.dispatchProgressEvent("loadend", .{ .loaded = loaded, .total = loaded });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn httpErrorCallback(transfer: *http.Transfer, err: anyerror) void {
|
||||||
|
const self: *XMLHttpRequest = @alignCast(@ptrCast(transfer.ctx));
|
||||||
|
// http client will close it after an error, it isn't safe to keep around
|
||||||
|
self.transfer = null;
|
||||||
|
self.onErr(err);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn _abort(self: *XMLHttpRequest) void {
|
||||||
|
self.onErr(DOMError.Abort);
|
||||||
|
self.destructor();
|
||||||
|
}
|
||||||
|
|
||||||
fn onErr(self: *XMLHttpRequest, err: anyerror) void {
|
fn onErr(self: *XMLHttpRequest, err: anyerror) void {
|
||||||
self.send_flag = false;
|
self.send_flag = false;
|
||||||
|
|
||||||
@@ -610,15 +533,10 @@ pub const XMLHttpRequest = struct {
|
|||||||
log.log(.http, level, "error", .{
|
log.log(.http, level, "error", .{
|
||||||
.url = self.url,
|
.url = self.url,
|
||||||
.err = err,
|
.err = err,
|
||||||
.source = "xhr",
|
.source = "xhr.OnErr",
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn _abort(self: *XMLHttpRequest) void {
|
|
||||||
self.onErr(DOMError.Abort);
|
|
||||||
self.destructor();
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn get_responseType(self: *XMLHttpRequest) []const u8 {
|
pub fn get_responseType(self: *XMLHttpRequest) []const u8 {
|
||||||
return switch (self.response_type) {
|
return switch (self.response_type) {
|
||||||
.Empty => "",
|
.Empty => "",
|
||||||
@@ -660,9 +578,8 @@ pub const XMLHttpRequest = struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// TODO retrieve the redirected url
|
// TODO retrieve the redirected url
|
||||||
pub fn get_responseURL(self: *XMLHttpRequest) ?[]const u8 {
|
pub fn get_responseURL(self: *XMLHttpRequest) ?[:0]const u8 {
|
||||||
const url = &(self.url orelse return null);
|
return self.url;
|
||||||
return url.raw;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_responseXML(self: *XMLHttpRequest) !?Response {
|
pub fn get_responseXML(self: *XMLHttpRequest) !?Response {
|
||||||
@@ -766,18 +683,8 @@ pub const XMLHttpRequest = struct {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
var ccharset: [:0]const u8 = "utf-8";
|
|
||||||
if (mime.charset) |rc| {
|
|
||||||
if (std.mem.eql(u8, rc, "utf-8") == false) {
|
|
||||||
ccharset = self.arena.dupeZ(u8, rc) catch {
|
|
||||||
self.response_obj = .{ .Failure = {} };
|
|
||||||
return;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var fbs = std.io.fixedBufferStream(self.response_bytes.items);
|
var fbs = std.io.fixedBufferStream(self.response_bytes.items);
|
||||||
const doc = parser.documentHTMLParse(fbs.reader(), ccharset) catch {
|
const doc = parser.documentHTMLParse(fbs.reader(), mime.charset orelse "UTF-8") catch {
|
||||||
self.response_obj = .{ .Failure = {} };
|
self.response_obj = .{ .Failure = {} };
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
@@ -814,26 +721,27 @@ pub const XMLHttpRequest = struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn _getResponseHeader(self: *XMLHttpRequest, name: []const u8) ?[]const u8 {
|
pub fn _getResponseHeader(self: *XMLHttpRequest, name: []const u8) ?[]const u8 {
|
||||||
return self.response_headers.getFirstValue(name);
|
for (self.response_headers.items) |entry| {
|
||||||
|
if (entry.len <= name.len) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (std.ascii.eqlIgnoreCase(name, entry[0..name.len]) == false) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (entry[name.len] != ':') {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
return std.mem.trimLeft(u8, entry[name.len + 1..], " ");
|
||||||
|
}
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
// The caller owns the string returned.
|
|
||||||
// TODO change the return type to express the string ownership and let
|
|
||||||
// jsruntime free the string once copied to v8.
|
|
||||||
// see https://github.com/lightpanda-io/jsruntime-lib/issues/195
|
|
||||||
pub fn _getAllResponseHeaders(self: *XMLHttpRequest) ![]const u8 {
|
pub fn _getAllResponseHeaders(self: *XMLHttpRequest) ![]const u8 {
|
||||||
if (self.response_headers.list.items.len == 0) return "";
|
|
||||||
self.response_headers.sort();
|
|
||||||
|
|
||||||
var buf: std.ArrayListUnmanaged(u8) = .{};
|
var buf: std.ArrayListUnmanaged(u8) = .{};
|
||||||
const w = buf.writer(self.arena);
|
const w = buf.writer(self.arena);
|
||||||
|
|
||||||
for (self.response_headers.list.items) |entry| {
|
for (self.response_headers.items) |entry| {
|
||||||
if (entry.value.len == 0) continue;
|
try w.writeAll(entry);
|
||||||
|
|
||||||
try w.writeAll(entry.name);
|
|
||||||
try w.writeAll(": ");
|
|
||||||
try w.writeAll(entry.value);
|
|
||||||
try w.writeAll("\r\n");
|
try w.writeAll("\r\n");
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -865,8 +773,7 @@ test "Browser.XHR.XMLHttpRequest" {
|
|||||||
.{ "req.onload", "function cbk(event) { nb ++; evt = event; }" },
|
.{ "req.onload", "function cbk(event) { nb ++; evt = event; }" },
|
||||||
.{ "req.onload = cbk", "function cbk(event) { nb ++; evt = event; }" },
|
.{ "req.onload = cbk", "function cbk(event) { nb ++; evt = event; }" },
|
||||||
|
|
||||||
.{ "req.open('GET', 'https://127.0.0.1:9581/xhr')", "undefined" },
|
.{ "req.open('GET', 'http://127.0.0.1:9582/xhr')", null },
|
||||||
.{ "req.setRequestHeader('User-Agent', 'lightpanda/1.0')", "undefined" },
|
|
||||||
|
|
||||||
// ensure open resets values
|
// ensure open resets values
|
||||||
.{ "req.status ", "0" },
|
.{ "req.status ", "0" },
|
||||||
@@ -886,7 +793,12 @@ test "Browser.XHR.XMLHttpRequest" {
|
|||||||
.{ "req.status", "200" },
|
.{ "req.status", "200" },
|
||||||
.{ "req.statusText", "OK" },
|
.{ "req.statusText", "OK" },
|
||||||
.{ "req.getResponseHeader('Content-Type')", "text/html; charset=utf-8" },
|
.{ "req.getResponseHeader('Content-Type')", "text/html; charset=utf-8" },
|
||||||
.{ "req.getAllResponseHeaders().length", "80" },
|
.{
|
||||||
|
"req.getAllResponseHeaders()",
|
||||||
|
"content-length: 100\r\n" ++
|
||||||
|
"Content-Type: text/html; charset=utf-8\r\n" ++
|
||||||
|
"Connection: Close\r\n"
|
||||||
|
},
|
||||||
.{ "req.responseText.length", "100" },
|
.{ "req.responseText.length", "100" },
|
||||||
.{ "req.response.length == req.responseText.length", "true" },
|
.{ "req.response.length == req.responseText.length", "true" },
|
||||||
.{ "req.responseXML instanceof Document", "true" },
|
.{ "req.responseXML instanceof Document", "true" },
|
||||||
@@ -894,7 +806,7 @@ test "Browser.XHR.XMLHttpRequest" {
|
|||||||
|
|
||||||
try runner.testCases(&.{
|
try runner.testCases(&.{
|
||||||
.{ "const req2 = new XMLHttpRequest()", "undefined" },
|
.{ "const req2 = new XMLHttpRequest()", "undefined" },
|
||||||
.{ "req2.open('GET', 'https://127.0.0.1:9581/xhr')", "undefined" },
|
.{ "req2.open('GET', 'http://127.0.0.1:9582/xhr')", "undefined" },
|
||||||
.{ "req2.responseType = 'document'", "document" },
|
.{ "req2.responseType = 'document'", "document" },
|
||||||
|
|
||||||
.{ "req2.send()", "undefined" },
|
.{ "req2.send()", "undefined" },
|
||||||
@@ -909,7 +821,7 @@ test "Browser.XHR.XMLHttpRequest" {
|
|||||||
|
|
||||||
try runner.testCases(&.{
|
try runner.testCases(&.{
|
||||||
.{ "const req3 = new XMLHttpRequest()", "undefined" },
|
.{ "const req3 = new XMLHttpRequest()", "undefined" },
|
||||||
.{ "req3.open('GET', 'https://127.0.0.1:9581/xhr/json')", "undefined" },
|
.{ "req3.open('GET', 'http://127.0.0.1:9582/xhr/json')", "undefined" },
|
||||||
.{ "req3.responseType = 'json'", "json" },
|
.{ "req3.responseType = 'json'", "json" },
|
||||||
|
|
||||||
.{ "req3.send()", "undefined" },
|
.{ "req3.send()", "undefined" },
|
||||||
@@ -923,7 +835,7 @@ test "Browser.XHR.XMLHttpRequest" {
|
|||||||
|
|
||||||
try runner.testCases(&.{
|
try runner.testCases(&.{
|
||||||
.{ "const req4 = new XMLHttpRequest()", "undefined" },
|
.{ "const req4 = new XMLHttpRequest()", "undefined" },
|
||||||
.{ "req4.open('POST', 'https://127.0.0.1:9581/xhr')", "undefined" },
|
.{ "req4.open('POST', 'http://127.0.0.1:9582/xhr')", "undefined" },
|
||||||
.{ "req4.send('foo')", "undefined" },
|
.{ "req4.send('foo')", "undefined" },
|
||||||
|
|
||||||
// Each case executed waits for all loop callaback calls.
|
// Each case executed waits for all loop callaback calls.
|
||||||
@@ -935,7 +847,7 @@ test "Browser.XHR.XMLHttpRequest" {
|
|||||||
|
|
||||||
try runner.testCases(&.{
|
try runner.testCases(&.{
|
||||||
.{ "const req5 = new XMLHttpRequest()", "undefined" },
|
.{ "const req5 = new XMLHttpRequest()", "undefined" },
|
||||||
.{ "req5.open('GET', 'https://127.0.0.1:9581/xhr')", "undefined" },
|
.{ "req5.open('GET', 'http://127.0.0.1:9582/xhr')", "undefined" },
|
||||||
.{ "var status = 0; req5.onload = function () { status = this.status };", "function () { status = this.status }" },
|
.{ "var status = 0; req5.onload = function () { status = this.status };", "function () { status = this.status }" },
|
||||||
.{ "req5.send()", "undefined" },
|
.{ "req5.send()", "undefined" },
|
||||||
|
|
||||||
@@ -956,7 +868,7 @@ test "Browser.XHR.XMLHttpRequest" {
|
|||||||
,
|
,
|
||||||
null,
|
null,
|
||||||
},
|
},
|
||||||
.{ "req6.open('GET', 'https://127.0.0.1:9581/xhr')", null },
|
.{ "req6.open('GET', 'http://127.0.0.1:9582/xhr')", null },
|
||||||
.{ "req6.send()", null },
|
.{ "req6.send()", null },
|
||||||
.{ "readyStates.length", "4" },
|
.{ "readyStates.length", "4" },
|
||||||
.{ "readyStates[0] === XMLHttpRequest.OPENED", "true" },
|
.{ "readyStates[0] === XMLHttpRequest.OPENED", "true" },
|
||||||
|
|||||||
@@ -155,6 +155,7 @@ fn navigate(cmd: anytype) !void {
|
|||||||
.reason = .address_bar,
|
.reason = .address_bar,
|
||||||
.cdp_id = cmd.input.id,
|
.cdp_id = cmd.input.id,
|
||||||
});
|
});
|
||||||
|
try page.wait(5);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn pageNavigate(arena: Allocator, bc: anytype, event: *const Notification.PageNavigate) !void {
|
pub fn pageNavigate(arena: Allocator, bc: anytype, event: *const Notification.PageNavigate) !void {
|
||||||
@@ -189,13 +190,13 @@ pub fn pageNavigate(arena: Allocator, bc: anytype, event: *const Notification.Pa
|
|||||||
.frameId = target_id,
|
.frameId = target_id,
|
||||||
.delay = 0,
|
.delay = 0,
|
||||||
.reason = reason,
|
.reason = reason,
|
||||||
.url = event.url.raw,
|
.url = event.url,
|
||||||
}, .{ .session_id = session_id });
|
}, .{ .session_id = session_id });
|
||||||
|
|
||||||
try cdp.sendEvent("Page.frameRequestedNavigation", .{
|
try cdp.sendEvent("Page.frameRequestedNavigation", .{
|
||||||
.frameId = target_id,
|
.frameId = target_id,
|
||||||
.reason = reason,
|
.reason = reason,
|
||||||
.url = event.url.raw,
|
.url = event.url,
|
||||||
.disposition = "currentTab",
|
.disposition = "currentTab",
|
||||||
}, .{ .session_id = session_id });
|
}, .{ .session_id = session_id });
|
||||||
}
|
}
|
||||||
@@ -203,7 +204,7 @@ pub fn pageNavigate(arena: Allocator, bc: anytype, event: *const Notification.Pa
|
|||||||
// frameStartedNavigating event
|
// frameStartedNavigating event
|
||||||
try cdp.sendEvent("Page.frameStartedNavigating", .{
|
try cdp.sendEvent("Page.frameStartedNavigating", .{
|
||||||
.frameId = target_id,
|
.frameId = target_id,
|
||||||
.url = event.url.raw,
|
.url = event.url,
|
||||||
.loaderId = loader_id,
|
.loaderId = loader_id,
|
||||||
.navigationType = "differentDocument",
|
.navigationType = "differentDocument",
|
||||||
}, .{ .session_id = session_id });
|
}, .{ .session_id = session_id });
|
||||||
@@ -306,7 +307,7 @@ pub fn pageNavigated(bc: anytype, event: *const Notification.PageNavigated) !voi
|
|||||||
.type = "Navigation",
|
.type = "Navigation",
|
||||||
.frame = Frame{
|
.frame = Frame{
|
||||||
.id = target_id,
|
.id = target_id,
|
||||||
.url = event.url.raw,
|
.url = event.url,
|
||||||
.loaderId = bc.loader_id,
|
.loaderId = bc.loader_id,
|
||||||
.securityOrigin = bc.security_origin,
|
.securityOrigin = bc.security_origin,
|
||||||
.secureContextType = bc.secure_context_type,
|
.secureContextType = bc.secure_context_type,
|
||||||
|
|||||||
@@ -23,6 +23,7 @@ pub const c = @cImport({
|
|||||||
const ENABLE_DEBUG = false;
|
const ENABLE_DEBUG = false;
|
||||||
|
|
||||||
const std = @import("std");
|
const std = @import("std");
|
||||||
|
const log = @import("../log.zig");
|
||||||
const builtin = @import("builtin");
|
const builtin = @import("builtin");
|
||||||
const errors = @import("errors.zig");
|
const errors = @import("errors.zig");
|
||||||
|
|
||||||
@@ -36,14 +37,11 @@ pub fn init() !void {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const LogFn = *const fn(ctx: []const u8, err: anyerror) void;
|
|
||||||
|
|
||||||
pub fn deinit() void {
|
pub fn deinit() void {
|
||||||
c.curl_global_cleanup();
|
c.curl_global_cleanup();
|
||||||
}
|
}
|
||||||
|
|
||||||
pub const Client = struct {
|
pub const Client = struct {
|
||||||
log: LogFn,
|
|
||||||
active: usize,
|
active: usize,
|
||||||
multi: *c.CURLM,
|
multi: *c.CURLM,
|
||||||
handles: Handles,
|
handles: Handles,
|
||||||
@@ -57,7 +55,6 @@ pub const Client = struct {
|
|||||||
const RequestQueue = std.DoublyLinkedList(Request);
|
const RequestQueue = std.DoublyLinkedList(Request);
|
||||||
|
|
||||||
const Opts = struct {
|
const Opts = struct {
|
||||||
log: ?LogFn = null,
|
|
||||||
timeout_ms: u31 = 0,
|
timeout_ms: u31 = 0,
|
||||||
max_redirects: u8 = 10,
|
max_redirects: u8 = 10,
|
||||||
connect_timeout_ms: u31 = 5000,
|
connect_timeout_ms: u31 = 5000,
|
||||||
@@ -80,7 +77,6 @@ pub const Client = struct {
|
|||||||
errdefer _ = c.curl_multi_cleanup(multi);
|
errdefer _ = c.curl_multi_cleanup(multi);
|
||||||
|
|
||||||
client.* = .{
|
client.* = .{
|
||||||
.log = opts.log orelse noopLog,
|
|
||||||
.queue = .{},
|
.queue = .{},
|
||||||
.active = 0,
|
.active = 0,
|
||||||
.multi = multi,
|
.multi = multi,
|
||||||
@@ -101,7 +97,7 @@ pub const Client = struct {
|
|||||||
self.allocator.destroy(self);
|
self.allocator.destroy(self);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn tick(self: *Client, timeout_ms: usize) !usize {
|
pub fn tick(self: *Client, timeout_ms: usize) !void {
|
||||||
var handles = &self.handles.available;
|
var handles = &self.handles.available;
|
||||||
while (true) {
|
while (true) {
|
||||||
if (handles.first == null) {
|
if (handles.first == null) {
|
||||||
@@ -116,7 +112,6 @@ pub const Client = struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
try self.perform(@intCast(timeout_ms));
|
try self.perform(@intCast(timeout_ms));
|
||||||
return self.active;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn request(self: *Client, req: Request) !void {
|
pub fn request(self: *Client, req: Request) !void {
|
||||||
@@ -144,16 +139,11 @@ pub const Client = struct {
|
|||||||
.OPTIONS => try errorCheck(c.curl_easy_setopt(easy, c.CURLOPT_CUSTOMREQUEST, "options")),
|
.OPTIONS => try errorCheck(c.curl_easy_setopt(easy, c.CURLOPT_CUSTOMREQUEST, "options")),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const header_list = c.curl_slist_append(null, "User-Agent: Lightpanda/1.0");
|
||||||
const header_list = c.curl_slist_append(null, "User-Agent: lightpanda/1");
|
|
||||||
errdefer c.curl_slist_free_all(header_list);
|
errdefer c.curl_slist_free_all(header_list);
|
||||||
|
|
||||||
try errorCheck(c.curl_easy_setopt(easy, c.CURLOPT_HTTPHEADER, header_list));
|
try errorCheck(c.curl_easy_setopt(easy, c.CURLOPT_HTTPHEADER, header_list));
|
||||||
|
|
||||||
|
|
||||||
//try errorCheck(c.curl_easy_setopt(easy, c.CURLOPT_POSTFIELDSIZE, @as(c_long, @intCast(STRING.len))));
|
|
||||||
//try errorCheck(c.curl_easy_setopt(easy, c.CURLOPT_POSTFIELDS, STRING.ptr));
|
|
||||||
|
|
||||||
break :blk header_list;
|
break :blk header_list;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -345,8 +335,9 @@ pub const Request = struct {
|
|||||||
// arbitrary data that can be associated with this request
|
// arbitrary data that can be associated with this request
|
||||||
ctx: *anyopaque = undefined,
|
ctx: *anyopaque = undefined,
|
||||||
|
|
||||||
start_callback: ?*const fn(req: *Transfer) anyerror!void = noopStart,
|
start_callback: ?*const fn(req: *Transfer) anyerror!void = null,
|
||||||
header_callback: *const fn (req: *Transfer) anyerror!void,
|
header_callback: ?*const fn (req: *Transfer, header: []const u8) anyerror!void = null ,
|
||||||
|
header_done_callback: *const fn (req: *Transfer) anyerror!void,
|
||||||
data_callback: *const fn(req: *Transfer, data: []const u8) anyerror!void,
|
data_callback: *const fn(req: *Transfer, data: []const u8) anyerror!void,
|
||||||
done_callback: *const fn(req: *Transfer) anyerror!void,
|
done_callback: *const fn(req: *Transfer) anyerror!void,
|
||||||
error_callback: *const fn(req: *Transfer, err: anyerror) void,
|
error_callback: *const fn(req: *Transfer, err: anyerror) void,
|
||||||
@@ -366,6 +357,12 @@ pub const Transfer = struct {
|
|||||||
// needs to be freed when we're done
|
// needs to be freed when we're done
|
||||||
_request_header_list: ?*c.curl_slist = null,
|
_request_header_list: ?*c.curl_slist = null,
|
||||||
|
|
||||||
|
fn deinit(self: *Transfer) void {
|
||||||
|
if (self._request_header_list) |list| {
|
||||||
|
c.curl_slist_free_all(list);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn format(self: *const Transfer, comptime _: []const u8, _: std.fmt.FormatOptions, writer: anytype) !void {
|
pub fn format(self: *const Transfer, comptime _: []const u8, _: std.fmt.FormatOptions, writer: anytype) !void {
|
||||||
const req = self.req;
|
const req = self.req;
|
||||||
return writer.print("[{d}] {s} {s}", .{self.id, @tagName(req.method), req.url});
|
return writer.print("[{d}] {s} {s}", .{self.id, @tagName(req.method), req.url});
|
||||||
@@ -375,10 +372,23 @@ pub const Transfer = struct {
|
|||||||
self.req.error_callback(self, err);
|
self.req.error_callback(self, err);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn deinit(self: *Transfer) void {
|
pub fn setBody(self: *Transfer, body: []const u8) !void {
|
||||||
if (self._request_header_list) |list| {
|
const easy = self.handle.easy;
|
||||||
c.curl_slist_free_all(list);
|
try errorCheck(c.curl_easy_setopt(easy, c.CURLOPT_POSTFIELDSIZE, @as(c_long, @intCast(body.len))));
|
||||||
}
|
try errorCheck(c.curl_easy_setopt(easy, c.CURLOPT_POSTFIELDS, body.ptr));
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn addHeader(self: *Transfer, value: [:0]const u8) !void {
|
||||||
|
self._request_header_list = c.curl_slist_append(self._request_header_list, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn abort(self: *Transfer) void {
|
||||||
|
var client = self.handle.client;
|
||||||
|
errorMCheck(c.curl_multi_remove_handle(client.multi, self.handle.easy)) catch |err| {
|
||||||
|
log.err(.http, "Failed to abort", .{.err = err});
|
||||||
|
};
|
||||||
|
client.active -= 1;
|
||||||
|
self.deinit();
|
||||||
}
|
}
|
||||||
|
|
||||||
fn headerCallback(buffer: [*]const u8, header_count: usize, buf_len: usize, data: *anyopaque) callconv(.c) usize {
|
fn headerCallback(buffer: [*]const u8, header_count: usize, buf_len: usize, data: *anyopaque) callconv(.c) usize {
|
||||||
@@ -387,11 +397,13 @@ pub const Transfer = struct {
|
|||||||
|
|
||||||
const handle: *Handle = @alignCast(@ptrCast(data));
|
const handle: *Handle = @alignCast(@ptrCast(data));
|
||||||
var transfer = fromEasy(handle.easy) catch |err| {
|
var transfer = fromEasy(handle.easy) catch |err| {
|
||||||
handle.client.log("retrieve private info", err);
|
log.err(.http, "retrive private info", .{.err = err});
|
||||||
return 0;
|
return 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
const header = buffer[0..buf_len];
|
std.debug.assert(std.mem.endsWith(u8, buffer[0..buf_len], "\r\n"));
|
||||||
|
|
||||||
|
const header = buffer[0..buf_len - 2];
|
||||||
|
|
||||||
if (transfer.response_header == null) {
|
if (transfer.response_header == null) {
|
||||||
if (buf_len < 13 or std.mem.startsWith(u8, header, "HTTP/") == false) {
|
if (buf_len < 13 or std.mem.startsWith(u8, header, "HTTP/") == false) {
|
||||||
@@ -439,8 +451,7 @@ pub const Transfer = struct {
|
|||||||
if (buf_len > CONTENT_TYPE_LEN) {
|
if (buf_len > CONTENT_TYPE_LEN) {
|
||||||
if (std.ascii.eqlIgnoreCase(header[0..CONTENT_TYPE_LEN], "content-type:")) {
|
if (std.ascii.eqlIgnoreCase(header[0..CONTENT_TYPE_LEN], "content-type:")) {
|
||||||
const value = std.mem.trimLeft(u8, header[CONTENT_TYPE_LEN..], " ");
|
const value = std.mem.trimLeft(u8, header[CONTENT_TYPE_LEN..], " ");
|
||||||
// -2 to trim the trailing \r\n
|
const len = @min(value.len, hdr._content_type.len);
|
||||||
const len = @min(value.len - 2, hdr._content_type.len);
|
|
||||||
hdr._content_type_len = len;
|
hdr._content_type_len = len;
|
||||||
@memcpy(hdr._content_type[0..len], value[0..len]);
|
@memcpy(hdr._content_type[0..len], value[0..len]);
|
||||||
}
|
}
|
||||||
@@ -448,10 +459,14 @@ pub const Transfer = struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (buf_len == 2) {
|
if (buf_len == 2) {
|
||||||
transfer.req.header_callback(transfer) catch {
|
transfer.req.header_done_callback(transfer) catch {
|
||||||
// returning < buf_len terminates the request
|
// returning < buf_len terminates the request
|
||||||
return 0;
|
return 0;
|
||||||
};
|
};
|
||||||
|
} else {
|
||||||
|
if (transfer.req.header_callback) |cb| {
|
||||||
|
cb(transfer, header) catch return 0;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return buf_len;
|
return buf_len;
|
||||||
}
|
}
|
||||||
@@ -462,8 +477,8 @@ pub const Transfer = struct {
|
|||||||
|
|
||||||
const handle: *Handle = @alignCast(@ptrCast(data));
|
const handle: *Handle = @alignCast(@ptrCast(data));
|
||||||
var transfer = fromEasy(handle.easy) catch |err| {
|
var transfer = fromEasy(handle.easy) catch |err| {
|
||||||
handle.client.log("retrieve private info", err);
|
log.err(.http, "retrive private info", .{.err = err});
|
||||||
return 0;
|
return c.CURL_WRITEFUNC_ERROR;
|
||||||
};
|
};
|
||||||
|
|
||||||
if (transfer._redirecting) {
|
if (transfer._redirecting) {
|
||||||
@@ -535,10 +550,3 @@ pub const ProxyAuth = union(enum) {
|
|||||||
bearer: struct { token: []const u8 },
|
bearer: struct { token: []const u8 },
|
||||||
};
|
};
|
||||||
|
|
||||||
fn noopLog(ctx: []const u8, _: anyerror) void {
|
|
||||||
_ = ctx;
|
|
||||||
}
|
|
||||||
|
|
||||||
fn noopStart(transfer: *Transfer) !void {
|
|
||||||
_ = transfer;
|
|
||||||
}
|
|
||||||
|
|||||||
110
src/main.zig
110
src/main.zig
@@ -667,88 +667,42 @@ fn serveHTTP(address: std.net.Address) !void {
|
|||||||
test_wg.finish();
|
test_wg.finish();
|
||||||
|
|
||||||
var read_buffer: [1024]u8 = undefined;
|
var read_buffer: [1024]u8 = undefined;
|
||||||
ACCEPT: while (true) {
|
while (true) {
|
||||||
defer _ = arena.reset(.{ .free_all = {} });
|
|
||||||
const aa = arena.allocator();
|
|
||||||
|
|
||||||
var conn = try listener.accept();
|
var conn = try listener.accept();
|
||||||
defer conn.stream.close();
|
defer conn.stream.close();
|
||||||
var http_server = std.http.Server.init(conn, &read_buffer);
|
var http_server = std.http.Server.init(conn, &read_buffer);
|
||||||
var connect_headers: std.ArrayListUnmanaged(std.http.Header) = .{};
|
|
||||||
REQUEST: while (true) {
|
var request = http_server.receiveHead() catch |err| switch (err) {
|
||||||
var request = http_server.receiveHead() catch |err| switch (err) {
|
error.HttpConnectionClosing => continue,
|
||||||
error.HttpConnectionClosing => continue :ACCEPT,
|
else => {
|
||||||
else => {
|
std.debug.print("Test HTTP Server error: {}\n", .{err});
|
||||||
std.debug.print("Test HTTP Server error: {}\n", .{err});
|
return err;
|
||||||
return err;
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const path = request.head.target;
|
||||||
|
|
||||||
|
if (std.mem.eql(u8, path, "/loader")) {
|
||||||
|
try request.respond("Hello!", .{
|
||||||
|
.extra_headers = &.{.{ .name = "Connection", .value = "close" }},
|
||||||
|
});
|
||||||
|
} else if (std.mem.eql(u8, path, "/xhr")) {
|
||||||
|
try request.respond("1234567890" ** 10, .{
|
||||||
|
.extra_headers = &.{
|
||||||
|
.{ .name = "Content-Type", .value = "text/html; charset=utf-8" },
|
||||||
|
.{ .name = "Connection", .value = "Close" },
|
||||||
},
|
},
|
||||||
};
|
});
|
||||||
|
} else if (std.mem.eql(u8, path, "/xhr/json")) {
|
||||||
if (request.head.method == .CONNECT) {
|
try request.respond("{\"over\":\"9000!!!\"}", .{
|
||||||
try request.respond("", .{ .status = .ok });
|
.extra_headers = &.{
|
||||||
|
.{ .name = "Content-Type", .value = "application/json" },
|
||||||
// Proxy headers and destination headers are separated in the case of a CONNECT proxy
|
.{ .name = "Connection", .value = "Close" },
|
||||||
// We store the CONNECT headers, then continue with the request for the destination
|
},
|
||||||
var it = request.iterateHeaders();
|
});
|
||||||
while (it.next()) |hdr| {
|
} else {
|
||||||
try connect_headers.append(aa, .{
|
// should not have an unknown path
|
||||||
.name = try std.fmt.allocPrint(aa, "__{s}", .{hdr.name}),
|
unreachable;
|
||||||
.value = try aa.dupe(u8, hdr.value),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
continue :REQUEST;
|
|
||||||
}
|
|
||||||
|
|
||||||
const path = request.head.target;
|
|
||||||
if (std.mem.eql(u8, path, "/loader")) {
|
|
||||||
try request.respond("Hello!", .{
|
|
||||||
.extra_headers = &.{.{ .name = "Connection", .value = "close" }},
|
|
||||||
});
|
|
||||||
} else if (std.mem.eql(u8, path, "/http_client/simple")) {
|
|
||||||
try request.respond("", .{
|
|
||||||
.extra_headers = &.{.{ .name = "Connection", .value = "close" }},
|
|
||||||
});
|
|
||||||
} else if (std.mem.eql(u8, path, "/http_client/redirect")) {
|
|
||||||
try request.respond("", .{
|
|
||||||
.status = .moved_permanently,
|
|
||||||
.extra_headers = &.{
|
|
||||||
.{ .name = "Connection", .value = "close" },
|
|
||||||
.{ .name = "LOCATION", .value = "../http_client/echo" },
|
|
||||||
},
|
|
||||||
});
|
|
||||||
} else if (std.mem.eql(u8, path, "/http_client/redirect/secure")) {
|
|
||||||
try request.respond("", .{
|
|
||||||
.status = .moved_permanently,
|
|
||||||
.extra_headers = &.{ .{ .name = "Connection", .value = "close" }, .{ .name = "LOCATION", .value = "https://127.0.0.1:9581/http_client/body" } },
|
|
||||||
});
|
|
||||||
} else if (std.mem.eql(u8, path, "/http_client/gzip")) {
|
|
||||||
const body = &.{ 0x1f, 0x8b, 0x08, 0x08, 0x01, 0xc6, 0x19, 0x68, 0x00, 0x03, 0x74, 0x65, 0x73, 0x74, 0x2e, 0x68, 0x74, 0x6d, 0x6c, 0x00, 0x73, 0x54, 0xc8, 0x4b, 0x2d, 0x57, 0x48, 0x2a, 0xca, 0x2f, 0x2f, 0x4e, 0x2d, 0x52, 0x48, 0x2a, 0xcd, 0xcc, 0x29, 0x51, 0x48, 0xcb, 0x2f, 0x52, 0xc8, 0x4d, 0x4c, 0xce, 0xc8, 0xcc, 0x4b, 0x2d, 0xe6, 0x02, 0x00, 0xe7, 0xc3, 0x4b, 0x27, 0x21, 0x00, 0x00, 0x00 };
|
|
||||||
try request.respond(body, .{
|
|
||||||
.extra_headers = &.{ .{ .name = "Connection", .value = "close" }, .{ .name = "Content-Encoding", .value = "gzip" } },
|
|
||||||
});
|
|
||||||
} else if (std.mem.eql(u8, path, "/http_client/echo")) {
|
|
||||||
var headers: std.ArrayListUnmanaged(std.http.Header) = .{};
|
|
||||||
|
|
||||||
var it = request.iterateHeaders();
|
|
||||||
while (it.next()) |hdr| {
|
|
||||||
try headers.append(aa, .{
|
|
||||||
.name = try std.fmt.allocPrint(aa, "_{s}", .{hdr.name}),
|
|
||||||
.value = hdr.value,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (connect_headers.items.len > 0) {
|
|
||||||
try headers.appendSlice(aa, connect_headers.items);
|
|
||||||
connect_headers.clearRetainingCapacity();
|
|
||||||
}
|
|
||||||
try headers.append(aa, .{ .name = "Connection", .value = "Close" });
|
|
||||||
|
|
||||||
try request.respond("over 9000!", .{
|
|
||||||
.status = .created,
|
|
||||||
.extra_headers = headers.items,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
continue :ACCEPT;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -81,13 +81,13 @@ pub const Notification = struct {
|
|||||||
|
|
||||||
pub const PageNavigate = struct {
|
pub const PageNavigate = struct {
|
||||||
timestamp: u32,
|
timestamp: u32,
|
||||||
url: *const URL,
|
url: []const u8,
|
||||||
opts: page.NavigateOpts,
|
opts: page.NavigateOpts,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub const PageNavigated = struct {
|
pub const PageNavigated = struct {
|
||||||
timestamp: u32,
|
timestamp: u32,
|
||||||
url: *const URL,
|
url: []const u8,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub const RequestStart = struct {
|
pub const RequestStart = struct {
|
||||||
|
|||||||
@@ -1052,7 +1052,15 @@ pub fn run(
|
|||||||
// infinite loop on I/O events, either:
|
// infinite loop on I/O events, either:
|
||||||
// - cmd from incoming connection on server socket
|
// - cmd from incoming connection on server socket
|
||||||
// - JS callbacks events from scripts
|
// - JS callbacks events from scripts
|
||||||
|
// var http_client = app.http_client;
|
||||||
while (true) {
|
while (true) {
|
||||||
|
// // @newhttp
|
||||||
|
// // This is a temporary hack for the newhttp work. The issue is that we
|
||||||
|
// // now have 2 event loops.
|
||||||
|
// if (http_client.active > 0) {
|
||||||
|
// _ = try http_client.tick(10);
|
||||||
|
// }
|
||||||
|
|
||||||
try loop.io.run_for_ns(10 * std.time.ns_per_ms);
|
try loop.io.run_for_ns(10 * std.time.ns_per_ms);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -35,7 +35,7 @@ pub const LightPanda = struct {
|
|||||||
.thread = null,
|
.thread = null,
|
||||||
.running = true,
|
.running = true,
|
||||||
.allocator = allocator,
|
.allocator = allocator,
|
||||||
.client = &app.http_client,
|
.client = app.http_client,
|
||||||
.uri = std.Uri.parse(URL) catch unreachable,
|
.uri = std.Uri.parse(URL) catch unreachable,
|
||||||
.node_pool = std.heap.MemoryPool(List.Node).init(allocator),
|
.node_pool = std.heap.MemoryPool(List.Node).init(allocator),
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -79,7 +79,7 @@ fn TelemetryT(comptime P: type) type {
|
|||||||
const self: *Self = @alignCast(@ptrCast(ctx));
|
const self: *Self = @alignCast(@ptrCast(ctx));
|
||||||
self.record(.{ .navigate = .{
|
self.record(.{ .navigate = .{
|
||||||
.proxy = false,
|
.proxy = false,
|
||||||
.tls = std.ascii.eqlIgnoreCase(data.url.scheme(), "https"),
|
.tls = std.ascii.startsWithIgnoreCase(data.url, "https://"),
|
||||||
} });
|
} });
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -409,6 +409,10 @@ pub const JsRunner = struct {
|
|||||||
const html_doc = try parser.documentHTMLParseFromStr(opts.html);
|
const html_doc = try parser.documentHTMLParseFromStr(opts.html);
|
||||||
try page.setDocument(html_doc);
|
try page.setDocument(html_doc);
|
||||||
|
|
||||||
|
// after the page is considered loaded, page.wait can exit early if
|
||||||
|
// there's no IO/timeouts. So setting this speeds up our tests
|
||||||
|
page.loaded = true;
|
||||||
|
|
||||||
return .{
|
return .{
|
||||||
.app = app,
|
.app = app,
|
||||||
.page = page,
|
.page = page,
|
||||||
@@ -441,7 +445,7 @@ pub const JsRunner = struct {
|
|||||||
}
|
}
|
||||||
return err;
|
return err;
|
||||||
};
|
};
|
||||||
try self.page.loop.run(std.time.ns_per_ms * 200);
|
try self.page.wait(1);
|
||||||
@import("root").js_runner_duration += std.time.Instant.since(try std.time.Instant.now(), start);
|
@import("root").js_runner_duration += std.time.Instant.since(try std.time.Instant.now(), start);
|
||||||
|
|
||||||
if (case.@"1") |expected| {
|
if (case.@"1") |expected| {
|
||||||
|
|||||||
Reference in New Issue
Block a user