0

SpeculationRules: Dedup and sort tags passed to SpeculationRulesTags

This CL dedups and sorts given speculation rules tags by using std::set.
Also, this CL adds unit tests for the class.

Bug: 381687257
Change-Id: Ibbe4f4b5232ba2674bf46b362662f62b333869a7
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/6399977
Commit-Queue: Hiroki Nakagawa <nhiroki@chromium.org>
Reviewed-by: Huanpo Lin <robertlin@chromium.org>
Reviewed-by: Taiyo Mizuhashi <taiyo@chromium.org>
Cr-Commit-Position: refs/heads/main@{#1439351}
This commit is contained in:
Hiroki Nakagawa
2025-03-28 06:22:58 -07:00
committed by Chromium LUCI CQ
parent c033d20c2d
commit 105595025a
4 changed files with 73 additions and 3 deletions

@ -11,8 +11,11 @@ namespace content {
SpeculationRulesTags::SpeculationRulesTags() = default;
SpeculationRulesTags::SpeculationRulesTags(
std::vector<std::optional<std::string>> tags)
: tags_(std::move(tags)) {}
std::vector<std::optional<std::string>> tags) {
for (auto& tag : tags) {
tags_.insert(std::move(tag));
}
}
SpeculationRulesTags::~SpeculationRulesTags() = default;

@ -5,6 +5,7 @@
#ifndef CONTENT_BROWSER_PRELOADING_SPECULATION_RULES_SPECULATION_RULES_TAGS_H_
#define CONTENT_BROWSER_PRELOADING_SPECULATION_RULES_SPECULATION_RULES_TAGS_H_
#include <set>
#include <string>
#include <vector>
@ -19,6 +20,7 @@ namespace content {
class CONTENT_EXPORT SpeculationRulesTags {
public:
SpeculationRulesTags();
// TODO(crbug.com/381687257): Use std::set instead of std::vector.
explicit SpeculationRulesTags(std::vector<std::optional<std::string>> tags);
~SpeculationRulesTags();
@ -33,7 +35,7 @@ class CONTENT_EXPORT SpeculationRulesTags {
private:
net::structured_headers::List ConvertStringToStructuredHeader();
std::vector<std::optional<std::string>> tags_;
std::set<std::optional<std::string>> tags_;
};
} // namespace content

@ -0,0 +1,64 @@
// Copyright 2025 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "content/browser/preloading/speculation_rules/speculation_rules_tags.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace content {
namespace {
using Tags = std::vector<std::optional<std::string>>;
void TestTags(const Tags& tags, const std::string& expected) {
EXPECT_EQ(expected, SpeculationRulesTags(tags).ConvertStringToHeaderString());
}
// Each string tag should be wrapped with double quotes during parsing.
// https://www.rfc-editor.org/rfc/rfc8941.html#name-serializing-a-string
TEST(SpeculationRulesTagsTest, Basic) {
TestTags({R"(my-rules)"}, R"("my-rules")");
TestTags({R"(my-rules1)", R"(my-rules2)"}, R"("my-rules1", "my-rules2")");
}
// No tags should be parsed as null token.
// https://www.rfc-editor.org/rfc/rfc8941.html#section-4.1.7
TEST(SpeculationRulesTagsTest, NoTags) {
TestTags({std::nullopt}, R"(null)");
TestTags({std::nullopt, R"(my-rules)"}, R"(null, "my-rules")");
TestTags({std::nullopt, R"(my-rules)", R"(null)"},
R"(null, "my-rules", "null")");
}
// A double quote (DQUOTE) should be converted to \" during parsing:
// https://www.rfc-editor.org/rfc/rfc8941.html#name-serializing-a-string
TEST(SpeculationRulesTagsTest, DoubleQuote) {
TestTags({R"(")"}, R"("\"")");
TestTags({R"("")"}, R"("\"\"")");
TestTags({R"(my"rules)"}, R"("my\"rules")");
TestTags({R"(my-rules)", R"(")"}, R"("\"", "my-rules")");
}
// A backslash should be converted to \\ during parsing:
// https://www.rfc-editor.org/rfc/rfc8941.html#name-serializing-a-string
TEST(SpeculationRulesTagsTest, BackSlash) {
TestTags({R"(\)"}, R"("\\")");
TestTags({R"(\\)"}, R"("\\\\")");
TestTags({R"(my\rules)"}, R"("my\\rules")");
TestTags({R"(my-rules)", R"(\)"}, R"("\\", "my-rules")");
}
TEST(SpeculationRulesTagsTest, Duplicate) {
TestTags({std::nullopt, R"(my-rules)", R"(my-rules)"}, R"(null, "my-rules")");
TestTags({std::nullopt, std::nullopt, R"(my-rules)"}, R"(null, "my-rules")");
}
TEST(SpeculationRulesTagsTest, Sort) {
TestTags({R"(def)", R"(jkl)", R"(def)", R"(null)", std::nullopt, R"(abc)",
std::nullopt, R"(ghi)"},
R"(null, "abc", "def", "ghi", "jkl", "null")");
}
} // namespace
} // namespace content

@ -2698,6 +2698,7 @@ test("content_unittests") {
"../browser/preloading/prerender/prerender_metrics_unittest.cc",
"../browser/preloading/prerenderer_impl_unittest.cc",
"../browser/preloading/speculation_rules/speculation_host_impl_unittest.cc",
"../browser/preloading/speculation_rules/speculation_rules_tags_unittest.cc",
"../browser/presentation/presentation_service_impl_unittest.cc",
"../browser/private_aggregation/private_aggregation_budget_key_unittest.cc",
"../browser/private_aggregation/private_aggregation_budget_storage_unittest.cc",