diff --git a/src/browser/html/elements.zig b/src/browser/html/elements.zig
index 38d39fbd..6dd18601 100644
--- a/src/browser/html/elements.zig
+++ b/src/browser/html/elements.zig
@@ -221,9 +221,29 @@ pub const HTMLAnchorElement = struct {
return parser.anchorGetHref(self);
}
- pub fn set_href(self: *parser.Anchor, href: []const u8, page: *const Page) !void {
+ pub fn set_href(self: *parser.Anchor, href: []const u8, page: *Page) !void {
const full = try urlStitch(page.call_arena, href, page.url.getHref(), .{});
- return parser.anchorSetHref(self, full);
+
+ // Get the stored internal URL if we had one.
+ if (page.getObjectData(self)) |internal_url| {
+ const u = NativeURL.fromInternal(internal_url);
+ // Reparse with the new href.
+ _ = try u.reparse(full);
+ errdefer u.deinit();
+
+ // TODO: Remove the entry from the map on an error situation.
+
+ return parser.anchorSetHref(self, u.getHref());
+ }
+
+ // We don't have internal URL stored in object_data yet.
+ // Create one for this anchor element.
+ const u = try NativeURL.parse(full, null);
+ errdefer u.deinit();
+ // Save to map.
+ try page.putObjectData(self, u.internal.?);
+
+ return parser.anchorSetHref(self, u.getHref());
}
pub fn get_hreflang(self: *parser.Anchor) ![]const u8 {
@@ -258,170 +278,188 @@ pub const HTMLAnchorElement = struct {
return try parser.nodeSetTextContent(parser.anchorToNode(self), v);
}
- fn url(self: *parser.Anchor, page: *Page) !URL {
- // Although the URL.constructor union accepts an .{.element = X}, we
- // can't use this here because the behavior is different.
- // URL.constructor(document.createElement('a')
- // should fail (a.href isn't a valid URL)
- // But
- // document.createElement('a').host
- // should not fail, it should return an empty string
- if (try parser.elementGetAttribute(@ptrCast(@alignCast(self)), "href")) |href| {
- return URL.constructor(.{ .string = href }, null, page); // TODO inject base url
+ fn getHref(self: *parser.Anchor) !?[]const u8 {
+ return parser.elementGetAttribute(@ptrCast(@alignCast(self)), "href");
+ }
+
+ /// Returns the URL associated with given anchor element.
+ /// Creates a new URL object if not created before.
+ fn getURL(self: *parser.Anchor, page: *Page) !NativeURL {
+ if (page.getObjectData(self)) |internal_url| {
+ return NativeURL.fromInternal(internal_url);
}
- return .empty;
+
+ // Try to get href string.
+ const maybe_anchor_href = try getHref(self);
+ if (maybe_anchor_href) |anchor_href| {
+ // Allocate a URL for this anchor element.
+ const u = try NativeURL.parse(anchor_href, null);
+ // Save in map.
+ try page.putObjectData(self, u.internal.?);
+
+ return u;
+ }
+
+ // No anchor href string found; let's just return an error.
+ return error.HrefAttributeNotGiven;
}
// TODO return a disposable string
pub fn get_origin(self: *parser.Anchor, page: *Page) ![]const u8 {
- var u = try url(self, page);
- return u.get_origin(page);
+ const u = getURL(self, page) catch return "";
+ // Though we store the URL in object data map, we still have to allocate
+ // for origin string sadly.
+ return u.getOrigin(page.arena);
}
// TODO return a disposable string
pub fn get_protocol(self: *parser.Anchor, page: *Page) ![]const u8 {
- var u = try url(self, page);
- return u.get_protocol();
+ const u = getURL(self, page) catch return "";
+ return u.getProtocol();
}
pub fn set_protocol(self: *parser.Anchor, v: []const u8, page: *Page) !void {
- const arena = page.arena;
- _ = arena;
- var u = try url(self, page);
-
- u.set_protocol(v);
- const href = try u.get_href(page);
- try parser.anchorSetHref(self, href);
+ const u = try getURL(self, page);
+ try u.setProtocol(v);
+ return parser.anchorSetHref(self, u.getHref());
}
+ const NativeURL = @import("../../url.zig").URL;
+
+ // TODO: Return a disposable string.
pub fn get_host(self: *parser.Anchor, page: *Page) ![]const u8 {
- var u = try url(self, page);
- return u.get_host();
+ const u = getURL(self, page) catch return "";
+ return u.host();
}
- pub fn set_host(self: *parser.Anchor, v: []const u8, page: *Page) !void {
- // search : separator
- var p: ?[]const u8 = null;
- var h: []const u8 = undefined;
- for (v, 0..) |c, i| {
- if (c == ':') {
- h = v[0..i];
- //p = try std.fmt.parseInt(u16, v[i + 1 ..], 10);
- p = v[i + 1 ..];
- break;
+ pub fn set_host(self: *parser.Anchor, host_str: []const u8, page: *Page) !void {
+ const u = blk: {
+ if (page.getObjectData(self)) |internal_url| {
+ break :blk NativeURL.fromInternal(internal_url);
}
- }
- var u = try url(self, page);
+ const maybe_anchor_href = try getHref(self);
+ if (maybe_anchor_href) |anchor_href| {
+ const new_u = try NativeURL.parse(anchor_href, null);
+ try page.putObjectData(self, new_u.internal.?);
+ break :blk new_u;
+ }
- if (p) |port| {
- u.set_host(h);
- u.set_port(port);
- } else {
- u.set_host(v);
- }
+ // Last resort; try to create URL object out of host_str.
+ const new_u = try NativeURL.parse(host_str, null);
+ // We can just return here since host is updated.
+ return page.putObjectData(self, new_u.internal.?);
+ };
- const href = try u.get_href(page);
- try parser.anchorSetHref(self, href);
+ try u.setHost(host_str);
+ return parser.anchorSetHref(self, u.getHref());
}
- pub fn get_hostname(self: *parser.Anchor, page: *Page) ![]const u8 {
- var u = try url(self, page);
- return u.get_hostname();
- }
+ //pub fn get_hostname(self: *parser.Anchor, page: *Page) ![]const u8 {
+ // const maybe_href_str = try getAnchorHref(self);
+ // const href_str = maybe_href_str orelse return "";
+ //
+ // const u = try NativeURL.parse(href_str, null);
+ // defer u.deinit();
+ //
+ // return page.arena.dupe(u8, u.getHostname());
+ //}
- pub fn set_hostname(self: *parser.Anchor, v: []const u8, page: *Page) !void {
- var u = try url(self, page);
- u.set_host(v);
- const href = try u.get_href(page);
- try parser.anchorSetHref(self, href);
- }
+ //pub fn set_hostname(self: *parser.Anchor, v: []const u8) !void {
+ // const maybe_href_str = try getAnchorHref(self);
+ //
+ // if (maybe_href_str) |href_str| {
+ // const u = try NativeURL.parse(href_str, null);
+ // defer u.deinit();
+ //
+ // try u.setHostname(v);
+ //
+ // return parser.anchorSetHref(self, u.getHref());
+ // }
+ //
+ // // No href string there; use the given value as href.
+ // const u = try NativeURL.parse(v, null);
+ // defer u.deinit();
+ //
+ // return parser.anchorSetHref(self, u.getHref());
+ //}
// TODO return a disposable string
pub fn get_port(self: *parser.Anchor, page: *Page) ![]const u8 {
- var u = try url(self, page);
- return u.get_port();
+ const u = getURL(self, page) catch return "";
+ return u.getPort();
}
- pub fn set_port(self: *parser.Anchor, v: ?[]const u8, page: *Page) !void {
- var u = try url(self, page);
+ pub fn set_port(self: *parser.Anchor, maybe_port: ?[]const u8, page: *Page) !void {
+ // TODO: Check for valid port (u16 integer).
+ if (maybe_port) |port| {
+ const u = try getURL(self, page);
+ try u.setPort(port);
- if (v != null and v.?.len > 0) {
- u.set_host(v.?);
+ return parser.anchorSetHref(self, u.getHref());
}
-
- const href = try u.get_href(page);
- try parser.anchorSetHref(self, href);
}
// TODO return a disposable string
pub fn get_username(self: *parser.Anchor, page: *Page) ![]const u8 {
- var u = try url(self, page);
- return u.get_username();
+ const u = try getURL(self, page);
+ return u.getUsername() orelse "";
}
- pub fn set_username(self: *parser.Anchor, v: ?[]const u8, page: *Page) !void {
- if (v) |username| {
- var u = try url(self, page);
- u.set_username(username);
-
- const href = try u.get_href(page);
- try parser.anchorSetHref(self, href);
+ pub fn set_username(self: *parser.Anchor, maybe_username: ?[]const u8, page: *Page) !void {
+ if (maybe_username) |username| {
+ const u = try getURL(self, page);
+ try u.setUsername(username);
+ try parser.anchorSetHref(self, u.getHref());
}
}
pub fn get_password(self: *parser.Anchor, page: *Page) ![]const u8 {
- var u = try url(self, page);
- return u.get_password();
+ const u = try getURL(self, page);
+ return u.getPassword() orelse "";
}
- pub fn set_password(self: *parser.Anchor, v: ?[]const u8, page: *Page) !void {
- if (v) |password| {
- var u = try url(self, page);
- u.set_password(password);
-
- const href = try u.get_href(page);
- try parser.anchorSetHref(self, href);
+ pub fn set_password(self: *parser.Anchor, maybe_password: ?[]const u8, page: *Page) !void {
+ if (maybe_password) |password| {
+ const u = try getURL(self, page);
+ try u.setPassword(password);
+ try parser.anchorSetHref(self, u.getHref());
}
}
// TODO return a disposable string
pub fn get_pathname(self: *parser.Anchor, page: *Page) ![]const u8 {
- var u = try url(self, page);
- return u.get_pathname();
+ const u = try getURL(self, page);
+ return u.getPath();
}
- pub fn set_pathname(self: *parser.Anchor, v: []const u8, page: *Page) !void {
- var u = try url(self, page);
- u.set_pathname(v);
- const href = try u.get_href(page);
- try parser.anchorSetHref(self, href);
+ pub fn set_pathname(self: *parser.Anchor, pathname: []const u8, page: *Page) !void {
+ const u = try getURL(self, page);
+ try u.setPath(pathname);
+ return parser.anchorSetHref(self, u.getHref());
}
pub fn get_search(self: *parser.Anchor, page: *Page) ![]const u8 {
- var u = try url(self, page);
- return u.get_search(page);
+ const u = try getURL(self, page);
+ return u.getSearch() orelse "";
}
- pub fn set_search(self: *parser.Anchor, v: []const u8, page: *Page) !void {
- var u = try url(self, page);
- try u.set_search(v, page);
-
- const href = try u.get_href(page);
- try parser.anchorSetHref(self, href);
+ pub fn set_search(self: *parser.Anchor, search: []const u8, page: *Page) !void {
+ const u = try getURL(self, page);
+ u.setSearch(search);
+ return parser.anchorSetHref(self, u.getHref());
}
// TODO return a disposable string
pub fn get_hash(self: *parser.Anchor, page: *Page) ![]const u8 {
- var u = try url(self, page);
- return u.get_hash();
+ const u = try getURL(self, page);
+ return u.getHash() orelse "";
}
- pub fn set_hash(self: *parser.Anchor, v: []const u8, page: *Page) !void {
- var u = try url(self, page);
- u.set_hash(v);
- const href = try u.get_href(page);
- try parser.anchorSetHref(self, href);
+ pub fn set_hash(self: *parser.Anchor, hash: []const u8, page: *Page) !void {
+ const u = try getURL(self, page);
+ u.setHash(hash);
+ return parser.anchorSetHref(self, u.getHref());
}
};
diff --git a/src/browser/page.zig b/src/browser/page.zig
index 718ed756..a553ae11 100644
--- a/src/browser/page.zig
+++ b/src/browser/page.zig
@@ -84,6 +84,11 @@ pub const Page = struct {
polyfill_loader: polyfill.Loader = .{},
+ /// KV map for various object data; use pointers as unsigned integer keys
+ /// and store any `*anyopaque` as values. If a key or value will be
+ /// deinitialized (freed), it should be removed from the map too.
+ object_data: ObjectDataMap = .{},
+
scheduler: Scheduler,
http_client: *Http.Client,
script_manager: ScriptManager,
@@ -122,6 +127,21 @@ pub const Page = struct {
complete,
};
+ const ObjectDataMap = std.HashMapUnmanaged(
+ usize,
+ *anyopaque,
+ struct {
+ pub fn hash(_: @This(), key: usize) usize {
+ return key;
+ }
+
+ pub fn eql(_: @This(), a: usize, b: usize) bool {
+ return a == b;
+ }
+ },
+ std.hash_map.default_max_load_percentage,
+ );
+
pub fn init(self: *Page, arena: Allocator, call_arena: Allocator, session: *Session) !void {
const browser = session.browser;
const script_manager = ScriptManager.init(browser, self);
@@ -160,6 +180,7 @@ pub const Page = struct {
self.http_client.abort();
self.script_manager.deinit();
self.url.deinit();
+ self.object_data.deinit(self.arena);
}
fn reset(self: *Page) !void {
@@ -170,6 +191,12 @@ pub const Page = struct {
self.http_client.abort();
self.script_manager.reset();
+ _ = try self.url.reparse("about:blank");
+ errdefer self.url.deinit();
+
+ self.object_data.deinit(self.arena);
+ self.object_data = .{};
+
self.load_state = .parsing;
self.mode = .{ .pre = {} };
_ = self.session.browser.page_arena.reset(.{ .retain_with_limit = 1 * 1024 * 1024 });
@@ -200,6 +227,21 @@ pub const Page = struct {
}.runMessageLoop, 5, .{ .name = "page.messageLoop" });
}
+ /// Returns the object data by given key.
+ /// `key` must be a pointer type.
+ /// Type of value is unknown to map; so the caller must do the type casting.
+ pub fn getObjectData(self: *Page, key: anytype) ?*anyopaque {
+ std.debug.assert(@typeInfo(@TypeOf(key)) == .pointer);
+ return self.object_data.get(@intFromPtr(key));
+ }
+
+ /// Puts the object data by given key.
+ /// `key` must be a pointer type.
+ pub fn putObjectData(self: *Page, key: anytype, value: *anyopaque) Allocator.Error!void {
+ std.debug.assert(@typeInfo(@TypeOf(key)) == .pointer);
+ return self.object_data.put(self.arena, @intFromPtr(key), value);
+ }
+
pub const DumpOpts = struct {
// set to include element shadowroots in the dump
page: ?*const Page = null,
diff --git a/src/url.zig b/src/url.zig
index 42d083ab..8266af1d 100644
--- a/src/url.zig
+++ b/src/url.zig
@@ -44,6 +44,11 @@ pub const URL = struct {
return self;
}
+ /// Forms a `URL` from given `internal`. Memory is not copied.
+ pub fn fromInternal(internal: ada.URL) URL {
+ return .{ .internal = internal };
+ }
+
/// Deinitializes internal url.
pub fn deinit(self: URL) void {
std.debug.assert(self.internal != null);
@@ -100,6 +105,28 @@ pub const URL = struct {
if (!is_set) return error.InvalidHostname;
}
+ pub fn getUsername(self: URL) ?[]const u8 {
+ const username = ada.getUsernameNullable(self.internal);
+ if (username.data == null) return null;
+ return username.data[0..username.length];
+ }
+
+ pub fn setUsername(self: URL, username: []const u8) error{InvalidUsername}!void {
+ const is_set = ada.setUsername(self.internal, username);
+ if (!is_set) return error.InvalidUsername;
+ }
+
+ pub fn getPassword(self: URL) ?[]const u8 {
+ const password = ada.getPasswordNullable(self.internal);
+ if (password.data == null) return null;
+ return password.data[0..password.length];
+ }
+
+ pub fn setPassword(self: URL, password: []const u8) error{InvalidPassword}!void {
+ const is_set = ada.setPassword(self.internal, password);
+ if (!is_set) return error.InvalidPassword;
+ }
+
pub fn getFragment(self: URL) ?[]const u8 {
// Ada calls it "hash" instead of "fragment".
const hash = ada.getHashNullable(self.internal);
@@ -108,6 +135,26 @@ pub const URL = struct {
return hash.data[0..hash.length];
}
+ pub fn getSearch(self: URL) ?[]const u8 {
+ const search = ada.getSearchNullable(self.internal);
+ if (search.data == null) return null;
+ return search.data[0..search.length];
+ }
+
+ pub fn setSearch(self: URL, search: []const u8) void {
+ return ada.setSearch(self.internal, search);
+ }
+
+ pub fn getHash(self: URL) ?[]const u8 {
+ const hash = ada.getHashNullable(self.internal);
+ if (hash.data == null) return null;
+ return hash.data[0..hash.length];
+ }
+
+ pub fn setHash(self: URL, hash: []const u8) void {
+ return ada.setHash(self.internal, hash);
+ }
+
pub fn getProtocol(self: URL) []const u8 {
return ada.getProtocol(self.internal);
}
@@ -135,6 +182,11 @@ pub const URL = struct {
return pathname.data[0..pathname.length];
}
+ pub fn setPath(self: URL, path: []const u8) error{InvalidPath}!void {
+ const is_set = ada.setPathname(self.internal, path);
+ if (!is_set) return error.InvalidPath;
+ }
+
/// Returns true if the URL's protocol is secure.
pub fn isSecure(self: URL) bool {
const scheme = ada.getSchemeType(self.internal);
diff --git a/vendor/ada/root.zig b/vendor/ada/root.zig
index 1af8e823..1a54b40e 100644
--- a/vendor/ada/root.zig
+++ b/vendor/ada/root.zig
@@ -71,12 +71,24 @@ pub inline fn getHrefNullable(url: URL) String {
return c.ada_get_href(url);
}
+pub inline fn getUsernameNullable(url: URL) String {
+ return c.ada_get_username(url);
+}
+
/// Can return an empty string.
pub inline fn getUsername(url: URL) []const u8 {
const username = c.ada_get_username(url);
return username.data[0..username.length];
}
+pub inline fn getPasswordNullable(url: URL) String {
+ return c.ada_get_password(url);
+}
+
+pub inline fn getSearchNullable(url: URL) String {
+ return c.ada_get_search(url);
+}
+
/// Can return an empty string.
pub inline fn getPassword(url: URL) []const u8 {
const password = c.ada_get_password(url);