fix: adjust initial webContents focus calculation (#29204)

* fix: adjust initial webContents focus calculation

* fix: active window check on mac

* fix: about:blank focus behavior

* chore: add spec

Co-authored-by: Raymond Zhao <raymondzhao@microsoft.com>
This commit is contained in:
Robo 2021-05-19 02:27:35 -07:00 committed by GitHub
parent 014bdc9f8a
commit 77297f37a3
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
10 changed files with 70 additions and 28 deletions

View file

@ -20,20 +20,6 @@ BrowserWindow.prototype._init = function (this: BWT) {
nativeSetBounds.call(this, bounds, ...opts); nativeSetBounds.call(this, bounds, ...opts);
}; };
// Sometimes the webContents doesn't get focus when window is shown, so we
// have to force focusing on webContents in this case. The safest way is to
// focus it when we first start to load URL, if we do it earlier it won't
// have effect, if we do it later we might move focus in the page.
//
// Though this hack is only needed on macOS when the app is launched from
// Finder, we still do it on all platforms in case of other bugs we don't
// know.
if (this.webContents._initiallyShown) {
this.webContents.once('load-url' as any, function (this: WebContents) {
this.focus();
});
}
// Redirect focus/blur event to app instance too. // Redirect focus/blur event to app instance too.
this.on('blur', (event: Event) => { this.on('blur', (event: Event) => {
app.emit('browser-window-blur', event, this); app.emit('browser-window-blur', event, this);

View file

@ -289,6 +289,7 @@ void BrowserWindow::OnWindowIsKeyChanged(bool is_key) {
auto* rwhv = web_contents()->GetRenderWidgetHostView(); auto* rwhv = web_contents()->GetRenderWidgetHostView();
if (rwhv) if (rwhv)
rwhv->SetActive(is_key); rwhv->SetActive(is_key);
window()->SetActive(is_key);
#endif #endif
} }

View file

@ -701,8 +701,8 @@ WebContents::WebContents(v8::Isolate* isolate,
// BrowserViews are not attached to a window initially so they should start // BrowserViews are not attached to a window initially so they should start
// off as hidden. This is also important for compositor recycling. See: // off as hidden. This is also important for compositor recycling. See:
// https://github.com/electron/electron/pull/21372 // https://github.com/electron/electron/pull/21372
initially_shown_ = type_ != Type::kBrowserView; bool initially_shown = type_ != Type::kBrowserView;
options.Get(options::kShow, &initially_shown_); options.Get(options::kShow, &initially_shown);
// Obtain the session. // Obtain the session.
std::string partition; std::string partition;
@ -758,7 +758,7 @@ WebContents::WebContents(v8::Isolate* isolate,
#endif #endif
} else { } else {
content::WebContents::CreateParams params(session->browser_context()); content::WebContents::CreateParams params(session->browser_context());
params.initially_hidden = !initially_shown_; params.initially_hidden = !initially_shown;
web_contents = content::WebContents::Create(params); web_contents = content::WebContents::Create(params);
} }
@ -1650,6 +1650,27 @@ void WebContents::DidRedirectNavigation(
EmitNavigationEvent("did-redirect-navigation", navigation_handle); EmitNavigationEvent("did-redirect-navigation", navigation_handle);
} }
void WebContents::ReadyToCommitNavigation(
content::NavigationHandle* navigation_handle) {
// Don't focus content in an inactive window.
if (!owner_window())
return;
#if defined(OS_MAC)
if (!owner_window()->IsActive())
return;
#else
if (!owner_window()->widget()->IsActive())
return;
#endif
// Don't focus content after subframe navigations.
if (!navigation_handle->IsInMainFrame())
return;
// Only focus for top-level contents.
if (type_ != Type::kBrowserWindow)
return;
web_contents()->SetInitialFocus();
}
void WebContents::DidFinishNavigation( void WebContents::DidFinishNavigation(
content::NavigationHandle* navigation_handle) { content::NavigationHandle* navigation_handle) {
if (!navigation_handle->HasCommitted()) if (!navigation_handle->HasCommitted())
@ -3110,10 +3131,6 @@ v8::Local<v8::Value> WebContents::Debugger(v8::Isolate* isolate) {
return v8::Local<v8::Value>::New(isolate, debugger_); return v8::Local<v8::Value>::New(isolate, debugger_);
} }
bool WebContents::WasInitiallyShown() {
return initially_shown_;
}
content::RenderFrameHost* WebContents::MainFrame() { content::RenderFrameHost* WebContents::MainFrame() {
return web_contents()->GetMainFrame(); return web_contents()->GetMainFrame();
} }
@ -3683,7 +3700,6 @@ v8::Local<v8::ObjectTemplate> WebContents::FillObjectTemplate(
.SetProperty("hostWebContents", &WebContents::HostWebContents) .SetProperty("hostWebContents", &WebContents::HostWebContents)
.SetProperty("devToolsWebContents", &WebContents::DevToolsWebContents) .SetProperty("devToolsWebContents", &WebContents::DevToolsWebContents)
.SetProperty("debugger", &WebContents::Debugger) .SetProperty("debugger", &WebContents::Debugger)
.SetProperty("_initiallyShown", &WebContents::WasInitiallyShown)
.SetProperty("mainFrame", &WebContents::MainFrame) .SetProperty("mainFrame", &WebContents::MainFrame)
.Build(); .Build();
} }

View file

@ -324,7 +324,6 @@ class WebContents : public gin::Wrappable<WebContents>,
content::WebContents* HostWebContents() const; content::WebContents* HostWebContents() const;
v8::Local<v8::Value> DevToolsWebContents(v8::Isolate* isolate); v8::Local<v8::Value> DevToolsWebContents(v8::Isolate* isolate);
v8::Local<v8::Value> Debugger(v8::Isolate* isolate); v8::Local<v8::Value> Debugger(v8::Isolate* isolate);
bool WasInitiallyShown();
content::RenderFrameHost* MainFrame(); content::RenderFrameHost* MainFrame();
WebContentsZoomController* GetZoomController() { return zoom_controller_; } WebContentsZoomController* GetZoomController() { return zoom_controller_; }
@ -559,6 +558,8 @@ class WebContents : public gin::Wrappable<WebContents>,
content::NavigationHandle* navigation_handle) override; content::NavigationHandle* navigation_handle) override;
void DidRedirectNavigation( void DidRedirectNavigation(
content::NavigationHandle* navigation_handle) override; content::NavigationHandle* navigation_handle) override;
void ReadyToCommitNavigation(
content::NavigationHandle* navigation_handle) override;
void DidFinishNavigation( void DidFinishNavigation(
content::NavigationHandle* navigation_handle) override; content::NavigationHandle* navigation_handle) override;
bool OnMessageReceived(const IPC::Message& message) override; bool OnMessageReceived(const IPC::Message& message) override;
@ -722,8 +723,6 @@ class WebContents : public gin::Wrappable<WebContents>,
v8::Global<v8::Value> pending_child_web_preferences_; v8::Global<v8::Value> pending_child_web_preferences_;
bool initially_shown_ = true;
// The window that this WebContents belongs to. // The window that this WebContents belongs to.
base::WeakPtr<NativeWindow> owner_window_; base::WeakPtr<NativeWindow> owner_window_;

