Skip to content

Commit d028852

Browse files
clarej12Devtools-frontend LUCI CQ
authored andcommitted
Reland "[IPP Devtools] Create basic side panel to hold IPP in P&S panel"
This is a reland of commit b3481fd Original change's description: > [IPP Devtools] Create basic side panel to hold IPP in P&S panel > > Create a new side panel named “IP Protection Proxy” that will hold all > information in the privacy and security panel that is associated with IP > protection. > > Bug: 429151565 > Change-Id: I522b1199dabf756b69944ca776d481d3ef333195 > Reviewed-on: https://chromium-review.googlesource.com/c/devtools/devtools-frontend/+/6718272 > Reviewed-by: Philip Pfaffe <[email protected]> > Reviewed-by: Danil Somsikov <[email protected]> > Commit-Queue: Clare Jin <[email protected]> Bug: 429151565 Change-Id: Ib7869cc134fa4555c76488cf5ec9fb396097062e Reviewed-on: https://chromium-review.googlesource.com/c/devtools/devtools-frontend/+/6837896 Reviewed-by: Philip Pfaffe <[email protected]> Reviewed-by: Danil Somsikov <[email protected]> Commit-Queue: Clare Jin <[email protected]>
1 parent 419a263 commit d028852

File tree

12 files changed

+258
-0
lines changed

12 files changed

+258
-0
lines changed

