mirror of
https://github.com/electron/electron.git
synced 2026-03-19 03:02:02 -04:00
Compare commits
3 Commits
refactor/e
...
feat/notif
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ae1bf816f7 | ||
|
|
771a502c76 | ||
|
|
9e57b2bc04 |
2
.github/workflows/apply-patches.yml
vendored
2
.github/workflows/apply-patches.yml
vendored
@@ -26,7 +26,7 @@ jobs:
|
||||
# Use dorny/paths-filter instead of the path filter under the on: pull_request: block
|
||||
# so that the output can be used to conditionally run the apply-patches job, which lets
|
||||
# the job be marked as a required status check (conditional skip counts as a success).
|
||||
- uses: dorny/paths-filter@fbd0ab8f3e69293af611ebaee6363fc25e6d187d # v4.0.1
|
||||
- uses: dorny/paths-filter@de90cc6fb38fc0963ad72b210f1f284cd68cea36 # v3.0.2
|
||||
id: filter
|
||||
with:
|
||||
filters: |
|
||||
|
||||
2
.github/workflows/audit-branch-ci.yml
vendored
2
.github/workflows/audit-branch-ci.yml
vendored
@@ -155,7 +155,7 @@ jobs:
|
||||
await core.summary.write();
|
||||
- name: Send Slack message if errors
|
||||
if: ${{ always() && steps.audit-errors.outputs.errorsFound && github.ref == 'refs/heads/main' }}
|
||||
uses: slackapi/slack-github-action@af78098f536edbc4de71162a307590698245be95 # v3.0.1
|
||||
uses: slackapi/slack-github-action@91efab103c0de0a537f72a35f6b8cda0ee76bf0a # v2.1.1
|
||||
with:
|
||||
payload: |
|
||||
link: "https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}"
|
||||
|
||||
2
.github/workflows/build.yml
vendored
2
.github/workflows/build.yml
vendored
@@ -61,7 +61,7 @@ jobs:
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd
|
||||
with:
|
||||
ref: ${{ github.event.pull_request.head.sha }}
|
||||
- uses: dorny/paths-filter@fbd0ab8f3e69293af611ebaee6363fc25e6d187d # v4.0.1
|
||||
- uses: dorny/paths-filter@de90cc6fb38fc0963ad72b210f1f284cd68cea36 # v3.0.2
|
||||
id: filter
|
||||
with:
|
||||
filters: |
|
||||
|
||||
@@ -126,7 +126,7 @@ jobs:
|
||||
cd src/electron
|
||||
git pack-refs
|
||||
- name: Download Out Gen Artifacts
|
||||
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c
|
||||
uses: actions/download-artifact@70fc10c6e5e1ce46ad2ea6f2b72d43f7d47b13c3
|
||||
with:
|
||||
name: out_gen_artifacts_${{ env.ARTIFACT_KEY }}
|
||||
path: ./src/out/${{ env.ELECTRON_OUT_DIR }}/gen
|
||||
|
||||
@@ -173,12 +173,12 @@ jobs:
|
||||
echo "DISABLE_CRASH_REPORTER_TESTS=true" >> $GITHUB_ENV
|
||||
echo "IS_ASAN=true" >> $GITHUB_ENV
|
||||
- name: Download Generated Artifacts
|
||||
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c
|
||||
uses: actions/download-artifact@70fc10c6e5e1ce46ad2ea6f2b72d43f7d47b13c3
|
||||
with:
|
||||
name: generated_artifacts_${{ env.ARTIFACT_KEY }}
|
||||
path: ./generated_artifacts_${{ matrix.build-type }}_${{ inputs.target-arch }}
|
||||
- name: Download Src Artifacts
|
||||
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c
|
||||
uses: actions/download-artifact@70fc10c6e5e1ce46ad2ea6f2b72d43f7d47b13c3
|
||||
with:
|
||||
name: src_artifacts_${{ env.ARTIFACT_KEY }}
|
||||
path: ./src_artifacts_${{ matrix.build-type }}_${{ inputs.target-arch }}
|
||||
@@ -214,7 +214,7 @@ jobs:
|
||||
|
||||
- name: Run Electron Tests
|
||||
shell: bash
|
||||
timeout-minutes: 60
|
||||
timeout-minutes: 40
|
||||
env:
|
||||
MOCHA_REPORTER: mocha-multi-reporters
|
||||
MOCHA_MULTI_REPORTERS: mocha-junit-reporter, tap
|
||||
|
||||
@@ -65,12 +65,12 @@ jobs:
|
||||
- name: Install Dependencies
|
||||
uses: ./src/electron/.github/actions/install-dependencies
|
||||
- name: Download Generated Artifacts
|
||||
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c
|
||||
uses: actions/download-artifact@70fc10c6e5e1ce46ad2ea6f2b72d43f7d47b13c3
|
||||
with:
|
||||
name: generated_artifacts_${{ env.BUILD_TYPE }}_${{ env.TARGET_ARCH }}
|
||||
path: ./generated_artifacts_${{ env.BUILD_TYPE }}_${{ env.TARGET_ARCH }}
|
||||
- name: Download Src Artifacts
|
||||
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c
|
||||
uses: actions/download-artifact@70fc10c6e5e1ce46ad2ea6f2b72d43f7d47b13c3
|
||||
with:
|
||||
name: src_artifacts_linux_${{ env.TARGET_ARCH }}
|
||||
path: ./src_artifacts_linux_${{ env.TARGET_ARCH }}
|
||||
@@ -121,12 +121,12 @@ jobs:
|
||||
- name: Install Dependencies
|
||||
uses: ./src/electron/.github/actions/install-dependencies
|
||||
- name: Download Generated Artifacts
|
||||
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c
|
||||
uses: actions/download-artifact@70fc10c6e5e1ce46ad2ea6f2b72d43f7d47b13c3
|
||||
with:
|
||||
name: generated_artifacts_${{ env.BUILD_TYPE }}_${{ env.TARGET_ARCH }}
|
||||
path: ./generated_artifacts_${{ env.BUILD_TYPE }}_${{ env.TARGET_ARCH }}
|
||||
- name: Download Src Artifacts
|
||||
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c
|
||||
uses: actions/download-artifact@70fc10c6e5e1ce46ad2ea6f2b72d43f7d47b13c3
|
||||
with:
|
||||
name: src_artifacts_linux_${{ env.TARGET_ARCH }}
|
||||
path: ./src_artifacts_linux_${{ env.TARGET_ARCH }}
|
||||
|
||||
2
.github/workflows/pull-request-labeled.yml
vendored
2
.github/workflows/pull-request-labeled.yml
vendored
@@ -14,7 +14,7 @@ jobs:
|
||||
permissions: {}
|
||||
steps:
|
||||
- name: Trigger Slack workflow
|
||||
uses: slackapi/slack-github-action@af78098f536edbc4de71162a307590698245be95 # v3.0.1
|
||||
uses: slackapi/slack-github-action@91efab103c0de0a537f72a35f6b8cda0ee76bf0a # v2.1.1
|
||||
with:
|
||||
webhook: ${{ secrets.BACKPORT_REQUESTED_SLACK_WEBHOOK_URL }}
|
||||
webhook-type: webhook-trigger
|
||||
|
||||
2
.github/workflows/scorecards.yml
vendored
2
.github/workflows/scorecards.yml
vendored
@@ -51,6 +51,6 @@ jobs:
|
||||
|
||||
# Upload the results to GitHub's code scanning dashboard.
|
||||
- name: "Upload to code-scanning"
|
||||
uses: github/codeql-action/upload-sarif@b1bff81932f5cdfc8695c7752dcee935dcd061c8 # v3.29.5
|
||||
uses: github/codeql-action/upload-sarif@0d579ffd059c29b07949a3cce3983f0780820c98 # v3.29.5
|
||||
with:
|
||||
sarif_file: results.sarif
|
||||
|
||||
@@ -76,11 +76,87 @@ app.whenReady().then(() => {
|
||||
})
|
||||
```
|
||||
|
||||
#### `Notification.getHistory()` _macOS_
|
||||
|
||||
Returns `Promise<Notification[]>` - Resolves with an array of `Notification` objects representing all delivered notifications still present in Notification Center.
|
||||
|
||||
Each returned `Notification` is a live object connected to the corresponding delivered notification. Interaction events (`click`, `reply`, `action`, `close`) will fire on these objects when the user interacts with the notification in Notification Center. This is useful after an app restart to re-attach event handlers to notifications from a previous session.
|
||||
|
||||
The returned notifications have their `id`, `groupId`, `title`, `subtitle`, and `body` properties populated from what macOS provides. Other properties (e.g., `actions`, `silent`, `icon`) are not available from delivered notifications and will have default values.
|
||||
|
||||
> [!NOTE]
|
||||
> Like all macOS notification APIs, this method requires the application to be
|
||||
> code-signed. In unsigned development builds, notifications are not delivered
|
||||
> to Notification Center and this method will resolve with an empty array.
|
||||
|
||||
> [!NOTE]
|
||||
> Unlike notifications created with `new Notification()`, notifications returned
|
||||
> by `getHistory()` will remain visible in Notification Center when the object
|
||||
> is garbage collected.
|
||||
|
||||
```js
|
||||
const { Notification, app } = require('electron')
|
||||
|
||||
app.whenReady().then(async () => {
|
||||
// Restore notifications from a previous session
|
||||
const notifications = await Notification.getHistory()
|
||||
for (const n of notifications) {
|
||||
console.log(`Found delivered notification: ${n.id} - ${n.title}`)
|
||||
n.on('click', () => {
|
||||
console.log(`User clicked: ${n.id}`)
|
||||
})
|
||||
n.on('reply', (event) => {
|
||||
console.log(`User replied to ${n.id}: ${event.reply}`)
|
||||
})
|
||||
}
|
||||
// Keep references so events continue to fire
|
||||
})
|
||||
```
|
||||
|
||||
#### `Notification.remove(id)` _macOS_
|
||||
|
||||
* `id` (string | string[]) - The notification identifier(s) to remove. These correspond to the `id` values set in the [`Notification` constructor](#new-notificationoptions).
|
||||
|
||||
Removes one or more delivered notifications from Notification Center by their identifier(s).
|
||||
|
||||
```js
|
||||
const { Notification } = require('electron')
|
||||
|
||||
// Remove a single notification
|
||||
Notification.remove('my-notification-id')
|
||||
|
||||
// Remove multiple notifications
|
||||
Notification.remove(['msg-1', 'msg-2', 'msg-3'])
|
||||
```
|
||||
|
||||
#### `Notification.removeAll()` _macOS_
|
||||
|
||||
Removes all of the app's delivered notifications from Notification Center.
|
||||
|
||||
```js
|
||||
const { Notification } = require('electron')
|
||||
|
||||
Notification.removeAll()
|
||||
```
|
||||
|
||||
#### `Notification.removeGroup(groupId)` _macOS_
|
||||
|
||||
* `groupId` string - The group identifier of the notifications to remove. This corresponds to the `groupId` value set in the [`Notification` constructor](#new-notificationoptions).
|
||||
|
||||
Removes all delivered notifications with the given `groupId` from Notification Center.
|
||||
|
||||
```js
|
||||
const { Notification } = require('electron')
|
||||
|
||||
// Remove all notifications in the 'chat-thread-1' group
|
||||
Notification.removeGroup('chat-thread-1')
|
||||
```
|
||||
|
||||
### `new Notification([options])`
|
||||
|
||||
* `options` Object (optional)
|
||||
* `id` string (optional) _macOS_ - A unique identifier for the notification, mapping to `UNNotificationRequest`'s [`identifier`](https://developer.apple.com/documentation/usernotifications/unnotificationrequest/identifier) property. Defaults to a random UUID if not provided or if an empty string is passed. This can be used to remove or update previously delivered notifications.
|
||||
* `groupId` string (optional) _macOS_ - A string identifier used to visually group notifications together in Notification Center. Maps to `UNNotificationContent`'s [`threadIdentifier`](https://developer.apple.com/documentation/usernotifications/unnotificationcontent/threadidentifier) property.
|
||||
* `id` string (optional) _macOS_ - A unique identifier for the notification, mapping to `UNNotificationRequest`'s [`identifier`](https://developer.apple.com/documentation/usernotifications/unnotificationrequest/identifier) property. Defaults to a random UUID if not provided or if an empty string is passed. Use this identifier with [`Notification.remove()`](#notificationremoveid-macos) to remove specific delivered notifications, or with [`Notification.getHistory()`](#notificationgethistory-macos) to identify them.
|
||||
* `groupId` string (optional) _macOS_ - A string identifier used to visually group notifications together in Notification Center. Maps to `UNNotificationContent`'s [`threadIdentifier`](https://developer.apple.com/documentation/usernotifications/unnotificationcontent/threadidentifier) property. Use this identifier with [`Notification.removeGroup()`](#notificationremovegroupgroupid-macos) to remove all notifications in a group.
|
||||
* `title` string (optional) - A title for the notification, which will be displayed at the top of the notification window when it is shown.
|
||||
* `subtitle` string (optional) _macOS_ - A subtitle for the notification, which will be displayed below the title.
|
||||
* `body` string (optional) - The body text of the notification, which will be displayed below the title or subtitle.
|
||||
|
||||
@@ -179,7 +179,6 @@ auto_filenames = {
|
||||
"lib/common/define-properties.ts",
|
||||
"lib/common/deprecate.ts",
|
||||
"lib/common/ipc-messages.ts",
|
||||
"lib/common/timers-shim.ts",
|
||||
"lib/common/web-view-methods.ts",
|
||||
"lib/common/webpack-globals-provider.ts",
|
||||
"lib/renderer/api/context-bridge.ts",
|
||||
|
||||
@@ -1,36 +1,2 @@
|
||||
const { createGlobalShortcut } = process._linkedBinding('electron_browser_global_shortcut');
|
||||
|
||||
let globalShortcut: Electron.GlobalShortcut;
|
||||
|
||||
const createGlobalShortcutIfNeeded = () => {
|
||||
if (globalShortcut === undefined) {
|
||||
globalShortcut = createGlobalShortcut();
|
||||
}
|
||||
};
|
||||
|
||||
export default new Proxy({}, {
|
||||
get: (_target, property: keyof Electron.GlobalShortcut) => {
|
||||
createGlobalShortcutIfNeeded();
|
||||
const value = globalShortcut[property];
|
||||
if (typeof value === 'function') {
|
||||
return value.bind(globalShortcut);
|
||||
}
|
||||
return value;
|
||||
},
|
||||
set: (_target, property: string, value: unknown) => {
|
||||
createGlobalShortcutIfNeeded();
|
||||
return Reflect.set(globalShortcut, property, value);
|
||||
},
|
||||
ownKeys: () => {
|
||||
createGlobalShortcutIfNeeded();
|
||||
return Reflect.ownKeys(globalShortcut);
|
||||
},
|
||||
has: (_target, property: string) => {
|
||||
createGlobalShortcutIfNeeded();
|
||||
return property in globalShortcut;
|
||||
},
|
||||
getOwnPropertyDescriptor: (_target, property: string) => {
|
||||
createGlobalShortcutIfNeeded();
|
||||
return Reflect.getOwnPropertyDescriptor(globalShortcut, property);
|
||||
}
|
||||
});
|
||||
const { globalShortcut } = process._linkedBinding('electron_browser_global_shortcut');
|
||||
export default globalShortcut;
|
||||
|
||||
@@ -2,6 +2,10 @@ const binding = process._linkedBinding('electron_browser_notification');
|
||||
|
||||
const ElectronNotification = binding.Notification;
|
||||
ElectronNotification.isSupported = binding.isSupported;
|
||||
ElectronNotification.getHistory = binding.getHistory;
|
||||
ElectronNotification.remove = binding.remove;
|
||||
ElectronNotification.removeAll = binding.removeAll;
|
||||
ElectronNotification.removeGroup = binding.removeGroup;
|
||||
|
||||
if (process.platform === 'win32' && binding.handleActivation) {
|
||||
ElectronNotification.handleActivation = binding.handleActivation;
|
||||
|
||||
@@ -8,10 +8,10 @@ electron objects that extend gin::Wrappable and gets
|
||||
allocated on the cpp heap
|
||||
|
||||
diff --git a/gin/public/wrappable_pointer_tags.h b/gin/public/wrappable_pointer_tags.h
|
||||
index fee622ebde42211de6f702b754cfa38595df5a1c..3bdcd3f3f0a36314694495ca7361be14d95da911 100644
|
||||
index fee622ebde42211de6f702b754cfa38595df5a1c..6b524632ebb405e473cf4fe8e253bd13bf7b67e5 100644
|
||||
--- a/gin/public/wrappable_pointer_tags.h
|
||||
+++ b/gin/public/wrappable_pointer_tags.h
|
||||
@@ -77,7 +77,21 @@ enum WrappablePointerTag : uint16_t {
|
||||
@@ -77,7 +77,20 @@ enum WrappablePointerTag : uint16_t {
|
||||
kWebAXObjectProxy, // content::WebAXObjectProxy
|
||||
kWrappedExceptionHandler, // extensions::WrappedExceptionHandler
|
||||
kIndigoContext, // indigo::IndigoContext
|
||||
@@ -20,7 +20,6 @@ index fee622ebde42211de6f702b754cfa38595df5a1c..3bdcd3f3f0a36314694495ca7361be14
|
||||
+ kElectronDataPipeHolder, // electron::api::DataPipeHolder
|
||||
+ kElectronDebugger, // electron::api::Debugger
|
||||
+ kElectronEvent, // gin_helper::internal::Event
|
||||
+ kElectronGlobalShortcut, // electron::api::GlobalShortcut
|
||||
+ kElectronMenu, // electron::api::Menu
|
||||
+ kElectronNetLog, // electron::api::NetLog
|
||||
+ kElectronPowerMonitor, // electron::api::PowerMonitor
|
||||
|
||||
@@ -15,16 +15,14 @@
|
||||
#include "electron/shell/browser/electron_browser_context.h"
|
||||
#include "electron/shell/common/electron_constants.h"
|
||||
#include "extensions/common/command.h"
|
||||
#include "gin/dictionary.h"
|
||||
#include "gin/object_template_builder.h"
|
||||
#include "gin/persistent.h"
|
||||
#include "shell/browser/api/electron_api_system_preferences.h"
|
||||
#include "shell/browser/browser.h"
|
||||
#include "shell/common/gin_converters/accelerator_converter.h"
|
||||
#include "shell/common/gin_converters/callback_converter.h"
|
||||
#include "shell/common/gin_helper/dictionary.h"
|
||||
#include "shell/common/gin_helper/handle.h"
|
||||
#include "shell/common/node_includes.h"
|
||||
#include "v8/include/cppgc/allocation.h"
|
||||
#include "v8/include/v8-cppgc.h"
|
||||
|
||||
#if BUILDFLAG(IS_MAC)
|
||||
#include "base/mac/mac_util.h"
|
||||
@@ -52,9 +50,8 @@ bool MapHasMediaKeys(
|
||||
|
||||
namespace electron::api {
|
||||
|
||||
const gin::WrapperInfo GlobalShortcut::kWrapperInfo = {
|
||||
{gin::kEmbedderNativeGin},
|
||||
gin::kElectronGlobalShortcut};
|
||||
gin::DeprecatedWrapperInfo GlobalShortcut::kWrapperInfo = {
|
||||
gin::kEmbedderNativeGin};
|
||||
|
||||
GlobalShortcut::GlobalShortcut() = default;
|
||||
|
||||
@@ -63,7 +60,7 @@ GlobalShortcut::~GlobalShortcut() {
|
||||
if (instance && instance->IsRegistrationHandledExternally()) {
|
||||
// Eagerly cancel callbacks so PruneStaleCommands() can clear them before
|
||||
// the WeakPtrFactory destructor runs.
|
||||
weak_factory_.Invalidate();
|
||||
weak_ptr_factory_.InvalidateWeakPtrs();
|
||||
instance->PruneStaleCommands();
|
||||
}
|
||||
|
||||
@@ -72,8 +69,7 @@ GlobalShortcut::~GlobalShortcut() {
|
||||
|
||||
void GlobalShortcut::OnKeyPressed(const ui::Accelerator& accelerator) {
|
||||
if (auto* cb = base::FindOrNull(accelerator_callback_map_, accelerator)) {
|
||||
auto callback = *cb;
|
||||
callback.Run();
|
||||
cb->Run();
|
||||
} else {
|
||||
// This should never occur, because if it does,
|
||||
// ui::GlobalAcceleratorListener notifies us with wrong accelerator.
|
||||
@@ -84,8 +80,7 @@ void GlobalShortcut::OnKeyPressed(const ui::Accelerator& accelerator) {
|
||||
void GlobalShortcut::ExecuteCommand(const extensions::ExtensionId& extension_id,
|
||||
const std::string& command_id) {
|
||||
if (auto* cb = base::FindOrNull(command_callback_map_, command_id)) {
|
||||
auto callback = *cb;
|
||||
callback.Run();
|
||||
cb->Run();
|
||||
} else {
|
||||
// This should never occur, because if it does, GlobalAcceleratorListener
|
||||
// notifies us with wrong command.
|
||||
@@ -117,14 +112,11 @@ bool GlobalShortcut::RegisterAll(
|
||||
|
||||
bool GlobalShortcut::Register(const ui::Accelerator& accelerator,
|
||||
const base::RepeatingClosure& callback) {
|
||||
v8::Isolate* const isolate = JavascriptEnvironment::GetIsolate();
|
||||
|
||||
if (!electron::Browser::Get()->is_ready()) {
|
||||
gin_helper::ErrorThrower(isolate).ThrowError(
|
||||
"globalShortcut cannot be used before the app is ready");
|
||||
gin_helper::ErrorThrower(JavascriptEnvironment::GetIsolate())
|
||||
.ThrowError("globalShortcut cannot be used before the app is ready");
|
||||
return false;
|
||||
}
|
||||
|
||||
#if BUILDFLAG(IS_MAC)
|
||||
if (accelerator.IsMediaKey()) {
|
||||
if (RegisteringMediaKeyForUntrustedClient(accelerator))
|
||||
@@ -178,10 +170,8 @@ bool GlobalShortcut::Register(const ui::Accelerator& accelerator,
|
||||
const std::string fake_extension_id = command_str + "+" + profile_id;
|
||||
instance->OnCommandsChanged(
|
||||
fake_extension_id, profile_id, commands, gfx::kNullAcceleratedWidget,
|
||||
base::BindRepeating(
|
||||
&GlobalShortcut::ExecuteCommand,
|
||||
gin::WrapPersistent(weak_factory_.GetWeakCell(
|
||||
isolate->GetCppHeap()->GetAllocationHandle()))));
|
||||
base::BindRepeating(&GlobalShortcut::ExecuteCommand,
|
||||
weak_ptr_factory_.GetWeakPtr()));
|
||||
command_callback_map_[command_str] = callback;
|
||||
return true;
|
||||
} else {
|
||||
@@ -243,15 +233,16 @@ void GlobalShortcut::UnregisterAll() {
|
||||
}
|
||||
|
||||
// static
|
||||
GlobalShortcut* GlobalShortcut::Create(v8::Isolate* isolate) {
|
||||
return cppgc::MakeGarbageCollected<GlobalShortcut>(
|
||||
isolate->GetCppHeap()->GetAllocationHandle());
|
||||
gin_helper::Handle<GlobalShortcut> GlobalShortcut::Create(
|
||||
v8::Isolate* isolate) {
|
||||
return gin_helper::CreateHandle(isolate, new GlobalShortcut());
|
||||
}
|
||||
|
||||
// static
|
||||
gin::ObjectTemplateBuilder GlobalShortcut::GetObjectTemplateBuilder(
|
||||
v8::Isolate* isolate) {
|
||||
return gin::Wrappable<GlobalShortcut>::GetObjectTemplateBuilder(isolate)
|
||||
return gin_helper::DeprecatedWrappable<
|
||||
GlobalShortcut>::GetObjectTemplateBuilder(isolate)
|
||||
.SetMethod("registerAll", &GlobalShortcut::RegisterAll)
|
||||
.SetMethod("register", &GlobalShortcut::Register)
|
||||
.SetMethod("isRegistered", &GlobalShortcut::IsRegistered)
|
||||
@@ -259,17 +250,8 @@ gin::ObjectTemplateBuilder GlobalShortcut::GetObjectTemplateBuilder(
|
||||
.SetMethod("unregisterAll", &GlobalShortcut::UnregisterAll);
|
||||
}
|
||||
|
||||
void GlobalShortcut::Trace(cppgc::Visitor* visitor) const {
|
||||
gin::Wrappable<GlobalShortcut>::Trace(visitor);
|
||||
visitor->Trace(weak_factory_);
|
||||
}
|
||||
|
||||
const gin::WrapperInfo* GlobalShortcut::wrapper_info() const {
|
||||
return &kWrapperInfo;
|
||||
}
|
||||
|
||||
const char* GlobalShortcut::GetHumanReadableName() const {
|
||||
return "Electron / GlobalShortcut";
|
||||
const char* GlobalShortcut::GetTypeName() {
|
||||
return "GlobalShortcut";
|
||||
}
|
||||
|
||||
} // namespace electron::api
|
||||
@@ -281,9 +263,8 @@ void Initialize(v8::Local<v8::Object> exports,
|
||||
v8::Local<v8::Context> context,
|
||||
void* priv) {
|
||||
v8::Isolate* const isolate = electron::JavascriptEnvironment::GetIsolate();
|
||||
gin_helper::Dictionary dict{isolate, exports};
|
||||
dict.SetMethod("createGlobalShortcut",
|
||||
base::BindRepeating(&electron::api::GlobalShortcut::Create));
|
||||
gin::Dictionary dict{isolate, exports};
|
||||
dict.Set("globalShortcut", electron::api::GlobalShortcut::Create(isolate));
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
@@ -11,35 +11,37 @@
|
||||
#include "base/functional/callback_forward.h"
|
||||
#include "base/memory/weak_ptr.h"
|
||||
#include "extensions/common/extension_id.h"
|
||||
#include "gin/weak_cell.h"
|
||||
#include "gin/wrappable.h"
|
||||
#include "shell/common/gin_helper/self_keep_alive.h"
|
||||
#include "shell/common/gin_helper/wrappable.h"
|
||||
#include "ui/base/accelerators/accelerator.h"
|
||||
#include "ui/base/accelerators/global_accelerator_listener/global_accelerator_listener.h"
|
||||
|
||||
namespace gin_helper {
|
||||
template <typename T>
|
||||
class Handle;
|
||||
} // namespace gin_helper
|
||||
|
||||
namespace electron::api {
|
||||
|
||||
class GlobalShortcut final : private ui::GlobalAcceleratorListener::Observer,
|
||||
public gin::Wrappable<GlobalShortcut> {
|
||||
class GlobalShortcut final
|
||||
: private ui::GlobalAcceleratorListener::Observer,
|
||||
public gin_helper::DeprecatedWrappable<GlobalShortcut> {
|
||||
public:
|
||||
static GlobalShortcut* Create(v8::Isolate* isolate);
|
||||
static gin_helper::Handle<GlobalShortcut> Create(v8::Isolate* isolate);
|
||||
|
||||
// gin::Wrappable
|
||||
static const gin::WrapperInfo kWrapperInfo;
|
||||
void Trace(cppgc::Visitor* visitor) const override;
|
||||
const gin::WrapperInfo* wrapper_info() const override;
|
||||
const char* GetHumanReadableName() const override;
|
||||
// gin_helper::Wrappable
|
||||
static gin::DeprecatedWrapperInfo kWrapperInfo;
|
||||
gin::ObjectTemplateBuilder GetObjectTemplateBuilder(
|
||||
v8::Isolate* isolate) override;
|
||||
|
||||
// Make public for cppgc::MakeGarbageCollected.
|
||||
GlobalShortcut();
|
||||
~GlobalShortcut() override;
|
||||
const char* GetTypeName() override;
|
||||
|
||||
// disable copy
|
||||
GlobalShortcut(const GlobalShortcut&) = delete;
|
||||
GlobalShortcut& operator=(const GlobalShortcut&) = delete;
|
||||
|
||||
protected:
|
||||
GlobalShortcut();
|
||||
~GlobalShortcut() override;
|
||||
|
||||
private:
|
||||
typedef std::map<ui::Accelerator, base::RepeatingClosure>
|
||||
AcceleratorCallbackMap;
|
||||
@@ -62,9 +64,7 @@ class GlobalShortcut final : private ui::GlobalAcceleratorListener::Observer,
|
||||
AcceleratorCallbackMap accelerator_callback_map_;
|
||||
CommandCallbackMap command_callback_map_;
|
||||
|
||||
gin::WeakCellFactory<GlobalShortcut> weak_factory_{this};
|
||||
|
||||
gin_helper::SelfKeepAlive<GlobalShortcut> keep_alive_{this};
|
||||
base::WeakPtrFactory<GlobalShortcut> weak_ptr_factory_{this};
|
||||
};
|
||||
|
||||
} // namespace electron::api
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
#include "shell/browser/api/electron_api_notification.h"
|
||||
|
||||
#include "base/functional/bind.h"
|
||||
#include "base/strings/utf_string_conversions.h"
|
||||
#include "base/uuid.h"
|
||||
#include "build/build_config.h"
|
||||
#include "content/public/browser/browser_task_traits.h"
|
||||
@@ -13,10 +14,12 @@
|
||||
#include "shell/browser/browser.h"
|
||||
#include "shell/browser/electron_browser_client.h"
|
||||
#include "shell/common/gin_converters/image_converter.h"
|
||||
#include "shell/common/gin_converters/value_converter.h"
|
||||
#include "shell/common/gin_helper/dictionary.h"
|
||||
#include "shell/common/gin_helper/error_thrower.h"
|
||||
#include "shell/common/gin_helper/handle.h"
|
||||
#include "shell/common/gin_helper/object_template_builder.h"
|
||||
#include "shell/common/gin_helper/promise.h"
|
||||
#include "shell/common/node_includes.h"
|
||||
#include "url/gurl.h"
|
||||
|
||||
@@ -72,27 +75,29 @@ Notification::Notification(gin::Arguments* args) {
|
||||
presenter_ = static_cast<ElectronBrowserClient*>(ElectronBrowserClient::Get())
|
||||
->GetNotificationPresenter();
|
||||
|
||||
gin::Dictionary opts(nullptr);
|
||||
if (args->GetNext(&opts)) {
|
||||
opts.Get("id", &id_);
|
||||
opts.Get("groupId", &group_id_);
|
||||
opts.Get("title", &title_);
|
||||
opts.Get("subtitle", &subtitle_);
|
||||
opts.Get("body", &body_);
|
||||
opts.Get("icon", &icon_);
|
||||
opts.Get("silent", &silent_);
|
||||
opts.Get("replyPlaceholder", &reply_placeholder_);
|
||||
opts.Get("urgency", &urgency_);
|
||||
opts.Get("hasReply", &has_reply_);
|
||||
opts.Get("timeoutType", &timeout_type_);
|
||||
opts.Get("actions", &actions_);
|
||||
opts.Get("sound", &sound_);
|
||||
opts.Get("closeButtonText", &close_button_text_);
|
||||
opts.Get("toastXml", &toast_xml_);
|
||||
}
|
||||
if (args) {
|
||||
gin::Dictionary opts(nullptr);
|
||||
if (args->GetNext(&opts)) {
|
||||
opts.Get("id", &id_);
|
||||
opts.Get("groupId", &group_id_);
|
||||
opts.Get("title", &title_);
|
||||
opts.Get("subtitle", &subtitle_);
|
||||
opts.Get("body", &body_);
|
||||
opts.Get("icon", &icon_);
|
||||
opts.Get("silent", &silent_);
|
||||
opts.Get("replyPlaceholder", &reply_placeholder_);
|
||||
opts.Get("urgency", &urgency_);
|
||||
opts.Get("hasReply", &has_reply_);
|
||||
opts.Get("timeoutType", &timeout_type_);
|
||||
opts.Get("actions", &actions_);
|
||||
opts.Get("sound", &sound_);
|
||||
opts.Get("closeButtonText", &close_button_text_);
|
||||
opts.Get("toastXml", &toast_xml_);
|
||||
}
|
||||
|
||||
if (id_.empty())
|
||||
id_ = base::Uuid::GenerateRandomV4().AsLowercaseString();
|
||||
if (id_.empty())
|
||||
id_ = base::Uuid::GenerateRandomV4().AsLowercaseString();
|
||||
}
|
||||
}
|
||||
|
||||
Notification::~Notification() {
|
||||
@@ -342,6 +347,115 @@ void Notification::HandleActivation(v8::Isolate* isolate,
|
||||
}
|
||||
#endif
|
||||
|
||||
// static
|
||||
v8::Local<v8::Promise> Notification::GetHistory(v8::Isolate* isolate) {
|
||||
gin_helper::Promise<v8::Local<v8::Value>> promise(isolate);
|
||||
v8::Local<v8::Promise> handle = promise.GetHandle();
|
||||
|
||||
auto* presenter =
|
||||
static_cast<ElectronBrowserClient*>(ElectronBrowserClient::Get())
|
||||
->GetNotificationPresenter();
|
||||
if (!presenter) {
|
||||
promise.Resolve(v8::Array::New(isolate));
|
||||
return handle;
|
||||
}
|
||||
|
||||
presenter->GetDeliveredNotifications(base::BindOnce(
|
||||
[](gin_helper::Promise<v8::Local<v8::Value>> promise,
|
||||
electron::NotificationPresenter* presenter,
|
||||
std::vector<electron::NotificationInfo> notifications) {
|
||||
v8::Isolate* isolate = promise.isolate();
|
||||
v8::HandleScope handle_scope(isolate);
|
||||
|
||||
v8::Local<v8::Array> result =
|
||||
v8::Array::New(isolate, notifications.size());
|
||||
for (size_t i = 0; i < notifications.size(); i++) {
|
||||
const auto& info = notifications[i];
|
||||
|
||||
// Create a live Notification object for each delivered notification.
|
||||
auto* notif = new Notification(/*args=*/nullptr);
|
||||
notif->id_ = info.id;
|
||||
notif->group_id_ = info.group_id;
|
||||
notif->title_ = base::UTF8ToUTF16(info.title);
|
||||
notif->subtitle_ = base::UTF8ToUTF16(info.subtitle);
|
||||
notif->body_ = base::UTF8ToUTF16(info.body);
|
||||
|
||||
// Register with the presenter so click/reply events route here.
|
||||
if (presenter) {
|
||||
notif->notification_ =
|
||||
presenter->CreateNotification(notif, notif->id_);
|
||||
if (notif->notification_)
|
||||
notif->notification_->Restore();
|
||||
}
|
||||
|
||||
auto handle = gin_helper::CreateHandle(isolate, notif);
|
||||
result
|
||||
->Set(isolate->GetCurrentContext(), static_cast<uint32_t>(i),
|
||||
handle.ToV8())
|
||||
.Check();
|
||||
}
|
||||
|
||||
promise.Resolve(result.As<v8::Value>());
|
||||
},
|
||||
std::move(promise), presenter));
|
||||
|
||||
return handle;
|
||||
}
|
||||
|
||||
// static
|
||||
void Notification::Remove(gin::Arguments* args) {
|
||||
auto* presenter =
|
||||
static_cast<ElectronBrowserClient*>(ElectronBrowserClient::Get())
|
||||
->GetNotificationPresenter();
|
||||
if (!presenter)
|
||||
return;
|
||||
|
||||
// Accept either a single string or an array of strings.
|
||||
// Peek at the value type first to avoid gin::Arguments cursor issues.
|
||||
v8::Local<v8::Value> val;
|
||||
if (!args->GetNext(&val)) {
|
||||
args->ThrowTypeError("Expected a string or array of strings");
|
||||
return;
|
||||
}
|
||||
|
||||
if (val->IsString()) {
|
||||
std::string id;
|
||||
gin::ConvertFromV8(args->isolate(), val, &id);
|
||||
presenter->RemoveDeliveredNotifications({id});
|
||||
} else if (val->IsArray()) {
|
||||
std::vector<std::string> ids;
|
||||
if (!gin::ConvertFromV8(args->isolate(), val, &ids)) {
|
||||
args->ThrowTypeError("Expected a string or array of strings");
|
||||
return;
|
||||
}
|
||||
presenter->RemoveDeliveredNotifications(ids);
|
||||
} else {
|
||||
args->ThrowTypeError("Expected a string or array of strings");
|
||||
}
|
||||
}
|
||||
|
||||
// static
|
||||
void Notification::RemoveAll() {
|
||||
auto* presenter =
|
||||
static_cast<ElectronBrowserClient*>(ElectronBrowserClient::Get())
|
||||
->GetNotificationPresenter();
|
||||
if (!presenter)
|
||||
return;
|
||||
|
||||
presenter->RemoveAllDeliveredNotifications();
|
||||
}
|
||||
|
||||
// static
|
||||
void Notification::RemoveGroup(const std::string& group_id) {
|
||||
auto* presenter =
|
||||
static_cast<ElectronBrowserClient*>(ElectronBrowserClient::Get())
|
||||
->GetNotificationPresenter();
|
||||
if (!presenter)
|
||||
return;
|
||||
|
||||
presenter->RemoveDeliveredNotificationsByGroupId(group_id);
|
||||
}
|
||||
|
||||
void Notification::FillObjectTemplate(v8::Isolate* isolate,
|
||||
v8::Local<v8::ObjectTemplate> templ) {
|
||||
gin::ObjectTemplateBuilder(isolate, GetClassName(), templ)
|
||||
@@ -395,6 +509,10 @@ void Initialize(v8::Local<v8::Object> exports,
|
||||
#if BUILDFLAG(IS_WIN)
|
||||
dict.SetMethod("handleActivation", &Notification::HandleActivation);
|
||||
#endif
|
||||
dict.SetMethod("getHistory", &Notification::GetHistory);
|
||||
dict.SetMethod("remove", &Notification::Remove);
|
||||
dict.SetMethod("removeAll", &Notification::RemoveAll);
|
||||
dict.SetMethod("removeGroup", &Notification::RemoveGroup);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
@@ -38,6 +38,10 @@ class Notification final : public gin_helper::DeprecatedWrappable<Notification>,
|
||||
public NotificationDelegate {
|
||||
public:
|
||||
static bool IsSupported();
|
||||
static v8::Local<v8::Promise> GetHistory(v8::Isolate* isolate);
|
||||
static void Remove(gin::Arguments* args);
|
||||
static void RemoveAll();
|
||||
static void RemoveGroup(const std::string& group_id);
|
||||
|
||||
#if BUILDFLAG(IS_WIN)
|
||||
// Register a callback to handle all notification activations.
|
||||
|
||||
@@ -24,6 +24,7 @@ class CocoaNotification : public Notification {
|
||||
// Notification:
|
||||
void Show(const NotificationOptions& options) override;
|
||||
void Dismiss() override;
|
||||
void Restore() override;
|
||||
|
||||
void NotificationDisplayed();
|
||||
void NotificationReplied(const std::string& reply);
|
||||
@@ -38,6 +39,7 @@ class CocoaNotification : public Notification {
|
||||
void LogAction(const char* action);
|
||||
void ScheduleNotification(UNMutableNotificationContent* content);
|
||||
|
||||
bool is_restored_ = false;
|
||||
UNNotificationRequest* __strong notification_request_;
|
||||
};
|
||||
|
||||
|
||||
@@ -31,7 +31,10 @@ CocoaNotification::CocoaNotification(NotificationDelegate* delegate,
|
||||
: Notification(delegate, presenter) {}
|
||||
|
||||
CocoaNotification::~CocoaNotification() {
|
||||
if (notification_request_)
|
||||
// Don't remove from Notification Center if this was a restored notification.
|
||||
// Restored notifications are observed, not owned — destruction should just
|
||||
// disconnect the event handler, not remove the visible notification.
|
||||
if (notification_request_ && !is_restored_)
|
||||
[[UNUserNotificationCenter currentNotificationCenter]
|
||||
removeDeliveredNotificationsWithIdentifiers:@[
|
||||
notification_request_.identifier
|
||||
@@ -233,6 +236,24 @@ void CocoaNotification::ScheduleNotification(
|
||||
}];
|
||||
}
|
||||
|
||||
void CocoaNotification::Restore() {
|
||||
// Create a minimal UNNotificationRequest with just the identifier so that
|
||||
// GetNotification() can match this object when the user interacts with the
|
||||
// notification in Notification Center.
|
||||
NSString* identifier = base::SysUTF8ToNSString(notification_id());
|
||||
UNMutableNotificationContent* content =
|
||||
[[UNMutableNotificationContent alloc] init];
|
||||
notification_request_ =
|
||||
[UNNotificationRequest requestWithIdentifier:identifier
|
||||
content:content
|
||||
trigger:nil];
|
||||
is_restored_ = true;
|
||||
|
||||
if (electron::debug_notifications) {
|
||||
LOG(INFO) << "Notification restored (" << [identifier UTF8String] << ")";
|
||||
}
|
||||
}
|
||||
|
||||
void CocoaNotification::Dismiss() {
|
||||
if (notification_request_)
|
||||
[[UNUserNotificationCenter currentNotificationCenter]
|
||||
|
||||
@@ -24,6 +24,15 @@ class NotificationPresenterMac : public NotificationPresenter {
|
||||
NotificationPresenterMac();
|
||||
~NotificationPresenterMac() override;
|
||||
|
||||
// NotificationPresenter
|
||||
void GetDeliveredNotifications(
|
||||
GetDeliveredNotificationsCallback callback) override;
|
||||
void RemoveDeliveredNotifications(
|
||||
const std::vector<std::string>& identifiers) override;
|
||||
void RemoveAllDeliveredNotifications() override;
|
||||
void RemoveDeliveredNotificationsByGroupId(
|
||||
const std::string& group_id) override;
|
||||
|
||||
NotificationImageRetainer* image_retainer() { return image_retainer_.get(); }
|
||||
scoped_refptr<base::SequencedTaskRunner> image_task_runner() {
|
||||
return image_task_runner_;
|
||||
|
||||
@@ -2,11 +2,16 @@
|
||||
// Use of this source code is governed by the MIT license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#include "base/logging.h"
|
||||
#include "base/task/thread_pool.h"
|
||||
|
||||
#include "shell/browser/notifications/mac/notification_presenter_mac.h"
|
||||
|
||||
#include <string>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include "base/logging.h"
|
||||
#include "base/strings/sys_string_conversions.h"
|
||||
#include "base/task/sequenced_task_runner.h"
|
||||
#include "base/task/thread_pool.h"
|
||||
#include "shell/browser/notifications/mac/cocoa_notification.h"
|
||||
#include "shell/browser/notifications/mac/notification_center_delegate.h"
|
||||
|
||||
@@ -80,4 +85,72 @@ Notification* NotificationPresenterMac::CreateNotificationObject(
|
||||
return new CocoaNotification(delegate, this);
|
||||
}
|
||||
|
||||
void NotificationPresenterMac::GetDeliveredNotifications(
|
||||
GetDeliveredNotificationsCallback callback) {
|
||||
scoped_refptr<base::SequencedTaskRunner> task_runner =
|
||||
base::SequencedTaskRunner::GetCurrentDefault();
|
||||
|
||||
__block GetDeliveredNotificationsCallback block_callback =
|
||||
std::move(callback);
|
||||
|
||||
[[UNUserNotificationCenter currentNotificationCenter]
|
||||
getDeliveredNotificationsWithCompletionHandler:^(
|
||||
NSArray<UNNotification*>* _Nonnull notifications) {
|
||||
std::vector<NotificationInfo> results;
|
||||
results.reserve([notifications count]);
|
||||
|
||||
for (UNNotification* notification in notifications) {
|
||||
UNNotificationContent* content = notification.request.content;
|
||||
NotificationInfo info;
|
||||
info.id = base::SysNSStringToUTF8(notification.request.identifier);
|
||||
info.title = base::SysNSStringToUTF8(content.title);
|
||||
info.subtitle = base::SysNSStringToUTF8(content.subtitle);
|
||||
info.body = base::SysNSStringToUTF8(content.body);
|
||||
info.group_id = base::SysNSStringToUTF8(content.threadIdentifier);
|
||||
results.push_back(std::move(info));
|
||||
}
|
||||
|
||||
task_runner->PostTask(
|
||||
FROM_HERE,
|
||||
base::BindOnce(std::move(block_callback), std::move(results)));
|
||||
}];
|
||||
}
|
||||
|
||||
void NotificationPresenterMac::RemoveDeliveredNotifications(
|
||||
const std::vector<std::string>& identifiers) {
|
||||
NSMutableArray* ns_identifiers =
|
||||
[NSMutableArray arrayWithCapacity:identifiers.size()];
|
||||
for (const auto& id : identifiers) {
|
||||
[ns_identifiers addObject:base::SysUTF8ToNSString(id)];
|
||||
}
|
||||
[[UNUserNotificationCenter currentNotificationCenter]
|
||||
removeDeliveredNotificationsWithIdentifiers:ns_identifiers];
|
||||
}
|
||||
|
||||
void NotificationPresenterMac::RemoveAllDeliveredNotifications() {
|
||||
[[UNUserNotificationCenter currentNotificationCenter]
|
||||
removeAllDeliveredNotifications];
|
||||
}
|
||||
|
||||
void NotificationPresenterMac::RemoveDeliveredNotificationsByGroupId(
|
||||
const std::string& group_id) {
|
||||
NSString* target_group = base::SysUTF8ToNSString(group_id);
|
||||
UNUserNotificationCenter* center =
|
||||
[UNUserNotificationCenter currentNotificationCenter];
|
||||
|
||||
[center getDeliveredNotificationsWithCompletionHandler:^(
|
||||
NSArray<UNNotification*>* _Nonnull notifications) {
|
||||
NSMutableArray* matching_ids = [NSMutableArray array];
|
||||
for (UNNotification* notification in notifications) {
|
||||
if ([notification.request.content.threadIdentifier
|
||||
isEqualToString:target_group]) {
|
||||
[matching_ids addObject:notification.request.identifier];
|
||||
}
|
||||
}
|
||||
if (matching_ids.count > 0) {
|
||||
[center removeDeliveredNotificationsWithIdentifiers:matching_ids];
|
||||
}
|
||||
}];
|
||||
}
|
||||
|
||||
} // namespace electron
|
||||
|
||||
@@ -22,6 +22,15 @@ NotificationOptions& NotificationOptions::operator=(NotificationOptions&&) =
|
||||
default;
|
||||
NotificationOptions::~NotificationOptions() = default;
|
||||
|
||||
NotificationInfo::NotificationInfo() = default;
|
||||
NotificationInfo::~NotificationInfo() = default;
|
||||
NotificationInfo::NotificationInfo(const NotificationInfo&) = default;
|
||||
NotificationInfo& NotificationInfo::operator=(const NotificationInfo&) =
|
||||
default;
|
||||
NotificationInfo::NotificationInfo(NotificationInfo&&) noexcept = default;
|
||||
NotificationInfo& NotificationInfo::operator=(NotificationInfo&&) noexcept =
|
||||
default;
|
||||
|
||||
NotificationAction::NotificationAction() = default;
|
||||
NotificationAction::~NotificationAction() = default;
|
||||
NotificationAction::NotificationAction(const NotificationAction&) = default;
|
||||
|
||||
@@ -59,6 +59,21 @@ struct NotificationOptions {
|
||||
~NotificationOptions();
|
||||
};
|
||||
|
||||
struct NotificationInfo {
|
||||
std::string id;
|
||||
std::string title;
|
||||
std::string subtitle;
|
||||
std::string body;
|
||||
std::string group_id;
|
||||
|
||||
NotificationInfo();
|
||||
~NotificationInfo();
|
||||
NotificationInfo(const NotificationInfo&);
|
||||
NotificationInfo& operator=(const NotificationInfo&);
|
||||
NotificationInfo(NotificationInfo&&) noexcept;
|
||||
NotificationInfo& operator=(NotificationInfo&&) noexcept;
|
||||
};
|
||||
|
||||
class Notification {
|
||||
public:
|
||||
virtual ~Notification();
|
||||
@@ -75,6 +90,11 @@ class Notification {
|
||||
// as can happen on some platforms including Windows.
|
||||
virtual void Remove() {}
|
||||
|
||||
// Restores a previously delivered notification for event handling without
|
||||
// re-showing it. Sets up platform state so interaction events (click, reply,
|
||||
// etc.) route to this object.
|
||||
virtual void Restore() {}
|
||||
|
||||
// Should be called by derived classes.
|
||||
void NotificationClicked();
|
||||
void NotificationDismissed(bool should_destroy = true,
|
||||
|
||||
@@ -44,4 +44,24 @@ void NotificationPresenter::CloseNotificationWithId(
|
||||
}
|
||||
}
|
||||
|
||||
void NotificationPresenter::GetDeliveredNotifications(
|
||||
GetDeliveredNotificationsCallback callback) {
|
||||
// Default: return empty list. Overridden on macOS.
|
||||
std::move(callback).Run({});
|
||||
}
|
||||
|
||||
void NotificationPresenter::RemoveDeliveredNotifications(
|
||||
const std::vector<std::string>& identifiers) {
|
||||
// Default: no-op. Overridden on macOS.
|
||||
}
|
||||
|
||||
void NotificationPresenter::RemoveAllDeliveredNotifications() {
|
||||
// Default: no-op. Overridden on macOS.
|
||||
}
|
||||
|
||||
void NotificationPresenter::RemoveDeliveredNotificationsByGroupId(
|
||||
const std::string& group_id) {
|
||||
// Default: no-op. Overridden on macOS.
|
||||
}
|
||||
|
||||
} // namespace electron
|
||||
|
||||
@@ -8,12 +8,14 @@
|
||||
#include <memory>
|
||||
#include <set>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "base/functional/callback.h"
|
||||
#include "base/memory/weak_ptr.h"
|
||||
#include "shell/browser/notifications/notification.h"
|
||||
|
||||
namespace electron {
|
||||
|
||||
class Notification;
|
||||
class NotificationDelegate;
|
||||
|
||||
class NotificationPresenter {
|
||||
@@ -22,11 +24,22 @@ class NotificationPresenter {
|
||||
|
||||
virtual ~NotificationPresenter();
|
||||
|
||||
using GetDeliveredNotificationsCallback =
|
||||
base::OnceCallback<void(std::vector<NotificationInfo>)>;
|
||||
|
||||
base::WeakPtr<Notification> CreateNotification(
|
||||
NotificationDelegate* delegate,
|
||||
const std::string& notification_id);
|
||||
void CloseNotificationWithId(const std::string& notification_id);
|
||||
|
||||
virtual void GetDeliveredNotifications(
|
||||
GetDeliveredNotificationsCallback callback);
|
||||
virtual void RemoveDeliveredNotifications(
|
||||
const std::vector<std::string>& identifiers);
|
||||
virtual void RemoveAllDeliveredNotifications();
|
||||
virtual void RemoveDeliveredNotificationsByGroupId(
|
||||
const std::string& group_id);
|
||||
|
||||
std::set<Notification*> notifications() const { return notifications_; }
|
||||
|
||||
// disable copy
|
||||
|
||||
@@ -261,4 +261,223 @@ describe('Notification module', () => {
|
||||
});
|
||||
|
||||
// TODO(sethlu): Find way to test init with notification icon?
|
||||
|
||||
describe('static methods', () => {
|
||||
ifit(process.platform === 'darwin')('getHistory returns a promise that resolves to an array', async () => {
|
||||
const result = Notification.getHistory();
|
||||
expect(result).to.be.a('promise');
|
||||
const history = await result;
|
||||
expect(history).to.be.an('array');
|
||||
});
|
||||
|
||||
ifit(process.platform === 'darwin')('remove does not throw with a string argument', () => {
|
||||
expect(() => Notification.remove('nonexistent-id')).to.not.throw();
|
||||
});
|
||||
|
||||
ifit(process.platform === 'darwin')('remove does not throw with an array argument', () => {
|
||||
expect(() => Notification.remove(['id-1', 'id-2'])).to.not.throw();
|
||||
});
|
||||
|
||||
ifit(process.platform === 'darwin')('remove throws with no arguments', () => {
|
||||
expect(() => (Notification.remove as any)()).to.throw(/Expected a string or array of strings/);
|
||||
});
|
||||
|
||||
ifit(process.platform === 'darwin')('remove throws with an invalid argument type', () => {
|
||||
expect(() => (Notification.remove as any)(123)).to.throw(/Expected a string or array of strings/);
|
||||
});
|
||||
|
||||
ifit(process.platform === 'darwin')('removeAll does not throw', () => {
|
||||
expect(() => Notification.removeAll()).to.not.throw();
|
||||
});
|
||||
|
||||
ifit(process.platform === 'darwin')('getHistory returns Notification instances with correct properties', async () => {
|
||||
const n = new Notification({
|
||||
id: 'history-test-id',
|
||||
title: 'history test',
|
||||
subtitle: 'history subtitle',
|
||||
body: 'history body',
|
||||
groupId: 'history-group',
|
||||
silent: true
|
||||
});
|
||||
|
||||
const shown = once(n, 'show');
|
||||
n.show();
|
||||
await shown;
|
||||
|
||||
const history = await Notification.getHistory();
|
||||
// getHistory requires code-signed builds to return results;
|
||||
// skip the content assertions if Notification Center is empty.
|
||||
if (history.length > 0) {
|
||||
const found = history.find((item: any) => item.id === 'history-test-id');
|
||||
expect(found).to.not.be.undefined();
|
||||
expect(found).to.be.an.instanceOf(Notification);
|
||||
expect(found.title).to.equal('history test');
|
||||
expect(found.subtitle).to.equal('history subtitle');
|
||||
expect(found.body).to.equal('history body');
|
||||
expect(found.groupId).to.equal('history-group');
|
||||
}
|
||||
|
||||
n.close();
|
||||
});
|
||||
|
||||
ifit(process.platform === 'darwin')('getHistory returned notifications can be shown and closed', async () => {
|
||||
const n = new Notification({
|
||||
id: 'history-show-close',
|
||||
title: 'show close test',
|
||||
body: 'body',
|
||||
silent: true
|
||||
});
|
||||
|
||||
const shown = once(n, 'show');
|
||||
n.show();
|
||||
await shown;
|
||||
|
||||
const history = await Notification.getHistory();
|
||||
if (history.length > 0) {
|
||||
const found = history.find((item: any) => item.id === 'history-show-close');
|
||||
expect(found).to.not.be.undefined();
|
||||
// Calling show() and close() on a restored notification should not throw
|
||||
expect(() => {
|
||||
found.show();
|
||||
found.close();
|
||||
}).to.not.throw();
|
||||
}
|
||||
|
||||
Notification.removeAll();
|
||||
});
|
||||
|
||||
ifit(process.platform === 'darwin')('remove removes a notification by id', async () => {
|
||||
const n = new Notification({
|
||||
id: 'remove-test-id',
|
||||
title: 'remove test',
|
||||
body: 'remove body',
|
||||
silent: true
|
||||
});
|
||||
|
||||
const shown = once(n, 'show');
|
||||
n.show();
|
||||
await shown;
|
||||
|
||||
Notification.remove('remove-test-id');
|
||||
|
||||
// Give the notification center a moment to process the removal
|
||||
await new Promise(resolve => setTimeout(resolve, 100));
|
||||
|
||||
const history = await Notification.getHistory();
|
||||
const found = history.find((item: any) => item.id === 'remove-test-id');
|
||||
expect(found).to.be.undefined();
|
||||
});
|
||||
|
||||
ifit(process.platform === 'darwin')('remove accepts an array of ids', async () => {
|
||||
const n1 = new Notification({
|
||||
id: 'remove-array-1',
|
||||
title: 'test 1',
|
||||
body: 'body 1',
|
||||
silent: true
|
||||
});
|
||||
const n2 = new Notification({
|
||||
id: 'remove-array-2',
|
||||
title: 'test 2',
|
||||
body: 'body 2',
|
||||
silent: true
|
||||
});
|
||||
|
||||
const shown1 = once(n1, 'show');
|
||||
n1.show();
|
||||
await shown1;
|
||||
|
||||
const shown2 = once(n2, 'show');
|
||||
n2.show();
|
||||
await shown2;
|
||||
|
||||
Notification.remove(['remove-array-1', 'remove-array-2']);
|
||||
|
||||
await new Promise(resolve => setTimeout(resolve, 100));
|
||||
|
||||
const history = await Notification.getHistory();
|
||||
const found1 = history.find((item: any) => item.id === 'remove-array-1');
|
||||
const found2 = history.find((item: any) => item.id === 'remove-array-2');
|
||||
expect(found1).to.be.undefined();
|
||||
expect(found2).to.be.undefined();
|
||||
});
|
||||
|
||||
ifit(process.platform === 'darwin')('removeAll removes all notifications', async () => {
|
||||
const n = new Notification({
|
||||
id: 'remove-all-test',
|
||||
title: 'removeAll test',
|
||||
body: 'body',
|
||||
silent: true
|
||||
});
|
||||
|
||||
const shown = once(n, 'show');
|
||||
n.show();
|
||||
await shown;
|
||||
|
||||
Notification.removeAll();
|
||||
|
||||
await new Promise(resolve => setTimeout(resolve, 100));
|
||||
|
||||
const history = await Notification.getHistory();
|
||||
const found = history.find((item: any) => item.id === 'remove-all-test');
|
||||
expect(found).to.be.undefined();
|
||||
});
|
||||
|
||||
ifit(process.platform === 'darwin')('remove does not throw with an empty array', () => {
|
||||
expect(() => Notification.remove([])).to.not.throw();
|
||||
});
|
||||
|
||||
ifit(process.platform === 'darwin')('remove does not throw with an empty string', () => {
|
||||
expect(() => Notification.remove('')).to.not.throw();
|
||||
});
|
||||
|
||||
ifit(process.platform === 'darwin')('removeGroup does not throw', () => {
|
||||
expect(() => Notification.removeGroup('nonexistent-group')).to.not.throw();
|
||||
});
|
||||
|
||||
ifit(process.platform === 'darwin')('removeGroup removes notifications by groupId', async () => {
|
||||
const n1 = new Notification({
|
||||
id: 'group-keep',
|
||||
title: 'keep',
|
||||
body: 'body',
|
||||
groupId: 'group-a',
|
||||
silent: true
|
||||
});
|
||||
const n2 = new Notification({
|
||||
id: 'group-remove-1',
|
||||
title: 'remove 1',
|
||||
body: 'body',
|
||||
groupId: 'group-b',
|
||||
silent: true
|
||||
});
|
||||
const n3 = new Notification({
|
||||
id: 'group-remove-2',
|
||||
title: 'remove 2',
|
||||
body: 'body',
|
||||
groupId: 'group-b',
|
||||
silent: true
|
||||
});
|
||||
|
||||
for (const n of [n1, n2, n3]) {
|
||||
const shown = once(n, 'show');
|
||||
n.show();
|
||||
await shown;
|
||||
}
|
||||
|
||||
Notification.removeGroup('group-b');
|
||||
|
||||
// Give the notification center a moment to fetch and remove
|
||||
await new Promise(resolve => setTimeout(resolve, 500));
|
||||
|
||||
const history = await Notification.getHistory();
|
||||
// In code-signed builds, group-a notification should remain
|
||||
// while group-b notifications should be gone
|
||||
const foundB1 = history.find((item: any) => item.id === 'group-remove-1');
|
||||
const foundB2 = history.find((item: any) => item.id === 'group-remove-2');
|
||||
expect(foundB1).to.be.undefined();
|
||||
expect(foundB2).to.be.undefined();
|
||||
|
||||
// Clean up
|
||||
Notification.removeAll();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
6
typings/internal-ambient.d.ts
vendored
6
typings/internal-ambient.d.ts
vendored
@@ -118,6 +118,10 @@ declare namespace NodeJS {
|
||||
|
||||
interface NotificationBinding {
|
||||
isSupported(): boolean;
|
||||
getHistory(): Promise<Electron.Notification[]>;
|
||||
remove(id: string | string[]): void;
|
||||
removeAll(): void;
|
||||
removeGroup(groupId: string): void;
|
||||
Notification: typeof Electron.Notification;
|
||||
// Windows-only callback for cold-start notification activation
|
||||
handleActivation?: (callback: (details: ActivationArgumentsInternal) => void) => void;
|
||||
@@ -242,7 +246,7 @@ declare namespace NodeJS {
|
||||
_linkedBinding(name: 'electron_browser_crash_reporter'): CrashReporterBinding;
|
||||
_linkedBinding(name: 'electron_browser_desktop_capturer'): { createDesktopCapturer(): ElectronInternal.DesktopCapturer; isDisplayMediaSystemPickerAvailable(): boolean; };
|
||||
_linkedBinding(name: 'electron_browser_event_emitter'): { setEventEmitterPrototype(prototype: Object): void; };
|
||||
_linkedBinding(name: 'electron_browser_global_shortcut'): { createGlobalShortcut(): Electron.GlobalShortcut };
|
||||
_linkedBinding(name: 'electron_browser_global_shortcut'): { globalShortcut: Electron.GlobalShortcut };
|
||||
_linkedBinding(name: 'electron_browser_image_view'): { ImageView: any };
|
||||
_linkedBinding(name: 'electron_browser_in_app_purchase'): { inAppPurchase: Electron.InAppPurchase };
|
||||
_linkedBinding(name: 'electron_browser_message_port'): { createPair(): { port1: Electron.MessagePortMain, port2: Electron.MessagePortMain }; };
|
||||
|
||||
Reference in New Issue
Block a user