View file

@ -135,6 +135,10 @@ class NativeWindow : public base::SupportsUserData,
virtual void Invalidate() = 0; virtual void Invalidate() = 0;
virtual void SetTitle(const std::string& title) = 0; virtual void SetTitle(const std::string& title) = 0;
virtual std::string GetTitle() = 0; virtual std::string GetTitle() = 0;
#if defined(OS_MAC)
virtual void SetActive(bool is_key) = 0;
virtual bool IsActive() const = 0;
#endif
// Ability to augment the window title for the screen readers. // Ability to augment the window title for the screen readers.
void SetAccessibleTitle(const std::string& title); void SetAccessibleTitle(const std::string& title);

View file

@ -149,6 +149,8 @@ class NativeWindowMac : public NativeWindow,
gfx::Rect WindowBoundsToContentBounds(const gfx::Rect& bounds) const override; gfx::Rect WindowBoundsToContentBounds(const gfx::Rect& bounds) const override;
void NotifyWindowEnterFullScreen() override; void NotifyWindowEnterFullScreen() override;
void NotifyWindowLeaveFullScreen() override; void NotifyWindowLeaveFullScreen() override;
void SetActive(bool is_key) override;
bool IsActive() const override;
void NotifyWindowWillEnterFullScreen(); void NotifyWindowWillEnterFullScreen();
void NotifyWindowWillLeaveFullScreen(); void NotifyWindowWillLeaveFullScreen();
@ -267,6 +269,7 @@ class NativeWindowMac : public NativeWindow,
bool is_simple_fullscreen_ = false; bool is_simple_fullscreen_ = false;
bool was_maximizable_ = false; bool was_maximizable_ = false;
bool was_movable_ = false; bool was_movable_ = false;
bool is_active_ = false;
NSRect original_frame_; NSRect original_frame_;
NSInteger original_level_; NSInteger original_level_;
NSUInteger simple_fullscreen_mask_; NSUInteger simple_fullscreen_mask_;

