Skip to content

Commit d0d41e8

Browse files
masnobleDevtools-frontend LUCI CQ
authored andcommitted
Add Cookie Controls tool view
This tool is not yet functional. Work to come: 1) Show enterprise UI: https://crrev.com/c/6033409 2) Create and use a new CDP method to send controls to chromium This work will come in future CLs https://screenshot.googleplex.com/8ZcUUWhgWYKdzeN.png Bug: 375352611 Change-Id: I2c3c32ca6dde959ac4a3397c61edf8ec4eaf07dc Reviewed-on: https://chromium-review.googlesource.com/c/devtools/devtools-frontend/+/6026108 Reviewed-by: Danil Somsikov <[email protected]> Reviewed-by: Kim-Anh Tran <[email protected]> Commit-Queue: Joshua Thomas <[email protected]>
1 parent f29ffc6 commit d0d41e8

File tree

12 files changed

+403
-1
lines changed

12 files changed

+403
-1
lines changed

config/gni/devtools_grd_files.gni

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1682,13 +1682,16 @@ grd_files_debug_sources = [
16821682
"front_end/panels/search/SearchView.js",
16831683
"front_end/panels/search/searchResultsPane.css.js",
16841684
"front_end/panels/search/searchView.css.js",
1685+
"front_end/panels/security/CookieControlsTreeElement.js",
1686+
"front_end/panels/security/CookieControlsView.js",
16851687
"front_end/panels/security/CookieReportTreeElement.js",
16861688
"front_end/panels/security/CookieReportView.js",
16871689
"front_end/panels/security/OriginTreeElement.js",
16881690
"front_end/panels/security/SecurityModel.js",
16891691
"front_end/panels/security/SecurityPanel.js",
16901692
"front_end/panels/security/SecurityPanelSidebar.js",
16911693
"front_end/panels/security/SecurityPanelSidebarTreeElement.js",
1694+
"front_end/panels/security/cookieControlsView.css.js",
16921695
"front_end/panels/security/cookieReportView.css.js",
16931696
"front_end/panels/security/lockIcon.css.js",
16941697
"front_end/panels/security/mainView.css.js",

front_end/core/common/SettingRegistration.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,10 @@ const UIStrings = {
7474
* section allows users to configure which DevTools data is synced via Chrome Sync.
7575
*/
7676
sync: 'Sync',
77+
/**
78+
* @description Text for the privacy section of the page.
79+
*/
80+
privacy: 'Privacy',
7781
};
7882
const str_ = i18n.i18n.registerUIStrings('core/common/SettingRegistration.ts', UIStrings);
7983
const i18nString = i18n.i18n.getLocalizedString.bind(undefined, str_);
@@ -142,6 +146,7 @@ export const enum SettingCategory {
142146
EXTENSIONS = 'EXTENSIONS',
143147
ADORNER = 'ADORNER',
144148
SYNC = 'SYNC',
149+
PRIVACY = 'PRIVACY',
145150
}
146151

147152
export function getLocalizedSettingsCategory(category: SettingCategory): Platform.UIString.LocalizedString {
@@ -182,6 +187,8 @@ export function getLocalizedSettingsCategory(category: SettingCategory): Platfor
182187
return i18n.i18n.lockedString('');
183188
case SettingCategory.SYNC:
184189
return i18nString(UIStrings.sync);
190+
case SettingCategory.PRIVACY:
191+
return i18nString(UIStrings.privacy);
185192
}
186193
}
187194

front_end/core/sdk/sdk-meta.ts

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1050,6 +1050,33 @@ Common.Settings.registerSettingExtension({
10501050
},
10511051
});
10521052

