Skip to content

Commit 45cd494

Browse files
committed
initial URL refactor
1 parent 8f99e36 commit 45cd494

File tree

1 file changed

+62
-269
lines changed

1 file changed

+62
-269
lines changed

src/browser/url/url.zig

Lines changed: 62 additions & 269 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818

1919
const std = @import("std");
2020
const Allocator = std.mem.Allocator;
21+
const ada = @import("ada");
2122

2223
const js = @import("../js/js.zig");
2324
const parser = @import("../netsurf.zig");
@@ -35,215 +36,113 @@ pub const Interfaces = .{
3536
EntryIterable,
3637
};
3738

38-
// https://url.spec.whatwg.org/#url
39-
//
40-
// TODO we could avoid many of these getter string allocatoration in two differents
41-
// way:
42-
//
43-
// 1. We can eventually get the slice of scheme *with* the following char in
44-
// the underlying string. But I don't know if it's possible and how to do that.
45-
// I mean, if the rawuri contains `https://foo.bar`, uri.scheme is a slice
46-
// containing only `https`. I want `https:` so, in theory, I don't need to
47-
// allocatorate data, I should be able to retrieve the scheme + the following `:`
48-
// from rawuri.
49-
//
50-
// 2. The other way would be to copy the `std.Uri` code to have a dedicated
51-
// parser including the characters we want for the web API.
39+
/// https://developer.mozilla.org/en-US/docs/Web/API/URL/URL
5240
pub const URL = struct {
53-
uri: std.Uri,
54-
search_params: URLSearchParams,
41+
internal: ada.URL,
5542

56-
pub const empty = URL{
57-
.uri = .{ .scheme = "" },
58-
.search_params = .{},
59-
};
60-
61-
const URLArg = union(enum) {
43+
// You can use an existing URL object for either argument, and it will be
44+
// stringified from the object's href property.
45+
pub const ConstructorArg = union(enum) {
6246
url: *URL,
63-
element: *parser.ElementHTML,
47+
element: *parser.Element,
6448
string: []const u8,
6549

66-
fn toString(self: URLArg, arena: Allocator) !?[]const u8 {
67-
switch (self) {
68-
.string => |s| return s,
69-
.url => |url| return try url.toString(arena),
70-
.element => |e| return try parser.elementGetAttribute(@ptrCast(e), "href"),
71-
}
50+
fn toString(self: *const ConstructorArg) error{Invalid}![]const u8 {
51+
return switch (self) {
52+
.string => |s| s,
53+
.url => |url| url._toString(),
54+
.element => |e| parser.elementGetAttribute(@ptrCast(e), "href") orelse error.Invalid,
55+
};
7256
}
7357
};
7458

75-
pub fn constructor(url: URLArg, base: ?URLArg, page: *Page) !URL {
76-
const arena = page.arena;
77-
const url_str = try url.toString(arena) orelse return error.InvalidArgument;
78-
79-
var raw: ?[]const u8 = null;
80-
if (base) |b| {
81-
if (try b.toString(arena)) |bb| {
82-
raw = try @import("../../url.zig").URL.stitch(arena, url_str, bb, .{});
59+
pub fn constructor(url: ConstructorArg, maybe_base: ?ConstructorArg, _: *Page) !URL {
60+
const u = blk: {
61+
const url_str = try url.toString();
62+
if (maybe_base) |base| {
63+
break :blk ada.parseWithBase(url_str, try base.toString());
8364
}
84-
}
85-
86-
if (raw == null) {
87-
// if it was a URL, then it's already be owned by the arena
88-
raw = if (url == .url) url_str else try arena.dupe(u8, url_str);
89-
}
9065

91-
const uri = std.Uri.parse(raw.?) catch blk: {
92-
if (!std.mem.endsWith(u8, raw.?, "://")) {
93-
return error.TypeError;
94-
}
95-
// schema only is valid!
96-
break :blk std.Uri{
97-
.scheme = raw.?[0 .. raw.?.len - 3],
98-
.host = .{ .percent_encoded = "" },
99-
};
66+
break :blk ada.parse(url_str);
10067
};
10168

102-
return init(arena, uri);
69+
return .{ .url = u };
10370
}
10471

105-
pub fn init(arena: Allocator, uri: std.Uri) !URL {
106-
return .{
107-
.uri = uri,
108-
.search_params = try URLSearchParams.init(
109-
arena,
110-
uriComponentNullStr(uri.query),
111-
),
112-
};
72+
pub fn destructor(self: *const URL) void {
73+
ada.free(self.internal);
11374
}
11475

11576
pub fn initWithoutSearchParams(uri: std.Uri) URL {
11677
return .{ .uri = uri, .search_params = .{} };
11778
}
118-
119-
pub fn get_origin(self: *URL, page: *Page) ![]const u8 {
120-
var aw = std.Io.Writer.Allocating.init(page.arena);
121-
try self.uri.writeToStream(&aw.writer, .{
122-
.scheme = true,
123-
.authentication = false,
124-
.authority = true,
125-
.path = false,
126-
.query = false,
127-
.fragment = false,
128-
});
129-
return aw.written();
79+
pub fn _toString(self: *const URL) []const u8 {
80+
return ada.getHref(self.internal);
13081
}
13182

132-
// get_href returns the URL by writing all its components.
133-
pub fn get_href(self: *URL, page: *Page) ![]const u8 {
134-
return self.toString(page.arena);
135-
}
83+
// Getters.
13684

137-
pub fn _toString(self: *URL, page: *Page) ![]const u8 {
138-
return self.toString(page.arena);
139-
}
140-
141-
// format the url with all its components.
142-
pub fn toString(self: *const URL, arena: Allocator) ![]const u8 {
143-
var aw = std.Io.Writer.Allocating.init(arena);
144-
try self.uri.writeToStream(&aw.writer, .{
145-
.scheme = true,
146-
.authentication = true,
147-
.authority = true,
148-
.path = uriComponentNullStr(self.uri.path).len > 0,
149-
});
150-
151-
if (self.search_params.get_size() > 0) {
152-
try aw.writer.writeByte('?');
153-
try self.search_params.write(&aw.writer);
154-
}
155-
156-
{
157-
const fragment = uriComponentNullStr(self.uri.fragment);
158-
if (fragment.len > 0) {
159-
try aw.writer.writeByte('#');
160-
try aw.writer.writeAll(fragment);
161-
}
162-
}
85+
pub fn get_origin(self: *const URL, page: *Page) ![]const u8 {
86+
const arena = page.arena;
87+
// `ada.getOrigin` allocates memory in order to find the `origin`.
88+
// We'd like to use our arena allocator for such case;
89+
// so here we allocate the `origin` in page arena and free the original.
90+
const origin = ada.getOrigin(self.internal);
91+
// `OwnedString` itself is not heap allocated so this is safe.
92+
defer ada.freeOwnedString(.{ .data = origin.ptr, .length = origin.len });
16393

164-
return aw.written();
94+
return arena.dupe(u8, origin);
16595
}
16696

167-
pub fn get_protocol(self: *const URL) []const u8 {
168-
// std.Uri keeps a pointer to "https", "http" (scheme part) so we know
169-
// its followed by ':'.
170-
const scheme = self.uri.scheme;
171-
return scheme.ptr[0 .. scheme.len + 1];
97+
pub fn get_href(self: *const URL) []const u8 {
98+
return ada.getHref(self.internal);
17299
}
173100

174-
pub fn get_username(self: *URL) []const u8 {
175-
return uriComponentNullStr(self.uri.user);
101+
pub fn get_username(self: *const URL) []const u8 {
102+
return ada.getUsername(self.internal);
176103
}
177104

178-
pub fn get_password(self: *URL) []const u8 {
179-
return uriComponentNullStr(self.uri.password);
105+
pub fn get_password(self: *const URL) []const u8 {
106+
return ada.getPassword(self.internal);
180107
}
181108

182-
pub fn get_host(self: *URL, page: *Page) ![]const u8 {
183-
var aw = std.Io.Writer.Allocating.init(page.arena);
184-
try self.uri.writeToStream(&aw.writer, .{
185-
.scheme = false,
186-
.authentication = false,
187-
.authority = true,
188-
.path = false,
189-
.query = false,
190-
.fragment = false,
191-
});
192-
return aw.written();
109+
pub fn get_port(self: *const URL) []const u8 {
110+
return ada.getPort(self.internal);
193111
}
194112

195-
pub fn get_hostname(self: *URL) []const u8 {
196-
return uriComponentNullStr(self.uri.host);
113+
pub fn get_hash(self: *const URL) []const u8 {
114+
return ada.getHash(self.internal);
197115
}
198116

199-
pub fn get_port(self: *URL, page: *Page) ![]const u8 {
200-
const arena = page.arena;
201-
if (self.uri.port == null) return try arena.dupe(u8, "");
202-
203-
var aw = std.Io.Writer.Allocating.init(arena);
204-
try aw.writer.printInt(self.uri.port.?, 10, .lower, .{});
205-
return aw.written();
117+
pub fn get_host(self: *const URL) []const u8 {
118+
return ada.getHost(self.internal);
206119
}
207120

208-
pub fn get_pathname(self: *URL) []const u8 {
209-
if (uriComponentStr(self.uri.path).len == 0) return "/";
210-
return uriComponentStr(self.uri.path);
121+
pub fn get_hostname(self: *const URL) []const u8 {
122+
return ada.getHostname(self.internal);
211123
}
212124

213-
pub fn get_search(self: *URL, page: *Page) ![]const u8 {
214-
const arena = page.arena;
215-
216-
if (self.search_params.get_size() == 0) {
217-
return "";
218-
}
219-
220-
var buf: std.ArrayListUnmanaged(u8) = .{};
221-
try buf.append(arena, '?');
222-
try self.search_params.encode(buf.writer(arena));
223-
return buf.items;
125+
pub fn get_pathname(self: *const URL) []const u8 {
126+
return ada.getPathname(self.internal);
224127
}
225128

226-
pub fn set_search(self: *URL, qs_: ?[]const u8, page: *Page) !void {
227-
self.search_params = .{};
228-
if (qs_) |qs| {
229-
self.search_params = try URLSearchParams.init(page.arena, qs);
230-
}
129+
pub fn get_search(self: *const URL) []const u8 {
130+
return ada.getSearch(self.internal);
231131
}
232132

233-
pub fn get_hash(self: *URL, page: *Page) ![]const u8 {
234-
const arena = page.arena;
235-
if (self.uri.fragment == null) return try arena.dupe(u8, "");
236-
237-
return try std.mem.concat(arena, u8, &[_][]const u8{ "#", uriComponentNullStr(self.uri.fragment) });
133+
pub fn get_protocol(self: *const URL) []const u8 {
134+
return ada.getProtocol(self.internal);
238135
}
136+
};
239137

240-
pub fn get_searchParams(self: *URL) *URLSearchParams {
241-
return &self.search_params;
242-
}
138+
pub const URLSearchParams = struct {
139+
internal: ada.URLSearchParams,
243140

244-
pub fn _toJSON(self: *URL, page: *Page) ![]const u8 {
245-
return self.get_href(page);
246-
}
141+
pub const ConstructorOptions = union(enum) {
142+
string: []const u8,
143+
form_data: *const FormData,
144+
object: js.JsObject,
145+
};
247146
};
248147

249148
// uriComponentNullStr converts an optional std.Uri.Component to string value.
@@ -261,112 +160,6 @@ fn uriComponentStr(c: std.Uri.Component) []const u8 {
261160
};
262161
}
263162

264-
// https://url.spec.whatwg.org/#interface-urlsearchparams
265-
pub const URLSearchParams = struct {
266-
entries: kv.List = .{},
267-
268-
const URLSearchParamsOpts = union(enum) {
269-
qs: []const u8,
270-
form_data: *const FormData,
271-
js_obj: js.Object,
272-
};
273-
pub fn constructor(opts_: ?URLSearchParamsOpts, page: *Page) !URLSearchParams {
274-
const opts = opts_ orelse return .{ .entries = .{} };
275-
return switch (opts) {
276-
.qs => |qs| init(page.arena, qs),
277-
.form_data => |fd| .{ .entries = try fd.entries.clone(page.arena) },
278-
.js_obj => |js_obj| {
279-
const arena = page.arena;
280-
var it = js_obj.nameIterator();
281-
282-
var entries: kv.List = .{};
283-
try entries.ensureTotalCapacity(arena, it.count);
284-
285-
while (try it.next()) |js_name| {
286-
const name = try js_name.toString(arena);
287-
const js_val = try js_obj.get(name);
288-
entries.appendOwnedAssumeCapacity(
289-
name,
290-
try js_val.toString(arena),
291-
);
292-
}
293-
294-
return .{ .entries = entries };
295-
},
296-
};
297-
}
298-
299-
pub fn init(arena: Allocator, qs_: ?[]const u8) !URLSearchParams {
300-
return .{
301-
.entries = if (qs_) |qs| try parseQuery(arena, qs) else .{},
302-
};
303-
}
304-
305-
pub fn get_size(self: *const URLSearchParams) u32 {
306-
return @intCast(self.entries.count());
307-
}
308-
309-
pub fn _append(self: *URLSearchParams, name: []const u8, value: []const u8, page: *Page) !void {
310-
return self.entries.append(page.arena, name, value);
311-
}
312-
313-
pub fn _set(self: *URLSearchParams, name: []const u8, value: []const u8, page: *Page) !void {
314-
return self.entries.set(page.arena, name, value);
315-
}
316-
317-
pub fn _delete(self: *URLSearchParams, name: []const u8, value_: ?[]const u8) void {
318-
if (value_) |value| {
319-
return self.entries.deleteKeyValue(name, value);
320-
}
321-
return self.entries.delete(name);
322-
}
323-
324-
pub fn _get(self: *const URLSearchParams, name: []const u8) ?[]const u8 {
325-
return self.entries.get(name);
326-
}
327-
328-
pub fn _getAll(self: *const URLSearchParams, name: []const u8, page: *Page) ![]const []const u8 {
329-
return self.entries.getAll(page.call_arena, name);
330-
}
331-
332-
pub fn _has(self: *const URLSearchParams, name: []const u8) bool {
333-
return self.entries.has(name);
334-
}
335-
336-
pub fn _keys(self: *const URLSearchParams) KeyIterable {
337-
return .{ .inner = self.entries.keyIterator() };
338-
}
339-
340-
pub fn _values(self: *const URLSearchParams) ValueIterable {
341-
return .{ .inner = self.entries.valueIterator() };
342-
}
343-
344-
pub fn _entries(self: *const URLSearchParams) EntryIterable {
345-
return .{ .inner = self.entries.entryIterator() };
346-
}
347-
348-
pub fn _symbol_iterator(self: *const URLSearchParams) EntryIterable {
349-
return self._entries();
350-
}
351-
352-
pub fn _toString(self: *const URLSearchParams, page: *Page) ![]const u8 {
353-
var arr: std.ArrayListUnmanaged(u8) = .empty;
354-
try self.write(arr.writer(page.call_arena));
355-
return arr.items;
356-
}
357-
358-
fn write(self: *const URLSearchParams, writer: anytype) !void {
359-
return kv.urlEncode(self.entries, .query, writer);
360-
}
361-
362-
// TODO
363-
pub fn _sort(_: *URLSearchParams) void {}
364-
365-
fn encode(self: *const URLSearchParams, writer: anytype) !void {
366-
return kv.urlEncode(self.entries, .query, writer);
367-
}
368-
};
369-
370163
// Parse the given query.
371164
fn parseQuery(arena: Allocator, s: []const u8) !kv.List {
372165
var list = kv.List{};

0 commit comments

Comments
 (0)