chrome-devtools-frontend
Version:
Chrome DevTools UI
361 lines (308 loc) • 16.2 kB
text/typescript
// Copyright 2020 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 SDK from '../../core/sdk/sdk.js';
import * as Protocol from '../../generated/protocol.js';
import {createFakeSetting, createTarget} from '../../testing/EnvironmentHelpers.js';
import {describeWithMockConnection, dispatchEvent} from '../../testing/MockConnection.js';
import {activate, getMainFrame, navigate} from '../../testing/ResourceTreeHelpers.js';
import {
mkInspectorCspIssue,
StubIssue,
ThirdPartyStubIssue,
} from '../../testing/StubIssue.js';
import * as IssuesManager from '../issues_manager/issues_manager.js';
describeWithMockConnection('IssuesManager', () => {
let target: SDK.Target.Target;
let model: SDK.IssuesModel.IssuesModel;
beforeEach(() => {
target = createTarget();
const maybeModel = target.model(SDK.IssuesModel.IssuesModel);
assert.exists(maybeModel);
model = maybeModel;
});
it('collects issues from an issues model', () => {
const issuesManager = new IssuesManager.IssuesManager.IssuesManager();
const dispatchedIssues: IssuesManager.Issue.Issue[] = [];
issuesManager.addEventListener(
IssuesManager.IssuesManager.Events.ISSUE_ADDED, event => dispatchedIssues.push(event.data.issue));
model.dispatchEventToListeners(
SDK.IssuesModel.Events.ISSUE_ADDED, {issuesModel: model, inspectorIssue: mkInspectorCspIssue('url1')});
model.dispatchEventToListeners(
SDK.IssuesModel.Events.ISSUE_ADDED, {issuesModel: model, inspectorIssue: mkInspectorCspIssue('url2')});
const expected = ['ContentSecurityPolicyIssue::kURLViolation', 'ContentSecurityPolicyIssue::kURLViolation'];
assert.deepEqual(dispatchedIssues.map(i => i.code()), expected);
const issueCodes = Array.from(issuesManager.issues()).map(r => r.code());
assert.deepEqual(issueCodes, expected);
});
function getBlockedUrl(issue: IssuesManager.Issue.Issue): string|undefined {
const cspIssue = issue as IssuesManager.ContentSecurityPolicyIssue.ContentSecurityPolicyIssue;
return cspIssue.details().blockedURL;
}
function assertOutOfScopeIssuesAreFiltered():
{issuesManager: IssuesManager.IssuesManager.IssuesManager, prerenderTarget: SDK.Target.Target} {
const issuesManager = new IssuesManager.IssuesManager.IssuesManager();
const dispatchedIssues: IssuesManager.Issue.Issue[] = [];
issuesManager.addEventListener(
IssuesManager.IssuesManager.Events.ISSUE_ADDED, event => dispatchedIssues.push(event.data.issue));
model.dispatchEventToListeners(
SDK.IssuesModel.Events.ISSUE_ADDED, {issuesModel: model, inspectorIssue: mkInspectorCspIssue('url1')});
const prerenderTarget = createTarget({subtype: 'prerender'});
const prerenderModel = prerenderTarget.model(SDK.IssuesModel.IssuesModel);
assert.exists(prerenderModel);
prerenderModel.dispatchEventToListeners(
SDK.IssuesModel.Events.ISSUE_ADDED, {issuesModel: prerenderModel, inspectorIssue: mkInspectorCspIssue('url2')});
const expected = ['url1'];
assert.deepEqual(dispatchedIssues.map(getBlockedUrl), expected);
assert.deepEqual(Array.from(issuesManager.issues()).map(getBlockedUrl), expected);
return {issuesManager, prerenderTarget};
}
it('updates filtered issues when switching scope', () => {
const {issuesManager, prerenderTarget} = assertOutOfScopeIssuesAreFiltered();
SDK.TargetManager.TargetManager.instance().setScopeTarget(prerenderTarget);
assert.deepEqual(Array.from(issuesManager.issues()).map(getBlockedUrl), ['url2']);
});
it('keeps issues of prerendered page upon activation', () => {
const {issuesManager, prerenderTarget} = assertOutOfScopeIssuesAreFiltered();
SDK.TargetManager.TargetManager.instance().setScopeTarget(prerenderTarget);
activate(prerenderTarget);
assert.deepEqual(Array.from(issuesManager.issues()).map(getBlockedUrl), ['url2']);
});
const updatesOnPrimaryPageChange = (primary: boolean) => () => {
const issuesManager = new IssuesManager.IssuesManager.IssuesManager();
model.dispatchEventToListeners(
SDK.IssuesModel.Events.ISSUE_ADDED, {issuesModel: model, inspectorIssue: mkInspectorCspIssue('url1')});
assert.strictEqual(issuesManager.numberOfIssues(), 1);
navigate(getMainFrame(primary ? target : createTarget({subtype: 'prerender'})));
assert.strictEqual(issuesManager.numberOfIssues(), primary ? 0 : 1);
};
it('clears issues after primary page navigation', updatesOnPrimaryPageChange(true));
it('does not clear issues after non-primary page navigation', updatesOnPrimaryPageChange(false));
it('filters third-party issues when the third-party issues setting is false, includes them otherwise', () => {
const issues = [
new ThirdPartyStubIssue('AllowedStubIssue1', false),
new ThirdPartyStubIssue('StubIssue2', true),
new ThirdPartyStubIssue('AllowedStubIssue3', false),
new ThirdPartyStubIssue('StubIssue4', true),
];
const showThirdPartyIssuesSetting = createFakeSetting('third party flag', false);
const issuesManager = new IssuesManager.IssuesManager.IssuesManager(showThirdPartyIssuesSetting);
const firedIssueAddedEventCodes: string[] = [];
issuesManager.addEventListener(
IssuesManager.IssuesManager.Events.ISSUE_ADDED,
event => firedIssueAddedEventCodes.push(event.data.issue.code()));
for (const issue of issues) {
issuesManager.addIssue(model, issue);
}
let issueCodes = Array.from(issuesManager.issues()).map(i => i.code());
assert.deepEqual(issueCodes, ['AllowedStubIssue1', 'AllowedStubIssue3']);
assert.deepEqual(firedIssueAddedEventCodes, ['AllowedStubIssue1', 'AllowedStubIssue3']);
showThirdPartyIssuesSetting.set(true);
issueCodes = Array.from(issuesManager.issues()).map(i => i.code());
assert.deepEqual(issueCodes, ['AllowedStubIssue1', 'StubIssue2', 'AllowedStubIssue3', 'StubIssue4']);
});
it('reports issue counts by kind', () => {
const issue1 = new StubIssue('StubIssue1', ['id1'], [], IssuesManager.Issue.IssueKind.IMPROVEMENT);
const issue2 = new StubIssue('StubIssue1', ['id2'], [], IssuesManager.Issue.IssueKind.IMPROVEMENT);
const issue3 = new StubIssue('StubIssue1', ['id3'], [], IssuesManager.Issue.IssueKind.BREAKING_CHANGE);
const issuesManager = new IssuesManager.IssuesManager.IssuesManager();
issuesManager.addIssue(model, issue1);
issuesManager.addIssue(model, issue2);
issuesManager.addIssue(model, issue3);
assert.deepEqual(issuesManager.numberOfIssues(), 3);
assert.deepEqual(issuesManager.numberOfIssues(IssuesManager.Issue.IssueKind.IMPROVEMENT), 2);
assert.deepEqual(issuesManager.numberOfIssues(IssuesManager.Issue.IssueKind.BREAKING_CHANGE), 1);
assert.deepEqual(issuesManager.numberOfIssues(IssuesManager.Issue.IssueKind.PAGE_ERROR), 0);
});
describe('instance', () => {
it('throws an Error if its not the first instance created with "ensureFirst" set', () => {
IssuesManager.IssuesManager.IssuesManager.instance();
assert.throws(() => IssuesManager.IssuesManager.IssuesManager.instance({forceNew: true, ensureFirst: true}));
assert.throws(() => IssuesManager.IssuesManager.IssuesManager.instance({forceNew: false, ensureFirst: true}));
});
});
it('hides issues added after setting has been initialised', () => {
const issues = [
new StubIssue('HiddenStubIssue1', [], []),
new StubIssue('HiddenStubIssue2', [], []),
new StubIssue('UnhiddenStubIssue1', [], []),
new StubIssue('UnhiddenStubIssue2', [], []),
];
const hideIssueByCodeSetting =
createFakeSetting('hide by code', ({} as IssuesManager.IssuesManager.HideIssueMenuSetting));
const showThirdPartyIssuesSetting = createFakeSetting('third party flag', true);
const issuesManager =
new IssuesManager.IssuesManager.IssuesManager(showThirdPartyIssuesSetting, hideIssueByCodeSetting);
const hiddenIssues: string[] = [];
issuesManager.addEventListener(IssuesManager.IssuesManager.Events.ISSUE_ADDED, event => {
if (event.data.issue.isHidden()) {
hiddenIssues.push(event.data.issue.code());
}
});
// This Setting can either have been initialised in a previous Devtools session and retained
// through to a new session.
// OR
// These settings have been updated by clicking on "hide issue" and cause the updateHiddenIssues
// method to be called. These issues are being added to the IssuesManager after this has happened.
hideIssueByCodeSetting.set({
HiddenStubIssue1: IssuesManager.IssuesManager.IssueStatus.HIDDEN,
HiddenStubIssue2: IssuesManager.IssuesManager.IssueStatus.HIDDEN,
});
for (const issue of issues) {
issuesManager.addIssue(model, issue);
}
assert.deepEqual(hiddenIssues, ['HiddenStubIssue1', 'HiddenStubIssue2']);
});
it('hides issues present in IssuesManager when setting is updated', () => {
const issues = [
new StubIssue('HiddenStubIssue1', [], []),
new StubIssue('HiddenStubIssue2', [], []),
new StubIssue('UnhiddenStubIssue1', [], []),
new StubIssue('UnhiddenStubIssue2', [], []),
];
const hideIssueByCodeSetting =
createFakeSetting('hide by code', ({} as IssuesManager.IssuesManager.HideIssueMenuSetting));
const showThirdPartyIssuesSetting = createFakeSetting('third party flag', true);
const issuesManager =
new IssuesManager.IssuesManager.IssuesManager(showThirdPartyIssuesSetting, hideIssueByCodeSetting);
let hiddenIssues: string[] = [];
issuesManager.addEventListener(IssuesManager.IssuesManager.Events.FULL_UPDATE_REQUIRED, () => {
hiddenIssues = [];
for (const issue of issuesManager.issues()) {
if (issue.isHidden()) {
hiddenIssues.push(issue.code());
}
}
});
for (const issue of issues) {
issuesManager.addIssue(model, issue);
}
// Setting is updated by clicking on "hide issue".
hideIssueByCodeSetting.set({
HiddenStubIssue1: IssuesManager.IssuesManager.IssueStatus.HIDDEN,
});
assert.deepEqual(hiddenIssues, ['HiddenStubIssue1']);
hideIssueByCodeSetting.set({
HiddenStubIssue1: IssuesManager.IssuesManager.IssueStatus.HIDDEN,
HiddenStubIssue2: IssuesManager.IssuesManager.IssueStatus.HIDDEN,
});
assert.deepEqual(hiddenIssues, ['HiddenStubIssue1', 'HiddenStubIssue2']);
});
it('unhides issues present in IssuesManager when setting is updated', () => {
const issues = [
new StubIssue('HiddenStubIssue1', [], []),
new StubIssue('HiddenStubIssue2', [], []),
new StubIssue('UnhiddenStubIssue1', [], []),
new StubIssue('UnhiddenStubIssue2', [], []),
];
const hideIssueByCodeSetting =
createFakeSetting('hide by code', ({} as IssuesManager.IssuesManager.HideIssueMenuSetting));
const showThirdPartyIssuesSetting = createFakeSetting('third party flag', true);
const issuesManager =
new IssuesManager.IssuesManager.IssuesManager(showThirdPartyIssuesSetting, hideIssueByCodeSetting);
for (const issue of issues) {
issuesManager.addIssue(model, issue);
}
hideIssueByCodeSetting.set({
HiddenStubIssue1: IssuesManager.IssuesManager.IssueStatus.HIDDEN,
HiddenStubIssue2: IssuesManager.IssuesManager.IssueStatus.HIDDEN,
UnhiddenStubIssue1: IssuesManager.IssuesManager.IssueStatus.HIDDEN,
UnhiddenStubIssue2: IssuesManager.IssuesManager.IssueStatus.HIDDEN,
});
let unhiddenIssues: string[] = [];
issuesManager.addEventListener(IssuesManager.IssuesManager.Events.FULL_UPDATE_REQUIRED, () => {
unhiddenIssues = [];
for (const issue of issuesManager.issues()) {
if (!issue.isHidden()) {
unhiddenIssues.push(issue.code());
}
}
});
// Setting updated by clicking on "unhide issue"
hideIssueByCodeSetting.set({
HiddenStubIssue1: IssuesManager.IssuesManager.IssueStatus.HIDDEN,
HiddenStubIssue2: IssuesManager.IssuesManager.IssueStatus.HIDDEN,
UnhiddenStubIssue1: IssuesManager.IssuesManager.IssueStatus.UNHIDDEN,
UnhiddenStubIssue2: IssuesManager.IssuesManager.IssueStatus.HIDDEN,
});
assert.deepEqual(unhiddenIssues, ['UnhiddenStubIssue1']);
hideIssueByCodeSetting.set({
HiddenStubIssue1: IssuesManager.IssuesManager.IssueStatus.HIDDEN,
HiddenStubIssue2: IssuesManager.IssuesManager.IssueStatus.HIDDEN,
UnhiddenStubIssue1: IssuesManager.IssuesManager.IssueStatus.UNHIDDEN,
UnhiddenStubIssue2: IssuesManager.IssuesManager.IssueStatus.UNHIDDEN,
});
assert.deepEqual(unhiddenIssues, ['UnhiddenStubIssue1', 'UnhiddenStubIssue2']);
});
it('unhides all issues correctly', () => {
const issues = [
new StubIssue('HiddenStubIssue1', [], []),
new StubIssue('HiddenStubIssue2', [], []),
new StubIssue('UnhiddenStubIssue1', [], []),
new StubIssue('UnhiddenStubIssue2', [], []),
];
const hideIssueByCodeSetting =
createFakeSetting('hide by code', ({} as IssuesManager.IssuesManager.HideIssueMenuSetting));
const showThirdPartyIssuesSetting = createFakeSetting('third party flag', true);
const issuesManager =
new IssuesManager.IssuesManager.IssuesManager(showThirdPartyIssuesSetting, hideIssueByCodeSetting);
for (const issue of issues) {
issuesManager.addIssue(model, issue);
}
hideIssueByCodeSetting.set({
HiddenStubIssue1: IssuesManager.IssuesManager.IssueStatus.HIDDEN,
HiddenStubIssue2: IssuesManager.IssuesManager.IssueStatus.HIDDEN,
UnhiddenStubIssue1: IssuesManager.IssuesManager.IssueStatus.HIDDEN,
UnhiddenStubIssue2: IssuesManager.IssuesManager.IssueStatus.HIDDEN,
});
let unhiddenIssues: string[] = [];
issuesManager.addEventListener(IssuesManager.IssuesManager.Events.FULL_UPDATE_REQUIRED, () => {
unhiddenIssues = [];
for (const issue of issuesManager.issues()) {
if (!issue.isHidden()) {
unhiddenIssues.push(issue.code());
}
}
});
issuesManager.unhideAllIssues();
assert.deepEqual(
unhiddenIssues, ['HiddenStubIssue1', 'HiddenStubIssue2', 'UnhiddenStubIssue1', 'UnhiddenStubIssue2']);
});
it('send update event on scope change', async () => {
const issuesManager = new IssuesManager.IssuesManager.IssuesManager();
const updateRequired = issuesManager.once(IssuesManager.IssuesManager.Events.FULL_UPDATE_REQUIRED);
const anotherTarget = createTarget();
SDK.TargetManager.TargetManager.instance().setScopeTarget(anotherTarget);
await updateRequired;
});
it('clears BounceTrackingIssue only on user-initiated navigation', () => {
const issuesManager = new IssuesManager.IssuesManager.IssuesManager();
const issue = {
code: Protocol.Audits.InspectorIssueCode.BounceTrackingIssue,
details: {
bounceTrackingIssueDetails: {
trackingSites: ['example_1.test'],
},
},
};
model.dispatchEventToListeners(SDK.IssuesModel.Events.ISSUE_ADDED, {issuesModel: model, inspectorIssue: issue});
assert.strictEqual(issuesManager.numberOfIssues(), 1);
dispatchEvent(target, 'Network.requestWillBeSent', {
requestId: 'requestId1',
loaderId: 'loaderId1',
request: {url: 'http://example.com'},
hasUserGesture: false,
} as unknown as Protocol.Network.RequestWillBeSentEvent);
const frame = getMainFrame(target);
navigate(frame, {loaderId: 'loaderId1' as Protocol.Network.LoaderId});
assert.strictEqual(issuesManager.numberOfIssues(), 1);
dispatchEvent(target, 'Network.requestWillBeSent', {
requestId: 'requestId2',
loaderId: 'loaderId2',
request: {url: 'http://example.com/page'},
hasUserGesture: true,
} as unknown as Protocol.Network.RequestWillBeSentEvent);
navigate(frame, {loaderId: 'loaderId2' as Protocol.Network.LoaderId});
assert.strictEqual(issuesManager.numberOfIssues(), 0);
});
});