Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
131 changes: 69 additions & 62 deletions src/browser/html/elements.zig
Original file line number Diff line number Diff line change
Expand Up @@ -194,8 +194,10 @@ pub const HTMLAnchorElement = struct {
return try parser.anchorGetHref(self);
}

pub fn set_href(self: *parser.Anchor, href: []const u8) !void {
return try parser.anchorSetHref(self, href);
pub fn set_href(self: *parser.Anchor, href: []const u8, page: *const Page) !void {
const stitch = @import("../../url.zig").stitch;
const full = try stitch(page.call_arena, href, page.url.raw, .{});
return try parser.anchorSetHref(self, full);
}

pub fn get_hreflang(self: *parser.Anchor) ![]const u8 {
Expand Down Expand Up @@ -289,10 +291,9 @@ pub const HTMLAnchorElement = struct {
try parser.anchorSetHref(self, href);
}

// TODO return a disposable string
pub fn get_hostname(self: *parser.Anchor, page: *Page) ![]const u8 {
var u = try url(self, page);
return try page.arena.dupe(u8, u.get_hostname());
return u.get_hostname();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we could construct the url here with .if_needed, no?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The whole URL constructing per getter is a bit much. I guess it isn't called too often, plus it's maybe just as likely to get mutated.

Everything touching URLs needs a rewrite at some point. It's a bit of a mess, starting with std.Uri.

}

pub fn set_hostname(self: *parser.Anchor, v: []const u8, page: *Page) !void {
Expand Down Expand Up @@ -326,7 +327,7 @@ pub const HTMLAnchorElement = struct {
// TODO return a disposable string
pub fn get_username(self: *parser.Anchor, page: *Page) ![]const u8 {
var u = try url(self, page);
return try page.arena.dupe(u8, u.get_username());
return u.get_username();
}

pub fn set_username(self: *parser.Anchor, v: ?[]const u8, page: *Page) !void {
Expand Down Expand Up @@ -366,7 +367,7 @@ pub const HTMLAnchorElement = struct {
// TODO return a disposable string
pub fn get_pathname(self: *parser.Anchor, page: *Page) ![]const u8 {
var u = try url(self, page);
return try page.arena.dupe(u8, u.get_pathname());
return u.get_pathname();
}

pub fn set_pathname(self: *parser.Anchor, v: []const u8, page: *Page) !void {
Expand Down Expand Up @@ -1056,62 +1057,62 @@ test "Browser.HTML.Element" {
defer runner.deinit();

try runner.testCases(&.{
.{ "let a = document.getElementById('link')", "undefined" },
.{ "a.target", "" },
.{ "a.target = '_blank'", "_blank" },
.{ "a.target", "_blank" },
.{ "a.target = ''", "" },

.{ "a.href", "foo" },
.{ "a.href = 'https://lightpanda.io/'", "https://lightpanda.io/" },
.{ "a.href", "https://lightpanda.io/" },

.{ "a.origin", "https://lightpanda.io" },

.{ "a.host = 'lightpanda.io:443'", "lightpanda.io:443" },
.{ "a.host", "lightpanda.io:443" },
.{ "a.port", "443" },
.{ "a.hostname", "lightpanda.io" },

.{ "a.host = 'lightpanda.io'", "lightpanda.io" },
.{ "a.host", "lightpanda.io" },
.{ "a.port", "" },
.{ "a.hostname", "lightpanda.io" },

.{ "a.host", "lightpanda.io" },
.{ "a.hostname", "lightpanda.io" },
.{ "a.hostname = 'foo.bar'", "foo.bar" },
.{ "a.href", "https://foo.bar/" },

.{ "a.search", "" },
.{ "a.search = 'q=bar'", "q=bar" },
.{ "a.search", "?q=bar" },
.{ "a.href", "https://foo.bar/?q=bar" },

.{ "a.hash", "" },
.{ "a.hash = 'frag'", "frag" },
.{ "a.hash", "#frag" },
.{ "a.href", "https://foo.bar/?q=bar#frag" },

.{ "a.port", "" },
.{ "a.port = '443'", "443" },
.{ "a.host", "foo.bar:443" },
.{ "a.hostname", "foo.bar" },
.{ "a.href", "https://foo.bar:443/?q=bar#frag" },
.{ "a.port = null", "null" },
.{ "a.href", "https://foo.bar/?q=bar#frag" },

.{ "a.href = 'foo'", "foo" },

.{ "a.type", "" },
.{ "a.type = 'text/html'", "text/html" },
.{ "a.type", "text/html" },
.{ "a.type = ''", "" },

.{ "a.text", "OK" },
.{ "a.text = 'foo'", "foo" },
.{ "a.text", "foo" },
.{ "a.text = 'OK'", "OK" },
.{ "let link = document.getElementById('link')", "undefined" },
.{ "link.target", "" },
.{ "link.target = '_blank'", "_blank" },
.{ "link.target", "_blank" },
.{ "link.target = ''", "" },

.{ "link.href", "foo" },
.{ "link.href = 'https://lightpanda.io/'", "https://lightpanda.io/" },
.{ "link.href", "https://lightpanda.io/" },

.{ "link.origin", "https://lightpanda.io" },

.{ "link.host = 'lightpanda.io:443'", "lightpanda.io:443" },
.{ "link.host", "lightpanda.io:443" },
.{ "link.port", "443" },
.{ "link.hostname", "lightpanda.io" },

.{ "link.host = 'lightpanda.io'", "lightpanda.io" },
.{ "link.host", "lightpanda.io" },
.{ "link.port", "" },
.{ "link.hostname", "lightpanda.io" },

.{ "link.host", "lightpanda.io" },
.{ "link.hostname", "lightpanda.io" },
.{ "link.hostname = 'foo.bar'", "foo.bar" },
.{ "link.href", "https://foo.bar/" },

.{ "link.search", "" },
.{ "link.search = 'q=bar'", "q=bar" },
.{ "link.search", "?q=bar" },
.{ "link.href", "https://foo.bar/?q=bar" },

.{ "link.hash", "" },
.{ "link.hash = 'frag'", "frag" },
.{ "link.hash", "#frag" },
.{ "link.href", "https://foo.bar/?q=bar#frag" },

.{ "link.port", "" },
.{ "link.port = '443'", "443" },
.{ "link.host", "foo.bar:443" },
.{ "link.hostname", "foo.bar" },
.{ "link.href", "https://foo.bar:443/?q=bar#frag" },
.{ "link.port = null", "null" },
.{ "link.href", "https://foo.bar/?q=bar#frag" },

.{ "link.href = 'foo'", "foo" },

.{ "link.type", "" },
.{ "link.type = 'text/html'", "text/html" },
.{ "link.type", "text/html" },
.{ "link.type = ''", "" },

.{ "link.text", "OK" },
.{ "link.text = 'foo'", "foo" },
.{ "link.text", "foo" },
.{ "link.text = 'OK'", "OK" },
}, .{});

try runner.testCases(&.{
Expand Down Expand Up @@ -1174,4 +1175,10 @@ test "Browser.HTML.Element" {
.{ "lyric.src = 15", "15" },
.{ "lyric.src", "15" },
}, .{});

try runner.testCases(&.{
.{ "let a = document.createElement('a');", null },
.{ "a.href = 'about'", null },
.{ "a.href", "https://lightpanda.io/opensource-browser/about" },
}, .{});
}
52 changes: 48 additions & 4 deletions src/url.zig
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ const Uri = std.Uri;
const Allocator = std.mem.Allocator;
const WebApiURL = @import("browser/url/url.zig").URL;

pub const stitch = URL.stitch;

pub const URL = struct {
uri: Uri,
raw: []const u8,
Expand Down Expand Up @@ -91,6 +93,7 @@ pub const URL = struct {
if_needed,
};
};

/// Properly stitches two URL fragments together.
///
/// For URLs with a path, it will replace the last entry with the src.
Expand All @@ -101,7 +104,7 @@ pub const URL = struct {
base: []const u8,
opts: StitchOpts,
) ![]const u8 {
if (base.len == 0) {
if (base.len == 0 or isURL(src)) {
if (opts.alloc == .always) {
return allocator.dupe(u8, src);
}
Expand Down Expand Up @@ -154,7 +157,41 @@ pub const URL = struct {
}
};

test "Url resolve size" {
fn isURL(url: []const u8) bool {
if (std.mem.startsWith(u8, url, "://")) {
return true;
}

if (url.len < 8) {
return false;
}

if (!std.ascii.startsWithIgnoreCase(url, "http")) {
return false;
}

var pos: usize = 4;
if (url[4] == 's' or url[4] == 'S') {
pos = 5;
}
return std.mem.startsWith(u8, url[pos..], "://");
}

const testing = @import("testing.zig");
test "URL: isURL" {
try testing.expectEqual(true, isURL("://lightpanda.io"));
try testing.expectEqual(true, isURL("://lightpanda.io/about"));
try testing.expectEqual(true, isURL("http://lightpanda.io/about"));
try testing.expectEqual(true, isURL("HttP://lightpanda.io/about"));
try testing.expectEqual(true, isURL("httpS://lightpanda.io/about"));
try testing.expectEqual(true, isURL("HTTPs://lightpanda.io/about"));

try testing.expectEqual(false, isURL("/lightpanda.io"));
try testing.expectEqual(false, isURL("../../about"));
try testing.expectEqual(false, isURL("about"));
}

test "URL: resolve size" {
const base = "https://www.lightpande.io";
const url = try URL.parse(base, null);

Expand All @@ -170,8 +207,6 @@ test "Url resolve size" {
try std.testing.expectEqualStrings(out_url.raw[26..], &url_string);
}

const testing = @import("testing.zig");

test "URL: Stitching Base & Src URLs (Basic)" {
const allocator = testing.allocator;

Expand Down Expand Up @@ -212,6 +247,15 @@ test "URL: Stiching Base & Src URLs (Both Local)" {
try testing.expectString("./abcdef/something.js", result);
}

test "URL: Stiching src as full path" {
const allocator = testing.allocator;

const base = "https://www.lightpanda.io/";
const src = "https://lightpanda.io/something.js";
const result = try URL.stitch(allocator, src, base, .{});
try testing.expectString("https://lightpanda.io/something.js", result);
}

test "URL: concatQueryString" {
defer testing.reset();
const arena = testing.arena_allocator;
Expand Down