Skip to content

Commit 6ec9229

Browse files
ergunshDevtools-frontend LUCI CQ
authored andcommitted
[Changes] Add CombinedDiffView and render it when patching enabled
Bug: 393265936 Change-Id: Ia0ff8777371da43d4d8f13a617ac391c6d7fa438 Reviewed-on: https://chromium-review.googlesource.com/c/devtools/devtools-frontend/+/6291327 Reviewed-by: Alex Rudenko <[email protected]> Commit-Queue: Ergün Erdoğmuş <[email protected]>
1 parent b0e0d4c commit 6ec9229

File tree

8 files changed

+401
-16
lines changed

8 files changed

+401
-16
lines changed

config/gni/devtools_grd_files.gni

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1343,8 +1343,10 @@ grd_files_debug_sources = [
13431343
"front_end/panels/browser_debugger/xhrBreakpointsSidebarPane.css.js",
13441344
"front_end/panels/changes/ChangesSidebar.js",
13451345
"front_end/panels/changes/ChangesView.js",
1346+
"front_end/panels/changes/CombinedDiffView.js",
13461347
"front_end/panels/changes/changesSidebar.css.js",
13471348
"front_end/panels/changes/changesView.css.js",
1349+
"front_end/panels/changes/combinedDiffView.css.js",
13481350
"front_end/panels/console/ConsoleContextSelector.js",
13491351
"front_end/panels/console/ConsoleFilter.js",
13501352
"front_end/panels/console/ConsoleFormat.js",

front_end/panels/changes/BUILD.gn

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,13 +12,15 @@ generate_css("css_files") {
1212
sources = [
1313
"changesSidebar.css",
1414
"changesView.css",
15+
"combinedDiffView.css",
1516
]
1617
}
1718

1819
devtools_module("changes") {
1920
sources = [
2021
"ChangesSidebar.ts",
2122
"ChangesView.ts",
23+
"CombinedDiffView.ts",
2224
]
2325

2426
deps = [
@@ -67,10 +69,15 @@ devtools_entrypoint("meta") {
6769
ts_library("unittests") {
6870
testonly = true
6971

70-
sources = [ "changes.test.ts" ]
72+
sources = [
73+
"CombinedDiffView.test.ts",
74+
"changes.test.ts",
75+
]
7176

7277
deps = [
7378
":bundle",
79+
"../../models/workspace:bundle",
80+
"../../models/workspace_diff:bundle",
7481
"../../testing",
7582
]
7683
}

front_end/panels/changes/ChangesView.ts

Lines changed: 35 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import * as Common from '../../core/common/common.js';
88
import * as Host from '../../core/host/host.js';
99
import * as i18n from '../../core/i18n/i18n.js';
1010
import type * as Platform from '../../core/platform/platform.js';
11+
import * as Root from '../../core/root/root.js';
1112
import type * as Formatter from '../../models/formatter/formatter.js';
1213
import type * as Workspace from '../../models/workspace/workspace.js';
1314
import * as WorkspaceDiff from '../../models/workspace_diff/workspace_diff.js';
@@ -19,6 +20,7 @@ import * as VisualLogging from '../../ui/visual_logging/visual_logging.js';
1920

2021
import {ChangesSidebar, Events} from './ChangesSidebar.js';
2122
import changesViewStyles from './changesView.css.js';
23+
import * as CombinedDiffView from './CombinedDiffView.js';
2224

2325
const CHANGES_VIEW_URL = 'https://developer.chrome.com/docs/devtools/changes' as Platform.DevToolsPath.UrlString;
2426

@@ -79,8 +81,9 @@ export class ChangesView extends UI.Widget.VBox {
7981
#learnMoreLinkElement?: HTMLElement;
8082
private readonly diffContainer: HTMLElement;
8183
private readonly toolbar: UI.Toolbar.Toolbar;
82-
private readonly diffStats: UI.Toolbar.ToolbarText;
83-
private readonly diffView: DiffView.DiffView.DiffView;
84+
private readonly diffStats?: UI.Toolbar.ToolbarText;
85+
private readonly diffView?: DiffView.DiffView.DiffView;
86+
private readonly combinedDiffView?: CombinedDiffView.CombinedDiffView;
8487

8588
constructor() {
8689
super(true);
@@ -98,6 +101,7 @@ export class ChangesView extends UI.Widget.VBox {
98101

99102
this.workspaceDiff = WorkspaceDiff.WorkspaceDiff.workspaceDiff();
100103
this.changesSidebar = new ChangesSidebar(this.workspaceDiff);
104+
// TODO(ergunsh): Add scroll to singular diffs when they are clicked on sidebar.
101105
this.changesSidebar.addEventListener(
102106
Events.SELECTED_UI_SOURCE_CODE_CHANGED, this.selectedUISourceCodeChanged, this);
103107
splitWidget.setSidebarWidget(this.changesSidebar);
@@ -106,20 +110,29 @@ export class ChangesView extends UI.Widget.VBox {
106110

107111
this.diffContainer = mainWidget.element.createChild('div', 'diff-container');
108112
UI.ARIAUtils.markAsTabpanel(this.diffContainer);
109-
this.diffContainer.addEventListener('click', event => this.click(event));
110-
111-
this.diffView = this.diffContainer.appendChild(new DiffView.DiffView.DiffView());
113+
if (shouldRenderCombinedDiffView()) {
114+
// TODO(ergunsh): Handle clicks from CombinedDiffView too.
115+
this.combinedDiffView = new CombinedDiffView.CombinedDiffView();
116+
this.combinedDiffView.workspaceDiff = this.workspaceDiff;
117+
this.combinedDiffView.show(this.diffContainer);
118+
} else {
119+
this.diffView = this.diffContainer.appendChild(new DiffView.DiffView.DiffView());
120+
this.diffContainer.addEventListener('click', event => this.click(event));
121+
}
112122

113123
this.toolbar = mainWidget.element.createChild('devtools-toolbar', 'changes-toolbar');
114124
this.toolbar.setAttribute('jslog', `${VisualLogging.toolbar()}`);
115125
this.toolbar.appendToolbarItem(UI.Toolbar.Toolbar.createActionButton('changes.revert'));
116-
this.diffStats = new UI.Toolbar.ToolbarText('');
117-
this.toolbar.appendToolbarItem(this.diffStats);
118-
119-
this.toolbar.appendToolbarItem(new UI.Toolbar.ToolbarSeparator());
120-
this.toolbar.appendToolbarItem(UI.Toolbar.Toolbar.createActionButton('changes.copy', {
121-
label: i18nLazyString(UIStrings.copy),
122-
}));
126+
if (!shouldRenderCombinedDiffView()) {
127+
// TODO(ergunsh): We do not show the diff stats & the copy button for the combined view.
128+
this.diffStats = new UI.Toolbar.ToolbarText('');
129+
this.toolbar.appendToolbarItem(this.diffStats);
130+
131+
this.toolbar.appendToolbarItem(new UI.Toolbar.ToolbarSeparator());
132+
this.toolbar.appendToolbarItem(UI.Toolbar.Toolbar.createActionButton('changes.copy', {
133+
label: i18nLazyString(UIStrings.copy),
134+
}));
135+
}
123136

124137
this.hideDiff(i18nString(UIStrings.noChanges), i18nString(UIStrings.changesViewDescription), CHANGES_VIEW_URL);
125138
this.selectedUISourceCodeChanged();
@@ -235,7 +248,7 @@ export class ChangesView extends UI.Widget.VBox {
235248
}
236249

237250
private hideDiff(header: string, text: string, link?: Platform.DevToolsPath.UrlString): void {
238-
this.diffStats.setText('');
251+
this.diffStats?.setText('');
239252
this.toolbar.setEnabled(false);
240253
this.diffContainer.style.display = 'none';
241254
this.emptyWidget.header = header;
@@ -257,12 +270,14 @@ export class ChangesView extends UI.Widget.VBox {
257270
if (!diff || (diff.length === 1 && diff[0][0] === Diff.Diff.Operation.Equal)) {
258271
this.hideDiff(i18nString(UIStrings.noChanges), i18nString(UIStrings.changesViewDescription), CHANGES_VIEW_URL);
259272
} else {
260-
this.diffStats.setText(diffStats(diff));
273+
this.diffStats?.setText(diffStats(diff));
261274
this.toolbar.setEnabled(true);
262275
this.emptyWidget.hideWidget();
263276
const mimeType = (this.selectedUISourceCode as Workspace.UISourceCode.UISourceCode).mimeType();
264277
this.diffContainer.style.display = 'block';
265-
this.diffView.data = {diff, mimeType};
278+
if (this.diffView) {
279+
this.diffView.data = {diff, mimeType};
280+
}
266281
}
267282
}
268283
}
@@ -284,3 +299,8 @@ export class ActionDelegate implements UI.ActionRegistration.ActionDelegate {
284299
return false;
285300
}
286301
}
302+
303+
function shouldRenderCombinedDiffView(): boolean {
304+
const {hostConfig} = Root.Runtime;
305+
return Boolean(hostConfig.devToolsFreestyler?.patching);
306+
}
Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
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 Platform from '../../core/platform/platform.js';
6+
import * as SDK from '../../core/sdk/sdk.js';
7+
import * as Bindings from '../../models/bindings/bindings.js';
8+
import * as Breakpoints from '../../models/breakpoints/breakpoints.js';
9+
import * as Persistence from '../../models/persistence/persistence.js';
10+
import * as Workspace from '../../models/workspace/workspace.js';
11+
import * as WorkspaceDiff from '../../models/workspace_diff/workspace_diff.js';
12+
import {describeWithEnvironment, updateHostConfig} from '../../testing/EnvironmentHelpers.js';
13+
import {expectCall} from '../../testing/ExpectStubCall.js';
14+
import {createFileSystemUISourceCode} from '../../testing/UISourceCodeHelpers.js';
15+
16+
import * as CombinedDiffView from './CombinedDiffView.js';
17+
18+
const {urlString} = Platform.DevToolsPath;
19+
20+
const URL = urlString`file:///tmp/example.html`;
21+
22+
function createWorkspace(): Workspace.Workspace.WorkspaceImpl {
23+
return Workspace.Workspace.WorkspaceImpl.instance({forceNew: true});
24+
}
25+
26+
function createWorkspaceDiff({workspace}: {workspace: Workspace.Workspace.WorkspaceImpl}):
27+
WorkspaceDiff.WorkspaceDiff.WorkspaceDiffImpl {
28+
const debuggerWorkspaceBinding = Bindings.DebuggerWorkspaceBinding.DebuggerWorkspaceBinding.instance({
29+
forceNew: true,
30+
targetManager: SDK.TargetManager.TargetManager.instance(),
31+
resourceMapping:
32+
new Bindings.ResourceMapping.ResourceMapping(SDK.TargetManager.TargetManager.instance(), workspace),
33+
});
34+
const breakpointManager = Breakpoints.BreakpointManager.BreakpointManager.instance({
35+
forceNew: true,
36+
targetManager: SDK.TargetManager.TargetManager.instance(),
37+
workspace,
38+
debuggerWorkspaceBinding,
39+
});
40+
Persistence.Persistence.PersistenceImpl.instance({forceNew: true, workspace, breakpointManager});
41+
Persistence.NetworkPersistenceManager.NetworkPersistenceManager.instance({forceNew: true, workspace});
42+
return new WorkspaceDiff.WorkspaceDiff.WorkspaceDiffImpl(workspace);
43+
}
44+
45+
async function createCombinedDiffView({workspaceDiff}: {workspaceDiff: WorkspaceDiff.WorkspaceDiff.WorkspaceDiffImpl}) {
46+
const view = sinon.stub<[CombinedDiffView.ViewInput, unknown, HTMLElement]>();
47+
const combinedDiffView = new CombinedDiffView.CombinedDiffView(undefined, view);
48+
combinedDiffView.workspaceDiff = workspaceDiff;
49+
50+
/**
51+
* Triggers the action and returns args of the next view function
52+
* call.
53+
*/
54+
async function expectViewUpdate(action: () => void) {
55+
const result = expectCall(view);
56+
action();
57+
const viewArgs = await result;
58+
return viewArgs[0];
59+
}
60+
61+
const initialViewInput = await expectViewUpdate(() => {
62+
combinedDiffView.markAsRoot();
63+
combinedDiffView.show(document.body);
64+
});
65+
66+
return {initialViewInput, combinedDiffView, view, expectViewUpdate};
67+
}
68+
69+
describeWithEnvironment('CombinedDiffView', () => {
70+
let workspaceDiff: WorkspaceDiff.WorkspaceDiff.WorkspaceDiffImpl;
71+
let uiSourceCode: Workspace.UISourceCode.UISourceCode;
72+
beforeEach(() => {
73+
// This is needed for tracking file system UI source codes.
74+
updateHostConfig({devToolsImprovedWorkspaces: {enabled: true}});
75+
const workspace = createWorkspace();
76+
workspaceDiff = createWorkspaceDiff({workspace});
77+
({uiSourceCode} =
78+
createFileSystemUISourceCode({url: URL, content: 'const data={original:true}', mimeType: 'text/javascript'}));
79+
});
80+
81+
it('should render modified UISourceCode from a workspaceDiff on initial render', async () => {
82+
uiSourceCode.setWorkingCopy('const data={original:false}');
83+
const {initialViewInput} = await createCombinedDiffView({workspaceDiff});
84+
85+
assert.lengthOf(initialViewInput.singleDiffViewInputs, 1);
86+
});
87+
88+
it('should render newly modified UISourceCode from a workspaceDiff', async () => {
89+
const {initialViewInput, expectViewUpdate} = await createCombinedDiffView({workspaceDiff});
90+
assert.lengthOf(initialViewInput.singleDiffViewInputs, 0);
91+
92+
const viewInput = await expectViewUpdate(() => {
93+
uiSourceCode.setWorkingCopy('const data={original:false}');
94+
});
95+
96+
assert.lengthOf(viewInput.singleDiffViewInputs, 1);
97+
});
98+
99+
it('should re-render modified UISourceCode from a workspaceDiff', async () => {
100+
uiSourceCode.setWorkingCopy('const data={original:false}');
101+
const {initialViewInput, expectViewUpdate} = await createCombinedDiffView({workspaceDiff});
102+
assert.lengthOf(initialViewInput.singleDiffViewInputs, 1);
103+
104+
await expectViewUpdate(() => {
105+
uiSourceCode.setWorkingCopy('const data={modified:true}');
106+
});
107+
});
108+
});

0 commit comments

Comments
 (0)