chrome-devtools-frontend
Version:
Chrome DevTools UI
277 lines (238 loc) • 11.1 kB
text/typescript
// Copyright 2023 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 Platform from '../../core/platform/platform.js';
import * as Root from '../../core/root/root.js';
import type * as SDK from '../../core/sdk/sdk.js';
import * as Bindings from '../../models/bindings/bindings.js';
import * as Breakpoints from '../../models/breakpoints/breakpoints.js';
import * as Persistence from '../../models/persistence/persistence.js';
import * as TextUtils from '../../models/text_utils/text_utils.js';
import * as Workspace from '../../models/workspace/workspace.js';
import {createTarget} from '../../testing/EnvironmentHelpers.js';
import {
describeWithMockConnection,
dispatchEvent,
setMockConnectionResponseHandler,
} from '../../testing/MockConnection.js';
import {addChildFrame, createResource, getMainFrame, setMockResourceTree} from '../../testing/ResourceTreeHelpers.js';
import * as UI from '../../ui/legacy/legacy.js';
import * as Sources from './sources.js';
const {urlString} = Platform.DevToolsPath;
describeWithMockConnection('NavigatorView', () => {
let target: SDK.Target.Target;
let workspace: Workspace.Workspace.WorkspaceImpl;
beforeEach(() => {
Root.Runtime.experiments.register(Root.Runtime.ExperimentName.AUTHORED_DEPLOYED_GROUPING, '');
Root.Runtime.experiments.register(Root.Runtime.ExperimentName.JUST_MY_CODE, '');
setMockResourceTree(false);
setMockConnectionResponseHandler('Page.getResourceTree', async () => {
return {
frameTree: null,
};
});
const actionRegistryInstance = UI.ActionRegistry.ActionRegistry.instance({forceNew: true});
UI.ShortcutRegistry.ShortcutRegistry.instance({forceNew: true, actionRegistry: actionRegistryInstance});
target = createTarget();
const targetManager = target.targetManager();
targetManager.setScopeTarget(target);
workspace = Workspace.Workspace.WorkspaceImpl.instance();
const resourceMapping = new Bindings.ResourceMapping.ResourceMapping(targetManager, workspace);
Bindings.CSSWorkspaceBinding.CSSWorkspaceBinding.instance({forceNew: true, resourceMapping, targetManager});
const debuggerWorkspaceBinding = Bindings.DebuggerWorkspaceBinding.DebuggerWorkspaceBinding.instance({
forceNew: true,
resourceMapping,
targetManager,
});
Bindings.IgnoreListManager.IgnoreListManager.instance({forceNew: true, debuggerWorkspaceBinding});
const breakpointManager = Breakpoints.BreakpointManager.BreakpointManager.instance(
{forceNew: true, targetManager, workspace, debuggerWorkspaceBinding});
Persistence.Persistence.PersistenceImpl.instance({forceNew: true, workspace, breakpointManager});
Persistence.NetworkPersistenceManager.NetworkPersistenceManager.instance({forceNew: true, workspace});
});
function addResourceAndUISourceCode(
url: Platform.DevToolsPath.UrlString, frame: SDK.ResourceTreeModel.ResourceTreeFrame, content: string,
mimeType: string) {
createResource(frame, url, 'text/html', content);
const uiSourceCode = workspace.uiSourceCodeForURL(url) as Workspace.UISourceCode.UISourceCode;
const projectType = Workspace.Workspace.projectTypes.Network;
const project = new Bindings.ContentProviderBasedProject.ContentProviderBasedProject(
workspace, 'PROJECT_ID', projectType, 'Test project', false /* isServiceProject*/);
Bindings.NetworkProject.NetworkProject.setTargetForProject(project, target);
const contentProvider = TextUtils.StaticContentProvider.StaticContentProvider.fromString(
url, Common.ResourceType.ResourceType.fromMimeType(mimeType), content);
const metadata = new Workspace.UISourceCode.UISourceCodeMetadata(null, null);
project.addUISourceCodeWithProvider(uiSourceCode, contentProvider, metadata, mimeType);
return {project};
}
it('can discard multiple childless frames', async () => {
const url = urlString`http://example.com/index.html`;
const childFrame = await addChildFrame(target);
const {project} = addResourceAndUISourceCode(url, childFrame, '', 'text/html');
const navigatorView = Sources.SourcesNavigator.NetworkNavigatorView.instance({forceNew: true});
const children = navigatorView.scriptsTree.rootElement().children();
assert.lengthOf(children, 1, 'The NavigatorView root node should have 1 child before node removal');
assert.strictEqual(children[0].title, 'top');
// Remove leaf node and assert that node removal propagates up to the root node.
project.removeUISourceCode(url);
assert.lengthOf(
navigatorView.scriptsTree.rootElement().children(), 0,
'The NavigarorView root node should not have any children after node removal');
});
describe('domain node display name', () => {
it('should use the project origin if the url matches the default context', async () => {
const mainFrame = await getMainFrame(target);
const url = urlString`http://example.com/index.html`;
addResourceAndUISourceCode(url, mainFrame, '', 'text/html');
dispatchEvent(target, 'Runtime.executionContextCreated', {
context: {
id: 1,
origin: 'http://example.com',
name: 'Main Context',
uniqueId: 'main_context',
auxData: {
isDefault: true,
type: 'default',
frameId: mainFrame.id,
},
},
});
dispatchEvent(target, 'Runtime.executionContextCreated', {
context: {
id: 2,
origin: 'chrome-extension://ahfhijdlegdabablpippeagghigmibma',
name: 'Extension Context',
uniqueId: 'extension_context',
auxData: {
isDefault: false,
type: 'isolated',
frameId: mainFrame.id,
},
},
});
const navigatorView = Sources.SourcesNavigator.NetworkNavigatorView.instance({forceNew: true});
const topChildren = navigatorView.scriptsTree.rootElement().children();
assert.lengthOf(topChildren, 1);
assert.strictEqual(topChildren[0].title, 'top');
const children = topChildren[0].children();
assert.lengthOf(children, 1);
assert.strictEqual(children[0].title, 'example.com');
});
it('should use a matching context name if the url does not match the default context', async () => {
const mainFrame = await getMainFrame(target);
const url = urlString`chrome-extension://ahfhijdlegdabablpippeagghigmibma/script.js`;
addResourceAndUISourceCode(url, mainFrame, '', 'text/html');
dispatchEvent(target, 'Runtime.executionContextCreated', {
context: {
id: 1,
origin: 'http://example.com',
name: 'Main Context',
uniqueId: 'main_context',
auxData: {
isDefault: true,
type: 'default',
frameId: mainFrame.id,
},
},
});
dispatchEvent(target, 'Runtime.executionContextCreated', {
context: {
id: 2,
origin: 'chrome-extension://ahfhijdlegdabablpippeagghigmibma',
name: 'Extension Context',
uniqueId: 'extension_context',
auxData: {
isDefault: false,
type: 'isolated',
frameId: mainFrame.id,
},
},
});
const navigatorView = Sources.SourcesNavigator.NetworkNavigatorView.instance({forceNew: true});
const topChildren = navigatorView.scriptsTree.rootElement().children();
assert.lengthOf(topChildren, 1);
assert.strictEqual(topChildren[0].title, 'top');
const children = topChildren[0].children();
assert.lengthOf(children, 1);
assert.strictEqual(children[0].title, 'Extension Context');
});
it('should prioritize the default context', async () => {
const mainFrame = await getMainFrame(target);
const url = urlString`http://example.com/index.html`;
addResourceAndUISourceCode(url, mainFrame, '', 'text/html');
dispatchEvent(target, 'Runtime.executionContextCreated', {
context: {
id: 1,
origin: 'http://example.com',
name: 'Other Context',
uniqueId: 'other_context',
auxData: {
isDefault: false,
type: 'isolated',
frameId: mainFrame.id,
},
},
});
// Default context comes last, but this should still indicate that the
// project origin should be used as the display name.
dispatchEvent(target, 'Runtime.executionContextCreated', {
context: {
id: 2,
origin: 'http://example.com',
name: 'Main Context',
uniqueId: 'main_context',
auxData: {
isDefault: true,
type: 'default',
frameId: mainFrame.id,
},
},
});
const navigatorView = Sources.SourcesNavigator.NetworkNavigatorView.instance({forceNew: true});
const topChildren = navigatorView.scriptsTree.rootElement().children();
assert.lengthOf(topChildren, 1);
assert.strictEqual(topChildren[0].title, 'top');
const children = topChildren[0].children();
assert.lengthOf(children, 1);
assert.strictEqual(children[0].title, 'example.com');
});
it('should ignore contexts with no name', async () => {
const mainFrame = await getMainFrame(target);
const url = urlString`http://example.com/index.html`;
addResourceAndUISourceCode(url, mainFrame, '', 'text/html');
dispatchEvent(target, 'Runtime.executionContextCreated', {
context: {
id: 1,
origin: 'http://example.com',
name: '',
uniqueId: 'no_name_context',
auxData: {
isDefault: false,
type: 'isolated',
frameId: mainFrame.id,
},
},
});
const navigatorView = Sources.SourcesNavigator.NetworkNavigatorView.instance({forceNew: true});
const topChildren = navigatorView.scriptsTree.rootElement().children();
assert.lengthOf(topChildren, 1);
assert.strictEqual(topChildren[0].title, 'top');
const children = topChildren[0].children();
assert.lengthOf(children, 1);
assert.strictEqual(children[0].title, 'example.com');
});
it('should indicate if a display name cannot be found', async () => {
const mainFrame = await getMainFrame(target);
const url = urlString`*bad url*`;
addResourceAndUISourceCode(url, mainFrame, '', 'text/html');
const navigatorView = Sources.SourcesNavigator.NetworkNavigatorView.instance({forceNew: true});
const topChildren = navigatorView.scriptsTree.rootElement().children();
assert.lengthOf(topChildren, 1);
assert.strictEqual(topChildren[0].title, 'top');
const children = topChildren[0].children();
assert.lengthOf(children, 1);
assert.strictEqual(children[0].title, '(no domain)');
});
});
});