mirror of
https://github.com/lightpanda-io/browser.git
synced 2025-12-14 23:38:57 +00:00
support form.submit()
Only supports application/x-www-form-urlencoded
This commit is contained in:
@@ -175,7 +175,7 @@ pub const HTMLDocument = struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn set_location(_: *const parser.DocumentHTML, url: []const u8, page: *Page) !void {
|
pub fn set_location(_: *const parser.DocumentHTML, url: []const u8, page: *Page) !void {
|
||||||
return page.navigateFromWebAPI(url);
|
return page.navigateFromWebAPI(url, .{ .reason = .script });
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_designMode(_: *parser.DocumentHTML) []const u8 {
|
pub fn get_designMode(_: *parser.DocumentHTML) []const u8 {
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ const std = @import("std");
|
|||||||
const Allocator = std.mem.Allocator;
|
const Allocator = std.mem.Allocator;
|
||||||
|
|
||||||
const parser = @import("../netsurf.zig");
|
const parser = @import("../netsurf.zig");
|
||||||
|
const Page = @import("../page.zig").Page;
|
||||||
const HTMLElement = @import("elements.zig").HTMLElement;
|
const HTMLElement = @import("elements.zig").HTMLElement;
|
||||||
const FormData = @import("../xhr/form_data.zig").FormData;
|
const FormData = @import("../xhr/form_data.zig").FormData;
|
||||||
|
|
||||||
@@ -27,6 +28,10 @@ pub const HTMLFormElement = struct {
|
|||||||
pub const prototype = *HTMLElement;
|
pub const prototype = *HTMLElement;
|
||||||
pub const subtype = .node;
|
pub const subtype = .node;
|
||||||
|
|
||||||
|
pub fn _submit(self: *parser.Form, page: *Page) !void {
|
||||||
|
return page.submitForm(self, null);
|
||||||
|
}
|
||||||
|
|
||||||
pub fn _requestSubmit(self: *parser.Form) !void {
|
pub fn _requestSubmit(self: *parser.Form) !void {
|
||||||
try parser.formElementSubmit(self);
|
try parser.formElementSubmit(self);
|
||||||
}
|
}
|
||||||
@@ -35,20 +40,3 @@ pub const HTMLFormElement = struct {
|
|||||||
try parser.formElementReset(self);
|
try parser.formElementReset(self);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
pub const Submission = struct {
|
|
||||||
method: ?[]const u8,
|
|
||||||
form_data: FormData,
|
|
||||||
};
|
|
||||||
|
|
||||||
pub fn processSubmission(arena: Allocator, form: *parser.Form) !?Submission {
|
|
||||||
const form_element: *parser.Element = @ptrCast(form);
|
|
||||||
const method = try parser.elementGetAttribute(form_element, "method");
|
|
||||||
|
|
||||||
return .{
|
|
||||||
.method = method,
|
|
||||||
.form_data = try FormData.fromForm(arena, form),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check xhr/form_data.zig for tests
|
|
||||||
|
|||||||
@@ -70,15 +70,15 @@ pub const Location = struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn _assign(_: *const Location, url: []const u8, page: *Page) !void {
|
pub fn _assign(_: *const Location, url: []const u8, page: *Page) !void {
|
||||||
return page.navigateFromWebAPI(url);
|
return page.navigateFromWebAPI(url, .{ .reason = .script });
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn _replace(_: *const Location, url: []const u8, page: *Page) !void {
|
pub fn _replace(_: *const Location, url: []const u8, page: *Page) !void {
|
||||||
return page.navigateFromWebAPI(url);
|
return page.navigateFromWebAPI(url, .{ .reason = .script });
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn _reload(_: *const Location, page: *Page) !void {
|
pub fn _reload(_: *const Location, page: *Page) !void {
|
||||||
return page.navigateFromWebAPI(page.url.raw);
|
return page.navigateFromWebAPI(page.url.raw, .{ .reason = .script });
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn _toString(self: *Location, page: *Page) ![]const u8 {
|
pub fn _toString(self: *Location, page: *Page) ![]const u8 {
|
||||||
|
|||||||
@@ -101,7 +101,7 @@ pub const Window = struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn set_location(_: *const Window, url: []const u8, page: *Page) !void {
|
pub fn set_location(_: *const Window, url: []const u8, page: *Page) !void {
|
||||||
return page.navigateFromWebAPI(url);
|
return page.navigateFromWebAPI(url, .{ .reason = .script });
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_console(self: *Window) *Console {
|
pub fn get_console(self: *Window) *Console {
|
||||||
|
|||||||
@@ -199,8 +199,9 @@ pub const Page = struct {
|
|||||||
self.url = request_url;
|
self.url = request_url;
|
||||||
|
|
||||||
// load the data
|
// load the data
|
||||||
var request = try self.newHTTPRequest(.GET, &self.url, .{ .navigation = true });
|
var request = try self.newHTTPRequest(opts.method, &self.url, .{ .navigation = true });
|
||||||
defer request.deinit();
|
defer request.deinit();
|
||||||
|
request.body = opts.body;
|
||||||
request.notification = notification;
|
request.notification = notification;
|
||||||
|
|
||||||
notification.dispatch(.page_navigate, &.{
|
notification.dispatch(.page_navigate, &.{
|
||||||
@@ -541,7 +542,7 @@ pub const Page = struct {
|
|||||||
.a => {
|
.a => {
|
||||||
const element: *parser.Element = @ptrCast(node);
|
const element: *parser.Element = @ptrCast(node);
|
||||||
const href = (try parser.elementGetAttribute(element, "href")) orelse return;
|
const href = (try parser.elementGetAttribute(element, "href")) orelse return;
|
||||||
try self.navigateFromWebAPI(href);
|
try self.navigateFromWebAPI(href, .{});
|
||||||
},
|
},
|
||||||
else => {},
|
else => {},
|
||||||
}
|
}
|
||||||
@@ -550,10 +551,11 @@ pub const Page = struct {
|
|||||||
// As such we schedule the function to be called as soon as possible.
|
// As such we schedule the function to be called as soon as possible.
|
||||||
// The page.arena is safe to use here, but the transfer_arena exists
|
// The page.arena is safe to use here, but the transfer_arena exists
|
||||||
// specifically for this type of lifetime.
|
// specifically for this type of lifetime.
|
||||||
pub fn navigateFromWebAPI(self: *Page, url: []const u8) !void {
|
pub fn navigateFromWebAPI(self: *Page, url: []const u8, opts: NavigateOpts) !void {
|
||||||
const arena = self.session.transfer_arena;
|
const arena = self.session.transfer_arena;
|
||||||
const navi = try arena.create(DelayedNavigation);
|
const navi = try arena.create(DelayedNavigation);
|
||||||
navi.* = .{
|
navi.* = .{
|
||||||
|
.opts = opts,
|
||||||
.session = self.session,
|
.session = self.session,
|
||||||
.url = try arena.dupe(u8, url),
|
.url = try arena.dupe(u8, url),
|
||||||
};
|
};
|
||||||
@@ -578,17 +580,45 @@ pub const Page = struct {
|
|||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn submitForm(self: *Page, form: *parser.Form, submitter: ?*parser.ElementHTML) !void {
|
||||||
|
const FormData = @import("xhr/form_data.zig").FormData;
|
||||||
|
|
||||||
|
const transfer_arena = self.session.transfer_arena;
|
||||||
|
var form_data = try FormData.fromForm(form, submitter, self);
|
||||||
|
|
||||||
|
const encoding = try parser.elementGetAttribute(@ptrCast(form), "enctype");
|
||||||
|
|
||||||
|
var buf: std.ArrayListUnmanaged(u8) = .empty;
|
||||||
|
try form_data.write(encoding, buf.writer(transfer_arena));
|
||||||
|
|
||||||
|
const method = try parser.elementGetAttribute(@ptrCast(form), "method") orelse "";
|
||||||
|
var action = try parser.elementGetAttribute(@ptrCast(form), "action") orelse self.url.raw;
|
||||||
|
|
||||||
|
var opts = NavigateOpts{
|
||||||
|
.reason = .form,
|
||||||
|
};
|
||||||
|
if (std.ascii.eqlIgnoreCase(method, "post")) {
|
||||||
|
opts.method = .POST;
|
||||||
|
opts.body = buf.items;
|
||||||
|
} else {
|
||||||
|
action = try URL.concatQueryString(transfer_arena, action, buf.items);
|
||||||
|
}
|
||||||
|
|
||||||
|
try self.navigateFromWebAPI(action, opts);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const DelayedNavigation = struct {
|
const DelayedNavigation = struct {
|
||||||
url: []const u8,
|
url: []const u8,
|
||||||
session: *Session,
|
session: *Session,
|
||||||
|
opts: NavigateOpts,
|
||||||
navigate_node: Loop.CallbackNode = .{ .func = delayNavigate },
|
navigate_node: Loop.CallbackNode = .{ .func = delayNavigate },
|
||||||
|
|
||||||
fn delayNavigate(node: *Loop.CallbackNode, repeat_delay: *?u63) void {
|
fn delayNavigate(node: *Loop.CallbackNode, repeat_delay: *?u63) void {
|
||||||
_ = repeat_delay;
|
_ = repeat_delay;
|
||||||
const self: *DelayedNavigation = @fieldParentPtr("navigate_node", node);
|
const self: *DelayedNavigation = @fieldParentPtr("navigate_node", node);
|
||||||
self.session.pageNavigate(self.url) catch |err| {
|
self.session.pageNavigate(self.url, self.opts) catch |err| {
|
||||||
log.err(.page, "delayed navigation error", .{ .err = err, .url = self.url });
|
log.err(.page, "delayed navigation error", .{ .err = err, .url = self.url });
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -697,11 +727,15 @@ const Script = struct {
|
|||||||
pub const NavigateReason = enum {
|
pub const NavigateReason = enum {
|
||||||
anchor,
|
anchor,
|
||||||
address_bar,
|
address_bar,
|
||||||
|
form,
|
||||||
|
script,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub const NavigateOpts = struct {
|
pub const NavigateOpts = struct {
|
||||||
cdp_id: ?i64 = null,
|
cdp_id: ?i64 = null,
|
||||||
reason: NavigateReason = .address_bar,
|
reason: NavigateReason = .address_bar,
|
||||||
|
method: http.Request.Method = .GET,
|
||||||
|
body: ?[]const u8 = null,
|
||||||
};
|
};
|
||||||
|
|
||||||
fn timestamp() u32 {
|
fn timestamp() u32 {
|
||||||
|
|||||||
@@ -23,6 +23,7 @@ const Allocator = std.mem.Allocator;
|
|||||||
const Env = @import("env.zig").Env;
|
const Env = @import("env.zig").Env;
|
||||||
const Page = @import("page.zig").Page;
|
const Page = @import("page.zig").Page;
|
||||||
const Browser = @import("browser.zig").Browser;
|
const Browser = @import("browser.zig").Browser;
|
||||||
|
const NavigateOpts = @import("page.zig").NavigateOpts;
|
||||||
|
|
||||||
const log = @import("../log.zig");
|
const log = @import("../log.zig");
|
||||||
const parser = @import("netsurf.zig");
|
const parser = @import("netsurf.zig");
|
||||||
@@ -122,7 +123,7 @@ pub const Session = struct {
|
|||||||
return &(self.page orelse return null);
|
return &(self.page orelse return null);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn pageNavigate(self: *Session, url_string: []const u8) !void {
|
pub fn pageNavigate(self: *Session, url_string: []const u8, opts: NavigateOpts) !void {
|
||||||
// currently, this is only called from the page, so let's hope
|
// currently, this is only called from the page, so let's hope
|
||||||
// it isn't null!
|
// it isn't null!
|
||||||
std.debug.assert(self.page != null);
|
std.debug.assert(self.page != null);
|
||||||
@@ -136,8 +137,6 @@ pub const Session = struct {
|
|||||||
|
|
||||||
self.removePage();
|
self.removePage();
|
||||||
var page = try self.createPage();
|
var page = try self.createPage();
|
||||||
return page.navigate(url, .{
|
return page.navigate(url, opts);
|
||||||
.reason = .anchor,
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -51,21 +51,15 @@ pub const Interfaces = .{
|
|||||||
|
|
||||||
// https://xhr.spec.whatwg.org/#interface-formdata
|
// https://xhr.spec.whatwg.org/#interface-formdata
|
||||||
pub const FormData = struct {
|
pub const FormData = struct {
|
||||||
entries: std.ArrayListUnmanaged(Entry),
|
entries: Entry.List,
|
||||||
|
|
||||||
pub fn constructor(form_: ?*parser.Form, submitter_: ?*parser.ElementHTML, page: *Page) !FormData {
|
pub fn constructor(form_: ?*parser.Form, submitter_: ?*parser.ElementHTML, page: *Page) !FormData {
|
||||||
const form = form_ orelse return .{ .entries = .empty };
|
const form = form_ orelse return .{ .entries = .empty };
|
||||||
return fromForm(form, submitter_, page, .{});
|
return fromForm(form, submitter_, page);
|
||||||
}
|
}
|
||||||
|
|
||||||
const FromFormOpts = struct {
|
pub fn fromForm(form: *parser.Form, submitter_: ?*parser.ElementHTML, page: *Page) !FormData {
|
||||||
// Uses the page.arena if null. This is needed for when we're handling
|
const entries = try collectForm(form, submitter_, page);
|
||||||
// form submission from the Page, and we want to capture the form within
|
|
||||||
// the session's transfer_arena.
|
|
||||||
allocator: ?Allocator = null,
|
|
||||||
};
|
|
||||||
pub fn fromForm(form: *parser.Form, submitter_: ?*parser.ElementHTML, page: *Page, opts: FromFormOpts) !FormData {
|
|
||||||
const entries = try collectForm(opts.allocator orelse page.arena, form, submitter_, page);
|
|
||||||
return .{ .entries = entries };
|
return .{ .entries = entries };
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -144,11 +138,78 @@ pub const FormData = struct {
|
|||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn write(self: *const FormData, encoding_: ?[]const u8, writer: anytype) !void {
|
||||||
|
const encoding = encoding_ orelse {
|
||||||
|
return urlEncode(self, writer);
|
||||||
|
};
|
||||||
|
|
||||||
|
if (std.ascii.eqlIgnoreCase(encoding, "application/x-www-form-urlencoded")) {
|
||||||
|
return urlEncode(self, writer);
|
||||||
|
}
|
||||||
|
|
||||||
|
log.warn(.form_data, "encoding not supported", .{ .encoding = encoding });
|
||||||
|
return error.EncodingNotSupported;
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
fn urlEncode(data: *const FormData, writer: anytype) !void {
|
||||||
|
const entries = data.entries.items;
|
||||||
|
if (entries.len == 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try urlEncodeEntry(entries[0], writer);
|
||||||
|
for (entries[1..]) |entry| {
|
||||||
|
try writer.writeByte('&');
|
||||||
|
try urlEncodeEntry(entry, writer);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn urlEncodeEntry(entry: Entry, writer: anytype) !void {
|
||||||
|
try urlEncodeValue(entry.key, writer);
|
||||||
|
try writer.writeByte('=');
|
||||||
|
try urlEncodeValue(entry.value, writer);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn urlEncodeValue(value: []const u8, writer: anytype) !void {
|
||||||
|
if (!urlEncodeShouldEscape(value)) {
|
||||||
|
return writer.writeAll(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (value) |b| {
|
||||||
|
if (urlEncodeUnreserved(b)) {
|
||||||
|
try writer.writeByte(b);
|
||||||
|
} else if (b == ' ') {
|
||||||
|
// for form submission, space should be encoded as '+', not '%20'
|
||||||
|
try writer.writeByte('+');
|
||||||
|
} else {
|
||||||
|
try writer.print("%{X:0>2}", .{b});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn urlEncodeShouldEscape(value: []const u8) bool {
|
||||||
|
for (value) |b| {
|
||||||
|
if (!urlEncodeUnreserved(b)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn urlEncodeUnreserved(b: u8) bool {
|
||||||
|
return switch (b) {
|
||||||
|
'A'...'Z', 'a'...'z', '0'...'9', '-', '.', '_', '~' => true,
|
||||||
|
else => false,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
const Entry = struct {
|
const Entry = struct {
|
||||||
key: []const u8,
|
key: []const u8,
|
||||||
value: []const u8,
|
value: []const u8,
|
||||||
|
|
||||||
|
pub const List = std.ArrayListUnmanaged(Entry);
|
||||||
};
|
};
|
||||||
|
|
||||||
const KeyIterable = iterator.Iterable(KeyIterator, "FormDataKeyIterator");
|
const KeyIterable = iterator.Iterable(KeyIterator, "FormDataKeyIterator");
|
||||||
@@ -157,7 +218,7 @@ const EntryIterable = iterator.Iterable(EntryIterator, "FormDataEntryIterator");
|
|||||||
|
|
||||||
const KeyIterator = struct {
|
const KeyIterator = struct {
|
||||||
index: usize = 0,
|
index: usize = 0,
|
||||||
entries: *const std.ArrayListUnmanaged(Entry),
|
entries: *const Entry.List,
|
||||||
|
|
||||||
pub fn _next(self: *KeyIterator) ?[]const u8 {
|
pub fn _next(self: *KeyIterator) ?[]const u8 {
|
||||||
const index = self.index;
|
const index = self.index;
|
||||||
@@ -171,7 +232,7 @@ const KeyIterator = struct {
|
|||||||
|
|
||||||
const ValueIterator = struct {
|
const ValueIterator = struct {
|
||||||
index: usize = 0,
|
index: usize = 0,
|
||||||
entries: *const std.ArrayListUnmanaged(Entry),
|
entries: *const Entry.List,
|
||||||
|
|
||||||
pub fn _next(self: *ValueIterator) ?[]const u8 {
|
pub fn _next(self: *ValueIterator) ?[]const u8 {
|
||||||
const index = self.index;
|
const index = self.index;
|
||||||
@@ -185,7 +246,7 @@ const ValueIterator = struct {
|
|||||||
|
|
||||||
const EntryIterator = struct {
|
const EntryIterator = struct {
|
||||||
index: usize = 0,
|
index: usize = 0,
|
||||||
entries: *const std.ArrayListUnmanaged(Entry),
|
entries: *const Entry.List,
|
||||||
|
|
||||||
pub fn _next(self: *EntryIterator) ?struct { []const u8, []const u8 } {
|
pub fn _next(self: *EntryIterator) ?struct { []const u8, []const u8 } {
|
||||||
const index = self.index;
|
const index = self.index;
|
||||||
@@ -198,11 +259,13 @@ const EntryIterator = struct {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
fn collectForm(arena: Allocator, form: *parser.Form, submitter_: ?*parser.ElementHTML, page: *Page) !std.ArrayListUnmanaged(Entry) {
|
// TODO: handle disabled fieldsets
|
||||||
|
fn collectForm(form: *parser.Form, submitter_: ?*parser.ElementHTML, page: *Page) !Entry.List {
|
||||||
|
const arena = page.arena;
|
||||||
const collection = try parser.formGetCollection(form);
|
const collection = try parser.formGetCollection(form);
|
||||||
const len = try parser.htmlCollectionGetLength(collection);
|
const len = try parser.htmlCollectionGetLength(collection);
|
||||||
|
|
||||||
var entries: std.ArrayListUnmanaged(Entry) = .empty;
|
var entries: Entry.List = .empty;
|
||||||
try entries.ensureTotalCapacity(arena, len);
|
try entries.ensureTotalCapacity(arena, len);
|
||||||
|
|
||||||
const submitter_name_ = try getSubmitterName(submitter_);
|
const submitter_name_ = try getSubmitterName(submitter_);
|
||||||
@@ -275,7 +338,7 @@ fn collectForm(arena: Allocator, form: *parser.Form, submitter_: ?*parser.Elemen
|
|||||||
return entries;
|
return entries;
|
||||||
}
|
}
|
||||||
|
|
||||||
fn collectSelectValues(arena: Allocator, select: *parser.Select, name: []const u8, entries: *std.ArrayListUnmanaged(Entry), page: *Page) !void {
|
fn collectSelectValues(arena: Allocator, select: *parser.Select, name: []const u8, entries: *Entry.List, page: *Page) !void {
|
||||||
const HTMLSelectElement = @import("../html/select.zig").HTMLSelectElement;
|
const HTMLSelectElement = @import("../html/select.zig").HTMLSelectElement;
|
||||||
|
|
||||||
// Go through the HTMLSelectElement because it has specific logic for handling
|
// Go through the HTMLSelectElement because it has specific logic for handling
|
||||||
@@ -456,3 +519,34 @@ test "Browser.FormData" {
|
|||||||
},
|
},
|
||||||
}, .{});
|
}, .{});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
test "Browser.FormData: urlEncode" {
|
||||||
|
var arr: std.ArrayListUnmanaged(u8) = .empty;
|
||||||
|
defer arr.deinit(testing.allocator);
|
||||||
|
|
||||||
|
{
|
||||||
|
var fd = FormData{ .entries = .empty };
|
||||||
|
try testing.expectError(error.EncodingNotSupported, fd.write("unknown", arr.writer(testing.allocator)));
|
||||||
|
|
||||||
|
try fd.write(null, arr.writer(testing.allocator));
|
||||||
|
try testing.expectEqual("", arr.items);
|
||||||
|
|
||||||
|
try fd.write("application/x-www-form-urlencoded", arr.writer(testing.allocator));
|
||||||
|
try testing.expectEqual("", arr.items);
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
var fd = FormData{ .entries = Entry.List.fromOwnedSlice(@constCast(&[_]Entry{
|
||||||
|
.{ .key = "a", .value = "1" },
|
||||||
|
.{ .key = "it's over", .value = "9000 !!!" },
|
||||||
|
.{ .key = "emot", .value = "ok: ☺" },
|
||||||
|
})) };
|
||||||
|
const expected = "a=1&it%27s+over=9000+%21%21%21&emot=ok%3A+%E2%98%BA";
|
||||||
|
try fd.write(null, arr.writer(testing.allocator));
|
||||||
|
try testing.expectEqual(expected, arr.items);
|
||||||
|
|
||||||
|
arr.clearRetainingCapacity();
|
||||||
|
try fd.write("application/x-www-form-urlencoded", arr.writer(testing.allocator));
|
||||||
|
try testing.expectEqual(expected, arr.items);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -169,18 +169,27 @@ pub fn pageNavigate(arena: Allocator, bc: anytype, event: *const Notification.Pa
|
|||||||
|
|
||||||
bc.reset();
|
bc.reset();
|
||||||
|
|
||||||
const is_anchor = event.opts.reason == .anchor;
|
const reason_: ?[]const u8 = switch (event.opts.reason) {
|
||||||
if (is_anchor) {
|
.anchor => "anchorClick",
|
||||||
|
.script => "scriptInitiated",
|
||||||
|
.form => switch (event.opts.method) {
|
||||||
|
.GET => "formSubmissionGet",
|
||||||
|
.POST => "formSubmissionPost",
|
||||||
|
else => unreachable,
|
||||||
|
},
|
||||||
|
.address_bar => null,
|
||||||
|
};
|
||||||
|
if (reason_) |reason| {
|
||||||
try cdp.sendEvent("Page.frameScheduledNavigation", .{
|
try cdp.sendEvent("Page.frameScheduledNavigation", .{
|
||||||
.frameId = target_id,
|
.frameId = target_id,
|
||||||
.delay = 0,
|
.delay = 0,
|
||||||
.reason = "anchorClick",
|
.reason = reason,
|
||||||
.url = event.url.raw,
|
.url = event.url.raw,
|
||||||
}, .{ .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 = "anchorClick",
|
.reason = reason,
|
||||||
.url = event.url.raw,
|
.url = event.url.raw,
|
||||||
.disposition = "currentTab",
|
.disposition = "currentTab",
|
||||||
}, .{ .session_id = session_id });
|
}, .{ .session_id = session_id });
|
||||||
@@ -224,7 +233,7 @@ pub fn pageNavigate(arena: Allocator, bc: anytype, event: *const Notification.Pa
|
|||||||
}, .{ .session_id = session_id });
|
}, .{ .session_id = session_id });
|
||||||
}
|
}
|
||||||
|
|
||||||
if (is_anchor) {
|
if (reason_ != null) {
|
||||||
try cdp.sendEvent("Page.frameClearedScheduledNavigation", .{
|
try cdp.sendEvent("Page.frameClearedScheduledNavigation", .{
|
||||||
.frameId = target_id,
|
.frameId = target_id,
|
||||||
}, .{ .session_id = session_id });
|
}, .{ .session_id = session_id });
|
||||||
|
|||||||
57
src/url.zig
57
src/url.zig
@@ -87,7 +87,7 @@ pub const URL = struct {
|
|||||||
///
|
///
|
||||||
/// For URLs with a path, it will replace the last entry with the src.
|
/// For URLs with a path, it will replace the last entry with the src.
|
||||||
/// For URLs without a path, it will add src as the path.
|
/// For URLs without a path, it will add src as the path.
|
||||||
pub fn stitch(allocator: std.mem.Allocator, src: []const u8, base: []const u8) ![]const u8 {
|
pub fn stitch(allocator: Allocator, src: []const u8, base: []const u8) ![]const u8 {
|
||||||
if (base.len == 0) {
|
if (base.len == 0) {
|
||||||
return src;
|
return src;
|
||||||
}
|
}
|
||||||
@@ -111,6 +111,31 @@ pub const URL = struct {
|
|||||||
return std.fmt.allocPrint(allocator, "{s}/{s}", .{ base, src });
|
return std.fmt.allocPrint(allocator, "{s}/{s}", .{ base, src });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn concatQueryString(arena: Allocator, url: []const u8, query_string: []const u8) ![]const u8 {
|
||||||
|
std.debug.assert(url.len != 0);
|
||||||
|
|
||||||
|
if (query_string.len == 0) {
|
||||||
|
return url;
|
||||||
|
}
|
||||||
|
|
||||||
|
var buf: std.ArrayListUnmanaged(u8) = .empty;
|
||||||
|
|
||||||
|
// the most space well need is the url + ('?' or '&') + the query_string
|
||||||
|
try buf.ensureTotalCapacity(arena, url.len + 1 + query_string.len);
|
||||||
|
buf.appendSliceAssumeCapacity(url);
|
||||||
|
|
||||||
|
if (std.mem.indexOfScalar(u8, url, '?')) |index| {
|
||||||
|
const last_index = url.len - 1;
|
||||||
|
if (index != last_index and url[last_index] != '&') {
|
||||||
|
buf.appendAssumeCapacity('&');
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
buf.appendAssumeCapacity('?');
|
||||||
|
}
|
||||||
|
buf.appendSliceAssumeCapacity(query_string);
|
||||||
|
return buf.items;
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
test "Url resolve size" {
|
test "Url resolve size" {
|
||||||
@@ -170,3 +195,33 @@ test "URL: Stiching Base & Src URLs (Both Local)" {
|
|||||||
defer allocator.free(result);
|
defer allocator.free(result);
|
||||||
try testing.expectString("./abcdef/something.js", result);
|
try testing.expectString("./abcdef/something.js", result);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
test "URL: concatQueryString" {
|
||||||
|
defer testing.reset();
|
||||||
|
const arena = testing.arena_allocator;
|
||||||
|
|
||||||
|
{
|
||||||
|
const url = try URL.concatQueryString(arena, "https://www.lightpanda.io/", "");
|
||||||
|
try testing.expectEqual("https://www.lightpanda.io/", url);
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
const url = try URL.concatQueryString(arena, "https://www.lightpanda.io/index?", "");
|
||||||
|
try testing.expectEqual("https://www.lightpanda.io/index?", url);
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
const url = try URL.concatQueryString(arena, "https://www.lightpanda.io/index?", "a=b");
|
||||||
|
try testing.expectEqual("https://www.lightpanda.io/index?a=b", url);
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
const url = try URL.concatQueryString(arena, "https://www.lightpanda.io/index?1=2", "a=b");
|
||||||
|
try testing.expectEqual("https://www.lightpanda.io/index?1=2&a=b", url);
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
const url = try URL.concatQueryString(arena, "https://www.lightpanda.io/index?1=2&", "a=b");
|
||||||
|
try testing.expectEqual("https://www.lightpanda.io/index?1=2&a=b", url);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user