feat: add focusOnNavigation flag to WebPreferences (#49425)

* feat: add focusOnNavigation webPreference

* WebContentsView tests

* fix

* fix
This commit is contained in:
Kyle Cutler
2026-01-23 11:29:34 -08:00
committed by GitHub
parent 8a11d5afb1
commit d5de8883a2
7 changed files with 77 additions and 0 deletions

View File

@@ -157,6 +157,8 @@
`WebContents` when the preferred size changes. Default is `false`.
* `transparent` boolean (optional) - Whether to enable background transparency for the guest page. Default is `true`. **Note:** The guest page's text and background colors are derived from the [color scheme](https://developer.mozilla.org/en-US/docs/Web/CSS/color-scheme) of its root element. When transparency is enabled, the text color will still change accordingly but the background will remain transparent.
* `enableDeprecatedPaste` boolean (optional) _Deprecated_ - Whether to enable the `paste` [execCommand](https://developer.mozilla.org/en-US/docs/Web/API/Document/execCommand). Default is `false`.
* `focusOnNavigation` boolean (optional) - Whether to focus the WebContents
when navigating. Default is `true`.
[chrome-content-scripts]: https://developer.chrome.com/extensions/content_scripts#execution-environment
[runtime-enabled-features]: https://source.chromium.org/chromium/chromium/src/+/main:third_party/blink/renderer/platform/runtime_enabled_features.json5

View File

@@ -2098,6 +2098,10 @@ void WebContents::ReadyToCommitNavigation(
// Only focus for top-level contents.
if (type_ != Type::kBrowserWindow)
return;
// Don't focus if focusOnNavigation is disabled.
auto* prefs = WebContentsPreferences::From(web_contents());
if (prefs && !prefs->ShouldFocusOnNavigation())
return;
web_contents()->SetInitialFocus();
}

View File

@@ -149,6 +149,7 @@ void WebContentsPreferences::Clear() {
preload_path_ = std::nullopt;
v8_cache_options_ = blink::mojom::V8CacheOptions::kDefault;
deprecated_paste_enabled_ = false;
focus_on_navigation_ = true;
#if BUILDFLAG(IS_MAC)
scroll_bounce_ = false;
@@ -249,6 +250,8 @@ void WebContentsPreferences::SetFromDictionary(
web_preferences.Get(options::kEnableDeprecatedPaste,
&deprecated_paste_enabled_);
web_preferences.Get(options::kFocusOnNavigation, &focus_on_navigation_);
#if BUILDFLAG(IS_MAC)
web_preferences.Get(options::kScrollBounce, &scroll_bounce_);
#endif

View File

@@ -78,6 +78,7 @@ class WebContentsPreferences
bool ShouldDisablePopups() const { return disable_popups_; }
bool IsWebSecurityEnabled() const { return web_security_; }
std::optional<base::FilePath> GetPreloadPath() const { return preload_path_; }
bool ShouldFocusOnNavigation() const { return focus_on_navigation_; }
bool IsSandboxed() const;
private:
@@ -134,6 +135,7 @@ class WebContentsPreferences
std::optional<base::FilePath> preload_path_;
blink::mojom::V8CacheOptions v8_cache_options_;
bool deprecated_paste_enabled_ = false;
bool focus_on_navigation_;
#if BUILDFLAG(IS_MAC)
bool scroll_bounce_;

View File

@@ -224,6 +224,9 @@ inline constexpr std::string_view kSpellcheck = "spellcheck";
inline constexpr std::string_view kEnableDeprecatedPaste =
"enableDeprecatedPaste";
// Whether to focus the webContents on navigation.
inline constexpr std::string_view kFocusOnNavigation = "focusOnNavigation";
inline constexpr std::string_view kModal = "modal";
} // namespace options

View File

@@ -1610,6 +1610,35 @@ describe('webContents module', () => {
await expect(blurPromise).to.eventually.be.fulfilled();
});
});
describe('focusOnNavigation webPreference', () => {
afterEach(closeAllWindows);
it('focuses the webContents on navigation by default', async () => {
const w = new BrowserWindow({ show: true });
await once(w, 'focus');
await w.loadURL('about:blank');
await moveFocusToDevTools(w);
expect(w.webContents.isFocused()).to.be.false();
await w.loadURL('data:text/html,<body>test</body>');
expect(w.webContents.isFocused()).to.be.true();
});
it('does not focus the webContents on navigation when focusOnNavigation is false', async () => {
const w = new BrowserWindow({
show: true,
webPreferences: {
focusOnNavigation: false
}
});
await once(w, 'focus');
await w.loadURL('about:blank');
await moveFocusToDevTools(w);
expect(w.webContents.isFocused()).to.be.false();
await w.loadURL('data:text/html,<body>test</body>');
expect(w.webContents.isFocused()).to.be.false();
});
});
});
describe('getOSProcessId()', () => {

View File

@@ -398,4 +398,38 @@ describe('WebContentsView', () => {
v.setBorderRadius(100);
});
});
describe('focusOnNavigation webPreference', () => {
it('focuses the webContents on navigation by default', async () => {
const w = new BrowserWindow();
await once(w, 'focus');
const v = new WebContentsView();
w.setContentView(v);
await v.webContents.loadURL('about:blank');
const devToolsFocused = once(v.webContents, 'devtools-focused');
v.webContents.openDevTools({ mode: 'right' });
await devToolsFocused;
expect(v.webContents.isFocused()).to.be.false();
await v.webContents.loadURL('data:text/html,<body>test</body>');
expect(v.webContents.isFocused()).to.be.true();
});
it('does not focus the webContents on navigation when focusOnNavigation is false', async () => {
const w = new BrowserWindow();
await once(w, 'focus');
const v = new WebContentsView({
webPreferences: {
focusOnNavigation: false
}
});
w.setContentView(v);
await v.webContents.loadURL('about:blank');
const devToolsFocused = once(v.webContents, 'devtools-focused');
v.webContents.openDevTools({ mode: 'right' });
await devToolsFocused;
expect(v.webContents.isFocused()).to.be.false();
await v.webContents.loadURL('data:text/html,<body>test</body>');
expect(v.webContents.isFocused()).to.be.false();
});
});
});