Skip to content

feat: enable navigation between submenus via arrow keys - #52374

Open
mlaurencin wants to merge 2 commits into
mainfrom
feat-menu-arrows-switch
Open

feat: enable navigation between submenus via arrow keys#52374
mlaurencin wants to merge 2 commits into
mainfrom
feat-menu-arrows-switch

Conversation

@mlaurencin

@mlaurencin mlaurencin commented Jul 16, 2026

Copy link
Copy Markdown
Member

Description of Change

Closes #52055

Navigation can now occur between menu bar headers using the left and right arrows keys. Windows conventions were followed when determining situations when such navigation should be disallowed (e.g. navigating to the left when the currently selected item is a submenu).

Checklist

Release Notes

Notes: Added functionality to allow navigation between menu bar headers via left and right arrow keys

@mlaurencin mlaurencin added the semver/minor backwards-compatible functionality label Jul 16, 2026
@mlaurencin
mlaurencin requested a review from a team as a code owner July 16, 2026 22:17
@mlaurencin mlaurencin added the target/44-x-y PR should also be added to the "44-x-y" branch. label Jul 16, 2026
@mlaurencin
mlaurencin force-pushed the feat-menu-arrows-switch branch from 8a85fc4 to 5ec4905 Compare July 17, 2026 16:00

@jkleinsc jkleinsc left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

API LGTM

@jkleinsc jkleinsc left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Overall looks good, just some minor nits.

Also, I do think that this patch probably wouldn't be accepted upstream since Chromium doesn't use top level menus on Windows.

Comment thread shell/browser/ui/views/menu_delegate.cc Outdated

bool MenuDelegate::GetSiblingMenuByDirection(bool next) {
views::MenuButton* button = nullptr;
ElectronMenuModel* model = nullptr;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is never used

Comment thread shell/browser/ui/views/menu_bar.cc Outdated

bool MenuBar::GetSiblingMenuButtonByDirection(int current_id,
bool next,
ElectronMenuModel** menu_model,

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The lone caller to this method doesn't do anything with the modified ElectronMenuModel, so is this parameter really necessary?

Comment thread shell/browser/ui/views/menu_delegate.cc Outdated
views::MenuButton* button = nullptr;
ElectronMenuModel* model = nullptr;
if (!menu_bar_->GetSiblingMenuButtonByDirection(id_, next, &model, &button) ||
button->GetID() == id_) {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

menu_bar_->GetSiblingMenuButtonByDirection already does this check

Comment thread shell/browser/ui/views/menu_delegate.cc
w.focus();
// Give the compositor/window manager a moment to make the window active so
// that robotjs key events are routed to it.
await setTimeout(500);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

using setTimeout(s) in tests is a code smell for flaky tests because depending on CI sometimes the timeout isn't enough. If possible we should use the waitUntil helper to wait until a certain condition is satisfied.

@jkleinsc jkleinsc Jul 21, 2026

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

eg

diff --git a/spec/api-menu-keyboard-spec.ts b/spec/api-menu-keyboard-spec.ts
index 7fde930474..b727f4c0e9 100644
--- a/spec/api-menu-keyboard-spec.ts
+++ b/spec/api-menu-keyboard-spec.ts
@@ -5,7 +5,7 @@ import { expect } from 'chai';
 import { setTimeout } from 'node:timers/promises';
 
 import { hasCapturableScreen } from './lib/screen-helpers';
-import { ifdescribe } from './lib/spec-helpers';
+import { ifdescribe, waitUntil } from './lib/spec-helpers';
 import { closeAllWindows } from './lib/window-helpers';
 
 // These tests are skipped when robotjs or a capturable screen is unavailable
@@ -147,7 +147,7 @@ ifdescribe(process.platform !== 'darwin')('menu bar keyboard sibling switching',
       'enter' // activate Beta's first item
     ]);
 
-    await setTimeout(KEY_DELAY);
+    await waitUntil(() => lastClicked !== null);
     expect(lastClicked).to.equal('beta-leaf');
   });
 
@@ -160,7 +160,7 @@ ifdescribe(process.platform !== 'darwin')('menu bar keyboard sibling switching',
       'enter' // activate Gamma's first item
     ]);
 
-    await setTimeout(KEY_DELAY);
+    await waitUntil(() => lastClicked !== null);
     expect(lastClicked).to.equal('gamma-item');
   });
 
