Skip to content
Open
Show file tree
Hide file tree
Changes from 13 commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
bee4106
Initial PoC windows brand hints override
laghee Oct 14, 2025
b1a0458
Clean up extraneous quick test and comments
laghee Oct 14, 2025
9592f42
Mirror native approach for headers in js api override
laghee Oct 21, 2025
dbdebd9
Fix errant space
laghee Oct 21, 2025
d142735
Return early if brands config is missing or malformed
laghee Nov 2, 2025
cc7a316
Rename feature more generally
laghee Nov 2, 2025
3ce98f8
Merge remote-tracking branch 'origin/main' into km/add-ch-brand-shim
laghee Nov 2, 2025
4bc32a4
Clean up unnecessary bits, fix auto-complaints
laghee Nov 3, 2025
0bf2a5f
Merge remote-tracking branch 'origin/main' into km/add-ch-brand-shim
laghee Nov 3, 2025
271e18d
Fix missing GREASE value
laghee Nov 3, 2025
1125d9d
Lint
laghee Nov 3, 2025
c6b0a1f
Add debug logging
laghee Nov 21, 2025
bbc26a4
Add different logging attempt
laghee Nov 21, 2025
56e2faa
Force debug to surface logging
laghee Nov 21, 2025
308501b
Force debug logging in content-feature
laghee Nov 21, 2025
8aa428c
LOG, DAMMIT
laghee Nov 21, 2025
0ef8669
Figure out why not running
laghee Nov 21, 2025
ec67adf
Switch order to match header indices + clean up debug logging
laghee Nov 21, 2025
129f447
Address bot comment
laghee Nov 21, 2025
152f3d5
Merge branch 'origin/main' into km/add-ch-brand-shim
laghee Nov 21, 2025
ae9abf9
Clean up old cached code
laghee Nov 24, 2025
2f535c0
Simplify approach by replicating native: overwrite Edge, zap WebView2…
laghee Nov 24, 2025
941714d
Overwrite fullVersionList brands as well
laghee Nov 24, 2025
deb3631
Address cursorbot comment on not directly modifying browser objects
laghee Nov 24, 2025
2fefa76
Make test names clearer
laghee Nov 24, 2025
c90d720
Merge 'origin/main' into km/add-ch-brand-shim
laghee Nov 24, 2025
537a8d5
Address further cranky bot comment
laghee Nov 24, 2025
94796ff
Botttttt
laghee Nov 24, 2025
9e4d0a6
Dimishing returns critical mass
laghee Nov 24, 2025
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
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
{
"readme": "This config tests that uaChBrands can override navigator.userAgentData.brands",
"version": 1,
"unprotectedTemporary": [],
"features": {
"uaChBrands": {
"state": "enabled",
"exceptions": [],
"settings": {
"brands": [
{
"brand": "Chromium",
"version": "140"
},
{
"brand": "DuckDuckGo",
"version": "140"
}
]
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
{
"readme": "This config tests that uaChBrands feature returns early when brands setting is missing (feature is enabled but settings.brands is undefined)",
"version": 1,
"unprotectedTemporary": [],
"features": {
"uaChBrands": {
"state": "enabled",
"exceptions": [],
"settings": {
}
}
}
}
18 changes: 18 additions & 0 deletions injected/integration-test/test-pages/ua-ch-brands/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width">
<title>UA CH Brands</title>
<link rel="stylesheet" href="../shared/style.css">
</head>
<body>
<p><a href="../index.html">[Home]</a></p>

<p>UA CH Brands</p>
<ul>
<li><a href="./pages/brand-override.html">Brand Override</a> - <a href="./config/brand-override.json">Config</a></li>
<li><a href="./pages/brands-missing.html">Brands Missing</a> - <a href="./config/brands-missing.json">Config</a></li>
</ul>
</body>
</html>
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width">
<title>UA CH Brands - Brand Override</title>
<link rel="stylesheet" href="../../shared/style.css">
</head>
<body>
<script src="../../shared/utils.js"></script>
<p><a href="../index.html">[UA CH Brands]</a></p>

<p>This page verifies that navigator.userAgentData.brands uses the configured brands from the uaChBrands feature settings</p>

<script>
test('Brand override', async () => {
const results = [];

if (navigator.userAgentData && navigator.userAgentData.brands) {
const brands = navigator.userAgentData.brands;
const brandNames = new Set(brands.map(b => b.brand));

const hasGrease = brands.some(b => {
const name = b.brand;
return name.trim().startsWith('Not') || /[^\w\s.]/.test(name);
});

results.push({
name: 'contains Chromium brand',
result: brandNames.has('Chromium'),
expected: true
});

results.push({
name: 'contains DuckDuckGo brand',
result: brandNames.has('DuckDuckGo'),
expected: true
});

// Expect 3 brands if GREASE present, 2 if not
const expectedBrandCount = hasGrease ? 3 : 2;
results.push({
name: `has correct number of brands (${hasGrease ? 'with GREASE' : 'without GREASE'})`,
result: brands.length,
expected: expectedBrandCount
});

if (hasGrease) {
results.push({
name: 'GREASE value is preserved',
result: hasGrease,
expected: true
});
}

results.push({
name: 'does not contain Microsoft Edge brand',
result: !brandNames.has('Microsoft Edge'),
expected: true
});

results.push({
name: 'does not contain Microsoft Edge WebView2 brand',
result: !brandNames.has('Microsoft Edge WebView2'),
expected: true
});

if (navigator.userAgentData.getHighEntropyValues) {
try {
const highEntropyValues = await navigator.userAgentData.getHighEntropyValues(['brands']);
if (highEntropyValues.brands) {
const heBrands = highEntropyValues.brands;
const heBrandNames = new Set(heBrands.map(b => b.brand));

const heHasGrease = heBrands.some(b => {
const name = b.brand;
return name.trim().startsWith('Not') || /[^\w\s.]/.test(name);
});

results.push({
name: 'high entropy contains Chromium',
result: heBrandNames.has('Chromium'),
expected: true
});
results.push({
name: 'high entropy contains DuckDuckGo',
result: heBrandNames.has('DuckDuckGo'),
expected: true
});

if (heHasGrease) {
results.push({
name: 'high entropy GREASE value is preserved',
result: heHasGrease,
expected: true
});
}

results.push({
name: 'high entropy does not contain Microsoft Edge',
result: !heBrandNames.has('Microsoft Edge'),
expected: true
});

results.push({
name: 'high entropy does not contain Microsoft Edge WebView2',
result: !heBrandNames.has('Microsoft Edge WebView2'),
expected: true
});
}
} catch (error) {
results.push({
name: 'getHighEntropyValues works',
result: false,
expected: true
});
}
}
} else {
results.push({
name: 'navigator.userAgentData.brands available',
result: false,
expected: true
});
}

return results;
});

renderResults();
</script>
</body>
</html>
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width">
<title>UA CH Brands - Brands Missing</title>
<link rel="stylesheet" href="../../shared/style.css">
</head>
<body>
<script src="../../shared/utils.js"></script>
<p><a href="../index.html">[UA CH Brands]</a></p>

<p>This page verifies that when brands setting is missing/null/empty, the feature returns early and original brands are preserved</p>

<script>
test('Feature returns early when brands missing', async () => {
const results = [];

if (navigator.userAgentData && navigator.userAgentData.brands) {
// When brands setting is missing, feature returns early in init()

const brands = navigator.userAgentData.brands;

// Verify brands still works normally
results.push({
name: 'brands array exists and has content',
result: Array.isArray(brands) && brands.length > 0,
expected: true
});

// Verify getHighEntropyValues also works and returns matching values
if (navigator.userAgentData.getHighEntropyValues) {
try {
const highEntropyValues = await navigator.userAgentData.getHighEntropyValues(['brands']);
const heBrands = highEntropyValues.brands;

if (heBrands) {
const brandsStr = JSON.stringify(brands);
const heBrandsStr = JSON.stringify(heBrands);

results.push({
name: 'getHighEntropyValues and direct access return matching values',
result: brandsStr === heBrandsStr,
expected: true
});
}
} catch (error) {
results.push({
name: 'getHighEntropyValues works',
result: false,
expected: true
});
}
}
} else {
results.push({
name: 'navigator.userAgentData.brands available',
result: false,
expected: true
});
}

return results;
});

renderResults();
</script>
</body>
</html>
36 changes: 36 additions & 0 deletions injected/integration-test/ua-ch-brands.spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import { test, expect } from '@playwright/test';
import { ResultsCollector } from './page-objects/results-collector.js';

test('UA CH Brands override', async ({ page }, testInfo) => {
const collector = ResultsCollector.create(page, testInfo.project.use);
await collector.load(
'/ua-ch-brands/pages/brand-override.html',
'./integration-test/test-pages/ua-ch-brands/config/brand-override.json',
);
const results = await collector.results();

for (const key in results) {
for (const result of results[key]) {
await test.step(`${key}: ${result.name}`, () => {
expect(result.result).toEqual(result.expected);
});
}
}
});

test('UA CH Brands when brands missing', async ({ page }, testInfo) => {
const collector = ResultsCollector.create(page, testInfo.project.use);
await collector.load(
'/ua-ch-brands/pages/brands-missing.html',
'./integration-test/test-pages/ua-ch-brands/config/brands-missing.json',
);
const results = await collector.results();

for (const key in results) {
for (const result of results[key]) {
await test.step(`${key}: ${result.name}`, () => {
expect(result.result).toEqual(result.expected);
});
}
}
});
1 change: 1 addition & 0 deletions injected/playwright.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ export default defineConfig({
'integration-test/duckplayer-remote-config.spec.js',
'integration-test/harmful-apis.spec.js',
'integration-test/windows-permissions.spec.js',
'integration-test/ua-ch-brands.spec.js',
'integration-test/broker-protection-tests/**/*.spec.js',
'integration-test/breakage-reporting.spec.js',
'integration-test/duck-ai-data-clearing.spec.js',
Expand Down
2 changes: 2 additions & 0 deletions injected/src/features.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ const otherFeatures = /** @type {const} */ ([
'harmfulApis',
'webCompat',
'windowsPermissionUsage',
'uaChBrands',
'brokerProtection',
'performanceMetrics',
'breakageReporting',
Expand Down Expand Up @@ -68,6 +69,7 @@ export const platformSupport = {
...baseFeatures,
'webTelemetry',
'windowsPermissionUsage',
'uaChBrands',
'duckPlayer',
'brokerProtection',
'breakageReporting',
Expand Down
Loading
Loading