config/gni/devtools_grd_files.gni

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1796,13 +1796,16 @@ grd_files_unbundled_sources = [
17961796
"front_end/panels/security/CookieControlsView.js",
17971797
"front_end/panels/security/CookieReportTreeElement.js",
17981798
"front_end/panels/security/CookieReportView.js",
1799+
"front_end/panels/security/IPProtectionTreeElement.js",
1800+
"front_end/panels/security/IPProtectionView.js",
17991801
"front_end/panels/security/OriginTreeElement.js",
18001802
"front_end/panels/security/SecurityModel.js",
18011803
"front_end/panels/security/SecurityPanel.js",
18021804
"front_end/panels/security/SecurityPanelSidebar.js",
18031805
"front_end/panels/security/SecurityPanelSidebarTreeElement.js",
18041806
"front_end/panels/security/cookieControlsView.css.js",
18051807
"front_end/panels/security/cookieReportView.css.js",
1808+
"front_end/panels/security/ipProtectionView.css.js",
18061809
"front_end/panels/security/lockIcon.css.js",
18071810
"front_end/panels/security/mainView.css.js",
18081811
"front_end/panels/security/originView.css.js",

front_end/core/host/InspectorFrontendHost.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -419,6 +419,9 @@ export class InspectorFrontendHostStub implements InspectorFrontendHostAPI {
419419
thirdPartyCookieHeuristicsEnabled: true,
420420
managedBlockThirdPartyCookies: 'Unset',
421421
},
422+
devToolsIpProtectionPanelInDevTools: {
423+
enabled: false,
424+
}
422425
};
423426
if ('hostConfigForTesting' in globalThis) {
424427
const {hostConfigForTesting} = (globalThis as unknown as {hostConfigForTesting: Root.Runtime.HostConfig});

front_end/core/root/Runtime.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -442,6 +442,10 @@ export interface HostConfigThirdPartyCookieControls {
442442
managedBlockThirdPartyCookies: string|boolean;
443443
}
444444

445+
export interface HostConfigIPProtection {
446+
enabled: boolean;
447+
}
448+
445449
interface AiGeneratedTimelineLabels {
446450
enabled: boolean;
447451
}
@@ -491,6 +495,7 @@ export type HostConfig = Platform.TypeScriptUtilities.RecursivePartial<{
491495
devToolsVeLogging: HostConfigVeLogging,
492496
devToolsWellKnown: HostConfigWellKnown,
493497
devToolsPrivacyUI: HostConfigPrivacyUI,
498+
devToolsIpProtectionPanelInDevTools: HostConfigIPProtection,
494499
/**
495500
* OffTheRecord here indicates that the user's profile is either incognito,
496501
* or guest mode, rather than a "normal" profile.

front_end/panels/security/BUILD.gn

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ generate_css("css_files") {
1212
sources = [
1313
"cookieControlsView.css",
1414
"cookieReportView.css",
15+
"ipProtectionView.css",
1516
"lockIcon.css",
1617
"mainView.css",
1718
"originView.css",
@@ -25,6 +26,8 @@ devtools_module("security") {
2526
"CookieControlsView.ts",
2627
"CookieReportTreeElement.ts",
2728
"CookieReportView.ts",
29+
"IPProtectionTreeElement.ts",
30+
"IPProtectionView.ts",
2831
"OriginTreeElement.ts",
2932
"SecurityModel.ts",
3033
"SecurityPanel.ts",
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
// Copyright 2025 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 IPProtectionTreeElement extends SecurityPanelSidebarTreeElement {
9+
constructor(title: string, jslogContext: string|number) {
10+
super(title, false, jslogContext);
11+
this.setLeadingIcons([IconButton.Icon.create('shield', 'shield-icon')]);
12+
}
13+
14+
override get elemId(): string {
15+
return 'protection';
16+
}
17+
18+
override showElement(): void {
19+
this.listItemElement.dispatchEvent(new CustomEvent('showIPProtection', {bubbles: true, composed: true}));
20+
}
21+
}
Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
// Copyright 2025 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+
/* eslint-disable rulesdir/no-lit-render-outside-of-view */
5+
6+
import '../../ui/components/switch/switch.js';
7+
import '../../ui/components/cards/cards.js';
8+
9+
import * as i18n from '../../core/i18n/i18n.js';
10+
import * as UI from '../../ui/legacy/legacy.js';
11+
import * as Lit from '../../ui/lit/lit.js';
12+
13+
import ipProtectionViewStyles from './ipProtectionView.css.js';
14+
15+
const {render, html} = Lit;
16+
17+
const UIStrings = {
18+
/**
19+
*@description Title in the view's header for the IP Protection tool in the Privacy & Security panel
20+
*/
21+
viewTitle: 'IP Protection Proxy Controls',
22+
/**
23+
*@description Explanation in the view's header about the purpose of this IP Protection tool
24+
*/
25+
viewExplanation: 'Test how this site will perform if IP Proxy is enabled in Chrome',
26+
/**
27+
*@description Title in the card within the IP Protection tool
28+
*/
29+
cardTitle: 'Bypass IP Protection',
30+
/**
31+
*@description Description in the card within the IP Protection tool
32+
*/
33+
cardDescription: 'Only when DevTools is open',
34+
} as const;
35+
36+
const str_ = i18n.i18n.registerUIStrings('panels/security/IPProtectionView.ts', UIStrings);
37+
export const i18nString = i18n.i18n.getLocalizedString.bind(undefined, str_);
38+
export const i18nFormatString = i18n.i18n.getFormatLocalizedString.bind(undefined, str_);
39+
40+
export type View = (input: object, output: object, target: HTMLElement) => void;
41+
42+
export const DEFAULT_VIEW: View = (input, _, target) => {
43+
// clang-format off
44+
render(html`
45+
<style>
46+
${ipProtectionViewStyles}
47+
</style>
48+
<div class="overflow-auto">
49+
<div class="ip-protection">
50+
<div class="header">
51+
<h1>${i18nString(UIStrings.viewTitle)}</h1>
52+
<div class="body">${i18nString(UIStrings.viewExplanation)}</div>
53+
</div>
54+
<devtools-card class="card-container">
55+
<div class="card">
56+
<div class="card-header">
57+
<div class="lhs">
58+
<div class="text">
59+
<h2 class="main-text">${i18nString(UIStrings.cardTitle)}</h2>
60+
<div class="body-subtext">
61+
${i18nString(UIStrings.cardDescription)}
62+
</div>
63+
</div>
64+
<div>
65+
<devtools-switch></devtools-switch>
66+
</div>
67+
</div>
68+
</div>
69+
</div>
70+
</devtools-card>
71+
</div>
72+
</div>
73+
`, target);
74+
// clang-format on
75+
};
76+
77+
export class IPProtectionView extends UI.Widget.VBox {
78+
#view: View;
79+
80+
constructor(element?: HTMLElement, view: View = DEFAULT_VIEW) {
81+
super(element, {useShadowDom: true});
82+
this.#view = view;
83+
this.requestUpdate();
84+
}
85+
86+
override performUpdate(): void {
87+
this.#view(this, this, this.contentElement);
88+
}
89+
}

front_end/panels/security/SecurityPanel.test.ts

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,28 @@ describeWithMockConnection('SecurityAndPrivacyPanel', () => {
4444
securityPanel = Security.SecurityPanel.SecurityPanel.instance({forceNew: true});
4545
assert.instanceOf(securityPanel.visibleView, Security.SecurityPanel.SecurityMainView);
4646
});
47+
48+
it('remembers last selected view for IP Protection', () => {
49+
updateHostConfig({
50+
devToolsPrivacyUI: {enabled: true},
51+
devToolsIpProtectionPanelInDevTools: {enabled: true},
52+
});
53+
let securityPanel = Security.SecurityPanel.SecurityPanel.instance({forceNew: true});
54+
55+
// Should initially be the controls view, as it's the first item in the privacy section.
56+
assert.instanceOf(securityPanel.visibleView, Security.CookieControlsView.CookieControlsView);
57+
58+
const ipProtectionTreeElement = securityPanel.sidebar.ipProtectionTreeElement;
59+
assert.exists(ipProtectionTreeElement, 'IPProtectionTreeElement should exist when feature is enabled');
60+
61+
// Select and switch to the IP Protection view
62+
ipProtectionTreeElement.select(/* omitFocus=*/ false, /* selectedByUser=*/ true);
63+
assert.instanceOf(securityPanel.visibleView, Security.IPProtectionView.IPProtectionView);
64+
65+
// Create a new security panel. The last selected view memory should make the IP Protection view visible
66+
securityPanel = Security.SecurityPanel.SecurityPanel.instance({forceNew: true});
67+
assert.instanceOf(securityPanel.visibleView, Security.IPProtectionView.IPProtectionView);
68+
});
4769
});
4870

