mirror of
https://github.com/lightpanda-io/browser.git
synced 2026-09-23 21:15:31 -04:00
Handle immediate meta refresh navigation
Assisted-By: devx/cb5edaca-b259-4956-a65f-f2168ea91830
This commit is contained in:
1 parent
0590bd0f80
commit
6ea37f4dcc
4 files changed
+98
-7
No files matched your search
@@ -3151,6 +3151,12 @@ fn nodeIsReady(self: *Frame, comptime from_parser: bool, node: *Node) !void {
|
||||
log.err(.frame, "frame.nodeIsReady", .{ .err = err, .element = "iframe", .type = frame._type, .url = frame.url });
|
||||
return err;
|
||||
};
|
||||
} else if (node.is(Element.Html.Meta)) |meta| {
|
||||
const frame = if (comptime from_parser) self else node.ownerFrame(self);
|
||||
meta.processRefresh(frame) catch |err| {
|
||||
log.err(.frame, "frame.nodeIsReady", .{ .err = err, .element = "meta", .type = frame._type, .url = frame.url });
|
||||
return err;
|
||||
};
|
||||
} else if (node.is(Element.Html.Link)) |link| {
|
||||
const frame = if (comptime from_parser) self else node.ownerFrame(self);
|
||||
link.linkAddedCallback(frame) catch |err| {
|
||||
@@ -3728,6 +3734,30 @@ test "Page: isSameOrigin" {
|
||||
try testing.expectEqual(false, frame.isSameOrigin("//origin.com/foo"));
|
||||
}
|
||||
|
||||
test "Frame: static immediate meta refresh navigates" {
|
||||
const page = try testing.pageTest("fixtures/meta_refresh.html", .{});
|
||||
defer page.close();
|
||||
|
||||
try testing.expect(std.mem.endsWith(u8, page.frame().?.url, "/fixtures/meta_refresh_target.html"));
|
||||
}
|
||||
|
||||
test "Frame: dynamic immediate meta refresh schedules navigation" {
|
||||
const frame = try testing.createFrame();
|
||||
defer testing.test_session.closeAllPages();
|
||||
frame.url = "https://example.com/source";
|
||||
|
||||
const html = try frame.document.createElement("html", null, frame);
|
||||
_ = try frame.document.asNode().appendChild(html.asNode(), frame);
|
||||
const head = try frame.document.createElement("head", null, frame);
|
||||
_ = try html.asNode().appendChild(head.asNode(), frame);
|
||||
const meta = try frame.document.createElement("meta", null, frame);
|
||||
try meta.setAttribute(comptime .wrap("http-equiv"), comptime .wrap("refresh"), frame);
|
||||
try meta.setAttribute(comptime .wrap("content"), comptime .wrap("0; /target"), frame);
|
||||
_ = try head.asNode().appendChild(meta.asNode(), frame);
|
||||
|
||||
try testing.expectString("https://example.com/target", frame._queued_navigation.?.url);
|
||||
}
|
||||
|
||||
test "Frame: httpMetadata after navigation" {
|
||||
testing.expectLog(&.{.http});
|
||||
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
<!doctype html>
|
||||
<meta http-equiv="refresh" content="0; meta_refresh_target.html">
|
||||
<title>source</title>
|
||||
@@ -0,0 +1,3 @@
|
||||
<!doctype html>
|
||||
<title>target</title>
|
||||
<h1>target</h1>
|
||||
@@ -27,6 +27,7 @@ const Element = @import("../../Element.zig");
|
||||
const HtmlElement = @import("../Html.zig");
|
||||
const referrer = @import("../../../referrer.zig");
|
||||
|
||||
const String = lp.String;
|
||||
const Meta = @This();
|
||||
|
||||
pub const Proto = HtmlElement;
|
||||
@@ -84,19 +85,64 @@ pub fn setScheme(self: *Meta, value: []const u8, frame: *Frame) !void {
|
||||
try self.asElement().setAttributeSafe(comptime .wrap("scheme"), .wrap(value), frame);
|
||||
}
|
||||
|
||||
pub fn processRefresh(self: *Meta, frame: *Frame) !void {
|
||||
if (!self.asNode().isConnected()) return;
|
||||
try self.scheduleRefresh(frame);
|
||||
}
|
||||
|
||||
fn scheduleRefresh(self: *Meta, frame: *Frame) !void {
|
||||
if (!std.ascii.eqlIgnoreCase(self.getHttpEquiv(), "refresh")) return;
|
||||
|
||||
const target = immediateRefreshTarget(self.getContent()) orelse return;
|
||||
try frame.scheduleNavigation(target, .{
|
||||
.reason = .script,
|
||||
.kind = .{ .replace = null },
|
||||
}, .{ .script = frame });
|
||||
}
|
||||
|
||||
fn immediateRefreshTarget(content: []const u8) ?[]const u8 {
|
||||
const trimmed = std.mem.trim(u8, content, &std.ascii.whitespace);
|
||||
const separator = std.mem.indexOfAny(u8, trimmed, ";,") orelse return null;
|
||||
const delay = std.mem.trim(u8, trimmed[0..separator], &std.ascii.whitespace);
|
||||
const seconds = std.fmt.parseFloat(f64, delay) catch return null;
|
||||
if (!std.math.isFinite(seconds) or seconds != 0) return null;
|
||||
|
||||
var target = std.mem.trim(u8, trimmed[separator + 1 ..], &std.ascii.whitespace);
|
||||
if (target.len >= 3 and std.ascii.eqlIgnoreCase(target[0..3], "url")) {
|
||||
target = std.mem.trimStart(u8, target[3..], &std.ascii.whitespace);
|
||||
if (target.len == 0 or target[0] != '=') return null;
|
||||
target = std.mem.trim(u8, target[1..], &std.ascii.whitespace);
|
||||
}
|
||||
if (target.len >= 2 and ((target[0] == '"' and target[target.len - 1] == '"') or (target[0] == '\'' and target[target.len - 1] == '\''))) {
|
||||
target = std.mem.trim(u8, target[1 .. target.len - 1], &std.ascii.whitespace);
|
||||
}
|
||||
return if (target.len == 0) null else target;
|
||||
}
|
||||
|
||||
pub const Build = struct {
|
||||
// <meta name=referrer> sets the document's referrer policy.
|
||||
pub fn created(node: *Node, frame: *Frame) !void {
|
||||
const el = node.as(Element);
|
||||
const name = el.getAttributeSafe(comptime .wrap("name")) orelse return;
|
||||
if (std.ascii.eqlIgnoreCase(name, "referrer") == false) {
|
||||
return;
|
||||
const self = node.as(Meta);
|
||||
const el = self.asElement();
|
||||
if (el.getAttributeSafe(comptime .wrap("name"))) |name| {
|
||||
if (std.ascii.eqlIgnoreCase(name, "referrer")) {
|
||||
if (el.getAttributeSafe(comptime .wrap("content"))) |content| {
|
||||
if (referrer.parseMeta(content)) |rp| {
|
||||
frame.referrer_policy = rp;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
const content = el.getAttributeSafe(comptime .wrap("content")) orelse return;
|
||||
if (referrer.parseMeta(content)) |rp| {
|
||||
frame.referrer_policy = rp;
|
||||
|
||||
if (frame._parse_mode != .fragment) {
|
||||
try self.scheduleRefresh(frame);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn attributeChange(element: *Element, name: String, _: String, frame: *Frame) !void {
|
||||
if (!name.eql(comptime .wrap("http-equiv")) and !name.eql(comptime .wrap("content"))) return;
|
||||
try element.as(Meta).processRefresh(frame);
|
||||
}
|
||||
};
|
||||
|
||||
pub const JsApi = struct {
|
||||
@@ -114,3 +160,12 @@ pub const JsApi = struct {
|
||||
pub const media = bridge.accessor(MetaElement.getMedia, MetaElement.setMedia, .{ .ce_reactions = true });
|
||||
pub const scheme = bridge.accessor(MetaElement.getScheme, MetaElement.setScheme, .{ .ce_reactions = true });
|
||||
};
|
||||
|
||||
const testing = @import("../../../../testing.zig");
|
||||
test "WebApi: Meta immediate refresh target" {
|
||||
try testing.expectString("/target", immediateRefreshTarget("0; /target").?);
|
||||
try testing.expectString("/target", immediateRefreshTarget("0, URL = '/target'").?);
|
||||
try testing.expectString("https://example.com/", immediateRefreshTarget("0.0; https://example.com/").?);
|
||||
try testing.expectEqual(null, immediateRefreshTarget("1; /target"));
|
||||
try testing.expectEqual(null, immediateRefreshTarget("invalid; /target"));
|
||||
}
|
||||
Reference in new issue
Block a user