View file

@ -1640,6 +1640,16 @@ void NativeWindowMac::NotifyWindowWillLeaveFullScreen() {
UpdateVibrancyRadii(false); UpdateVibrancyRadii(false);
} }
void NativeWindowMac::SetActive(bool is_key) {
if (is_key)
widget()->Activate();
is_active_ = is_key;
}
bool NativeWindowMac::IsActive() const {
return is_active_;
}
void NativeWindowMac::Cleanup() { void NativeWindowMac::Cleanup() {
DCHECK(!IsClosed()); DCHECK(!IsClosed());
ui::NativeTheme::GetInstanceForNativeUi()->RemoveObserver(this); ui::NativeTheme::GetInstanceForNativeUi()->RemoveObserver(this);

View file

@ -419,7 +419,7 @@ describe('webContents module', () => {
const testFn = (process.platform === 'win32' && process.arch === 'arm64' ? it.skip : it); const testFn = (process.platform === 'win32' && process.arch === 'arm64' ? it.skip : it);
testFn('returns the focused web contents', async () => { testFn('returns the focused web contents', async () => {
const w = new BrowserWindow({ show: true }); const w = new BrowserWindow({ show: true });
await w.loadURL('about:blank'); await w.loadFile(path.join(__dirname, 'fixtures', 'blank.html'));
expect(webContents.getFocusedWebContents().id).to.equal(w.webContents.id); expect(webContents.getFocusedWebContents().id).to.equal(w.webContents.id);
const devToolsOpened = emittedOnce(w.webContents, 'devtools-opened'); const devToolsOpened = emittedOnce(w.webContents, 'devtools-opened');

View file

@ -1433,6 +1433,31 @@ describe('chromium features', () => {
expect(pageExists).to.be.true(); expect(pageExists).to.be.true();
}); });
}); });
describe('document.hasFocus', () => {
it('has correct value when multiple windows are opened', async () => {
const w1 = new BrowserWindow({ show: true });
const w2 = new BrowserWindow({ show: true });
const w3 = new BrowserWindow({ show: false });
await w1.loadFile(path.join(__dirname, 'fixtures', 'blank.html'));
await w2.loadFile(path.join(__dirname, 'fixtures', 'blank.html'));
await w3.loadFile(path.join(__dirname, 'fixtures', 'blank.html'));
expect(webContents.getFocusedWebContents().id).to.equal(w2.webContents.id);
let focus = false;
focus = await w1.webContents.executeJavaScript(
'document.hasFocus()'
);
expect(focus).to.be.false();
focus = await w2.webContents.executeJavaScript(
'document.hasFocus()'
);
expect(focus).to.be.true();
focus = await w3.webContents.executeJavaScript(
'document.hasFocus()'
);
expect(focus).to.be.false();
});
});
}); });
describe('font fallback', () => { describe('font fallback', () => {
@ -1617,7 +1642,6 @@ describe('navigator.clipboard', () => {
let w: BrowserWindow; let w: BrowserWindow;
before(async () => { before(async () => {
w = new BrowserWindow({ w = new BrowserWindow({
show: false,
webPreferences: { webPreferences: {
enableBlinkFeatures: 'Serial' enableBlinkFeatures: 'Serial'
} }

View file

@ -67,7 +67,6 @@ declare namespace Electron {
getLastWebPreferences(): Electron.WebPreferences; getLastWebPreferences(): Electron.WebPreferences;
_getPreloadPaths(): string[]; _getPreloadPaths(): string[];
equal(other: WebContents): boolean; equal(other: WebContents): boolean;
_initiallyShown: boolean;
browserWindowOptions: BrowserWindowConstructorOptions; browserWindowOptions: BrowserWindowConstructorOptions;
_windowOpenHandler: ((details: Electron.HandlerDetails) => any) | null; _windowOpenHandler: ((details: Electron.HandlerDetails) => any) | null;
_callWindowOpenHandler(event: any, details: Electron.HandlerDetails): Electron.BrowserWindowConstructorOptions | null; _callWindowOpenHandler(event: any, details: Electron.HandlerDetails): Electron.BrowserWindowConstructorOptions | null;