Skip to content

Commit 2731ecb

Browse files
pfaffeDevtools-frontend LUCI CQ
authored andcommitted
Render BlockedURLsPane contents with lit
Bug: 407751652 Change-Id: I3b3a3c8ce195183d7b25a335561da09f4eee56d1 Reviewed-on: https://chromium-review.googlesource.com/c/devtools/devtools-frontend/+/7021139 Commit-Queue: Philip Pfaffe <[email protected]> Reviewed-by: Kateryna Prokopenko <[email protected]> Auto-Submit: Philip Pfaffe <[email protected]>
1 parent 5514f7f commit 2731ecb

File tree

5 files changed

+110
-70
lines changed

5 files changed

+110
-70
lines changed

front_end/panels/network/BlockedURLsPane.test.ts

Lines changed: 17 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,12 @@
44

55
import * as Platform from '../../core/platform/platform.js';
66
import * as SDK from '../../core/sdk/sdk.js';
7+
import * as Protocol from '../../generated/protocol.js';
78
import * as Logs from '../../models/logs/logs.js';
89
import {assertScreenshot, dispatchClickEvent, renderElementIntoDOM} from '../../testing/DOMHelpers.js';
910
import {createTarget, registerNoopActions} from '../../testing/EnvironmentHelpers.js';
1011
import {describeWithMockConnection, setMockConnectionResponseHandler} from '../../testing/MockConnection.js';
12+
import {createViewFunctionStub} from '../../testing/ViewFunctionHelpers.js';
1113

1214
import * as Network from './network.js';
1315