4971
describe('updateOrigin', () => {

front_end/panels/security/SecurityPanel.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import * as VisualLogging from '../../ui/visual_logging/visual_logging.js';
1818

1919
import {CookieControlsView} from './CookieControlsView.js';
2020
import {CookieReportView} from './CookieReportView.js';
21+
import {IPProtectionView} from './IPProtectionView.js';
2122
import lockIconStyles from './lockIcon.css.js';
2223
import mainViewStyles from './mainView.css.js';
2324
import {ShowOriginEvent} from './OriginTreeElement.js';
@@ -568,6 +569,7 @@ export class SecurityPanel extends UI.Panel.Panel implements SDK.TargetManager.S
568569
<devtools-widget
569570
slot="sidebar"
570571
.widgetConfig=${widgetConfig(SecurityPanelSidebar)}
572+
@showIPProtection=${() => output.setVisibleView(new IPProtectionView())}
571573
@showCookieReport=${()=>output.setVisibleView(new CookieReportView())}
572574
@showFlagControls=${() => output.setVisibleView(new CookieControlsView())}
573575
${UI.Widget.widgetRef(SecurityPanelSidebar, e => {output.sidebar = e;})}>

front_end/panels/security/SecurityPanelSidebar.ts

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import * as UI from '../../ui/legacy/legacy.js';
1313

1414
import {CookieControlsTreeElement} from './CookieControlsTreeElement.js';
1515
import {CookieReportTreeElement} from './CookieReportTreeElement.js';
16+
import {IPProtectionTreeElement} from './IPProtectionTreeElement.js';
1617
import lockIconStyles from './lockIcon.css.js';
1718
import {OriginTreeElement} from './OriginTreeElement.js';
1819
import {
@@ -41,6 +42,10 @@ const UIStrings = {
4142
* @description Sidebar element text in the Security panel
4243
*/
4344
flagControls: 'Controls',
45+
/**
46+
* @description Sidebar element text in the Security panel
47+
*/
48+
ipProtection: 'IP Protection',
4449
/**
4550
* @description Text in Security Panel of the Security panel
4651
*/
@@ -78,6 +83,7 @@ export class SecurityPanelSidebar extends UI.Widget.VBox {
7883
securityOverviewElement: OriginTreeElement;
7984
readonly #cookieControlsTreeElement: CookieControlsTreeElement|undefined;
8085
readonly cookieReportTreeElement: CookieReportTreeElement|undefined;
86+
readonly ipProtectionTreeElement: IPProtectionTreeElement|undefined;
8187
readonly #elementsByOrigin: Map<string, OriginTreeElement>;
8288
readonly #mainViewReloadMessage: UI.TreeOutline.TreeElement;
8389
#mainOrigin: string|null;
@@ -102,6 +108,11 @@ export class SecurityPanelSidebar extends UI.Widget.VBox {
102108
this.cookieReportTreeElement = new CookieReportTreeElement(i18nString(UIStrings.cookieReport), 'cookie-report');
103109
privacyTreeSection.appendChild(this.cookieReportTreeElement);
104110

111+
if (Root.Runtime.hostConfig.devToolsIpProtectionPanelInDevTools?.enabled) {
112+
this.ipProtectionTreeElement = new IPProtectionTreeElement(i18nString(UIStrings.ipProtection), 'ip-protection');
113+
privacyTreeSection.appendChild(this.ipProtectionTreeElement);
114+
}
115+
105116
// If this if the first time this setting is set, go to the controls tool
106117
if (this.#securitySidebarLastItemSetting.get() === '') {
107118
this.#securitySidebarLastItemSetting.set(this.#cookieControlsTreeElement.elemId);
@@ -181,6 +192,11 @@ export class SecurityPanelSidebar extends UI.Widget.VBox {
181192
this.#securitySidebarLastItemSetting.get() === this.cookieReportTreeElement.elemId) {
182193
this.cookieReportTreeElement.select();
183194
this.cookieReportTreeElement.showElement();
195+
} else if (
196+
this.ipProtectionTreeElement &&
197+
this.#securitySidebarLastItemSetting.get() === this.ipProtectionTreeElement.elemId) {
198+
this.ipProtectionTreeElement.select();
199+
this.ipProtectionTreeElement.showElement();
184200
} else {
185201
this.securityOverviewElement.select();
186202
this.securityOverviewElement.showElement();
Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
/*
2+
* Copyright 2025 The Chromium Authors. All rights reserved.
3+
* Use of this source code is governed by a BSD-style license that can be
4+
* found in the LICENSE file.
5+
*/
6+
7+
@scope to (devtools-widget > *) {
8+
:scope {
9+
width: 100%;
10+
box-shadow: none;
11+
}
12+
13+
.overflow-auto {
14+
height: 100%;
15+
}
16+
17+
.ip-protection {
18+
display: flex;
19+
flex-direction: column;
20+
padding: var(--sys-size-5) var(--sys-size-3) var(--sys-size-5) var(--sys-size-5);
21+
min-width: var(--sys-size-33);
22+
}
23+
24+
.header {
25+
display: flex;
26+
flex-direction: column;
27+
gap: var(--sys-size-2);
28+
padding-left: var(--sys-size-5);
29+
}
30+
31+
h1 {
32+
margin: 0;
33+
font: var(--sys-typescale-headline4);
34+
}
35+
36+
.card-container {
37+
max-width: 100%;
38+
}
39+
40+
.card {
41+
display: flex;
42+
flex-direction: column;
43+
padding: var(--sys-size-6) var(--sys-size-8);
44+
gap: var(--sys-size-6);
45+
46+
&.enterprise-disabled {
47+
color: var(--sys-color-token-subtle);
48+
}
49+
}
50+
51+
.card-header {
52+
display: flex;
53+
align-items: center;
54+
}
55+
56+
.card-header > .lhs {
57+
width: 100%;
58+
display: flex;
59+
justify-content: space-between;
60+
align-items: center;
61+
padding-right: var(--sys-size-9);
62+
63+
& > devtools-icon {
64+
height: var(--sys-size-11);
65+
width: var(--sys-size-11);
66+
}
67+
}
68+
69+
.text {
70+
display: flex;
71+
flex-direction: column;
72+
gap: var(--sys-size-2);
73+
}
74+
75+
h2 {
76+
font: var(--sys-typescale-headline5);
77+
margin: 0;
78+
}
79+
80+
.body {
81+
font: var(--sys-typescale-body4-regular);
82+
}
83+
84+
.main-text {
85+
color: var(--sys-color-on-surface);
86+
}
87+
88+
.body-subtext {
89+
color: var(--sys-color-on-surface-subtle);
90+
}
91+
}

0 commit comments

Comments
 (0)