@@ -173,7 +173,7 @@ ifdescribe(process.platform !== 'darwin')('menu bar keyboard sibling switching',
       'enter' // activate Alpha's first item
     ]);
 
-    await setTimeout(KEY_DELAY);
+    await waitUntil(() => lastClicked !== null);
     expect(lastClicked).to.equal('alpha-item');
   });
 
@@ -187,7 +187,7 @@ ifdescribe(process.platform !== 'darwin')('menu bar keyboard sibling switching',
       'enter' // activate Beta More Item
     ]);
 
-    await setTimeout(KEY_DELAY);
+    await waitUntil(() => lastClicked !== null);
     expect(lastClicked).to.equal('beta-more-item');
   });
 
@@ -202,7 +202,7 @@ ifdescribe(process.platform !== 'darwin')('menu bar keyboard sibling switching',
       'enter' // activate Beta Leaf
     ]);
 
-    await setTimeout(KEY_DELAY);
+    await waitUntil(() => lastClicked !== null);
     expect(lastClicked).to.equal('beta-leaf');
   });
 
@@ -217,7 +217,7 @@ ifdescribe(process.platform !== 'darwin')('menu bar keyboard sibling switching',
       'enter' // activate Beta More Item
     ]);
 
-    await setTimeout(KEY_DELAY);
+    await waitUntil(() => lastClicked !== null);
     expect(lastClicked).to.equal('beta-more-item');
   });
 });

Comment thread spec/api-menu-keyboard-spec.ts Outdated
'enter' // activate Gamma's first item
]);

await setTimeout(KEY_DELAY);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

same comment as above about setTimeout vs waitUntil.

Comment thread spec/api-menu-keyboard-spec.ts Outdated
'enter' // activate Alpha's first item
]);

await setTimeout(KEY_DELAY);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

same comment as above about setTimeout vs waitUntil.

Comment thread spec/api-menu-keyboard-spec.ts Outdated
'enter' // activate Beta More Item
]);

await setTimeout(KEY_DELAY);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

same comment as above about setTimeout vs waitUntil.

Comment thread spec/api-menu-keyboard-spec.ts Outdated
'enter' // activate Beta Leaf
]);

await setTimeout(KEY_DELAY);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

same comment as above about setTimeout vs waitUntil.

Comment thread spec/api-menu-keyboard-spec.ts Outdated
'enter' // activate Beta More Item
]);

await setTimeout(KEY_DELAY);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

same comment as above about setTimeout vs waitUntil.

@erickzhao erickzhao left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

API LGTM

Co-Authored-By: Claude <noreply@anthropic.com>
Generated-By: GitHub Copilot

make lint happy

remove unused menu model and button references

add helper ScheduleSwitchToButton

update setTimeout to waitUntil

remove extra listed patch
@mlaurencin
mlaurencin force-pushed the feat-menu-arrows-switch branch from 8841903 to 32d97cc Compare July 27, 2026 19:21

@jkleinsc jkleinsc left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Test should be updated to use waitUntil instead of setTimeout. My comment on this didn't render properly, so I have updated it to more clearly explain the change.

@ckerr ckerr left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

API LGTM

};

before(async function () {
if (!robot || !robot.keyTap || !hasCapturableScreen()) {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These tests are intended to cover Linux, but I think they never run on Linux because hasCapturableScreen() is hardcoded to return false on Linux CI. We don't need screen capture for this test, and xvfb should be able to handle RobotJS, so let's try enabling the tests on Linux:

Suggested change
if (!robot || !robot.keyTap || !hasCapturableScreen()) {
if (!robot?.keyTap) {

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My understanding though is that the virtual display we have for Linux CI is 0x0, so the focus + mouseClick combination used to ensure each test has proper initial setup would not function. I may be missing an alternative, but I think tests for this functionality cannot be run with our current CI and can only be tested locally.

@mlaurencin

Copy link
Copy Markdown
Member Author

Test should be updated to use waitUntil instead of setTimeout. My comment on this didn't render properly, so I have updated it to more clearly explain the change.

I have an updated commit to address the setTimeout usage in window creation that I could push, but I think my previous Linux comment pertains to this as well since Windows virtual displays on CI are also 0x0.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

api-review/approved ✅ semver/minor backwards-compatible functionality target/44-x-y PR should also be added to the "44-x-y" branch.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Windows] Left/right arrow keys do not navigate between open submenus

5 participants