Skip to content

Commit

Permalink
feat: add BrowserWindow.isOccluded() (electron#38982)
Browse files Browse the repository at this point in the history
feat: add BrowserWindow.isOccluded()
  • Loading branch information
codebytere authored Feb 6, 2024
1 parent 08236f7 commit 768ece6
Show file tree
Hide file tree
Showing 9 changed files with 89 additions and 2 deletions.
8 changes: 8 additions & 0 deletions docs/api/browser-window.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
5 changes: 5 additions & 0 deletions shell/browser/api/electron_api_base_window.cc
Original file line number Diff line number Diff line change
Expand Up @@ -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();
}
Expand Down Expand Up @@ -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)
Expand Down
1 change: 1 addition & 0 deletions shell/browser/api/electron_api_base_window.h
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand Down
1 change: 1 addition & 0 deletions shell/browser/native_window.h
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
1 change: 1 addition & 0 deletions shell/browser/native_window_mac.h
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
7 changes: 5 additions & 2 deletions shell/browser/native_window_mac.mm
Original file line number Diff line number Diff line change
Expand Up @@ -561,9 +561,12 @@ void ReorderChildWindowAbove(NSWindow* child_window, NSWindow* other_window) {
[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 {
Expand Down
8 changes: 8 additions & 0 deletions shell/browser/native_window_views.cc
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
1 change: 1 addition & 0 deletions shell/browser/native_window_views.h
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
59 changes: 59 additions & 0 deletions spec/api-browser-window-spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down

0 comments on commit 768ece6

Please sign in to comment.