Skip to content

Commit f43c9c7

Browse files
bmeurerDevtools-frontend LUCI CQ
authored andcommitted
[persistence] Introduce initial AutomaticFileSystemManager.
This adds a new singleton `AutomaticFileSystemManager` whose functionality is gated behind the `DevToolsAutomaticFileSystems` feature flag, which attempts to automatically connect workspace folders based on project settings. This CL doesn't provide any means in terms of UI in which the user could trigger a first-time setup of an automatic file system. That will land in a follow-up CL. Bug: 395562934 Change-Id: I033b081e6859a04d47462ed916dd6de8edae5be8 Doc: http://go/chrome-devtools:automatic-workspace-folders-design Reviewed-on: https://chromium-review.googlesource.com/c/devtools/devtools-frontend/+/6276943 Auto-Submit: Benedikt Meurer <[email protected]> Reviewed-by: Alex Rudenko <[email protected]> Commit-Queue: Benedikt Meurer <[email protected]>
1 parent 1b36370 commit f43c9c7

File tree

12 files changed

+403
-3
lines changed

12 files changed

+403
-3
lines changed

config/gni/devtools_grd_files.gni

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1056,6 +1056,7 @@ grd_files_debug_sources = [
10561056
"front_end/models/logs/NetworkLog.js",
10571057
"front_end/models/logs/RequestResolver.js",
10581058
"front_end/models/persistence/Automapping.js",
1059+
"front_end/models/persistence/AutomaticFileSystemManager.js",
10591060
"front_end/models/persistence/EditFileSystemView.js",
10601061
"front_end/models/persistence/FileSystemWorkspaceBinding.js",
10611062
"front_end/models/persistence/IsolatedFileSystem.js",

front_end/core/host/InspectorFrontendHost.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -271,6 +271,18 @@ export class InspectorFrontendHostStub implements InspectorFrontendHostAPI {
271271
recordUserMetricsAction(umaName: string): void {
272272
}
273273

274+
connectAutomaticFileSystem(
275+
_fileSystemPath: Platform.DevToolsPath.RawPathString,
276+
_fileSystemUUID: string,
277+
_addIfMissing: boolean,
278+
callback: (result: {success: boolean}) => void,
279+
): void {
280+
queueMicrotask(() => callback({success: false}));
281+
}
282+
283+
disconnectAutomaticFileSystem(fileSystemPath: Platform.DevToolsPath.RawPathString): void {
284+
}
285+
274286
requestFileSystems(): void {
275287
this.events.dispatchEventToListeners(Events.FileSystemsLoaded, []);
276288
}

front_end/core/host/InspectorFrontendHostAPI.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -236,6 +236,15 @@ export interface EventTypes {
236236
}
237237

238238
export interface InspectorFrontendHostAPI {
239+
connectAutomaticFileSystem(
240+
fileSystemPath: Platform.DevToolsPath.RawPathString,
241+
fileSystemUUID: string,
242+
addIfMissing: boolean,
243+
callback: (result: {success: boolean}) => void,
244+
): void;
245+
246+
disconnectAutomaticFileSystem(fileSystemPath: Platform.DevToolsPath.RawPathString): void;
247+
239248
addFileSystem(type?: string): void;
240249

241250
loadCompleted(): void;

front_end/devtools_compatibility.js

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -802,6 +802,28 @@ const InspectorFrontendHostImpl = class {
802802
DevToolsAPI.sendMessageToEmbedder('recordUserMetricsAction', [umaName], null);
803803
}
804804

805+
/**
806+
* @override
807+
*/
808+
connectAutomaticFileSystem(fileSystemPath, fileSystemUUID, addIfMissing, callback) {
809+
DevToolsAPI.sendMessageToEmbedder(
810+
'connectAutomaticFileSystem',
811+
[fileSystemPath, fileSystemUUID, addIfMissing],
812+
callback,
813+
);
814+
}
815+
816+
/**
817+
* @override
818+
*/
819+
disconnectAutomaticFileSystem(fileSystemPath) {
820+
DevToolsAPI.sendMessageToEmbedder(
821+
'disconnectAutomaticFileSystem',
822+
[fileSystemPath],
823+
null,
824+
);
825+
}
826+
805827
/**
806828
* @override
807829
*/

front_end/entrypoints/main/MainImpl.ts

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -527,13 +527,20 @@ export class MainImpl {
527527
debuggerWorkspaceBinding: Bindings.DebuggerWorkspaceBinding.DebuggerWorkspaceBinding.instance(),
528528
});
529529

530-
ProjectSettings.ProjectSettingsModel.ProjectSettingsModel.instance({
530+
const projectSettingsModel = ProjectSettings.ProjectSettingsModel.ProjectSettingsModel.instance({
531531
forceNew: true,
532532
hostConfig: Root.Runtime.hostConfig,
533533
pageResourceLoader: SDK.PageResourceLoader.PageResourceLoader.instance(),
534534
targetManager,
535535
});
536536

537+
Persistence.AutomaticFileSystemManager.AutomaticFileSystemManager.instance({
538+
forceNew: true,
539+
hostConfig: Root.Runtime.hostConfig,
540+
inspectorFrontendHost: Host.InspectorFrontendHost.InspectorFrontendHostInstance,
541+
projectSettingsModel,
542+
});
543+
537544
AutofillManager.AutofillManager.AutofillManager.instance();
538545

539546
LiveMetrics.LiveMetrics.instance();
Lines changed: 156 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,156 @@
1+
// Copyright 2025 The Chromium Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style license that can be
3+
// found in the LICENSE file.
4+
5+
import * as Host from '../../core/host/host.js';
6+
import * as ProjectSettings from '../project_settings/project_settings.js';
7+
8+
import * as Persistence from './persistence.js';
9+
10+
describe('AutomaticFileSystemManager', () => {
11+
const AUTOMATIC_FILE_SYSTEM_CHANGED = Persistence.AutomaticFileSystemManager.Events.AUTOMATIC_FILE_SYSTEM_CHANGED;
12+
const {AutomaticFileSystemManager} = Persistence.AutomaticFileSystemManager;
13+
const root = '/path/to/bar';
14+
const uuid = '549bbf9b-48b2-4af7-aebd-d3ba68993094';
15+
const hostConfig = {devToolsAutomaticFileSystems: {enabled: true}};
16+
17+
afterEach(() => {
18+
AutomaticFileSystemManager.removeInstance();
19+
});
20+
21+
it('initially doesn\'t report an automatic file system', () => {
22+
const inspectorFrontendHost = sinon.createStubInstance(Host.InspectorFrontendHost.InspectorFrontendHostStub);
23+
const projectSettingsModel = sinon.createStubInstance(ProjectSettings.ProjectSettingsModel.ProjectSettingsModel);
24+
sinon.stub(projectSettingsModel, 'projectSettings').value({});
25+
26+
const manager = AutomaticFileSystemManager.instance({
27+
forceNew: true,
28+
hostConfig,
29+
inspectorFrontendHost,
30+
projectSettingsModel,
31+
});
32+
33+
assert.isNull(manager.automaticFileSystem);
34+
});
35+
36+
it('doesn\'t listen to project settings changes when `devToolsAutomaticFileSystems` is off', () => {
37+
const hostConfig = {devToolsAutomaticFileSystems: {enabled: false}};
38+
const inspectorFrontendHost = sinon.createStubInstance(Host.InspectorFrontendHost.InspectorFrontendHostStub);
39+
const projectSettingsModel = sinon.createStubInstance(ProjectSettings.ProjectSettingsModel.ProjectSettingsModel);
40+
41+
AutomaticFileSystemManager.instance({
42+
forceNew: true,
43+
hostConfig,
44+
inspectorFrontendHost,
45+
projectSettingsModel,
46+
});
47+
48+
assert(projectSettingsModel.addEventListener.notCalled);
49+
});
50+
51+
it('attempts to automatically connect the file system initially', () => {
52+
const inspectorFrontendHost = sinon.createStubInstance(Host.InspectorFrontendHost.InspectorFrontendHostStub);
53+
const projectSettingsModel = sinon.createStubInstance(ProjectSettings.ProjectSettingsModel.ProjectSettingsModel);
54+
sinon.stub(projectSettingsModel, 'projectSettings').value({workspace: {root, uuid}});
55+
56+
const manager = AutomaticFileSystemManager.instance({
57+
forceNew: true,
58+
hostConfig,
59+
inspectorFrontendHost,
60+
projectSettingsModel,
61+
});
62+
63+
assert.deepEqual(manager.automaticFileSystem, {root, uuid, state: 'connecting'});
64+
assert(inspectorFrontendHost.connectAutomaticFileSystem.calledOnceWith(root, uuid, false));
65+
});
66+
67+
it('reflects state correctly when automatic connection succeeds', async () => {
68+
const inspectorFrontendHost = sinon.createStubInstance(Host.InspectorFrontendHost.InspectorFrontendHostStub);
69+
const projectSettingsModel = sinon.createStubInstance(ProjectSettings.ProjectSettingsModel.ProjectSettingsModel);
70+
sinon.stub(projectSettingsModel, 'projectSettings').value({workspace: {root, uuid}});
71+
72+
const manager = AutomaticFileSystemManager.instance({
73+
forceNew: true,
74+
hostConfig,
75+
inspectorFrontendHost,
76+
projectSettingsModel,
77+
});
78+
const [, , , setupCallback] = inspectorFrontendHost.connectAutomaticFileSystem.lastCall.args;
79+
setupCallback({success: true});
80+
const automaticFileSystem = await manager.once(AUTOMATIC_FILE_SYSTEM_CHANGED);
81+
82+
assert.strictEqual(automaticFileSystem, manager.automaticFileSystem);
83+
assert.deepEqual(automaticFileSystem, {root, uuid, state: 'connected'});
84+
});
85+
86+
it('reflects state correctly when automatic connection fails', async () => {
87+
const inspectorFrontendHost = sinon.createStubInstance(Host.InspectorFrontendHost.InspectorFrontendHostStub);
88+
const projectSettingsModel = sinon.createStubInstance(ProjectSettings.ProjectSettingsModel.ProjectSettingsModel);
89+
sinon.stub(projectSettingsModel, 'projectSettings').value({workspace: {root, uuid}});
90+
91+
const manager = AutomaticFileSystemManager.instance({
92+
forceNew: true,
93+
hostConfig,
94+
inspectorFrontendHost,
95+
projectSettingsModel,
96+
});
97+
const [, , , setupCallback] = inspectorFrontendHost.connectAutomaticFileSystem.lastCall.args;
98+
setupCallback({success: false});
99+
const automaticFileSystem = await manager.once(AUTOMATIC_FILE_SYSTEM_CHANGED);
100+
101+
assert.strictEqual(automaticFileSystem, manager.automaticFileSystem);
102+
assert.deepEqual(automaticFileSystem, {root, uuid, state: 'disconnected'});
103+
});
104+
105+
it('performs first-time setup of automatic file system correctly', async () => {
106+
const inspectorFrontendHost = sinon.createStubInstance(Host.InspectorFrontendHost.InspectorFrontendHostStub);
107+
const projectSettingsModel = sinon.createStubInstance(ProjectSettings.ProjectSettingsModel.ProjectSettingsModel);
108+
sinon.stub(projectSettingsModel, 'projectSettings').value({workspace: {root, uuid}});
109+
const manager = AutomaticFileSystemManager.instance({
110+
forceNew: true,
111+
hostConfig,
112+
inspectorFrontendHost,
113+
projectSettingsModel,
114+
});
115+
const [, , , setupCallback] = inspectorFrontendHost.connectAutomaticFileSystem.lastCall.args;
116+
setupCallback({success: false});
117+
await manager.once(AUTOMATIC_FILE_SYSTEM_CHANGED);
118+
inspectorFrontendHost.connectAutomaticFileSystem.reset();
119+
const connectingPromise = manager.once(AUTOMATIC_FILE_SYSTEM_CHANGED);
120+
121+
const successPromise = manager.connectAutomaticFileSystem(/* addIfMissing= */ true);
122+
assert.strictEqual(manager.automaticFileSystem, await connectingPromise);
123+
assert.deepEqual(manager.automaticFileSystem, {root, uuid, state: 'connecting'});
124+
const connectedPromise = manager.once(AUTOMATIC_FILE_SYSTEM_CHANGED);
125+
const [, , , connectCallback] = inspectorFrontendHost.connectAutomaticFileSystem.lastCall.args;
126+
connectCallback({success: true});
127+
128+
const [success, automaticFileSystem] = await Promise.all([successPromise, connectedPromise]);
129+
assert.isTrue(success);
130+
assert.strictEqual(manager.automaticFileSystem, automaticFileSystem);
131+
assert.deepEqual(manager.automaticFileSystem, {root, uuid, state: 'connected'});
132+
});
133+
134+
it('correctly disconnects automatic file systems', async () => {
135+
const inspectorFrontendHost = sinon.createStubInstance(Host.InspectorFrontendHost.InspectorFrontendHostStub);
136+
const projectSettingsModel = sinon.createStubInstance(ProjectSettings.ProjectSettingsModel.ProjectSettingsModel);
137+
sinon.stub(projectSettingsModel, 'projectSettings').value({workspace: {root, uuid}});
138+
const manager = AutomaticFileSystemManager.instance({
139+
forceNew: true,
140+
hostConfig,
141+
inspectorFrontendHost,
142+
projectSettingsModel,
143+
});
144+
const [, , , setupCallback] = inspectorFrontendHost.connectAutomaticFileSystem.lastCall.args;
145+
setupCallback({success: true});
146+
await manager.once(AUTOMATIC_FILE_SYSTEM_CHANGED);
147+
const automaticFileSystemPromise = manager.once(AUTOMATIC_FILE_SYSTEM_CHANGED);
148+
149+
manager.disconnectedAutomaticFileSystem();
150+
151+
const automaticFileSystem = await automaticFileSystemPromise;
152+
assert(inspectorFrontendHost.disconnectAutomaticFileSystem.calledOnceWith(root));
153+
assert.strictEqual(manager.automaticFileSystem, automaticFileSystem);
154+
assert.deepEqual(manager.automaticFileSystem, {root, uuid, state: 'disconnected'});
155+
});
156+
});

0 commit comments

Comments
 (0)