Repository: electron/electron
Pull request: feat: add macOS-only api to determine if app is currently active

#### Description of Change

Adds new macOS only api for `app.isActive()` which returns a boolean value if the app is the "active" application, typically meaning it is the one receiving key events and has its menu displayed in the menubar. You can currently call `app.focus()` the app and get events for `did-become-active` and `did-resign-active`, but there's currently not a way I could find to directly check if your app is actually active, which we needed for a feature.

#### Checklist

- [x] PR description included
- [ ] `npm test` passes
- [x] tests are [changed or added](https://github.com/electron/electron/blob/main/docs/development/testing.md)
- [x] relevant API documentation, tutorials, and examples are updated and follow the [documentation style guide](https://github.com/electron/electron/blob/main/docs/development/style-guide.md)
- [x] [PR release notes](https://github.com/electron/clerk/blob/main/README.md) describe the change in a way relevant to app developers, and are [capitalized, punctuated, and past tense](https://github.com/electron/clerk/blob/main/README.md#examples).

#### Release Notes

Notes: Added `app.isActive()` to check if the app is the active/foreground application (macOS only)


Changed files:
filename: docs/api/app.md
patch: @@ -573,6 +573,10 @@ focus but may instead show a notification or flash the app icon (Wayland).
 
 You should seek to use the `steal` option as sparingly as possible.
 
+### `app.isActive()` _macOS_
+
+Returns `boolean` - `true` if the application is active (i.e. focused).
+
 ### `app.hide()` _macOS_
 
 Hides all application windows without minimizing them.

filename: shell/browser/api/electron_api_app.cc
patch: @@ -1873,6 +1873,7 @@ gin::ObjectTemplateBuilder App::GetObjectTemplateBuilder(v8::Isolate* isolate) {
       .SetMethod("isEmojiPanelSupported",
                  base::BindRepeating(&Browser::IsEmojiPanelSupported, browser))
 #if BUILDFLAG(IS_MAC)
+      .SetMethod("isActive", base::BindRepeating(&Browser::IsActive, browser))
       .SetMethod("hide", base::BindRepeating(&Browser::Hide, browser))
       .SetMethod("isHidden", base::BindRepeating(&Browser::IsHidden, browser))
       .SetMethod("show", base::BindRepeating(&Browser::Show, browser))

filename: shell/browser/browser.h
patch: @@ -165,6 +165,9 @@ class Browser : private WindowListObserver {
   // Set the handler which decides whether to shutdown.
   void SetShutdownHandler(base::RepeatingCallback<bool()> handler);
 
+  // Returns whether the application is active.
+  bool IsActive();
+
   // Hide the application.
   void Hide();
   bool IsHidden();

filename: shell/browser/browser_mac.mm
patch: @@ -146,6 +146,10 @@ LoginItemSettings GetLoginItemSettingsDeprecated() {
   [[AtomApplication sharedApplication] activateIgnoringOtherApps:steal_focus];
 }
 
+bool Browser::IsActive() {
+  return [[AtomApplication sharedApplication] isActive];
+}
+
 void Browser::Hide() {
   [[AtomApplication sharedApplication] hide:nil];
 }

filename: spec/api-app-spec.ts
patch: @@ -1773,6 +1773,31 @@ describe('app module', () => {
     });
   });
 
+  ifdescribe(process.platform === 'darwin')('app isActive API', () => {
+    describe('app.isActive', () => {
+      afterEach(closeAllWindows);
+
+      it('returns true when the app becomes active', async () => {
+        expect(app.isActive()).to.equal(false);
+
+        const w = new BrowserWindow({
+          width: 200,
+          height: 200,
+          show: false
+        });
+
+        w.show();
+
+        await expect(
+          waitUntil(() => app.isActive())
+        ).to.eventually.be.fulfilled();
+
+        w.close();
+        app.hide();
+      });
+    });
+  });
+
   ifdescribe(process.platform === 'darwin')('app hide and show API', () => {
     describe('app.isHidden', () => {
       it('returns true when the app is hidden', async () => {