blob: 8e299af2f6c740d7f20dfb2353a3b03da0c8d358 [file] [log] [blame]
// Copyright (c) 2015 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import * as Common from '../../core/common/common.js';
import * as i18n from '../../core/i18n/i18n.js';
import * as Platform from '../../core/platform/platform.js';
import * as SDK from '../../core/sdk/sdk.js';
import * as UI from '../../ui/legacy/legacy.js';
import blockedURLsPaneStyles from './blockedURLsPane.css.js';
const UIStrings = {
/**
*@description Text to enable blocking of network requests
*/
enableNetworkRequestBlocking: 'Enable network request blocking',
/**
*@description Tooltip text that appears when hovering over the largeicon add button in the Blocked URLs Pane of the Network panel
*/
addPattern: 'Add pattern',
/**
*@description Tooltip text that appears when hovering over the largeicon clear button in the Blocked URLs Pane of the Network panel
*/
removeAllPatterns: 'Remove all patterns',
/**
*@description Accessible label for the button to add request blocking patterns in the network request blocking tool
*/
addNetworkRequestBlockingPattern: 'Add network request blocking pattern',
/**
*@description Button to add a pattern to block netwrok requests in the Network request blocking tool
*@example {Add pattern} PH1
*/
networkRequestsAreNotBlockedS: 'Network requests are not blocked. {PH1}',
/**
*@description Text in Blocked URLs Pane of the Network panel
*@example {4} PH1
*/
dBlocked: '{PH1} blocked',
/**
*@description Text in Blocked URLs Pane of the Network panel
*/
textPatternToBlockMatching: 'Text pattern to block matching requests; use * for wildcard',
/**
*@description Error text for empty list widget input in Request Blocking tool
*/
patternInputCannotBeEmpty: 'Pattern input cannot be empty.',
/**
*@description Error text for duplicate list widget input in Request Blocking tool
*/
patternAlreadyExists: 'Pattern already exists.',
/**
*@description Message to be announced for a when list item is removed from list widget
*/
itemDeleted: 'Item successfully deleted',
};
const str_ = i18n.i18n.registerUIStrings('panels/network/BlockedURLsPane.ts', UIStrings);
const i18nString = i18n.i18n.getLocalizedString.bind(undefined, str_);
export let blockedURLsPaneInstance: BlockedURLsPane|null = null;
export class BlockedURLsPane extends UI.Widget.VBox implements
UI.ListWidget.Delegate<SDK.NetworkManager.BlockedPattern> {
private manager: SDK.NetworkManager.MultitargetNetworkManager;
private readonly toolbar: UI.Toolbar.Toolbar;
private readonly enabledCheckbox: UI.Toolbar.ToolbarCheckbox;
private readonly list: UI.ListWidget.ListWidget<SDK.NetworkManager.BlockedPattern>;
private editor: UI.ListWidget.Editor<SDK.NetworkManager.BlockedPattern>|null;
private blockedCountForUrl: Map<string, number>;
private readonly updateThrottler: Common.Throttler.Throttler;
constructor() {
super(true);
this.manager = SDK.NetworkManager.MultitargetNetworkManager.instance();
this.manager.addEventListener(SDK.NetworkManager.MultitargetNetworkManager.Events.BlockedPatternsChanged, () => {
void this.update();
}, this);
this.toolbar = new UI.Toolbar.Toolbar('', this.contentElement);
this.enabledCheckbox = new UI.Toolbar.ToolbarCheckbox(
i18nString(UIStrings.enableNetworkRequestBlocking), undefined, this.toggleEnabled.bind(this));
this.toolbar.appendToolbarItem(this.enabledCheckbox);
this.toolbar.appendSeparator();
const addButton = new UI.Toolbar.ToolbarButton(i18nString(UIStrings.addPattern), 'largeicon-add');
addButton.addEventListener(UI.Toolbar.ToolbarButton.Events.Click, this.addButtonClicked, this);
this.toolbar.appendToolbarItem(addButton);
const clearButton = new UI.Toolbar.ToolbarButton(i18nString(UIStrings.removeAllPatterns), 'largeicon-clear');
clearButton.addEventListener(UI.Toolbar.ToolbarButton.Events.Click, this.removeAll, this);
this.toolbar.appendToolbarItem(clearButton);
this.list = new UI.ListWidget.ListWidget(this);
this.list.element.classList.add('blocked-urls');
this.list.setEmptyPlaceholder(this.createEmptyPlaceholder());
this.list.show(this.contentElement);
this.editor = null;
this.blockedCountForUrl = new Map();
SDK.TargetManager.TargetManager.instance().addModelListener(
SDK.NetworkManager.NetworkManager, SDK.NetworkManager.Events.RequestFinished, this.onRequestFinished, this);
this.updateThrottler = new Common.Throttler.Throttler(200);
void this.update();
}
static instance(opts: {
forceNew: boolean|null,
} = {forceNew: null}): BlockedURLsPane {
const {forceNew} = opts;
if (!blockedURLsPaneInstance || forceNew) {
blockedURLsPaneInstance = new BlockedURLsPane();
}
return blockedURLsPaneInstance;
}
private createEmptyPlaceholder(): Element {
const element = this.contentElement.createChild('div', 'no-blocked-urls');
const addButton =
UI.UIUtils.createTextButton(i18nString(UIStrings.addPattern), this.addButtonClicked.bind(this), 'add-button');
UI.ARIAUtils.setAccessibleName(addButton, i18nString(UIStrings.addNetworkRequestBlockingPattern));
element.appendChild(
i18n.i18n.getFormatLocalizedString(str_, UIStrings.networkRequestsAreNotBlockedS, {PH1: addButton}));
return element;
}
static reset(): void {
if (blockedURLsPaneInstance) {
blockedURLsPaneInstance.reset();
}
}
private addButtonClicked(): void {
this.manager.setBlockingEnabled(true);
this.list.addNewItem(0, {url: Platform.DevToolsPath.EmptyUrlString, enabled: true});
}
renderItem(pattern: SDK.NetworkManager.BlockedPattern, editable: boolean): Element {
const count = this.blockedRequestsCount(pattern.url);
const element = document.createElement('div');
element.classList.add('blocked-url');
const checkbox = (element.createChild('input', 'blocked-url-checkbox') as HTMLInputElement);
checkbox.type = 'checkbox';
checkbox.checked = pattern.enabled;
checkbox.disabled = !editable;
element.createChild('div', 'blocked-url-label').textContent = pattern.url;
element.createChild('div', 'blocked-url-count').textContent = i18nString(UIStrings.dBlocked, {PH1: count});
if (editable) {
element.addEventListener('click', event => this.togglePattern(pattern, event));
checkbox.addEventListener('click', event => this.togglePattern(pattern, event));
}
return element;
}
private togglePattern(pattern: SDK.NetworkManager.BlockedPattern, event: Event): void {
event.consume(true);
const patterns = this.manager.blockedPatterns();
patterns.splice(patterns.indexOf(pattern), 1, {enabled: !pattern.enabled, url: pattern.url});
this.manager.setBlockedPatterns(patterns);
}
private toggleEnabled(): void {
this.manager.setBlockingEnabled(!this.manager.blockingEnabled());
void this.update();
}
removeItemRequested(pattern: SDK.NetworkManager.BlockedPattern, index: number): void {
const patterns = this.manager.blockedPatterns();
patterns.splice(index, 1);
this.manager.setBlockedPatterns(patterns);
UI.ARIAUtils.alert(UIStrings.itemDeleted);
}
beginEdit(pattern: SDK.NetworkManager.BlockedPattern): UI.ListWidget.Editor<SDK.NetworkManager.BlockedPattern> {
this.editor = this.createEditor();
this.editor.control('url').value = pattern.url;
return this.editor;
}
commitEdit(
item: SDK.NetworkManager.BlockedPattern, editor: UI.ListWidget.Editor<SDK.NetworkManager.BlockedPattern>,
isNew: boolean): void {
const url = editor.control('url').value as Platform.DevToolsPath.UrlString;
const patterns = this.manager.blockedPatterns();
if (isNew) {
patterns.push({enabled: true, url: url});
} else {
patterns.splice(patterns.indexOf(item), 1, {enabled: true, url: url});
}
this.manager.setBlockedPatterns(patterns);
}
private createEditor(): UI.ListWidget.Editor<SDK.NetworkManager.BlockedPattern> {
if (this.editor) {
return this.editor;
}
const editor = new UI.ListWidget.Editor<SDK.NetworkManager.BlockedPattern>();
const content = editor.contentElement();
const titles = content.createChild('div', 'blocked-url-edit-row');
titles.createChild('div').textContent = i18nString(UIStrings.textPatternToBlockMatching);
const fields = content.createChild('div', 'blocked-url-edit-row');
const validator = (_item: SDK.NetworkManager.BlockedPattern, _index: number, input: UI.ListWidget.EditorControl): {
valid: boolean,
errorMessage: Common.UIString.LocalizedString|undefined,
} => {
let valid = true;
let errorMessage;
if (!input.value) {
errorMessage = i18nString(UIStrings.patternInputCannotBeEmpty);
valid = false;
} else if (this.manager.blockedPatterns().find(pattern => pattern.url === input.value)) {
errorMessage = i18nString(UIStrings.patternAlreadyExists);
valid = false;
}
return {valid, errorMessage};
};
const urlInput = editor.createInput('url', 'text', '', validator);
fields.createChild('div', 'blocked-url-edit-value').appendChild(urlInput);
return editor;
}
private removeAll(): void {
this.manager.setBlockedPatterns([]);
}
private update(): Promise<void> {
const enabled = this.manager.blockingEnabled();
this.list.element.classList.toggle('blocking-disabled', !enabled && Boolean(this.manager.blockedPatterns().length));
this.enabledCheckbox.setChecked(enabled);
this.list.clear();
for (const pattern of this.manager.blockedPatterns()) {
this.list.appendItem(pattern, enabled);
}
return Promise.resolve();
}
private blockedRequestsCount(url: string): number {
if (!url) {
return 0;
}
let result = 0;
for (const blockedUrl of this.blockedCountForUrl.keys()) {
if (this.matches(url, blockedUrl)) {
result += (this.blockedCountForUrl.get(blockedUrl) as number);
}
}
return result;
}
private matches(pattern: string, url: string): boolean {
let pos = 0;
const parts = pattern.split('*');
for (let index = 0; index < parts.length; index++) {
const part = parts[index];
if (!part.length) {
continue;
}
pos = url.indexOf(part, pos);
if (pos === -1) {
return false;
}
pos += part.length;
}
return true;
}
reset(): void {
this.blockedCountForUrl.clear();
void this.updateThrottler.schedule(this.update.bind(this));
}
private onRequestFinished(event: Common.EventTarget.EventTargetEvent<SDK.NetworkRequest.NetworkRequest>): void {
const request = event.data;
if (request.wasBlocked()) {
const count = this.blockedCountForUrl.get(request.url()) || 0;
this.blockedCountForUrl.set(request.url(), count + 1);
void this.updateThrottler.schedule(this.update.bind(this));
}
}
wasShown(): void {
super.wasShown();
this.list.registerCSSFiles([blockedURLsPaneStyles]);
this.registerCSSFiles([blockedURLsPaneStyles]);
}
}