1053+
Common.Settings.registerSettingExtension({
1054+
category: Common.Settings.SettingCategory.PRIVACY,
1055+
title: i18nLazyString(UIStrings.disableCache),
1056+
settingName: 'cookie-control-override-enabled',
1057+
settingType: Common.Settings.SettingType.BOOLEAN,
1058+
storageType: Common.Settings.SettingStorageType.GLOBAL,
1059+
defaultValue: false,
1060+
});
1061+
1062+
Common.Settings.registerSettingExtension({
1063+
category: Common.Settings.SettingCategory.PRIVACY,
1064+
title: i18nLazyString(UIStrings.disableCache),
1065+
settingName: 'grace-period-mitigation-disabled',
1066+
settingType: Common.Settings.SettingType.BOOLEAN,
1067+
storageType: Common.Settings.SettingStorageType.GLOBAL,
1068+
defaultValue: false,
1069+
});
1070+
1071+
Common.Settings.registerSettingExtension({
1072+
category: Common.Settings.SettingCategory.PRIVACY,
1073+
title: i18nLazyString(UIStrings.disableCache),
1074+
settingName: 'heuristic-mitigation-disabled',
1075+
settingType: Common.Settings.SettingType.BOOLEAN,
1076+
storageType: Common.Settings.SettingStorageType.GLOBAL,
1077+
defaultValue: false,
1078+
});
1079+
10531080
Common.Settings.registerSettingExtension({
10541081
category: Common.Settings.SettingCategory.RENDERING,
10551082
title: i18nLazyString(UIStrings.emulateAutoDarkMode),

front_end/panels/security/BUILD.gn

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import("../visibility.gni")
1010

1111
generate_css("css_files") {
1212
sources = [
13+
"cookieControlsView.css",
1314
"cookieReportView.css",
1415
"lockIcon.css",
1516
"mainView.css",
@@ -20,6 +21,8 @@ generate_css("css_files") {
2021

2122
devtools_module("security") {
2223
sources = [
24+
"CookieControlsTreeElement.ts",
25+
"CookieControlsView.ts",
2326
"CookieReportTreeElement.ts",
2427
"CookieReportView.ts",
2528
"OriginTreeElement.ts",
@@ -37,6 +40,9 @@ devtools_module("security") {
3740
"../../generated:protocol",
3841
"../../models/issues_manager:bundle",
3942
"../../panels/network/forward:bundle",
43+
"../../ui/components/buttons:bundle",
44+
"../../ui/components/cards:bundle",
45+
"../../ui/components/switch:bundle",
4046
"../../ui/legacy:bundle",
4147
"../../ui/legacy/components/data_grid:bundle",
4248
]
@@ -74,6 +80,7 @@ ts_library("unittests") {
7480
testonly = true
7581

7682
sources = [
83+
"CookieControlsView.test.ts",
7784
"CookieReportView.test.ts",
7885
"SecurityModel.test.ts",
7986
"SecurityPanel.test.ts",
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
// Copyright 2024 The Chromium Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style license that can be
3+
// found in the LICENSE file.
4+
import * as IconButton from '../../ui/components/icon_button/icon_button.js';
5+
6+
import {SecurityPanelSidebarTreeElement} from './SecurityPanelSidebarTreeElement.js';
7+
8+
export class CookieControlsTreeElement extends SecurityPanelSidebarTreeElement {
9+
constructor(title: string) {
10+
super(title);
11+
this.setLeadingIcons([IconButton.Icon.create('gear', 'cookie-icon')]);
12+
}
13+
14+
override onselect(): boolean {
15+
this.listItemElement.dispatchEvent(new CustomEvent('showFlagControls', {bubbles: true, composed: true}));
16+
return true;
17+
}
18+
}
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
// Copyright 2024 The Chromium Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style license that can be
3+
// found in the LICENSE file.
4+
5+
import {createFakeSetting} from '../../testing/EnvironmentHelpers.js';
6+
import {describeWithMockConnection} from '../../testing/MockConnection.js';
7+
import * as UI from '../../ui/legacy/legacy.js';
8+
9+
import * as Security from './security.js';
10+
11+
describeWithMockConnection('CookieControlsView', () => {
12+
let mockView: sinon.SinonStub;
13+
14+
beforeEach(() => {
15+
mockView = sinon.stub();
16+
});
17+
18+
it('should update setting', async () => {
19+
const testSetting = createFakeSetting('test-control', true);
20+
const view = new Security.CookieControlsView.CookieControlsView(undefined, mockView);
21+
const reloadRequiredInfobarSpy =
22+
sinon.spy(UI.InspectorView.InspectorView.instance(), 'displayDebuggedTabReloadRequiredWarning');
23+
assert.strictEqual(testSetting.get(), true);
24+
25+
view.inputChanged(false, testSetting);
26+
assert.strictEqual(testSetting.get(), false);
27+
assert.isTrue(reloadRequiredInfobarSpy.calledOnce);
28+
});
29+
});
Lines changed: 206 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,206 @@
1+
// Copyright 2024 The Chromium Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style license that can be
3+
// found in the LICENSE file.
4+
5+
import '../../ui/components/switch/switch.js';
6+
7+
import * as Common from '../../core/common/common.js';
8+
import * as i18n from '../../core/i18n/i18n.js';
9+
import * as Cards from '../../ui/components/cards/cards.js'; // eslint-disable-line @typescript-eslint/no-unused-vars
10+
import * as Input from '../../ui/components/input/input.js';
11+
import * as UI from '../../ui/legacy/legacy.js';
12+
import * as LitHtml from '../../ui/lit-html/lit-html.js';
13+
import * as VisualLogging from '../../ui/visual_logging/visual_logging.js';
14+
15+
import cookieControlsViewStyles from './cookieControlsView.css.js';
16+
17+
const {render, html} = LitHtml;
18+
19+
const UIStrings = {
20+
/**
21+
*@description Title in the view's header for the controls tool in the Privacy & Security panel
22+
*/
23+
viewTitle: 'Controls',
24+
/**
25+
*@description Explanation in the view's header about the purpose of this controls tool
26+
*/
27+
viewExplanation: 'Test how this site will perform if a user chooses to restrict third-party cookies in Chrome',
28+
/**
29+
*@description Title in the card within the controls tool
30+
*/
31+
cardTitle: 'Temporarily restrict third-party cookies',
32+
/**
33+
*@description Disclaimer beneath the card title to tell the user that the controls will only persist while devtools is open
34+
*/
35+
cardDisclaimer: 'Only when DevTools is open',
36+
/**
37+
*@description Message as part of the banner that prompts the user to reload the page to see the changes take effect. This appears when the user makes any change within the tool
38+
*/
39+
siteReloadMessage: 'To apply your updated controls, reload the page',
40+
/**
41+
*@description Title of controls section. These are exceptions that the user will be able to override to test their site
42+
*/
43+
exceptions: 'Exceptions',
44+
/**
45+
*@description Explanation of what exceptions are in this context
46+
*/
47+
exceptionsExplanation: 'Scenarios that grant access to third-party cookies',
48+
/**
49+
*@description Title for the grace period exception control
50+
*/
51+
gracePeriodTitle: 'Third-party cookie grace period',
52+
/**
53+
*@description Explanation of the grace period and a link to learn more
54+
*@example {grace period} PH1
55+
*/
56+
gracePeriodExplanation:
57+
'If this site or a site embedded on it is enrolled in the {PH1}, then the site can access third-party cookies',
58+
/**
59+
*@description Text used for link within the gracePeriodExplanation to let the user learn more about the grace period
60+
*/
61+
gracePeriod: 'grace period',
62+
/**
63+
*@description Title for the heuristic exception control
64+
*/
65+
heuristicTitle: 'Heuristics based exception',
66+
/**
67+
*@description Explanation of the heuristics with a link to learn more about the scenarios in which they apply
68+
*@example {predefined scenarios} PH1
69+
*/
70+
heuristicExplanation:
71+
'In {PH1} like pop-ups or redirects, a site embedded on this site can access third-party cookies',
72+
/**
73+
*@description Text used for link within the heuristicExplanation to let the user learn more about the heuristic exception
74+
*/
75+
scenarios: 'predefined scenarios',
76+
};
77+
78+
const str_ = i18n.i18n.registerUIStrings('panels/security/CookieControlsView.ts', UIStrings);
79+
export const i18nString = i18n.i18n.getLocalizedString.bind(undefined, str_);
80+
export const i18nFormatString = i18n.i18n.getFormatLocalizedString.bind(undefined, str_);
81+
82+
export interface ViewInput {
83+
inputChanged: (newValue: boolean, setting: Common.Settings.Setting<boolean>) => void;
84+
}
85+
export interface ViewOutput {}
86+
87+
export type View = (input: ViewInput, output: ViewOutput, target: HTMLElement) => void;
88+
89+
export class CookieControlsView extends UI.Widget.VBox {
90+
#view: View;
91+
92+
constructor(element?: HTMLElement, view: View = (input, output, target) => {
93+
const toggleSetting = Common.Settings.Settings.instance().moduleSetting('cookie-control-override-enabled');
94+
const gracePeriodSetting = Common.Settings.Settings.instance().moduleSetting('grace-period-mitigation-disabled');
95+
const heuristicSetting = Common.Settings.Settings.instance().moduleSetting('heuristic-mitigation-disabled');
96+
97+
// clang-format off
98+
const cardHeader = html `
99+
<div class="card-header">
100+
<div class="lhs">
101+
<div class="text">
102+
<div class="card-title">${i18nString(UIStrings.cardTitle)}</div>
103+
<div class="body">${i18nString(UIStrings.cardDisclaimer)}</div>
104+
</div>
105+
</div>
106+
<div>
107+
<devtools-switch
108+
.checked=${Boolean(toggleSetting.get())}
109+
@switchchange=${(e: Event)=>{
110+
input.inputChanged((e.target as HTMLInputElement).checked, toggleSetting);
111+
}}>
112+
</devtools-switch>
113+
</div>
114+
</div>
115+
`;
116+
117+
const gracePeriodControl = html`
118+
<div class="card-row">
119+
<label class='checkbox-label'>
120+
<input type='checkbox'
121+
?disabled=${!Boolean(toggleSetting.get())}
122+
?checked=${!Boolean(gracePeriodSetting.get())}
123+
@change=${(e: Event)=>{
124+
input.inputChanged(!(e.target as HTMLInputElement).checked, gracePeriodSetting);
125+
}}
126+
>
127+
<div class="text">
128+
<div class="body">${i18nString(UIStrings.gracePeriodTitle)}</div>
129+
<div class="body">
130+
${i18nFormatString(UIStrings.gracePeriodExplanation, {
131+
PH1: UI.Fragment.html`<x-link class="x-link" href="https://developers.google.com/privacy-sandbox/cookies/temporary-exceptions/grace-period" jslog=${VisualLogging.link('grace-period-link').track({click: true})}>${i18nString(UIStrings.gracePeriod)}</x-link>`,
132+
})}
133+
</div>
134+
</div>
135+
</label>
136+
</div>
137+
`;
138+
139+
const heuristicControl = html`
140+
<div class="card-row">
141+
<label class='checkbox-label'>
142+
<input type='checkbox'
143+
?disabled=${!Boolean(toggleSetting.get())}
144+
?checked=${!Boolean(heuristicSetting.get())}
145+
@change=${(e: Event)=>{
146+
input.inputChanged(!(e.target as HTMLInputElement).checked, heuristicSetting);
147+
}}
148+
>
149+
<div class="text">
150+
<div class="body">${i18nString(UIStrings.heuristicTitle)}</div>
151+
<div class="body">
152+
${i18nFormatString(UIStrings.heuristicExplanation, {
153+
PH1: UI.Fragment.html`<x-link class="x-link" href="https://developers.google.com/privacy-sandbox/cookies/temporary-exceptions/heuristics-based-exceptions" jslog=${VisualLogging.link('heuristic-link').track({click: true})}>${i18nString(UIStrings.scenarios)}</x-link>`,
154+
})}
155+
</div>
156+
</div>
157+
</label>
158+
</div>
159+
`;
160+
161+
render(html `
162+
<div class="overflow-auto">
163+
<div class="controls">
164+
<div class="header">
165+
<div class="title">${i18nString(UIStrings.viewTitle)}</div>
166+
<div class="body">${i18nString(UIStrings.viewExplanation)}</div>
167+
</div>
168+
<devtools-card>
169+
<div slot="content" class='card'>
170+
${cardHeader}
171+
<div>
172+
<div class="card-row text">
173+
<div class="card-row-title">${i18nString(UIStrings.exceptions)}</div>
174+
<div class="body">${i18nString(UIStrings.exceptionsExplanation)}</div>
175+
</div>
176+
${gracePeriodControl}
177+
${heuristicControl}
178+
</div>
179+
</div>
180+
</devtools-card>
181+
</div>
182+
</div>
183+
`, target, {host: this});
184+
// clang-format on
185+
}) {
186+
super(true, undefined, element);
187+
this.#view = view;
188+
this.update();
189+
}
190+
191+
override async doUpdate(): Promise<void> {
192+
this.#view(this, this, this.contentElement);
193+
}
194+
195+
inputChanged(newValue: boolean, setting: Common.Settings.Setting<boolean>): void {
196+
setting.set(newValue);
197+
UI.InspectorView.InspectorView.instance().displayDebuggedTabReloadRequiredWarning(
198+
i18nString(UIStrings.siteReloadMessage));
199+
this.update();
200+
}
201+
202+
override wasShown(): void {
203+
super.wasShown();
204+
this.registerCSSFiles([Input.checkboxStyles, cookieControlsViewStyles]);
205+
}
206+
}

front_end/panels/security/SecurityPanel.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import * as UI from '../../ui/legacy/legacy.js';
1414
import * as LitHtml from '../../ui/lit-html/lit-html.js';
1515
import * as VisualLogging from '../../ui/visual_logging/visual_logging.js';
1616

17+
import {CookieControlsView} from './CookieControlsView.js';
1718
import {CookieReportView} from './CookieReportView.js';
1819
import lockIconStyles from './lockIcon.css.js';
1920
import mainViewStyles from './mainView.css.js';
@@ -572,6 +573,7 @@ export class SecurityPanel extends UI.Panel.Panel implements SDK.TargetManager.S
572573
slot="sidebar"
573574
.widgetClass=${SecurityPanelSidebar}
574575
@showCookieReport=${()=>output.setVisibleView(new CookieReportView())}
576+
@showFlagControls=${() => output.setVisibleView(new CookieControlsView())}
575577
${UI.Widget.widgetRef(SecurityPanelSidebar, e => {output.sidebar = e;})}>
576578
</devtools-widget>
577579
</devtools-split-widget>`,
@@ -825,7 +827,7 @@ export class SecurityPanel extends UI.Panel.Panel implements SDK.TargetManager.S
825827
const {frame} = event.data;
826828
const request = this.lastResponseReceivedForLoaderId.get(frame.loaderId);
827829

828-
if (!(this.visibleView instanceof CookieReportView)) {
830+
if (!(this.visibleView instanceof CookieReportView) && !(this.visibleView instanceof CookieControlsView)) {
829831
this.selectAndSwitchToMainView();
830832
}
831833
this.sidebar.clearOrigins();

0 commit comments

Comments
 (0)