| // Copyright 2020 The Chromium Authors |
| // 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); |
| }); |
| }); |