feat: improve Windows Toast actions support (#48132)

* feat: improve Windows Toast actions support

* fix: ensure MSIX compatibility

* test: add bad clsid format test
This commit is contained in:
Shelley Vohr
2026-02-12 23:25:20 +01:00
committed by GitHub
parent a65cfed500
commit 74fd10450f
20 changed files with 1004 additions and 60 deletions

View File

@@ -88,6 +88,7 @@
#if BUILDFLAG(IS_WIN)
#include "base/strings/utf_string_conversions.h"
#include "shell/browser/notifications/win/windows_toast_activator.h"
#include "shell/browser/ui/win/jump_list.h"
#endif
@@ -1840,6 +1841,10 @@ gin::ObjectTemplateBuilder App::GetObjectTemplateBuilder(v8::Isolate* isolate) {
#if BUILDFLAG(IS_WIN)
.SetMethod("setAppUserModelId",
base::BindRepeating(&Browser::SetAppUserModelID, browser))
.SetMethod("setToastActivatorCLSID",
base::BindRepeating(&App::SetToastActivatorCLSID,
base::Unretained(this)))
.SetProperty("toastActivatorCLSID", &App::GetToastActivatorCLSID)
#endif
.SetMethod(
"isDefaultProtocolClient",
@@ -1967,6 +1972,34 @@ gin::ObjectTemplateBuilder App::GetObjectTemplateBuilder(v8::Isolate* isolate) {
.SetMethod("resolveProxy", &App::ResolveProxy);
}
#if BUILDFLAG(IS_WIN)
void App::SetToastActivatorCLSID(gin_helper::ErrorThrower thrower,
const std::string& id) {
std::wstring wide = base::UTF8ToWide(id);
CLSID parsed;
if (FAILED(::CLSIDFromString(wide.c_str(), &parsed))) {
if (!wide.empty() && wide.front() != L'{') {
std::wstring with_braces = L"{" + wide + L"}";
if (FAILED(::CLSIDFromString(with_braces.c_str(), &parsed))) {
thrower.ThrowError("Invalid CLSID format");
return;
}
wide = std::move(with_braces);
} else {
thrower.ThrowError("Invalid CLSID format");
return;
}
}
SetAppToastActivatorCLSID(wide);
}
v8::Local<v8::Value> App::GetToastActivatorCLSID(v8::Isolate* isolate) {
return gin::ConvertToV8(isolate,
base::WideToUTF8(GetAppToastActivatorCLSID()));
}
#endif
const char* App::GetHumanReadableName() const {
return "Electron / App";
}

View File

@@ -265,6 +265,12 @@ class App final : public gin::Wrappable<App>,
// Set or remove a custom Jump List for the application.
JumpListResult SetJumpList(v8::Isolate* isolate, v8::Local<v8::Value> val);
// Set the toast activator CLSID.
void SetToastActivatorCLSID(gin_helper::ErrorThrower thrower,
const std::string& id);
// Get the toast activator CLSID.
v8::Local<v8::Value> GetToastActivatorCLSID(v8::Isolate* isolate);
#endif // BUILDFLAG(IS_WIN)
std::unique_ptr<ProcessSingleton> process_singleton_;

View File

@@ -31,6 +31,9 @@ struct Converter<electron::NotificationAction> {
return false;
}
dict.Get("text", &(out->text));
std::vector<std::u16string> items;
if (dict.Get("items", &items))
out->items = std::move(items);
return true;
}
@@ -39,6 +42,9 @@ struct Converter<electron::NotificationAction> {
auto dict = gin::Dictionary::CreateEmpty(isolate);
dict.Set("text", val.text);
dict.Set("type", val.type);
if (!val.items.empty()) {
dict.Set("items", val.items);
}
return ConvertToV8(isolate, dict);
}
};
@@ -138,8 +144,20 @@ void Notification::SetToastXml(const std::u16string& new_toast_xml) {
toast_xml_ = new_toast_xml;
}
void Notification::NotificationAction(int index) {
Emit("action", index);
void Notification::NotificationAction(int action_index, int selection_index) {
v8::Isolate* isolate = JavascriptEnvironment::GetIsolate();
v8::HandleScope handle_scope(isolate);
gin_helper::internal::Event* event =
gin_helper::internal::Event::New(isolate);
v8::Local<v8::Object> event_object =
event->GetWrapper(isolate).ToLocalChecked();
gin_helper::Dictionary dict(isolate, event_object);
dict.Set("selectionIndex", selection_index);
dict.Set("actionIndex", action_index);
EmitWithoutEvent("action", event_object, action_index, selection_index);
}
void Notification::NotificationClick() {
@@ -147,7 +165,18 @@ void Notification::NotificationClick() {
}
void Notification::NotificationReplied(const std::string& reply) {
Emit("reply", reply);
v8::Isolate* isolate = JavascriptEnvironment::GetIsolate();
v8::HandleScope handle_scope(isolate);
gin_helper::internal::Event* event =
gin_helper::internal::Event::New(isolate);
v8::Local<v8::Object> event_object =
event->GetWrapper(isolate).ToLocalChecked();
gin_helper::Dictionary dict(isolate, event_object);
dict.Set("reply", reply);
EmitWithoutEvent("reply", event_object, reply);
}
void Notification::NotificationDisplayed() {

View File

@@ -45,7 +45,7 @@ class Notification final : public gin_helper::DeprecatedWrappable<Notification>,
static const char* GetClassName() { return "Notification"; }
// NotificationDelegate:
void NotificationAction(int index) override;
void NotificationAction(int action_index, int selection_index) override;
void NotificationClick() override;
void NotificationReplied(const std::string& reply) override;
void NotificationDisplayed() override;