Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: adjust initial webContents focus calculation #29234

14 changes: 0 additions & 14 deletions lib/browser/api/browser-window.ts
Expand Up @@ -20,20 +20,6 @@ BrowserWindow.prototype._init = function (this: BWT) {
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.
this.on('blur', (event: Event) => {
app.emit('browser-window-blur', event, this);
Expand Down
1 change: 1 addition & 0 deletions shell/browser/api/electron_api_browser_window.cc
Expand Up @@ -301,6 +301,7 @@ void BrowserWindow::OnWindowIsKeyChanged(bool is_key) {
auto* rwhv = web_contents()->GetRenderWidgetHostView();
if (rwhv)
rwhv->SetActive(is_key);
window()->SetActive(is_key);
#endif
}

Expand Down
32 changes: 24 additions & 8 deletions shell/browser/api/electron_api_web_contents.cc
Expand Up @@ -702,8 +702,8 @@ WebContents::WebContents(v8::Isolate* isolate,
// BrowserViews are not attached to a window initially so they should start
// off as hidden. This is also important for compositor recycling. See:
// https://github.com/electron/electron/pull/21372
initially_shown_ = type_ != Type::kBrowserView;
options.Get(options::kShow, &initially_shown_);
bool initially_shown = type_ != Type::kBrowserView;
options.Get(options::kShow, &initially_shown);

// Obtain the session.
std::string partition;
Expand Down Expand Up @@ -759,7 +759,7 @@ WebContents::WebContents(v8::Isolate* isolate,
#endif
} else {
content::WebContents::CreateParams params(session->browser_context());
params.initially_hidden = !initially_shown_;
params.initially_hidden = !initially_shown;
web_contents = content::WebContents::Create(params);
}

Expand Down Expand Up @@ -1655,6 +1655,27 @@ void WebContents::DidRedirectNavigation(
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(
content::NavigationHandle* navigation_handle) {
if (!navigation_handle->HasCommitted())
Expand Down Expand Up @@ -3126,10 +3147,6 @@ v8::Local<v8::Value> WebContents::Debugger(v8::Isolate* isolate) {
return v8::Local<v8::Value>::New(isolate, debugger_);
}

bool WebContents::WasInitiallyShown() {
return initially_shown_;
}

content::RenderFrameHost* WebContents::MainFrame() {
return web_contents()->GetMainFrame();
}
Expand Down Expand Up @@ -3752,7 +3769,6 @@ v8::Local<v8::ObjectTemplate> WebContents::FillObjectTemplate(
.SetProperty("hostWebContents", &WebContents::HostWebContents)
.SetProperty("devToolsWebContents", &WebContents::DevToolsWebContents)
.SetProperty("debugger", &WebContents::Debugger)
.SetProperty("_initiallyShown", &WebContents::WasInitiallyShown)
.SetProperty("mainFrame", &WebContents::MainFrame)
.Build();
}
Expand Down
5 changes: 2 additions & 3 deletions shell/browser/api/electron_api_web_contents.h
Expand Up @@ -330,7 +330,6 @@ class WebContents : public gin::Wrappable<WebContents>,
content::WebContents* HostWebContents() const;
v8::Local<v8::Value> DevToolsWebContents(v8::Isolate* isolate);
v8::Local<v8::Value> Debugger(v8::Isolate* isolate);
bool WasInitiallyShown();
content::RenderFrameHost* MainFrame();

WebContentsZoomController* GetZoomController() { return zoom_controller_; }
Expand Down Expand Up @@ -567,6 +566,8 @@ class WebContents : public gin::Wrappable<WebContents>,
content::NavigationHandle* navigation_handle) override;
void DidRedirectNavigation(
content::NavigationHandle* navigation_handle) override;
void ReadyToCommitNavigation(
content::NavigationHandle* navigation_handle) override;
void DidFinishNavigation(
content::NavigationHandle* navigation_handle) override;
bool OnMessageReceived(const IPC::Message& message) override;
Expand Down Expand Up @@ -735,8 +736,6 @@ class WebContents : public gin::Wrappable<WebContents>,

v8::Global<v8::Value> pending_child_web_preferences_;

bool initially_shown_ = true;

// The window that this WebContents belongs to.
base::WeakPtr<NativeWindow> owner_window_;

Expand Down
4 changes: 4 additions & 0 deletions shell/browser/native_window.h
Expand Up @@ -135,6 +135,10 @@ class NativeWindow : public base::SupportsUserData,
virtual void Invalidate() = 0;
virtual void SetTitle(const std::string& title) = 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.
void SetAccessibleTitle(const std::string& title);
Expand Down
3 changes: 3 additions & 0 deletions shell/browser/native_window_mac.h
Expand Up @@ -149,6 +149,8 @@ class NativeWindowMac : public NativeWindow,
gfx::Rect WindowBoundsToContentBounds(const gfx::Rect& bounds) const override;
void NotifyWindowEnterFullScreen() override;
void NotifyWindowLeaveFullScreen() override;
void SetActive(bool is_key) override;
bool IsActive() const override;

void NotifyWindowWillEnterFullScreen();
void NotifyWindowWillLeaveFullScreen();
Expand Down Expand Up @@ -270,6 +272,7 @@ class NativeWindowMac : public NativeWindow,
bool is_simple_fullscreen_ = false;
bool was_maximizable_ = false;
bool was_movable_ = false;
bool is_active_ = false;
NSRect original_frame_;
NSInteger original_level_;
NSUInteger simple_fullscreen_mask_;
Expand Down
8 changes: 8 additions & 0 deletions shell/browser/native_window_mac.mm
Expand Up @@ -1671,6 +1671,14 @@ void ViewDidMoveToSuperview(NSView* self, SEL _cmd) {
UpdateVibrancyRadii(false);
}

void NativeWindowMac::SetActive(bool is_key) {
is_active_ = is_key;
}

bool NativeWindowMac::IsActive() const {
return is_active_;
}

void NativeWindowMac::ReorderButtonsView() {
if (buttons_view_) {
[buttons_view_ removeFromSuperview];
Expand Down
14 changes: 14 additions & 0 deletions spec-main/api-browser-window-spec.ts
Expand Up @@ -4311,6 +4311,20 @@ describe('BrowserWindow module', () => {
await leaveFullScreen;
expect(w.isFullScreen()).to.be.false('isFullScreen');
});

ifit(process.arch === 'x64')('multiple windows inherit correct fullscreen state', async () => {
const w = new BrowserWindow();
const enterFullScreen = emittedOnce(w, 'enter-full-screen');
w.setFullScreen(true);
await enterFullScreen;
expect(w.isFullScreen()).to.be.true('isFullScreen');
await delay();
const w2 = new BrowserWindow({ show: false });
const enterFullScreen2 = emittedOnce(w2, 'enter-full-screen');
w2.show();
await enterFullScreen2;
expect(w2.isFullScreen()).to.be.true('isFullScreen');
});
});

describe('closable state', () => {
Expand Down
2 changes: 1 addition & 1 deletion spec-main/api-web-contents-spec.ts
Expand Up @@ -392,7 +392,7 @@ describe('webContents module', () => {
const testFn = (process.platform === 'win32' && process.arch === 'arm64' ? it.skip : it);
testFn('returns the focused web contents', async () => {
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);

const devToolsOpened = emittedOnce(w.webContents, 'devtools-opened');
Expand Down
26 changes: 25 additions & 1 deletion spec-main/chromium-spec.ts
Expand Up @@ -1481,6 +1481,31 @@ describe('chromium features', () => {
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', () => {
Expand Down Expand Up @@ -1665,7 +1690,6 @@ describe('navigator.clipboard', () => {
let w: BrowserWindow;
before(async () => {
w = new BrowserWindow({
show: false,
webPreferences: {
enableBlinkFeatures: 'Serial'
}
Expand Down
47 changes: 47 additions & 0 deletions spec-main/webview-spec.ts
Expand Up @@ -783,4 +783,51 @@ describe('<webview> tag', function () {
})`);
});
});

describe('found-in-page event', () => {
let w: BrowserWindow;
beforeEach(async () => {
w = new BrowserWindow({ show: false, webPreferences: { webviewTag: true, contextIsolation: false } });
await w.loadURL('about:blank');
});
afterEach(closeAllWindows);

it('emits when a request is made', async () => {
await loadWebView(w.webContents, {
src: `file://${fixtures}/pages/content.html`
});
const result = await w.webContents.executeJavaScript(`new Promise((resolve, reject) => {
const webview = document.querySelector('webview')

const waitForEvent = (target, eventName) => {
return new Promise(resolve => {
target.addEventListener(eventName, resolve, { once: true })
})
}

async function startFind() {
const activeMatchOrdinal = []
const foundInPage = waitForEvent(webview, 'found-in-page')
webview.findInPage('virtual', { findNext: true })
const event = await foundInPage
if (event.result.matches) {
activeMatchOrdinal.push(event.result.activeMatchOrdinal)

for (let i=1; i < event.result.matches; i++) {
const foundInPage = waitForEvent(webview, 'found-in-page')
webview.findInPage('virtual', { findNext: false })
const event = await foundInPage
activeMatchOrdinal.push(event.result.activeMatchOrdinal)
}
}
webview.stopFindInPage('clearSelection')
resolve(activeMatchOrdinal)
}

webview.focus()
startFind()
})`);
expect(result).to.deep.equal([1, 2, 3]);
});
});
});
33 changes: 0 additions & 33 deletions spec/webview-spec.js
Expand Up @@ -973,39 +973,6 @@ describe('<webview> tag', function () {
});
});

describe('found-in-page event', () => {
it('emits when a request is made', async () => {
const didFinishLoad = waitForEvent(webview, 'did-finish-load');
loadWebView(webview, { src: `file://${fixtures}/pages/content.html` });
// TODO(deepak1556): With https://codereview.chromium.org/2836973002
// focus of the webContents is required when triggering the api.
// Remove this workaround after determining the cause for
// incorrect focus.
webview.focus();
await didFinishLoad;

const activeMatchOrdinal = [];

for (;;) {
const foundInPage = waitForEvent(webview, 'found-in-page');
const requestId = webview.findInPage('virtual');
const event = await foundInPage;

expect(event.result.requestId).to.equal(requestId);
expect(event.result.matches).to.equal(3);

activeMatchOrdinal.push(event.result.activeMatchOrdinal);

if (event.result.activeMatchOrdinal === event.result.matches) {
break;
}
}

expect(activeMatchOrdinal).to.deep.equal([1, 2, 3]);
webview.stopFindInPage('clearSelection');
});
});

describe('<webview>.getWebContentsId', () => {
it('can return the WebContents ID', async () => {
const src = 'about:blank';
Expand Down
1 change: 0 additions & 1 deletion typings/internal-electron.d.ts
Expand Up @@ -67,7 +67,6 @@ declare namespace Electron {
getLastWebPreferences(): Electron.WebPreferences;
_getPreloadPaths(): string[];
equal(other: WebContents): boolean;
_initiallyShown: boolean;
browserWindowOptions: BrowserWindowConstructorOptions;
_windowOpenHandler: ((details: Electron.HandlerDetails) => any) | null;
_callWindowOpenHandler(event: any, details: Electron.HandlerDetails): Electron.BrowserWindowConstructorOptions | null;
Expand Down