@@ -26,6 +28,7 @@ describeWithMockConnection('BlockedURLsPane', () => {
2628
it('shows a placeholder', async () => {
2729
const blockedURLsPane = new Network.BlockedURLsPane.BlockedURLsPane();
2830
renderElementIntoDOM(blockedURLsPane);
31+
await blockedURLsPane.updateComplete;
2932
const blockedElement = blockedURLsPane.contentElement.querySelector('.blocked-urls');
3033
const placeholder = blockedElement?.shadowRoot?.querySelector('.empty-state');
3134
assert.exists(placeholder);
@@ -40,6 +43,7 @@ describeWithMockConnection('BlockedURLsPane', () => {
4043
it('Add pattern button triggers showing the editor view', async () => {
4144
const blockedURLsPane = new Network.BlockedURLsPane.BlockedURLsPane();
4245
renderElementIntoDOM(blockedURLsPane);
46+
await blockedURLsPane.updateComplete;
4347
const blockedElement = blockedURLsPane.contentElement.querySelector('.blocked-urls');
4448
const list = blockedElement?.shadowRoot?.querySelector('.list');
4549
const placeholder = list?.querySelector('.empty-state');
@@ -49,6 +53,7 @@ describeWithMockConnection('BlockedURLsPane', () => {
4953

5054
assert.isNull(list?.querySelector('.editor-content'));
5155
dispatchClickEvent(button);
56+
await blockedURLsPane.updateComplete;
5257
assert.exists(list?.querySelector('.editor-content'));
5358

5459
await assertScreenshot('request_conditions/editor.png');
@@ -64,17 +69,19 @@ describeWithMockConnection('BlockedURLsPane', () => {
6469
]);
6570
const blockedURLsPane = new Network.BlockedURLsPane.BlockedURLsPane();
6671
renderElementIntoDOM(blockedURLsPane);
72+
await blockedURLsPane.updateComplete;
6773
assert.exists(networkManager);
68-
const updateStub = sinon.spy(blockedURLsPane, 'update');
74+
const updateStub = sinon.spy(blockedURLsPane, 'requestUpdate');
75+
76+
const request = new SDK.NetworkRequest.NetworkRequest(
77+
'', undefined, urlString`http://example.com`, urlString`http://example.com`, null, null, null);
78+
request.setBlockedReason(Protocol.Network.BlockedReason.Inspector);
6979

70-
const request = sinon.createStubInstance(SDK.NetworkRequest.NetworkRequest, {
71-
wasBlocked: true,
72-
url: urlString`http://example.com`,
73-
});
7480
networkManager.dispatchEventToListeners(SDK.NetworkManager.Events.RequestFinished, request);
7581

7682
assert.strictEqual(updateStub.calledOnce, inScope);
7783
if (inScope) {
84+
await blockedURLsPane.updateComplete;
7885
await assertScreenshot(`request_conditions/blocked-matched.png`);
7986
} else {
8087
await assertScreenshot(`request_conditions/blocked-not-matched.png`);
@@ -84,14 +91,14 @@ describeWithMockConnection('BlockedURLsPane', () => {
8491
it('is called upon RequestFinished event (when target is in scope)', updatesOnRequestFinishedEvent(true));
8592
it('is called upon RequestFinished event (when target is out of scope)', updatesOnRequestFinishedEvent(false));
8693

87-
it('is called upon Reset event', () => {
88-
const blockedURLsPane = new Network.BlockedURLsPane.BlockedURLsPane();
89-
const updateStub = sinon.stub(blockedURLsPane, 'update');
94+
it('is called upon Reset event', async () => {
95+
const viewFunction = createViewFunctionStub(Network.BlockedURLsPane.BlockedURLsPane);
96+
new Network.BlockedURLsPane.BlockedURLsPane(undefined, viewFunction);
97+
await viewFunction.nextInput;
9098

9199
Logs.NetworkLog.NetworkLog.instance().dispatchEventToListeners(
92100
Logs.NetworkLog.Events.Reset, {clearIfPreserved: true});
93-
94-
sinon.assert.calledOnce(updateStub);
101+
await viewFunction.nextInput;
95102
});
96103
});
97104
});

front_end/panels/network/BlockedURLsPane.ts

Lines changed: 93 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
// Copyright 2015 The Chromium Authors
22
// Use of this source code is governed by a BSD-style license that can be
33
// found in the LICENSE file.
4+
/* eslint-disable rulesdir/no-lit-render-outside-of-view */
45
/* eslint-disable rulesdir/no-imperative-dom-api */
56

67
import '../../ui/legacy/legacy.js';
@@ -12,10 +13,13 @@ import * as SDK from '../../core/sdk/sdk.js';
1213
import * as Logs from '../../models/logs/logs.js';
1314
import * as Buttons from '../../ui/components/buttons/buttons.js';
1415
import * as UI from '../../ui/legacy/legacy.js';
16+
import {Directives, html, render} from '../../ui/lit/lit.js';
1517
import * as VisualLogging from '../../ui/visual_logging/visual_logging.js';
1618

1719
import blockedURLsPaneStyles from './blockedURLsPane.css.js';
1820

21+
const {ref} = Directives;
22+
1923
const UIStrings = {
2024
/**
2125
* @description Text to enable blocking of network requests
@@ -70,45 +74,81 @@ const i18nString = i18n.i18n.getLocalizedString.bind(undefined, str_);
7074
const NETWORK_REQUEST_BLOCKING_EXPLANATION_URL =
7175
'https://developer.chrome.com/docs/devtools/network-request-blocking' as Platform.DevToolsPath.UrlString;
7276

77+
const {bindToAction} = UI.UIUtils;
78+
79+
interface ViewInput {
80+
list: UI.ListWidget.ListWidget<SDK.NetworkManager.BlockedPattern>;
81+
enabled: boolean;
82+
toggleEnabled: () => void;
83+
addPattern: () => void;
84+
}
85+
type View = (input: ViewInput, output: object, target: HTMLElement) => void;
86+
export const DEFAULT_VIEW: View = (input, output, target) => {
87+
render(
88+
// clang-format off
89+
html`
90+
<style>${blockedURLsPaneStyles}</style>
91+
<devtools-toolbar jslog=${VisualLogging.toolbar()}>
92+
<devtools-checkbox
93+
?checked=${input.enabled}
94+
@click=${input.toggleEnabled}
95+
.jslogContext=${'network.enable-request-blocking'}>
96+
${i18nString(UIStrings.enableNetworkRequestBlocking)}
97+
</devtools-checkbox>
98+
<div class="toolbar-divider"></div>
99+
<devtools-button ${bindToAction('network.add-network-request-blocking-pattern')}></devtools-button>
100+
<devtools-button ${bindToAction('network.remove-all-network-request-blocking-patterns')}></devtools-button>
101+
</devtools-toolbar>
102+
<div class=empty-state ${ref(e => input.list.setEmptyPlaceholder(e ?? null))}>
103+
<span class=empty-state-header>${i18nString(UIStrings.noNetworkRequestsBlocked)}</span>
104+
<div class=empty-state-description>
105+
<span>${i18nString(UIStrings.addPatternToBlock, {PH1: i18nString(UIStrings.addPattern)})}</span>
106+
<x-link
107+
href=${NETWORK_REQUEST_BLOCKING_EXPLANATION_URL}
108+
tabindex=0
109+
class=devtools-link
110+
jslog=${VisualLogging.link().track({click: true, keydown:'Enter|Space'}).context('learn-more')}>
111+
${i18nString(UIStrings.learnMore)}
112+
</x-link>
113+
</div>
114+
<devtools-button
115+
@click=${input.addPattern}
116+
class=add-button
117+
.jslogContext=${'network.add-network-request-blocking-pattern'}
118+
aria-label=${i18nString(UIStrings.addNetworkRequestBlockingPattern)}
119+
.variant=${Buttons.Button.Variant.TONAL}>
120+
${i18nString(UIStrings.addPattern)}
121+
</devtools-button>
122+
</div>
123+
<devtools-widget .widgetConfig=${UI.Widget.widgetConfig(UI.Widget.VBox)}>${input.list.element}</devtools-widget>
124+
`,
125+
// clang-format on
126+
target);
127+
};
128+
73129
export class BlockedURLsPane extends UI.Widget.VBox implements
74130
UI.ListWidget.Delegate<SDK.NetworkManager.BlockedPattern> {
75131
private manager: SDK.NetworkManager.MultitargetNetworkManager;
76-
private readonly toolbar: UI.Toolbar.Toolbar;
77-
private readonly enabledCheckbox: UI.Toolbar.ToolbarCheckbox;
78132
private readonly list: UI.ListWidget.ListWidget<SDK.NetworkManager.BlockedPattern>;
79133
private editor: UI.ListWidget.Editor<SDK.NetworkManager.BlockedPattern>|null;
80134
private blockedCountForUrl: Map<string, number>;
135+
#view: View;
81136

82-
constructor() {
83-
super({
137+
constructor(target?: HTMLElement, view = DEFAULT_VIEW) {
138+
super(target, {
84139
jslog: `${VisualLogging.panel('network.blocked-urls').track({resize: true})}`,
85140
useShadowDom: true,
86141
});
87-
this.registerRequiredCSS(blockedURLsPaneStyles);
142+
this.#view = view;
88143

89144
this.manager = SDK.NetworkManager.MultitargetNetworkManager.instance();
90145
this.manager.addEventListener(
91146
SDK.NetworkManager.MultitargetNetworkManager.Events.BLOCKED_PATTERNS_CHANGED, this.update, this);
92147

93-
this.toolbar = this.contentElement.createChild('devtools-toolbar');
94-
this.enabledCheckbox = new UI.Toolbar.ToolbarCheckbox(
95-
i18nString(UIStrings.enableNetworkRequestBlocking), undefined, this.toggleEnabled.bind(this),
96-
'network.enable-request-blocking');
97-
this.toolbar.appendToolbarItem(this.enabledCheckbox);
98-
this.toolbar.appendSeparator();
99-
this.toolbar.appendToolbarItem(
100-
UI.Toolbar.Toolbar.createActionButton('network.add-network-request-blocking-pattern'));
101-
this.toolbar.appendToolbarItem(
102-
UI.Toolbar.Toolbar.createActionButton('network.remove-all-network-request-blocking-patterns'));
103-
this.toolbar.setAttribute('jslog', `${VisualLogging.toolbar()}`);
104-
105148
this.list = new UI.ListWidget.ListWidget(this);
106149
this.list.registerRequiredCSS(blockedURLsPaneStyles);
107150
this.list.element.classList.add('blocked-urls');
108151

109-
this.list.setEmptyPlaceholder(this.createEmptyPlaceholder());
110-
this.list.show(this.contentElement);
111-
112152
this.editor = null;
113153

114154
this.blockedCountForUrl = new Map();
@@ -120,25 +160,17 @@ export class BlockedURLsPane extends UI.Widget.VBox implements
120160
Logs.NetworkLog.NetworkLog.instance().addEventListener(Logs.NetworkLog.Events.Reset, this.onNetworkLogReset, this);
121161
}
122162

123-
private createEmptyPlaceholder(): Element {
124-
const placeholder = this.contentElement.createChild('div', 'empty-state');
125-
placeholder.createChild('span', 'empty-state-header').textContent = i18nString(UIStrings.noNetworkRequestsBlocked);
126-
127-
const description = placeholder.createChild('div', 'empty-state-description');
128-
description.createChild('span').textContent =
129-
i18nString(UIStrings.addPatternToBlock, {PH1: i18nString(UIStrings.addPattern)});
130-
const link = UI.XLink.XLink.create(
131-
NETWORK_REQUEST_BLOCKING_EXPLANATION_URL, i18nString(UIStrings.learnMore), undefined, undefined, 'learn-more');
132-
description.appendChild(link);
133-
134-
const addButton = UI.UIUtils.createTextButton(i18nString(UIStrings.addPattern), this.addPattern.bind(this), {
135-
className: 'add-button',
136-
jslogContext: 'network.add-network-request-blocking-pattern',
137-
variant: Buttons.Button.Variant.TONAL,
138-
});
139-
UI.ARIAUtils.setLabel(addButton, i18nString(UIStrings.addNetworkRequestBlockingPattern));
140-
placeholder.appendChild(addButton);
141-
return placeholder;
163+
override performUpdate(): void {
164+
const enabled = this.manager.blockingEnabled();
165+
this.list.element.classList.toggle('blocking-disabled', !enabled && Boolean(this.manager.blockedPatterns().length));
166+
167+
const input: ViewInput = {
168+
addPattern: this.addPattern.bind(this),
169+
toggleEnabled: this.toggleEnabled.bind(this),
170+
enabled,
171+
list: this.list,
172+
};
173+
this.#view(input, {}, this.contentElement);
142174
}
143175

144176
addPattern(): void {
@@ -154,27 +186,30 @@ export class BlockedURLsPane extends UI.Widget.VBox implements
154186
const count = this.blockedRequestsCount(pattern.url);
155187
const element = document.createElement('div');
156188
element.classList.add('blocked-url');
157-
const checkbox = element.createChild('input', 'blocked-url-checkbox');
158-
checkbox.type = 'checkbox';
159-
checkbox.checked = pattern.enabled;
160-
checkbox.disabled = !editable;
161-
checkbox.setAttribute('jslog', `${VisualLogging.toggle().track({change: true})}`);
162-
element.createChild('div', 'blocked-url-label').textContent = pattern.url;
163-
element.createChild('div', 'blocked-url-count').textContent = i18nString(UIStrings.dBlocked, {PH1: count});
164-
if (editable) {
165-
element.addEventListener('click', event => this.togglePattern(pattern, event));
166-
checkbox.addEventListener('click', event => this.togglePattern(pattern, event));
167-
}
189+
const toggle = (e: Event): void => {
190+
if (editable) {
191+
e.consume(true);
192+
const patterns = this.manager.blockedPatterns();
193+
patterns.splice(patterns.indexOf(pattern), 1, {enabled: !pattern.enabled, url: pattern.url});
194+
this.manager.setBlockedPatterns(patterns);
195+
}
196+
};
197+
render(
198+
// clang-format off
199+
html`
200+
<input class=blocked-url-checkbox
201+
@click=${toggle}
202+
type=checkbox
203+
?checked=${pattern.enabled}
204+
?disabled=${!editable}
205+
.jslog=${VisualLogging.toggle().track({ change: true })}>
206+
<div @click=${toggle} class=blocked-url-label>${pattern.url}</div>
207+
<div class=blocked-url-count>${i18nString(UIStrings.dBlocked, {PH1: count})}</div>`,
208+
// clang-format off
209+
element);
168210
return element;
169211
}
170212

171-
private togglePattern(pattern: SDK.NetworkManager.BlockedPattern, event: Event): void {
172-
event.consume(true);
173-
const patterns = this.manager.blockedPatterns();
174-
patterns.splice(patterns.indexOf(pattern), 1, {enabled: !pattern.enabled, url: pattern.url});
175-
this.manager.setBlockedPatterns(patterns);
176-
}
177-
178213
private toggleEnabled(): void {
179214
this.manager.setBlockingEnabled(!this.manager.blockingEnabled());
180215
this.update();
@@ -239,13 +274,11 @@ export class BlockedURLsPane extends UI.Widget.VBox implements
239274

240275
update(): void {
241276
const enabled = this.manager.blockingEnabled();
242-
this.list.element.classList.toggle('blocking-disabled', !enabled && Boolean(this.manager.blockedPatterns().length));
243-
244-
this.enabledCheckbox.setChecked(enabled);
245277
this.list.clear();
246278
for (const pattern of this.manager.blockedPatterns()) {
247279
this.list.appendItem(pattern, enabled);
248280
}
281+
this.requestUpdate();
249282
}
250283

251284
private blockedRequestsCount(url: string): number {
-11 Bytes
Loading
-14 Bytes
Loading
-24 Bytes
Loading

0 commit comments

Comments
 (0)