mirror of
https://github.com/lightpanda-io/browser.git
synced 2025-12-15 15:58:57 +00:00
add FormData and base KeyValueList
This commit is contained in:
@@ -444,6 +444,7 @@ pub const JsApis = flattenTypes(&.{
|
||||
@import("../webapi/css/CSSStyleProperties.zig"),
|
||||
@import("../webapi/Document.zig"),
|
||||
@import("../webapi/HTMLDocument.zig"),
|
||||
@import("../webapi/KeyValueList.zig"),
|
||||
@import("../webapi/DocumentFragment.zig"),
|
||||
@import("../webapi/DOMException.zig"),
|
||||
@import("../webapi/DOMTreeWalker.zig"),
|
||||
@@ -489,6 +490,7 @@ pub const JsApis = flattenTypes(&.{
|
||||
@import("../webapi/EventTarget.zig"),
|
||||
@import("../webapi/Location.zig"),
|
||||
@import("../webapi/Navigator.zig"),
|
||||
@import("../webapi/net/FormData.zig"),
|
||||
@import("../webapi/net/Request.zig"),
|
||||
@import("../webapi/net/Response.zig"),
|
||||
@import("../webapi/net/URLSearchParams.zig"),
|
||||
|
||||
23
src/browser/tests/polyfill/webcomponents.html
Normal file
23
src/browser/tests/polyfill/webcomponents.html
Normal file
@@ -0,0 +1,23 @@
|
||||
<!DOCTYPE html>
|
||||
<script src="../testing.js"></script>
|
||||
|
||||
<div id=main></div>
|
||||
|
||||
<script id=webcomponents>
|
||||
class LightPanda extends HTMLElement {
|
||||
constructor() {
|
||||
super();
|
||||
}
|
||||
connectedCallback() {
|
||||
this.append('connected');
|
||||
}
|
||||
}
|
||||
|
||||
window.customElements.define("lightpanda-test", LightPanda);
|
||||
const main = document.getElementById('main');
|
||||
main.appendChild(document.createElement('lightpanda-test'));
|
||||
|
||||
testing.expectEqual('<lightpanda-test>connected</lightpanda-test>', main.innerHTML)
|
||||
testing.expectEqual('[object DataSet]', document.createElement('lightpanda-test').dataset.toString());
|
||||
testing.expectEqual('[object DataSet]', document.createElement('lightpanda-test.x').dataset.toString());
|
||||
</script>
|
||||
146
src/browser/webapi/KeyValueList.zig
Normal file
146
src/browser/webapi/KeyValueList.zig
Normal file
@@ -0,0 +1,146 @@
|
||||
const std = @import("std");
|
||||
|
||||
const String = @import("../../string.zig").String;
|
||||
|
||||
const js = @import("../js/js.zig");
|
||||
const Page = @import("../Page.zig");
|
||||
|
||||
const Allocator = std.mem.Allocator;
|
||||
|
||||
pub fn registerTypes() []const type {
|
||||
return &.{
|
||||
KeyIterator,
|
||||
ValueIterator,
|
||||
EntryIterator,
|
||||
};
|
||||
}
|
||||
|
||||
pub const KeyValueList = @This();
|
||||
|
||||
_entries: std.ArrayListUnmanaged(Entry) = .empty,
|
||||
|
||||
pub const empty: KeyValueList = .{
|
||||
._entries = .empty,
|
||||
};
|
||||
|
||||
pub const Entry = struct {
|
||||
name: String,
|
||||
value: String,
|
||||
};
|
||||
|
||||
pub fn init() KeyValueList {
|
||||
return .{};
|
||||
}
|
||||
|
||||
pub fn ensureTotalCapacity(self: *KeyValueList, allocator: Allocator, n: usize) !void {
|
||||
return self._entries.ensureTotalCapacity(allocator, n);
|
||||
}
|
||||
|
||||
pub fn get(self: *const KeyValueList, name: []const u8) ?[]const u8 {
|
||||
for (self._entries.items) |*entry| {
|
||||
if (entry.name.eqlSlice(name)) {
|
||||
return entry.value.str();
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
pub fn getAll(self: *const KeyValueList, name: []const u8, page: *Page) ![]const []const u8 {
|
||||
const arena = page.call_arena;
|
||||
var arr: std.ArrayList([]const u8) = .empty;
|
||||
for (self._entries.items) |*entry| {
|
||||
if (entry.name.eqlSlice(name)) {
|
||||
try arr.append(arena, entry.value.str());
|
||||
}
|
||||
}
|
||||
return arr.items;
|
||||
}
|
||||
|
||||
pub fn has(self: *const KeyValueList, name: []const u8) bool {
|
||||
for (self._entries.items) |*entry| {
|
||||
if (entry.name.eqlSlice(name)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
pub fn append(self: *KeyValueList, allocator: Allocator, name: []const u8, value: []const u8) !void {
|
||||
try self._entries.append(allocator, .{
|
||||
.name = try String.init(allocator, name, .{}),
|
||||
.value = try String.init(allocator, value, .{}),
|
||||
});
|
||||
}
|
||||
|
||||
pub fn appendAssumeCapacity(self: *KeyValueList, allocator: Allocator, name: []const u8, value: []const u8) !void {
|
||||
self._entries.appendAssumeCapacity(.{
|
||||
.name = try String.init(allocator, name, .{}),
|
||||
.value = try String.init(allocator, value, .{}),
|
||||
});
|
||||
}
|
||||
|
||||
pub fn delete(self: *KeyValueList, name: []const u8, value: ?[]const u8) void {
|
||||
var i: usize = 0;
|
||||
while (i < self._entries.items.len) {
|
||||
const entry = self._entries.items[i];
|
||||
if (entry.name.eqlSlice(name)) {
|
||||
if (value == null or entry.value.eqlSlice(value.?)) {
|
||||
_ = self._entries.swapRemove(i);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
i += 1;
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set(self: *KeyValueList, allocator: Allocator, name: []const u8, value: []const u8) !void {
|
||||
self.delete(name, null);
|
||||
try self.append(allocator, name, value);
|
||||
}
|
||||
|
||||
pub fn len(self: *const KeyValueList) usize {
|
||||
return self._entries.items.len;
|
||||
}
|
||||
|
||||
pub fn items(self: *const KeyValueList) []const Entry {
|
||||
return self._entries.items;
|
||||
}
|
||||
|
||||
pub const Iterator = struct {
|
||||
index: u32 = 0,
|
||||
kv: *KeyValueList,
|
||||
|
||||
// Why? Because whenever an Iterator is created, we need to increment the
|
||||
// RC of what it's iterating. And when the iterator is destroyed, we need
|
||||
// to decrement it. The generic iterator which will wrap this handles that
|
||||
// by using this "list" field. Most things that use the GenericIterator can
|
||||
// just set `list: *ZigCollection`, and everything will work. But KeyValueList
|
||||
// is being composed by various types, so it can't reference those types.
|
||||
// Using *anyopaque here is "dangerous", in that it requires the composer
|
||||
// to pass the right value, which normally would be itself (`*Self`), but
|
||||
// only because (as of now) everyting that uses KeyValueList has no prototype
|
||||
list: *anyopaque,
|
||||
|
||||
pub const Entry = struct { []const u8, []const u8 };
|
||||
|
||||
pub fn next(self: *Iterator, _: *const Page) ?Iterator.Entry {
|
||||
const index = self.index;
|
||||
const entries = self.kv._entries.items;
|
||||
if (index >= entries.len) {
|
||||
return null;
|
||||
}
|
||||
self.index = index + 1;
|
||||
|
||||
const e = &entries[index];
|
||||
return .{ e.name.str(), e.value.str() };
|
||||
}
|
||||
};
|
||||
|
||||
pub fn iterator(self: *const KeyValueList) Iterator {
|
||||
return .{ .list = self };
|
||||
}
|
||||
|
||||
const GenericIterator = @import("collections/iterator.zig").Entry;
|
||||
pub const KeyIterator = GenericIterator(Iterator, "0");
|
||||
pub const ValueIterator = GenericIterator(Iterator, "1");
|
||||
pub const EntryIterator = GenericIterator(Iterator, null);
|
||||
115
src/browser/webapi/net/FormData.zig
Normal file
115
src/browser/webapi/net/FormData.zig
Normal file
@@ -0,0 +1,115 @@
|
||||
const std = @import("std");
|
||||
|
||||
const log = @import("../../../log.zig");
|
||||
|
||||
const js = @import("../../js/js.zig");
|
||||
const Page = @import("../../Page.zig");
|
||||
const KeyValueList = @import("../KeyValueList.zig");
|
||||
|
||||
const Alloctor = std.mem.Allocator;
|
||||
|
||||
const FormData = @This();
|
||||
|
||||
_arena: Alloctor,
|
||||
_list: KeyValueList,
|
||||
|
||||
pub fn init(page: *Page) !*FormData {
|
||||
return page._factory.create(FormData{
|
||||
._arena = page.arena,
|
||||
._list = KeyValueList.init(),
|
||||
});
|
||||
}
|
||||
|
||||
pub fn get(self: *const FormData, name: []const u8) ?[]const u8 {
|
||||
return self._list.get(name);
|
||||
}
|
||||
|
||||
pub fn getAll(self: *const FormData, name: []const u8, page: *Page) ![]const []const u8 {
|
||||
return self._list.getAll(name, page);
|
||||
}
|
||||
|
||||
pub fn has(self: *const FormData, name: []const u8) bool {
|
||||
return self._list.has(name);
|
||||
}
|
||||
|
||||
pub fn set(self: *FormData, name: []const u8, value: []const u8) !void {
|
||||
return self._list.set(self._arena, name, value);
|
||||
}
|
||||
|
||||
pub fn append(self: *FormData, name: []const u8, value: []const u8) !void {
|
||||
return self._list.append(self._arena, name, value);
|
||||
}
|
||||
|
||||
pub fn delete(self: *FormData, name: []const u8) void {
|
||||
self._list.delete(name, null);
|
||||
}
|
||||
|
||||
pub fn keys(self: *FormData, page: *Page) !*KeyValueList.KeyIterator {
|
||||
return KeyValueList.KeyIterator.init(.{ .list = self, .kv = &self._list }, page);
|
||||
}
|
||||
|
||||
pub fn values(self: *FormData, page: *Page) !*KeyValueList.ValueIterator {
|
||||
return KeyValueList.ValueIterator.init(.{ .list = self, .kv = &self._list }, page);
|
||||
}
|
||||
|
||||
pub fn entries(self: *FormData, page: *Page) !*KeyValueList.EntryIterator {
|
||||
return KeyValueList.EntryIterator.init(.{ .list = self, .kv = &self._list }, page);
|
||||
}
|
||||
|
||||
pub fn forEach(self: *FormData, cb_: js.Function, js_this_: ?js.Object) !void {
|
||||
const cb = if (js_this_) |js_this| try cb_.withThis(js_this) else cb_;
|
||||
|
||||
for (self._list._entries.items) |entry| {
|
||||
cb.call(void, .{ entry.value.str(), entry.name.str(), self }) catch |err| {
|
||||
// this is a non-JS error
|
||||
log.warn(.js, "FormData.forEach", .{ .err = err });
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
pub const Iterator = struct {
|
||||
index: u32 = 0,
|
||||
list: *const FormData,
|
||||
|
||||
const Entry = struct { []const u8, []const u8 };
|
||||
|
||||
pub fn next(self: *Iterator, _: *Page) !?Iterator.Entry {
|
||||
const index = self.index;
|
||||
const items = self.list._list.items();
|
||||
if (index >= items.len) {
|
||||
return null;
|
||||
}
|
||||
self.index = index + 1;
|
||||
|
||||
const e = &items[index];
|
||||
return .{ e.name.str(), e.value.str() };
|
||||
}
|
||||
};
|
||||
|
||||
pub const JsApi = struct {
|
||||
pub const bridge = js.Bridge(FormData);
|
||||
|
||||
pub const Meta = struct {
|
||||
pub const name = "FormData";
|
||||
pub const prototype_chain = bridge.prototypeChain();
|
||||
pub var class_index: u16 = 0;
|
||||
};
|
||||
|
||||
pub const constructor = bridge.constructor(FormData.init, .{});
|
||||
pub const has = bridge.function(FormData.has, .{});
|
||||
pub const get = bridge.function(FormData.get, .{});
|
||||
pub const set = bridge.function(FormData.set, .{});
|
||||
pub const append = bridge.function(FormData.append, .{});
|
||||
pub const getAll = bridge.function(FormData.getAll, .{});
|
||||
pub const delete = bridge.function(FormData.delete, .{});
|
||||
pub const keys = bridge.function(FormData.keys, .{});
|
||||
pub const values = bridge.function(FormData.values, .{});
|
||||
pub const entries = bridge.function(FormData.entries, .{});
|
||||
pub const symbol_iterator = bridge.iterator(FormData.entries, .{});
|
||||
pub const forEach = bridge.function(FormData.forEach, .{});
|
||||
};
|
||||
|
||||
const testing = @import("../../../testing.zig");
|
||||
test "WebApi: FormData" {
|
||||
try testing.htmlRunner("net/form_data.html", .{});
|
||||
}
|
||||
@@ -7,24 +7,12 @@ const Allocator = std.mem.Allocator;
|
||||
|
||||
const Page = @import("../../Page.zig");
|
||||
const GenericIterator = @import("../collections/iterator.zig").Entry;
|
||||
|
||||
pub fn registerTypes() []const type {
|
||||
return &.{
|
||||
URLSearchParams,
|
||||
KeyIterator,
|
||||
ValueIterator,
|
||||
EntryIterator,
|
||||
};
|
||||
}
|
||||
const KeyValueList = @import("../KeyValueList.zig");
|
||||
|
||||
const URLSearchParams = @This();
|
||||
|
||||
_arena: Allocator,
|
||||
_params: Entry.List,
|
||||
|
||||
pub const KeyIterator = GenericIterator(Iterator, "0");
|
||||
pub const ValueIterator = GenericIterator(Iterator, "1");
|
||||
pub const EntryIterator = GenericIterator(Iterator, null);
|
||||
_params: KeyValueList,
|
||||
|
||||
const InitOpts = union(enum) {
|
||||
query_string: []const u8,
|
||||
@@ -33,7 +21,7 @@ const InitOpts = union(enum) {
|
||||
};
|
||||
pub fn init(opts_: ?InitOpts, page: *Page) !*URLSearchParams {
|
||||
const arena = page.arena;
|
||||
const params: Entry.List = blk: {
|
||||
const params: KeyValueList = blk: {
|
||||
const opts = opts_ orelse break :blk .empty;
|
||||
break :blk switch (opts) {
|
||||
.query_string => |str| try paramsFromString(arena, str, &page.buf),
|
||||
@@ -47,81 +35,64 @@ pub fn init(opts_: ?InitOpts, page: *Page) !*URLSearchParams {
|
||||
}
|
||||
|
||||
pub fn getSize(self: *const URLSearchParams) usize {
|
||||
return self._params.items.len;
|
||||
return self._params.len();
|
||||
}
|
||||
|
||||
pub fn get(self: *const URLSearchParams, name: []const u8) ?[]const u8 {
|
||||
const entry = self.getEntry(name) orelse return null;
|
||||
return entry.value.str();
|
||||
return self._params.get(name);
|
||||
}
|
||||
|
||||
pub fn getAll(self: *const URLSearchParams, name: []const u8, page: *Page) ![]const []const u8 {
|
||||
const arena = page.call_arena;
|
||||
var arr: std.ArrayList([]const u8) = .empty;
|
||||
for (self._params.items) |*entry| {
|
||||
if (entry.name.eqlSlice(name)) {
|
||||
try arr.append(arena, entry.value.str());
|
||||
}
|
||||
}
|
||||
return arr.items;
|
||||
return self._params.getAll(name, page);
|
||||
}
|
||||
|
||||
pub fn has(self: *const URLSearchParams, name: []const u8) bool {
|
||||
return self.getEntry(name) != null;
|
||||
return self._params.has(name);
|
||||
}
|
||||
|
||||
pub fn set(self: *URLSearchParams, name: []const u8, value: []const u8) !void {
|
||||
self.delete(name, null);
|
||||
return self.append(name, value);
|
||||
return self._params.set(self._arena, name, value);
|
||||
}
|
||||
|
||||
pub fn append(self: *URLSearchParams, name: []const u8, value: []const u8) !void {
|
||||
const arena = self._arena;
|
||||
return self._params.append(arena, .{
|
||||
.name = try String.init(arena, name, .{}),
|
||||
.value = try String.init(arena, value, .{}),
|
||||
});
|
||||
return self._params.append(self._arena, name, value);
|
||||
}
|
||||
|
||||
pub fn delete(self: *URLSearchParams, name: []const u8, value: ?[]const u8) void {
|
||||
var i: usize = 0;
|
||||
while (i < self._params.items.len) {
|
||||
const entry = self._params.items[i];
|
||||
if (entry.name.eqlSlice(name)) {
|
||||
if (value == null or entry.value.eqlSlice(value.?)) {
|
||||
_ = self._params.swapRemove(i);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
i += 1;
|
||||
}
|
||||
self._params.delete(name, value);
|
||||
}
|
||||
|
||||
pub fn keys(self: *const URLSearchParams, page: *Page) !*KeyIterator {
|
||||
return .init(.{ .list = self }, page);
|
||||
pub fn keys(self: *URLSearchParams, page: *Page) !*KeyValueList.KeyIterator {
|
||||
return KeyValueList.KeyIterator.init(.{ .list = self, .kv = &self._params }, page);
|
||||
}
|
||||
|
||||
pub fn values(self: *const URLSearchParams, page: *Page) !*ValueIterator {
|
||||
return .init(.{ .list = self }, page);
|
||||
pub fn values(self: *URLSearchParams, page: *Page) !*KeyValueList.ValueIterator {
|
||||
return KeyValueList.ValueIterator.init(.{ .list = self, .kv = &self._params }, page);
|
||||
}
|
||||
|
||||
pub fn entries(self: *const URLSearchParams, page: *Page) !*EntryIterator {
|
||||
return .init(.{ .list = self }, page);
|
||||
pub fn entries(self: *URLSearchParams, page: *Page) !*KeyValueList.EntryIterator {
|
||||
return KeyValueList.EntryIterator.init(.{ .list = self, .kv = &self._params }, page);
|
||||
}
|
||||
|
||||
pub fn toString(self: *const URLSearchParams, writer: *std.Io.Writer) !void {
|
||||
const items = self._params.items;
|
||||
const items = self._params._entries.items;
|
||||
if (items.len == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
try items[0].toString(writer);
|
||||
try writeEntry(&items[0], writer);
|
||||
for (items[1..]) |entry| {
|
||||
try writer.writeByte('&');
|
||||
try entry.toString(writer);
|
||||
try writeEntry(&entry, writer);
|
||||
}
|
||||
}
|
||||
|
||||
fn writeEntry(entry: *const KeyValueList.Entry, writer: *std.Io.Writer) !void {
|
||||
try escape(entry.name.str(), writer);
|
||||
try writer.writeByte('=');
|
||||
try escape(entry.value.str(), writer);
|
||||
}
|
||||
|
||||
pub fn format(self: *const URLSearchParams, writer: *std.Io.Writer) !void {
|
||||
return self.toString(writer);
|
||||
}
|
||||
@@ -129,7 +100,7 @@ pub fn format(self: *const URLSearchParams, writer: *std.Io.Writer) !void {
|
||||
pub fn forEach(self: *URLSearchParams, cb_: js.Function, js_this_: ?js.Object) !void {
|
||||
const cb = if (js_this_) |js_this| try cb_.withThis(js_this) else cb_;
|
||||
|
||||
for (self._params.items) |entry| {
|
||||
for (self._params._entries.items) |entry| {
|
||||
cb.call(void, .{ entry.value.str(), entry.name.str(), self }) catch |err| {
|
||||
// this is a non-JS error
|
||||
log.warn(.js, "URLSearchParams.forEach", .{ .err = err });
|
||||
@@ -138,23 +109,14 @@ pub fn forEach(self: *URLSearchParams, cb_: js.Function, js_this_: ?js.Object) !
|
||||
}
|
||||
|
||||
pub fn sort(self: *URLSearchParams) void {
|
||||
std.mem.sort(Entry, self._params.items, {}, entryLessThan);
|
||||
}
|
||||
|
||||
fn entryLessThan(_: void, a: Entry, b: Entry) bool {
|
||||
return std.mem.order(u8, a.name.str(), b.name.str()) == .lt;
|
||||
}
|
||||
|
||||
fn getEntry(self: *const URLSearchParams, name: []const u8) ?*Entry {
|
||||
for (self._params.items) |*entry| {
|
||||
if (entry.name.eqlSlice(name)) {
|
||||
return entry;
|
||||
std.mem.sort(KeyValueList.Entry, self._params._entries.items, {}, struct {
|
||||
fn cmp(_: void, a: KeyValueList.Entry, b: KeyValueList.Entry) bool {
|
||||
return std.mem.order(u8, a.name.str(), b.name.str()) == .lt;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}.cmp);
|
||||
}
|
||||
|
||||
fn paramsFromString(arena: Allocator, input_: []const u8, buf: []u8) !Entry.List {
|
||||
fn paramsFromString(allocator: Allocator, input_: []const u8, buf: []u8) !KeyValueList {
|
||||
if (input_.len == 0) {
|
||||
return .empty;
|
||||
}
|
||||
@@ -169,21 +131,24 @@ fn paramsFromString(arena: Allocator, input_: []const u8, buf: []u8) !Entry.List
|
||||
return .empty;
|
||||
}
|
||||
|
||||
var params: Entry.List = .empty;
|
||||
var params = KeyValueList.init();
|
||||
|
||||
var it = std.mem.splitScalar(u8, input, '&');
|
||||
while (it.next()) |entry| {
|
||||
var name: String = undefined;
|
||||
var value: String = undefined;
|
||||
|
||||
if (std.mem.indexOfScalarPos(u8, entry, 0, '=')) |idx| {
|
||||
name = try unescape(arena, entry[0..idx], buf);
|
||||
value = try unescape(arena, entry[idx + 1 ..], buf);
|
||||
name = try unescape(allocator, entry[0..idx], buf);
|
||||
value = try unescape(allocator, entry[idx + 1 ..], buf);
|
||||
} else {
|
||||
name = try unescape(arena, entry, buf);
|
||||
name = try unescape(allocator, entry, buf);
|
||||
value = String.init(undefined, "", .{}) catch unreachable;
|
||||
}
|
||||
|
||||
try params.append(arena, .{
|
||||
// optimized, unescape returns a String directly (Because unescape may
|
||||
// have to dupe itself, so it knows how best to create the String)
|
||||
try params._entries.append(allocator, .{
|
||||
.name = name,
|
||||
.value = value,
|
||||
});
|
||||
@@ -192,19 +157,6 @@ fn paramsFromString(arena: Allocator, input_: []const u8, buf: []u8) !Entry.List
|
||||
return params;
|
||||
}
|
||||
|
||||
const Entry = struct {
|
||||
name: String,
|
||||
value: String,
|
||||
|
||||
const List = std.ArrayListUnmanaged(Entry);
|
||||
|
||||
pub fn toString(self: *const Entry, writer: *std.Io.Writer) !void {
|
||||
try escape(self.name.str(), writer);
|
||||
try writer.writeByte('=');
|
||||
try escape(self.value.str(), writer);
|
||||
}
|
||||
};
|
||||
|
||||
fn unescape(arena: Allocator, value: []const u8, buf: []u8) !String {
|
||||
if (value.len == 0) {
|
||||
return String.init(undefined, "", .{});
|
||||
|
||||
Reference in New Issue
Block a user