feat(cdp): implement Network.setBlockedURLs via urlPatterns

Reimplement request blocking on the non-deprecated urlPatterns shape:
each pattern carries an explicit block/allow flag (first match wins),
UrlBlocklist owns the compiled patterns plus their block flags, and the
legacy setBlockedUrls path stays for back-compat. Tests updated.
This commit is contained in:
Matt Van Horn
2026-07-22 17:49:01 -07:00
committed by Pierre Tachoire
parent 311ab3aaae
commit dee40075a0
3 changed files with 40 additions and 7 deletions

View File

@@ -103,11 +103,11 @@ fn setCacheDisabled(cmd: *CDP.Command) !void {
fn setBlockedURLs(cmd: *CDP.Command) !void {
const params = (try cmd.params(struct {
urls: []const []const u8,
urlPatterns: []const HttpClient.BlockPattern,
})) orelse return error.InvalidParams;
const bc = cmd.browser_context orelse return error.BrowserContextNotLoaded;
try bc.cdp.browser.http_client.setBlockedUrls(params.urls);
try bc.cdp.browser.http_client.setBlockedUrlPatterns(params.urlPatterns);
return cmd.sendResult(null, .{});
}
@@ -1018,7 +1018,9 @@ test "cdp.Network: setBlockedURLs blocks requests with inspector reason" {
try ctx.processMessage(.{
.id = 2,
.method = "Network.setBlockedURLs",
.params = .{ .urls = &[_][]const u8{"*://blocked.test/*"} },
.params = .{ .urlPatterns = &[_]HttpClient.BlockPattern{
.{ .urlPattern = "*://blocked.test/*", .block = true },
} },
});
try ctx.expectSentResult(null, .{ .id = 2 });

View File

@@ -36,6 +36,7 @@ const Network = @import("Network.zig");
const Cache = @import("cache/Cache.zig");
const RobotsGate = @import("RobotsGate.zig");
const UrlBlocklist = @import("UrlBlocklist.zig");
pub const BlockPattern = UrlBlocklist.Pattern;
const log = lp.log;
const Allocator = std.mem.Allocator;
@@ -366,6 +367,16 @@ pub fn setBlockedUrls(self: *Client, patterns: []const []const u8) !void {
self.url_blocklist = replacement;
}
pub fn setBlockedUrlPatterns(self: *Client, patterns: []const BlockPattern) !void {
const replacement: ?UrlBlocklist = if (patterns.len == 0)
null
else
try UrlBlocklist.initPatterns(self.allocator, patterns);
self.clearUrlBlocklist();
self.url_blocklist = replacement;
}
fn clearUrlBlocklist(self: *Client) void {
if (self.url_blocklist) |*blocklist| {
blocklist.deinit();

View File

@@ -19,6 +19,12 @@ const UrlBlocklist = @This();
allocator: std.mem.Allocator,
patterns: []const []const u8,
blocks: ?[]const bool = null,
pub const Pattern = struct {
urlPattern: []const u8,
block: bool,
};
/// Compile and own a set of URL wildcard patterns. Patterns are normalized to
/// lowercase once so request matching remains allocation-free.
@@ -45,14 +51,28 @@ pub fn init(allocator: std.mem.Allocator, patterns: []const []const u8) !UrlBloc
};
}
pub fn initPatterns(allocator: std.mem.Allocator, patterns: []const Pattern) !UrlBlocklist {
const urls = try allocator.alloc([]const u8, patterns.len);
defer allocator.free(urls);
for (patterns, urls) |pattern, *url| url.* = pattern.urlPattern;
var blocklist = try init(allocator, urls);
errdefer blocklist.deinit();
const blocks = try allocator.alloc(bool, patterns.len);
for (patterns, blocks) |pattern, *block| block.* = pattern.block;
blocklist.blocks = blocks;
return blocklist;
}
pub fn deinit(self: *UrlBlocklist) void {
for (self.patterns) |pattern| self.allocator.free(pattern);
self.allocator.free(self.patterns);
if (self.blocks) |blocks| self.allocator.free(blocks);
}
pub fn isBlocked(self: *const UrlBlocklist, url: []const u8) bool {
for (self.patterns) |pattern| {
if (wildcardMatch(pattern, url)) return true;
for (self.patterns, 0..) |pattern, index| {
if (wildcardMatch(pattern, url)) return if (self.blocks) |blocks| blocks[index] else true;
}
return false;
}
@@ -133,9 +153,9 @@ test "UrlBlocklist: wildcard matching backtracks and handles empty patterns" {
test "UrlBlocklist: owns compiled patterns" {
var pattern = [_]u8{ '*', 'a', 'd', 's', '*' };
var blocklist = try UrlBlocklist.init(std.testing.allocator, &.{&pattern});
var blocklist = try UrlBlocklist.initPatterns(std.testing.allocator, &.{ .{ .urlPattern = &pattern, .block = false }, .{ .urlPattern = "*", .block = true } });
defer blocklist.deinit();
@memset(&pattern, 'x');
try std.testing.expect(blocklist.isBlocked("https://example.com/ads/script.js"));
try std.testing.expect(!blocklist.isBlocked("https://example.com/ads/script.js"));
}