diff --git a/shell/browser/browser.h b/shell/browser/browser.h index 4fbab02d8c42e..a5f597ad3c9f5 100644 --- a/shell/browser/browser.h +++ b/shell/browser/browser.h @@ -309,6 +309,7 @@ class Browser : public WindowListObserver { base::Value about_panel_options_; #elif defined(OS_MACOSX) base::DictionaryValue about_panel_options_; + base::Time last_dock_show_; #endif DISALLOW_COPY_AND_ASSIGN(Browser); diff --git a/shell/browser/browser_mac.mm b/shell/browser/browser_mac.mm index b51860340e48b..dc40767cd2873 100644 --- a/shell/browser/browser_mac.mm +++ b/shell/browser/browser_mac.mm @@ -329,6 +329,20 @@ void RemoveFromLoginItems() { } void Browser::DockHide() { + // Transforming application state from UIElement to Foreground is an + // asyncronous operation, and unfortunately there is currently no way to know + // when it is finished. + // So if we call DockHide => DockShow => DockHide => DockShow in a very short + // time, we would triger a bug of macOS that, there would be multiple dock + // icons of the app left in system. + // To work around this, we make sure DockHide does nothing if it is called + // immediately after DockShow. After some experiments, 1 second seems to be + // a proper interval. + if (!last_dock_show_.is_null() && + base::Time::Now() - last_dock_show_ < base::TimeDelta::FromSeconds(1)) { + return; + } + for (auto* const& window : WindowList::GetWindows()) [window->GetNativeWindow().GetNativeNSWindow() setCanHide:NO]; @@ -344,6 +358,7 @@ void RemoveFromLoginItems() { } v8::Local Browser::DockShow(v8::Isolate* isolate) { + last_dock_show_ = base::Time::Now(); gin_helper::Promise promise(isolate); v8::Local handle = promise.GetHandle(); diff --git a/spec-main/api-app-spec.ts b/spec-main/api-app-spec.ts index a415c82b4c8a5..c19e16fce8598 100644 --- a/spec-main/api-app-spec.ts +++ b/spec-main/api-app-spec.ts @@ -453,7 +453,7 @@ describe('app module', () => { await w.loadURL('about:blank'); const promise = emittedOnce(app, 'desktop-capturer-get-sources'); - w.webContents.executeJavaScript(`require('electron').desktopCapturer.getSources({ types: ['screen'] })`); + w.webContents.executeJavaScript('require(\'electron\').desktopCapturer.getSources({ types: [\'screen\'] })'); const [, webContents] = await promise; expect(webContents).to.equal(w.webContents); @@ -471,7 +471,7 @@ describe('app module', () => { await w.loadURL('about:blank'); const promise = emittedOnce(app, 'remote-require'); - w.webContents.executeJavaScript(`require('electron').remote.require('test')`); + w.webContents.executeJavaScript('require(\'electron\').remote.require(\'test\')'); const [, webContents, moduleName] = await promise; expect(webContents).to.equal(w.webContents); @@ -488,7 +488,7 @@ describe('app module', () => { await w.loadURL('about:blank'); const promise = emittedOnce(app, 'remote-get-global'); - w.webContents.executeJavaScript(`require('electron').remote.getGlobal('test')`); + w.webContents.executeJavaScript('require(\'electron\').remote.getGlobal(\'test\')'); const [, webContents, globalName] = await promise; expect(webContents).to.equal(w.webContents); @@ -505,7 +505,7 @@ describe('app module', () => { await w.loadURL('about:blank'); const promise = emittedOnce(app, 'remote-get-builtin'); - w.webContents.executeJavaScript(`require('electron').remote.app`); + w.webContents.executeJavaScript('require(\'electron\').remote.app'); const [, webContents, moduleName] = await promise; expect(webContents).to.equal(w.webContents); @@ -522,7 +522,7 @@ describe('app module', () => { await w.loadURL('about:blank'); const promise = emittedOnce(app, 'remote-get-current-window'); - w.webContents.executeJavaScript(`{ require('electron').remote.getCurrentWindow() }`); + w.webContents.executeJavaScript('{ require(\'electron\').remote.getCurrentWindow() }'); const [, webContents] = await promise; expect(webContents).to.equal(w.webContents); @@ -538,7 +538,7 @@ describe('app module', () => { await w.loadURL('about:blank'); const promise = emittedOnce(app, 'remote-get-current-web-contents'); - w.webContents.executeJavaScript(`{ require('electron').remote.getCurrentWebContents() }`); + w.webContents.executeJavaScript('{ require(\'electron\').remote.getCurrentWebContents() }'); const [, webContents] = await promise; expect(webContents).to.equal(w.webContents); @@ -601,7 +601,7 @@ describe('app module', () => { const updateExe = path.resolve(path.dirname(process.execPath), '..', 'Update.exe'); const processStartArgs = [ '--processStart', `"${path.basename(process.execPath)}"`, - '--process-start-args', `"--hidden"` + '--process-start-args', '"--hidden"' ]; before(function () { @@ -814,7 +814,7 @@ describe('app module', () => { const updateExe = path.resolve(path.dirname(process.execPath), '..', 'Update.exe'); const processStartArgs = [ '--processStart', `"${path.basename(process.execPath)}"`, - '--process-start-args', `"--hidden"` + '--process-start-args', '"--hidden"' ]; let Winreg: any; @@ -1323,6 +1323,16 @@ describe('app module', () => { }); }); + describe('dock.hide', () => { + it('should not throw', () => { + app.dock.hide(); + expect(app.dock.isVisible()).to.equal(false); + }); + }); + + // Note that dock.show tests should run after dock.hide tests, to work + // around a bug of macOS. + // See https://github.com/electron/electron/pull/25269 for more. describe('dock.show', () => { it('should not throw', () => { return app.dock.show().then(() => { @@ -1338,13 +1348,6 @@ describe('app module', () => { await expect(app.dock.show()).to.eventually.be.fulfilled.equal(undefined); }); }); - - describe('dock.hide', () => { - it('should not throw', () => { - app.dock.hide(); - expect(app.dock.isVisible()).to.equal(false); - }); - }); }); describe('whenReady', () => {