Chrome · DevTools
CVE-2026-14117
Logic Error in DevTools
Overview
Low
Severity
—
CVSS
No
Exploited ITW
Fixed
Fix Status
Files Changed
front_end/panels/sources/PersistenceAction.test.tsfront_end/panels/sources/PersistenceActions.ts
Patch
From 6c2d684f3933381f002c34d44e05f4b19b4da3a1 Mon Sep 17 00:00:00 2001 From: Wolfgang Beyer <[email protected]> Date: Wed, 27 May 2026 10:39:53 +0000 Subject: [PATCH] Limit 'open in containing folder' to FileSystem Bug: 513751020 Change-Id: I69496f779cea9a00f29a3c7821b838c789d37124 Reviewed-on: https://chromium-review.googlesource.com/c/devtools/devtools-frontend/+/7867926 Auto-Submit: Wolfgang Beyer <[email protected]> Reviewed-by: Alex Rudenko <[email protected]> Commit-Queue: Alex Rudenko <[email protected]> Commit-Queue: Wolfgang Beyer <[email protected]> --- diff --git a/front_end/panels/sources/PersistenceAction.test.ts b/front_end/panels/sources/PersistenceAction.test.ts index 974bcd2..c786ec5 100644 --- a/front_end/panels/sources/PersistenceAction.test.ts +++ b/front_end/panels/sources/PersistenceAction.test.ts @@ -94,4 +94,96 @@ /* forceSaveAs=*/ true, ]); }); + + describe('Open in containing folder', () => { + function createMockUISourceCode( + url: Platform.DevToolsPath.UrlString, projectType: Workspace.Workspace.projectTypes): + sinon.SinonStubbedInstance<Workspace.UISourceCode.UISourceCode> { + const uiSourceCode = sinon.createStubInstance(Workspace.UISourceCode.UISourceCode, { + contentURL: url, + contentType: Common.ResourceType.resourceTypes.Script, + }); + const stubProject = sinon.createStubInstance( + Bindings.ContentProviderBasedProject.ContentProviderBasedProject, {type: projectType}); + uiSourceCode.project.returns(stubProject); + return uiSourceCode; + } + + function setupSingletons( + uiSourceCode: Workspace.UISourceCode.UISourceCode|null, + binding: Persistence.Persistence.PersistenceBinding|null): void { + const mockWorkspaceImpl = sinon.createStubInstance(Workspace.Workspace.WorkspaceImpl); + sinon.stub(Workspace.Workspace.WorkspaceImpl, 'instance').returns(mockWorkspaceImpl); + mockWorkspaceImpl.uiSourceCodeForURL.returns(uiSourceCode); + + const mockPersistenceImpl = sinon.createStubInstance(Persistence.Persistence.PersistenceImpl); + sinon.stub(Persistence.Persistence.PersistenceImpl, 'instance').returns(mockPersistenceImpl); + mockPersistenceImpl.binding.returns(binding); + } + + async function getOpenInFolderMenuItem(targetUiSourceCode: Workspace.UISourceCode.UISourceCode): + Promise<UI.ContextMenu.Item|undefined> { + const event = new Event('contextmenu'); + sinon.stub(event, 'target').value(document); + const contextMenu = new UI.ContextMenu.ContextMenu(event); + const menuProvider = new Sources.PersistenceActions.ContextMenuProvider(); + + menuProvider.appendApplicableItems(event, contextMenu, targetUiSourceCode); + await contextMenu.show(); + + return contextMenu.revealSection().items.find( + item => item.buildDescriptor().label === 'Open in containing folder'); + } + + it('appends the item for direct workspace files (FileSystem project type)', async () => { + const uiSourceCode = createMockUISourceCode( + urlString`file:///path/to/project/file.js`, Workspace.Workspace.projectTypes.FileSystem); + setupSingletons(uiSourceCode, null); + + const openFolderItem = await getOpenInFolderMenuItem(uiSourceCode); + assert.exists(openFolderItem); + }); + + it('appends the item for network files mapped to workspace files (persistence binding exists)', async () => { + const networkUiSourceCode = + createMockUISourceCode(urlString`https://example.com/file.js`, Workspace.Workspace.projectTypes.Network); + const fileSystemUiSourceCode = createMockUISourceCode( + urlString`file:///path/to/project/file.js`, Workspace.Workspace.projectTypes.FileSystem); + + const binding = new Persistence.Persistence.PersistenceBinding(networkUiSourceCode, fileSystemUiSourceCode); + setupSingletons(networkUiSourceCode, binding); + + const openFolderItem = await getOpenInFolderMenuItem(networkUiSourceCode); + assert.exists(openFolderItem); + }); + + it('does not append the item for local file page resources that are not in a FileSystem project type', async () => { + const uiSourceCode = + createMockUISourceCode(urlString`file:///path/to/project/file.js`, Workspace.Workspace.projectTypes.Network); + setupSingletons(uiSourceCode, null); + + const openFolderItem = await getOpenInFolderMenuItem(uiSourceCode); + assert.isUndefined(openFolderItem); + }); + + it('appends the item if contentProvider is a FileSystem UISourceCode but uiSourceCodeForURL returns a Network UISourceCode (same URL, not bound)', + async () => { + const fileSystemUiSourceCode = createMockUISourceCode( + urlString`file:///path/to/project/file.js`, Workspace.Workspace.projectTypes.FileSystem); + const networkUiSourceCode = createMockUISourceCode( + urlString`file:///path/to/project/file.js`, Workspace.Workspace.projectTypes.Network); + + // uiSourceCodeForURL returns the Network UISourceCode due to order. + const mockWorkspaceImpl = sinon.createStubInstance(Workspace.Workspace.WorkspaceImpl); + sinon.stub(Workspace.Workspace.WorkspaceImpl, 'instance').returns(mockWorkspaceImpl); + mockWorkspaceImpl.uiSourceCodeForURL.returns(networkUiSourceCode); + + const mockPersistenceImpl = sinon.createStubInstance(Persistence.Persistence.PersistenceImpl); + sinon.stub(Persistence.Persistence.PersistenceImpl, 'instance').returns(mockPersistenceImpl); + mockPersistenceImpl.binding.returns(null); + + const openFolderItem = await getOpenInFolderMenuItem(fileSystemUiSourceCode); + assert.exists(openFolderItem); + }); + }); }); diff --git a/front_end/panels/sources/PersistenceActions.ts b/front_end/panels/sources/PersistenceActions.ts index ac0923d..316fa5a 100644 --- a/front_end/panels/sources/PersistenceActions.ts +++ b/front_end/panels/sources/PersistenceActions.ts @@ -115,14 +115,28 @@ const networkPersistenceManager = Persistence.NetworkPersistenceManager.NetworkPersistenceManager.instance(); const binding = uiSourceCode && Persistence.Persistence.PersistenceImpl.instance().binding(uiSourceCode); - const fileURL = binding ? binding.fileSystem.contentURL() : contentProvider.contentURL(); + // If contentProvider is already a FileSystem UISourceCode, there's a small chance that uiSourceCodeForURL + // might return a Network UISourceCode instead (e.g. if they share the same URL but aren't currently bound + // and the Network project represents the URL first in the lookup list). + // In this case, the binding will be null. To ensure "Open in containing folder" is still appended when + // right-clicking the FileSystem file in the Workspace tree, we fall back to contentProvider. + const fileSystemUISourceCode = binding ? + binding.fileSystem : + (contentProvider instanceof Workspace.UISourceCode.UISourceCode && + contentProvider.project().type() === Workspace.Workspace.projectTypes.FileSystem ? + contentProvider : + uiSourceCode); - if (Common.ParsedURL.schemeIs(fileURL, 'file:')) { - const path = Common.ParsedURL.ParsedURL.urlToRawPathString(fileURL, Host.Platform.isWin()); - contextMenu.revealSection().appendItem( - i18nString(UIStrings.openInContainingFolder), - () => Host.InspectorFrontendHost.InspectorFrontendHostInstance.showItemInFolder(path), - {jslogContext: 'open-in-containing-folder'}); + if (fileSystemUISourceCode && + fileSystemUISourceCode.project().type() === Workspace.Workspace.projectTypes.FileSystem) { + const fileURL = fileSystemUISourceCode.contentURL(); + if (Common.ParsedURL.schemeIs(fileURL, 'file:')) { + const path = Common.ParsedURL.ParsedURL.urlToRawPathString(fileURL, Host.Platform.isWin()); + contextMenu.revealSection().appendItem( + i18nString(UIStrings.openInContainingFolder), + () => Host.InspectorFrontendHost.InspectorFrontendHostInstance.showItemInFolder(path), + {jslogContext: 'open-in-containing-folder'}); + } } if (contentProvider instanceof Workspace.UISourceCode.UISourceCode &&
Loading diff…
Original Bug Report
The reporter's bug is still restricted on the tracker. Chrome de-restricts security bugs ~30–90 days after the fix ships; a later run will backfill it here.
References
On This Page