feat: add BrowserWindow.isOccluded() (#38982)
		
	feat: add BrowserWindow.isOccluded()
This commit is contained in:
		
					parent
					
						
							
								08236f7a9e
							
						
					
				
			
			
				commit
				
					
						768ece6b54
					
				
			
		
					 9 changed files with 89 additions and 2 deletions
				
			
		| 
						 | 
				
			
			@ -644,6 +644,14 @@ Shows the window but doesn't focus on it.
 | 
			
		|||
 | 
			
		||||
Hides the window.
 | 
			
		||||
 | 
			
		||||
#### `win.isOccluded()`
 | 
			
		||||
 | 
			
		||||
Returns `boolean` - Whether the window is covered by other windows.
 | 
			
		||||
 | 
			
		||||
On macOS, a `BrowserWindow` is occluded if the entire window is obscured by other windows. A completely transparent window may also be considered non-occluded. See [docs](https://developer.apple.com/documentation/appkit/nswindowocclusionstate?language=objc) for more details.
 | 
			
		||||
 | 
			
		||||
On Windows and Linux, a window is occluded if it or one of its descendants is visible, and all visible windows have their bounds completely covered by fully opaque windows. A window is considered "fully opaque" if it is visible, it is not transparent, and its combined opacity is 1. See [Chromium Source](https://source.chromium.org/chromium/chromium/src/+/refs/heads/main:ui/aura/window.h;l=124-151;drc=7d2d8ccab2b68fbbfc5e1611d45bd4ecf87090b8) for more details.
 | 
			
		||||
 | 
			
		||||
#### `win.isVisible()`
 | 
			
		||||
 | 
			
		||||
Returns `boolean` - Whether the window is visible to the user in the foreground of the app.
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -355,6 +355,10 @@ bool BaseWindow::IsVisible() const {
 | 
			
		|||
  return window_->IsVisible();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool BaseWindow::IsOccluded() const {
 | 
			
		||||
  return window_->IsOccluded();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool BaseWindow::IsEnabled() const {
 | 
			
		||||
  return window_->IsEnabled();
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -1086,6 +1090,7 @@ void BaseWindow::BuildPrototype(v8::Isolate* isolate,
 | 
			
		|||
      .SetMethod("showInactive", &BaseWindow::ShowInactive)
 | 
			
		||||
      .SetMethod("hide", &BaseWindow::Hide)
 | 
			
		||||
      .SetMethod("isVisible", &BaseWindow::IsVisible)
 | 
			
		||||
      .SetMethod("isOccluded", &BaseWindow::IsOccluded)
 | 
			
		||||
      .SetMethod("isEnabled", &BaseWindow::IsEnabled)
 | 
			
		||||
      .SetMethod("setEnabled", &BaseWindow::SetEnabled)
 | 
			
		||||
      .SetMethod("maximize", &BaseWindow::Maximize)
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -98,6 +98,7 @@ class BaseWindow : public gin_helper::TrackableObject<BaseWindow>,
 | 
			
		|||
  void ShowInactive();
 | 
			
		||||
  void Hide();
 | 
			
		||||
  bool IsVisible() const;
 | 
			
		||||
  bool IsOccluded() const;
 | 
			
		||||
  bool IsEnabled() const;
 | 
			
		||||
  void SetEnabled(bool enable);
 | 
			
		||||
  void Maximize();
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -87,6 +87,7 @@ class NativeWindow : public base::SupportsUserData,
 | 
			
		|||
  virtual void Show() = 0;
 | 
			
		||||
  virtual void ShowInactive() = 0;
 | 
			
		||||
  virtual void Hide() = 0;
 | 
			
		||||
  virtual bool IsOccluded() const = 0;
 | 
			
		||||
  virtual bool IsVisible() const = 0;
 | 
			
		||||
  virtual bool IsEnabled() const = 0;
 | 
			
		||||
  virtual void SetEnabled(bool enable) = 0;
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -44,6 +44,7 @@ class NativeWindowMac : public NativeWindow,
 | 
			
		|||
  void Show() override;
 | 
			
		||||
  void ShowInactive() override;
 | 
			
		||||
  void Hide() override;
 | 
			
		||||
  bool IsOccluded() const override;
 | 
			
		||||
  bool IsVisible() const override;
 | 
			
		||||
  bool IsEnabled() const override;
 | 
			
		||||
  void SetEnabled(bool enable) override;
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -561,9 +561,12 @@ void NativeWindowMac::Hide() {
 | 
			
		|||
  [window_ orderOut:nil];
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool NativeWindowMac::IsOccluded() const {
 | 
			
		||||
  return !([window_ occlusionState] & NSWindowOcclusionStateVisible);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool NativeWindowMac::IsVisible() const {
 | 
			
		||||
  bool occluded = [window_ occlusionState] == NSWindowOcclusionStateVisible;
 | 
			
		||||
  return [window_ isVisible] && !occluded && !IsMinimized();
 | 
			
		||||
  return [window_ isVisible] && !IsMinimized();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool NativeWindowMac::IsEnabled() const {
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -562,6 +562,14 @@ void NativeWindowViews::Hide() {
 | 
			
		|||
#endif
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool NativeWindowViews::IsOccluded() const {
 | 
			
		||||
  if (!GetNativeWindow())
 | 
			
		||||
    return false;
 | 
			
		||||
  auto occlusion_state =
 | 
			
		||||
      GetNativeWindow()->GetHost()->GetNativeWindowOcclusionState();
 | 
			
		||||
  return occlusion_state == aura::Window::OcclusionState::OCCLUDED;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool NativeWindowViews::IsVisible() const {
 | 
			
		||||
#if BUILDFLAG(IS_WIN)
 | 
			
		||||
  // widget()->IsVisible() calls ::IsWindowVisible, which returns non-zero if a
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -56,6 +56,7 @@ class NativeWindowViews : public NativeWindow,
 | 
			
		|||
  void Hide() override;
 | 
			
		||||
  bool IsVisible() const override;
 | 
			
		||||
  bool IsEnabled() const override;
 | 
			
		||||
  bool IsOccluded() const override;
 | 
			
		||||
  void SetEnabled(bool enable) override;
 | 
			
		||||
  void Maximize() override;
 | 
			
		||||
  void Unmaximize() override;
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -4164,6 +4164,65 @@ describe('BrowserWindow module', () => {
 | 
			
		|||
    });
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  describe('BrowserWindow.isOccluded()', () => {
 | 
			
		||||
    afterEach(closeAllWindows);
 | 
			
		||||
 | 
			
		||||
    it('returns false for a visible window', async () => {
 | 
			
		||||
      const w = new BrowserWindow({ show: false });
 | 
			
		||||
 | 
			
		||||
      const shown = once(w, 'show');
 | 
			
		||||
      w.show();
 | 
			
		||||
      await shown;
 | 
			
		||||
 | 
			
		||||
      expect(w.isOccluded()).to.be.false('window is occluded');
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    it('returns false when the window is only partially obscured', async () => {
 | 
			
		||||
      const w1 = new BrowserWindow({ width: 400, height: 400 });
 | 
			
		||||
      const w2 = new BrowserWindow({ show: false, width: 450, height: 450 });
 | 
			
		||||
 | 
			
		||||
      const focused = once(w2, 'focus');
 | 
			
		||||
      w2.show();
 | 
			
		||||
      await focused;
 | 
			
		||||
 | 
			
		||||
      await setTimeout(1000);
 | 
			
		||||
      expect(w1.isOccluded()).to.be.true('window is not occluded');
 | 
			
		||||
 | 
			
		||||
      const pos = w2.getPosition();
 | 
			
		||||
      const move = once(w2, 'move');
 | 
			
		||||
      w2.setPosition(pos[0] - 100, pos[1]);
 | 
			
		||||
      await move;
 | 
			
		||||
 | 
			
		||||
      await setTimeout(1000);
 | 
			
		||||
      expect(w1.isOccluded()).to.be.false('window is occluded');
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    // FIXME: this test fails on Linux CI due to windowing issues.
 | 
			
		||||
    ifit(process.platform !== 'linux')('returns false for a visible window covered by a transparent window', async () => {
 | 
			
		||||
      const w1 = new BrowserWindow({ width: 200, height: 200 });
 | 
			
		||||
      const w2 = new BrowserWindow({ show: false, transparent: true, frame: false });
 | 
			
		||||
 | 
			
		||||
      const focused = once(w2, 'focus');
 | 
			
		||||
      w2.show();
 | 
			
		||||
      await focused;
 | 
			
		||||
 | 
			
		||||
      await setTimeout(1000);
 | 
			
		||||
      expect(w1.isOccluded()).to.be.false('window is occluded');
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    it('returns true for an obscured window', async () => {
 | 
			
		||||
      const w1 = new BrowserWindow({ width: 200, height: 200 });
 | 
			
		||||
      const w2 = new BrowserWindow({ show: false });
 | 
			
		||||
 | 
			
		||||
      const focused = once(w2, 'focus');
 | 
			
		||||
      w2.show();
 | 
			
		||||
      await focused;
 | 
			
		||||
 | 
			
		||||
      await setTimeout(1000);
 | 
			
		||||
      expect(w1.isOccluded()).to.be.true('visible window');
 | 
			
		||||
    });
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  // TODO(codebytere): figure out how to make these pass in CI on Windows.
 | 
			
		||||
  ifdescribe(process.platform !== 'win32')('document.visibilityState/hidden', () => {
 | 
			
		||||
    afterEach(closeAllWindows);
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue