chrome-devtools-frontend
Version:
Chrome DevTools UI
990 lines (915 loc) • 35.7 kB
text/typescript
// Copyright 2022 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 Host from '../../core/host/host.js';
import * as Platform from '../../core/platform/platform.js';
import * as SDK from '../../core/sdk/sdk.js';
import * as Protocol from '../../generated/protocol.js';
import {
createTarget,
deinitializeGlobalVars,
initializeGlobalVars,
} from '../../testing/EnvironmentHelpers.js';
import {describeWithMockConnection} from '../../testing/MockConnection.js';
import {createWorkspaceProject, setUpEnvironment} from '../../testing/OverridesHelpers.js';
import {setMockResourceTree} from '../../testing/ResourceTreeHelpers.js';
import {createFileSystemUISourceCode} from '../../testing/UISourceCodeHelpers.js';
import * as Persistence from '../persistence/persistence.js';
import * as Workspace from '../workspace/workspace.js';
const {urlString} = Platform.DevToolsPath;
const setUpEnvironmentWithUISourceCode =
(url: string, resourceType: Common.ResourceType.ResourceType, project?: Workspace.Workspace.Project) => {
const {workspace, networkPersistenceManager} = setUpEnvironment();
if (!project) {
project = {id: () => url, type: () => Workspace.Workspace.projectTypes.Network} as Workspace.Workspace.Project;
}
const uiSourceCode = new Workspace.UISourceCode.UISourceCode(project, urlString`${url}`, resourceType);
project.uiSourceCodes = () => [uiSourceCode];
workspace.addProject(project);
return {workspace, project, uiSourceCode, networkPersistenceManager};
};
describeWithMockConnection('NetworkPersistenceManager', () => {
beforeEach(async () => {
SDK.NetworkManager.MultitargetNetworkManager.dispose();
const target = createTarget();
sinon.stub(target.fetchAgent(), 'invoke_enable');
});
it('can create an overridden file with Local Overrides enabled', async () => {
const url = 'http://www.example.com/list-fetch.json';
const resourceType = Common.ResourceType.resourceTypes.Document;
const {uiSourceCode} = setUpEnvironmentWithUISourceCode(url, resourceType);
const networkPersistenceManager = await createWorkspaceProject(urlString`file:///path/to/overrides`, []);
const saveSpy = sinon.spy(networkPersistenceManager, 'saveUISourceCodeForOverrides');
const actual = await networkPersistenceManager.setupAndStartLocalOverrides(uiSourceCode);
saveSpy.restore();
assert.isTrue(saveSpy.calledOnce, 'should override content once');
assert.isTrue(actual, 'should complete override successfully');
});
it('can create an overridden file with Local Overrides folder set up but disabled', async () => {
Common.Settings.Settings.instance().moduleSetting('persistence-network-overrides-enabled').set(false);
const url = 'http://www.example.com/list-xhr.json';
const resourceType = Common.ResourceType.resourceTypes.Document;
const {uiSourceCode} = setUpEnvironmentWithUISourceCode(url, resourceType);
const networkPersistenceManager = await createWorkspaceProject(urlString`file:///path/to/overrides`, []);
const saveSpy = sinon.spy(networkPersistenceManager, 'saveUISourceCodeForOverrides');
const actual = await networkPersistenceManager.setupAndStartLocalOverrides(uiSourceCode);
saveSpy.restore();
assert.isTrue(saveSpy.calledOnce, 'should override content once');
assert.isTrue(actual, 'should complete override successfully');
});
});
describeWithMockConnection('NetworkPersistenceManager', () => {
it('does not create interception patterns for forbidden URLs', async () => {
SDK.NetworkManager.MultitargetNetworkManager.dispose();
const target = createTarget();
const networkPersistenceManager = await createWorkspaceProject(urlString`file:///path/to/overrides`, [
{name: 'helloWorld.html', path: 'www.example.com/', content: 'Hello World!'},
{name: 'forbidden.html', path: 'chromewebstore.google.com/', content: 'Chrome Web Store'},
{name: 'flags', path: 'chrome:/', content: 'Chrome Flags'},
{name: 'index.html', path: 'chrome.google.com/', content: 'Chrome'},
{name: 'allowed.html', path: 'www.google.com/', content: 'Google Search'},
]);
const stub = sinon.stub(target.fetchAgent(), 'invoke_enable');
await networkPersistenceManager.updateInterceptionPatternsForTests();
const patterns = stub.lastCall.args[0].patterns;
const expected = [
{
urlPattern: 'http?://www.example.com/helloWorld.html',
requestStage: Protocol.Fetch.RequestStage.Response,
},
{
urlPattern: 'http?://www.google.com/allowed.html',
requestStage: Protocol.Fetch.RequestStage.Response,
},
];
assert.deepEqual(patterns, expected);
});
it('recognizes forbidden network URLs', () => {
assert.isTrue(Persistence.NetworkPersistenceManager.NetworkPersistenceManager.isForbiddenNetworkUrl(
urlString`chrome://version`));
assert.isTrue(Persistence.NetworkPersistenceManager.NetworkPersistenceManager.isForbiddenNetworkUrl(
urlString`https://chromewebstore.google.com/index.html`));
assert.isTrue(Persistence.NetworkPersistenceManager.NetworkPersistenceManager.isForbiddenNetworkUrl(
urlString`https://chrome.google.com/script.js`));
assert.isFalse(Persistence.NetworkPersistenceManager.NetworkPersistenceManager.isForbiddenNetworkUrl(
urlString`https://www.example.com/script.js`));
});
});
describeWithMockConnection('NetworkPersistenceManager', () => {
let networkPersistenceManager: Persistence.NetworkPersistenceManager.NetworkPersistenceManager;
beforeEach(async () => {
SDK.NetworkManager.MultitargetNetworkManager.dispose();
setMockResourceTree(false);
const target = createTarget();
networkPersistenceManager = await createWorkspaceProject(urlString`file:///path/to/overrides`, [
{
name: '.headers',
path: 'www.example.com/',
content: `[
{
"applyTo": "index.html",
"headers": [{
"name": "index-only",
"value": "only added to index.html"
}]
},
{
"applyTo": "*.css",
"headers": [{
"name": "css-only",
"value": "only added to css files"
}]
},
{
"applyTo": "path/to/*.js",
"headers": [{
"name": "another-header",
"value": "only added to specific path"
}]
},
{
"applyTo": "repeated.html",
"headers": [
{
"name": "repeated",
"value": "first override"
},
{
"name": "repeated",
"value": "second override"
}
]
}
]`,
},
{
name: '.headers',
path: '',
content: `[
{
"applyTo": "*",
"headers": [{
"name": "age",
"value": "overridden"
}]
}
]`,
},
{name: 'helloWorld.html', path: 'www.example.com/', content: 'Hello World!'},
]);
sinon.stub(target.fetchAgent(), 'invoke_enable');
await networkPersistenceManager.updateInterceptionPatternsForTests();
});
it('merges request headers with override without overlap', async () => {
const interceptedRequest = {
request: {
url: 'https://www.example.com/',
},
responseHeaders: [
{name: 'server', value: 'DevTools mock server'},
],
} as SDK.NetworkManager.InterceptedRequest;
const expected = [
{name: 'age', value: 'overridden'},
{name: 'index-only', value: 'only added to index.html'},
{name: 'server', value: 'DevTools mock server'},
];
const actual = await networkPersistenceManager.handleHeaderInterception(interceptedRequest);
assert.deepEqual(actual.sort((a, b) => (a.name.localeCompare(b.name))), expected);
});
it('merges request headers with override with overlap', async () => {
const interceptedRequest = {
request: {
url: 'https://www.example.com/index.html',
},
responseHeaders: [
{name: 'server', value: 'DevTools mock server'},
{name: 'age', value: '1'},
],
} as SDK.NetworkManager.InterceptedRequest;
const expected = [
{name: 'age', value: 'overridden'},
{name: 'index-only', value: 'only added to index.html'},
{name: 'server', value: 'DevTools mock server'},
];
const actual = await networkPersistenceManager.handleHeaderInterception(interceptedRequest);
assert.deepEqual(actual.sort((a, b) => (a.name.localeCompare(b.name))), expected);
});
it('merges request headers with override with file type wildcard', async () => {
const interceptedRequest = {
request: {
url: 'https://www.example.com/styles.css',
},
responseHeaders: [
{name: 'server', value: 'DevTools mock server'},
{name: 'age', value: '1'},
],
} as SDK.NetworkManager.InterceptedRequest;
const expected = [
{name: 'age', value: 'overridden'},
{name: 'css-only', value: 'only added to css files'},
{name: 'server', value: 'DevTools mock server'},
];
const actual = await networkPersistenceManager.handleHeaderInterception(interceptedRequest);
assert.deepEqual(actual.sort((a, b) => (a.name.localeCompare(b.name))), expected);
});
it('merges request headers with override with specific path', async () => {
const interceptedRequest = {
request: {
url: 'https://www.example.com/path/to/script.js',
},
responseHeaders: [
{name: 'server', value: 'DevTools mock server'},
{name: 'age', value: '1'},
],
} as SDK.NetworkManager.InterceptedRequest;
const expected = [
{name: 'age', value: 'overridden'},
{name: 'another-header', value: 'only added to specific path'},
{name: 'server', value: 'DevTools mock server'},
];
const actual = await networkPersistenceManager.handleHeaderInterception(interceptedRequest);
assert.deepEqual(actual.sort((a, b) => (a.name.localeCompare(b.name))), expected);
});
it('merges request headers only when domain matches', async () => {
const interceptedRequest = {
request: {
url: 'https://www.web.dev/index.html',
},
responseHeaders: [
{name: 'server', value: 'DevTools mock server'},
],
} as SDK.NetworkManager.InterceptedRequest;
const expected = [
{name: 'age', value: 'overridden'},
{name: 'server', value: 'DevTools mock server'},
];
const actual = await networkPersistenceManager.handleHeaderInterception(interceptedRequest);
assert.deepEqual(actual.sort((a, b) => (a.name.localeCompare(b.name))), expected);
});
it('merges headers while leaving muliple headers with the same name unchanged', async () => {
const interceptedRequest = {
request: {
url: 'https://www.example.com/index.html',
},
responseHeaders: [
{name: 'repeated', value: 'first'},
{name: 'repeated', value: 'second'},
{name: 'repeated', value: 'third'},
],
} as SDK.NetworkManager.InterceptedRequest;
const expected = [
{name: 'age', value: 'overridden'},
{name: 'index-only', value: 'only added to index.html'},
{name: 'repeated', value: 'first'},
{name: 'repeated', value: 'second'},
{name: 'repeated', value: 'third'},
];
const actual = await networkPersistenceManager.handleHeaderInterception(interceptedRequest);
assert.deepEqual(actual.sort((a, b) => (a.name.localeCompare(b.name))), expected);
});
it('merges headers and can override muliple headers with the same name', async () => {
const interceptedRequest = {
request: {
url: 'https://www.example.com/repeated.html',
},
responseHeaders: [
{name: 'repeated', value: 'first'},
{name: 'repeated', value: 'second'},
{name: 'repeated', value: 'third'},
],
} as SDK.NetworkManager.InterceptedRequest;
const expected = [
{name: 'age', value: 'overridden'},
{name: 'repeated', value: 'first override'},
{name: 'repeated', value: 'second override'},
];
const actual = await networkPersistenceManager.handleHeaderInterception(interceptedRequest);
assert.deepEqual(actual.sort((a, b) => (a.name.localeCompare(b.name))), expected);
});
it('translates URLs into raw and encoded paths', async () => {
let toTest = [
// Simple tests.
{
url: 'www.example.com/',
raw: 'www.example.com/index.html',
encoded: 'www.example.com/index.html',
},
{
url: 'www.example.com/simple',
raw: 'www.example.com/simple',
encoded: 'www.example.com/simple',
},
{
url: 'www.example.com/hello/foo/bar',
raw: 'www.example.com/hello/foo/bar',
encoded: 'www.example.com/hello/foo/bar',
},
{
url: 'www.example.com/.',
raw: 'www.example.com/.',
encoded: 'www.example.com/',
},
{
url: 'localhost:8090/endswith.',
raw: 'localhost:8090/endswith.',
encoded: 'localhost:8090/endswith.',
},
// Query parameters.
{
url: 'example.com/fo?o/bar',
raw: 'example.com/fo?o%2Fbar',
encoded: 'example.com/fo%3Fo%252Fbar',
},
{
url: 'example.com/foo?/bar',
raw: 'example.com/foo?%2Fbar',
encoded: 'example.com/foo%3F%252Fbar',
},
{
url: 'example.com/foo/?bar',
raw: 'example.com/foo/?bar',
encoded: 'example.com/foo/%3Fbar',
},
{
url: 'example.com/?foo/bar/3',
raw: 'example.com/?foo%2Fbar%2F3',
encoded: 'example.com/%3Ffoo%252Fbar%252F3',
},
{
url: 'example.com/foo/bar/?3hello/bar',
raw: 'example.com/foo/bar/?3hello%2Fbar',
encoded: 'example.com/foo/bar/%3F3hello%252Fbar',
},
{url: 'https://www.example.com/?foo=bar', raw: 'www.example.com/?foo=bar', encoded: 'www.example.com/%3Ffoo=bar'},
{
url: 'http://www.example.com/?foo=bar/',
raw: 'www.example.com/?foo=bar%2F',
encoded: 'www.example.com/%3Ffoo=bar%252F',
},
{
url: 'http://www.example.com/?foo=bar?',
raw: 'www.example.com/?foo=bar?',
encoded: 'www.example.com/%3Ffoo=bar%3F',
},
// Hash parameters.
{
url: 'example.com/?foo/bar/3#hello/bar',
raw: 'example.com/?foo%2Fbar%2F3',
encoded: 'example.com/%3Ffoo%252Fbar%252F3',
},
{
url: 'example.com/#foo/bar/3hello/bar',
raw: 'example.com/index.html',
encoded: 'example.com/index.html',
},
{
url: 'example.com/foo/bar/#?3hello/bar',
raw: 'example.com/foo/bar/index.html',
encoded: 'example.com/foo/bar/index.html',
},
{
url: 'example.com/foo.js#',
raw: 'example.com/foo.js',
encoded: 'example.com/foo.js',
},
{
url: 'http://www.web.dev/path/page.html#anchor',
raw: 'www.web.dev/path/page.html',
encoded: 'www.web.dev/path/page.html',
},
{
url: 'http://www.example.com/file&$*?.html',
raw: 'www.example.com/file&$%2A?.html',
encoded: 'www.example.com/file&$%252A%3F.html',
},
{
url: 'localhost:8090/',
raw: 'localhost:8090/index.html',
encoded: 'localhost:8090/index.html',
},
{url: 'localhost:8090/lpt1', raw: 'localhost:8090/lpt1', encoded: 'localhost:8090/lpt1'},
{
url: 'example.com/foo .js',
raw: 'example.com/foo%20.js',
encoded: 'example.com/foo%2520.js',
},
{
url: 'example.com///foo.js',
raw: 'example.com/foo.js',
encoded: 'example.com/foo.js',
},
{
url: 'example.com///',
raw: 'example.com/index.html',
encoded: 'example.com/index.html',
},
// Very long file names.
{
url: 'example.com' +
'/THIS/PATH/IS_MORE_THAN/200/Chars'.repeat(8),
raw: 'example.com/longurls/Chars-141a715a',
encoded: 'example.com/longurls/Chars-141a715a',
},
{
url: ('example.com' +
'/THIS/PATH/IS_LESS_THAN/200/Chars'.repeat(5))
.slice(0, -1),
raw:
'example.com/THIS/PATH/IS_LESS_THAN/200/Chars/THIS/PATH/IS_LESS_THAN/200/Chars/THIS/PATH/IS_LESS_THAN/200/Chars/THIS/PATH/IS_LESS_THAN/200/Chars/THIS/PATH/IS_LESS_THAN/200/Char',
encoded:
'example.com/THIS/PATH/IS_LESS_THAN/200/Chars/THIS/PATH/IS_LESS_THAN/200/Chars/THIS/PATH/IS_LESS_THAN/200/Chars/THIS/PATH/IS_LESS_THAN/200/Chars/THIS/PATH/IS_LESS_THAN/200/Char',
},
];
if (Host.Platform.isWin()) {
toTest = [
{
url: 'https://www.example.com/?foo=bar',
raw: 'www.example.com/%3Ffoo=bar',
encoded: 'www.example.com/%253Ffoo=bar',
},
{
url: 'http://www.web.dev/path/page.html#anchor',
raw: 'www.web.dev/path/page.html',
encoded: 'www.web.dev/path/page.html',
},
{
url: 'http://www.example.com/?foo=bar/',
raw: 'www.example.com/%3Ffoo=bar%2F',
encoded: 'www.example.com/%253Ffoo=bar%252F',
},
{
url: 'http://www.example.com/?foo=bar?',
raw: 'www.example.com/%3Ffoo=bar%3F',
encoded: 'www.example.com/%253Ffoo=bar%253F',
},
{
url: 'http://www.example.com/file&$*?.html',
raw: 'www.example.com/file&$%2A%3F.html',
encoded: 'www.example.com/file&$%252A%253F.html',
},
{
url: 'localhost:8090/',
raw: 'localhost%3A8090/index.html',
encoded: 'localhost%253A8090/index.html',
},
// Windows cannot end with . (period) and space.
{
url: 'example.com/foo.js.',
raw: 'example.com/foo.js%2E',
encoded: 'example.com/foo.js%252E',
},
{
url: 'localhost:8090/endswith.',
raw: 'localhost%3A8090/endswith%2E',
encoded: 'localhost%253A8090/endswith%252E',
},
{
url: 'example.com/foo.js ',
raw: 'example.com/foo.js%20',
encoded: 'example.com/foo.js%2520',
},
// Reserved filenames on Windows.
{
url: 'example.com/CON',
raw: 'example.com/%43%4F%4E',
encoded: 'example.com/%2543%254F%254E',
},
{
url: 'example.com/cOn',
raw: 'example.com/%63%4F%6E',
encoded: 'example.com/%2563%254F%256E',
},
{
url: 'example.com/cOn/hello',
raw: 'example.com/%63%4F%6E/hello',
encoded: 'example.com/%2563%254F%256E/hello',
},
{
url: 'example.com/PRN',
raw: 'example.com/%50%52%4E',
encoded: 'example.com/%2550%2552%254E',
},
{
url: 'example.com/AUX',
raw: 'example.com/%41%55%58',
encoded: 'example.com/%2541%2555%2558',
},
{
url: 'example.com/NUL',
raw: 'example.com/%4E%55%4C',
encoded: 'example.com/%254E%2555%254C',
},
{
url: 'example.com/COM1',
raw: 'example.com/%43%4F%4D%31',
encoded: 'example.com/%2543%254F%254D%2531',
},
{
url: 'example.com/COM2',
raw: 'example.com/%43%4F%4D%32',
encoded: 'example.com/%2543%254F%254D%2532',
},
{
url: 'example.com/COM3',
raw: 'example.com/%43%4F%4D%33',
encoded: 'example.com/%2543%254F%254D%2533',
},
{
url: 'example.com/COM4',
raw: 'example.com/%43%4F%4D%34',
encoded: 'example.com/%2543%254F%254D%2534',
},
{
url: 'example.com/COM5',
raw: 'example.com/%43%4F%4D%35',
encoded: 'example.com/%2543%254F%254D%2535',
},
{
url: 'example.com/COM6',
raw: 'example.com/%43%4F%4D%36',
encoded: 'example.com/%2543%254F%254D%2536',
},
{
url: 'example.com/COM7',
raw: 'example.com/%43%4F%4D%37',
encoded: 'example.com/%2543%254F%254D%2537',
},
{
url: 'example.com/COM8',
raw: 'example.com/%43%4F%4D%38',
encoded: 'example.com/%2543%254F%254D%2538',
},
{
url: 'example.com/COM9',
raw: 'example.com/%43%4F%4D%39',
encoded: 'example.com/%2543%254F%254D%2539',
},
{
url: 'localhost:8090/lpt1',
raw: 'localhost%3A8090/%6C%70%74%31',
encoded: 'localhost%253A8090/%256C%2570%2574%2531',
},
{
url: 'example.com/LPT1',
raw: 'example.com/%4C%50%54%31',
encoded: 'example.com/%254C%2550%2554%2531',
},
{
url: 'example.com/LPT2',
raw: 'example.com/%4C%50%54%32',
encoded: 'example.com/%254C%2550%2554%2532',
},
{
url: 'example.com/LPT3',
raw: 'example.com/%4C%50%54%33',
encoded: 'example.com/%254C%2550%2554%2533',
},
{
url: 'example.com/LPT4',
raw: 'example.com/%4C%50%54%34',
encoded: 'example.com/%254C%2550%2554%2534',
},
{
url: 'example.com/LPT5',
raw: 'example.com/%4C%50%54%35',
encoded: 'example.com/%254C%2550%2554%2535',
},
{
url: 'example.com/LPT6',
raw: 'example.com/%4C%50%54%36',
encoded: 'example.com/%254C%2550%2554%2536',
},
{
url: 'example.com/LPT7',
raw: 'example.com/%4C%50%54%37',
encoded: 'example.com/%254C%2550%2554%2537',
},
{
url: 'example.com/LPT8',
raw: 'example.com/%4C%50%54%38',
encoded: 'example.com/%254C%2550%2554%2538',
},
{
url: 'example.com/LPT9',
raw: 'example.com/%4C%50%54%39',
encoded: 'example.com/%254C%2550%2554%2539',
},
];
}
toTest.forEach(testStrings => {
assert.deepEqual(networkPersistenceManager.rawPathFromUrl(urlString`${testStrings.url}`), testStrings.raw);
assert.deepEqual(
networkPersistenceManager.encodedPathFromUrl(urlString`${testStrings.url}`), testStrings.encoded);
});
});
it('is aware of which \'.headers\' files are currently active', done => {
const workspace = Workspace.Workspace.WorkspaceImpl.instance();
const project = {
type: () => Workspace.Workspace.projectTypes.Network,
} as Workspace.Workspace.Project;
const networkUISourceCode = {
url: () => 'https://www.example.com/hello/world/index.html',
project: () => project,
contentType: () => Common.ResourceType.resourceTypes.Document,
} as Workspace.UISourceCode.UISourceCode;
project.uiSourceCodes = () => [networkUISourceCode];
const eventURLs: string[] = [];
networkPersistenceManager.addEventListener(
Persistence.NetworkPersistenceManager.Events.REQUEST_FOR_HEADER_OVERRIDES_FILE_CHANGED, event => {
eventURLs.push(event.data.url());
});
workspace.dispatchEventToListeners(Workspace.Workspace.Events.UISourceCodeAdded, networkUISourceCode);
assert.isTrue(networkPersistenceManager.hasMatchingNetworkUISourceCodeForHeaderOverridesFile({
url: () => 'file:///path/to/overrides/www.example.com/.headers',
project: () => networkPersistenceManager.project(),
} as Workspace.UISourceCode.UISourceCode));
assert.isTrue(networkPersistenceManager.hasMatchingNetworkUISourceCodeForHeaderOverridesFile({
url: () => 'file:///path/to/overrides/.headers',
project: () => networkPersistenceManager.project(),
} as Workspace.UISourceCode.UISourceCode));
assert.isFalse(networkPersistenceManager.hasMatchingNetworkUISourceCodeForHeaderOverridesFile({
url: () => 'file:///path/to/overrides/www.foo.com/.headers',
project: () => networkPersistenceManager.project(),
} as Workspace.UISourceCode.UISourceCode));
workspace.dispatchEventToListeners(Workspace.Workspace.Events.ProjectRemoved, project);
setTimeout(() => {
assert.deepEqual(
eventURLs, ['file:///path/to/overrides/.headers', 'file:///path/to/overrides/www.example.com/.headers']);
assert.isFalse(networkPersistenceManager.hasMatchingNetworkUISourceCodeForHeaderOverridesFile({
url: () => 'file:///path/to/overrides/www.example.com/.headers',
project: () => networkPersistenceManager.project(),
} as Workspace.UISourceCode.UISourceCode));
assert.isFalse(networkPersistenceManager.hasMatchingNetworkUISourceCodeForHeaderOverridesFile({
url: () => 'file:///path/to/overrides/.headers',
project: () => networkPersistenceManager.project(),
} as Workspace.UISourceCode.UISourceCode));
assert.isFalse(networkPersistenceManager.hasMatchingNetworkUISourceCodeForHeaderOverridesFile({
url: () => 'file:///path/to/overrides/www.foo.com/.headers',
project: () => networkPersistenceManager.project(),
} as Workspace.UISourceCode.UISourceCode));
done();
}, 0);
});
});
describeWithMockConnection('NetworkPersistenceManager', () => {
beforeEach(() => {
SDK.NetworkManager.MultitargetNetworkManager.dispose();
});
it('updates active state when target detach and attach', async () => {
const {networkPersistenceManager} = setUpEnvironment();
const {project} = createFileSystemUISourceCode({url: urlString`file:///tmp`, mimeType: 'text/plain'});
await networkPersistenceManager.setProject(project);
const targetManager = SDK.TargetManager.TargetManager.instance();
assert.isNull(targetManager.rootTarget());
assert.isFalse(networkPersistenceManager.active());
const target = await createTarget();
assert.isTrue(networkPersistenceManager.active());
targetManager.removeTarget(target);
target.dispose('test');
assert.isFalse(networkPersistenceManager.active());
});
});
describe('NetworkPersistenceManager', () => {
before(async () => {
await initializeGlobalVars();
});
after(async () => {
await deinitializeGlobalVars();
});
it('escapes patterns to be used in RegExes', () => {
assert.strictEqual(Persistence.NetworkPersistenceManager.escapeRegex('www.example.com/'), 'www\\.example\\.com/');
assert.strictEqual(
Persistence.NetworkPersistenceManager.escapeRegex('www.example.com/index.html'),
'www\\.example\\.com/index\\.html');
assert.strictEqual(
Persistence.NetworkPersistenceManager.escapeRegex('www.example.com/*'), 'www\\.example\\.com/.*');
assert.strictEqual(
Persistence.NetworkPersistenceManager.escapeRegex('www.example.com/*.js'), 'www\\.example\\.com/.*\\.js');
assert.strictEqual(
Persistence.NetworkPersistenceManager.escapeRegex('www.example.com/file([{with-special$_^chars}])'),
'www\\.example\\.com/file\\(\\[\\{with\\-special\\$_\\^chars\\}\\]\\)');
assert.strictEqual(
Persistence.NetworkPersistenceManager.escapeRegex('www.example.com/page.html?foo=bar'),
'www\\.example\\.com/page\\.html\\?foo=bar');
assert.strictEqual(
Persistence.NetworkPersistenceManager.escapeRegex('www.example.com/*?foo=bar'),
'www\\.example\\.com/.*\\?foo=bar');
});
it('detects when the tail of a path matches with a default index file', () => {
assert.deepEqual(
Persistence.NetworkPersistenceManager.extractDirectoryIndex('index.html'), {head: '', tail: 'index.html'});
assert.deepEqual(
Persistence.NetworkPersistenceManager.extractDirectoryIndex('index.htm'), {head: '', tail: 'index.htm'});
assert.deepEqual(
Persistence.NetworkPersistenceManager.extractDirectoryIndex('index.php'), {head: '', tail: 'index.php'});
assert.deepEqual(Persistence.NetworkPersistenceManager.extractDirectoryIndex('index.ht'), {head: 'index.ht'});
assert.deepEqual(Persistence.NetworkPersistenceManager.extractDirectoryIndex('*.html'), {head: '', tail: '*.html'});
assert.deepEqual(Persistence.NetworkPersistenceManager.extractDirectoryIndex('*.htm'), {head: '', tail: '*.htm'});
assert.deepEqual(
Persistence.NetworkPersistenceManager.extractDirectoryIndex('path/*.html'), {head: 'path/', tail: '*.html'});
assert.deepEqual(Persistence.NetworkPersistenceManager.extractDirectoryIndex('foo*.html'), {head: 'foo*.html'});
assert.deepEqual(Persistence.NetworkPersistenceManager.extractDirectoryIndex('a*'), {head: 'a*'});
assert.deepEqual(Persistence.NetworkPersistenceManager.extractDirectoryIndex('a/*'), {head: 'a/*'});
});
it('merges headers which do not overlap', () => {
const {networkPersistenceManager} = setUpEnvironment();
const baseHeaders = [{
name: 'age',
value: '0',
}];
const overrideHeaders = [{
name: 'accept-ranges',
value: 'bytes',
}];
const merged = [
{name: 'accept-ranges', value: 'bytes'},
{name: 'age', value: '0'},
];
assert.deepEqual(networkPersistenceManager.mergeHeaders(baseHeaders, overrideHeaders), merged);
});
it('merges headers which overlap', () => {
const {networkPersistenceManager} = setUpEnvironment();
const baseHeaders = [{
name: 'age',
value: '0',
}];
const overrideHeaders = [
{name: 'accept-ranges', value: 'bytes'},
{name: 'age', value: '1'},
];
const merged = [
{name: 'accept-ranges', value: 'bytes'},
{name: 'age', value: '1'},
];
assert.deepEqual(networkPersistenceManager.mergeHeaders(baseHeaders, overrideHeaders), merged);
});
it('generates header patterns', async () => {
const {networkPersistenceManager} = setUpEnvironment();
const headers = `[
{
"applyTo": "*",
"headers": [{
"name": "age",
"value": "0"
}]
},
{
"applyTo": "page.html",
"headers": [{
"name": "age",
"value": "1"
}]
},
{
"applyTo": "index.html",
"headers": [{
"name": "age",
"value": "2"
}]
},
{
"applyTo": "nested/path/*.js",
"headers": [{
"name": "age",
"value": "3"
}]
},
{
"applyTo": "*/path/*.js",
"headers": [{
"name": "age",
"value": "4"
}]
}
]`;
const {uiSourceCode} = createFileSystemUISourceCode({
url: urlString`file:///path/to/overrides/www.example.com/.headers`,
content: headers,
mimeType: 'text/plain',
fileSystemPath: 'file:///path/to/overrides',
});
const expectedPatterns = [
'http?://www.example.com/*',
'http?://www.example.com/page.html',
'http?://www.example.com/index.html',
'http?://www.example.com/',
'http?://www.example.com/nested/path/*.js',
'http?://www.example.com/*/path/*.js',
];
const {headerPatterns, path, overridesWithRegex} =
await networkPersistenceManager.generateHeaderPatterns(uiSourceCode);
assert.deepEqual(Array.from(headerPatterns).sort(), expectedPatterns.sort());
const expectedMapping = [
{
applyTo: /^www\.example\.com\/.*$/.toString(),
headers: [{name: 'age', value: '0'}],
},
{
applyTo: /^www\.example\.com\/page\.html$/.toString(),
headers: [{name: 'age', value: '1'}],
},
{
applyTo: /^www\.example\.com\/(index\.html)?$/.toString(),
headers: [{name: 'age', value: '2'}],
},
{
applyTo: /^www\.example\.com\/nested\/path\/.*\.js$/.toString(),
headers: [{name: 'age', value: '3'}],
},
{
applyTo: /^www\.example\.com\/.*\/path\/.*\.js$/.toString(),
headers: [{name: 'age', value: '4'}],
},
];
assert.strictEqual(path, 'www.example.com/');
const actualMapping = overridesWithRegex.map(
override => ({applyTo: override.applyToRegex.toString(), headers: override.headers}),
);
assert.deepEqual(actualMapping, expectedMapping);
});
it('generates header patterns for global header overrides', async () => {
const {networkPersistenceManager} = setUpEnvironment();
const headers = `[
{
"applyTo": "*",
"headers": [{
"name": "age",
"value": "0"
}]
}
]`;
const {uiSourceCode} = createFileSystemUISourceCode({
url: urlString`file:///path/to/overrides/.headers`,
content: headers,
mimeType: 'text/plain',
fileSystemPath: 'file:///path/to/overrides',
});
const {headerPatterns} = await networkPersistenceManager.generateHeaderPatterns(uiSourceCode);
assert.deepEqual(Array.from(headerPatterns), ['http?://*', 'file:///*']);
});
it('generates header patterns for long URLs', async () => {
const {networkPersistenceManager} = setUpEnvironment();
const headers = `[
{
"applyTo": "index.html-5b9f4873.html",
"headers": [{
"name": "foo",
"value": "bar"
}]
}
]`;
const {uiSourceCode} = createFileSystemUISourceCode({
url: urlString`file:///path/to/overrides/www.longurls.com/longurls/.headers`,
content: headers,
mimeType: 'text/plain',
fileSystemPath: 'file:///path/to/overrides',
});
const {headerPatterns, path, overridesWithRegex} =
await networkPersistenceManager.generateHeaderPatterns(uiSourceCode);
assert.deepEqual(Array.from(headerPatterns), ['http?://www.longurls.com/*']);
assert.strictEqual(path, 'www.longurls.com/longurls/');
const expectedMapping = [
{
applyTo: /^www\.longurls\.com\/longurls\/index\.html\-5b9f4873\.html$/.toString(),
headers: [{name: 'foo', value: 'bar'}],
},
];
const actualMapping = overridesWithRegex.map(
override => ({applyTo: override.applyToRegex.toString(), headers: override.headers}),
);
assert.deepEqual(actualMapping, expectedMapping);
});
it('updates interception patterns upon edit of .headers file', async () => {
const {networkPersistenceManager} = setUpEnvironment();
const headers = `[
{
"applyTo": "index.html",
"headers": [{
"name": "foo",
"value": "bar"
}]
}
]`;
const {uiSourceCode} = createFileSystemUISourceCode({
url: urlString`file:///path/to/overrides/www.example.com/.headers`,
content: headers,
mimeType: 'text/plain',
fileSystemPath: 'file:///path/to/overrides',
});
const spy = sinon.spy(networkPersistenceManager, 'updateInterceptionPatterns');
sinon.assert.notCalled(spy);
uiSourceCode.setWorkingCopy(`[
{
"applyTo": "index.html",
"headers": [{
"name": "foo2",
"value": "bar2"
}]
}
]`);
uiSourceCode.commitWorkingCopy();
sinon.assert.calledOnce(spy);
});
});