mirror of
https://github.com/lightpanda-io/browser.git
synced 2025-10-28 14:43:28 +00:00
refactor HTMLAnchorElement
Prefer new URL implementation with separate store for object data.
This commit is contained in:
@@ -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());
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -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,
|
||||
|
||||
52
src/url.zig
52
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);
|
||||
|
||||
12
vendor/ada/root.zig
vendored
12
vendor/ada/root.zig
vendored
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user