windows_toast_notification.cc 23 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677
  1. // Copyright (c) 2015 Felix Rieseberg <[email protected]> and Jason Poon
  2. // <[email protected]>. All rights reserved.
  3. // Copyright (c) 2015 Ryan McShane <[email protected]> and Brandon Smith
  4. // <[email protected]>
  5. // Thanks to both of those folks mentioned above who first thought up a bunch of
  6. // this code
  7. // and released it as MIT to the world.
  8. #include "shell/browser/notifications/win/windows_toast_notification.h"
  9. #include <string_view>
  10. #include <shlobj.h>
  11. #include <wrl\wrappers\corewrappers.h>
  12. #include "base/environment.h"
  13. #include "base/hash/hash.h"
  14. #include "base/logging.h"
  15. #include "base/strings/strcat.h"
  16. #include "base/strings/string_util_win.h"
  17. #include "content/public/browser/browser_task_traits.h"
  18. #include "content/public/browser/browser_thread.h"
  19. #include "shell/browser/notifications/notification_delegate.h"
  20. #include "shell/browser/notifications/win/notification_presenter_win.h"
  21. #include "shell/browser/win/scoped_hstring.h"
  22. #include "shell/common/application_info.h"
  23. #include "ui/base/l10n/l10n_util_win.h"
  24. #include "ui/strings/grit/ui_strings.h"
  25. using ABI::Windows::Data::Xml::Dom::IXmlAttribute;
  26. using ABI::Windows::Data::Xml::Dom::IXmlDocument;
  27. using ABI::Windows::Data::Xml::Dom::IXmlDocumentIO;
  28. using ABI::Windows::Data::Xml::Dom::IXmlElement;
  29. using ABI::Windows::Data::Xml::Dom::IXmlNamedNodeMap;
  30. using ABI::Windows::Data::Xml::Dom::IXmlNode;
  31. using ABI::Windows::Data::Xml::Dom::IXmlNodeList;
  32. using ABI::Windows::Data::Xml::Dom::IXmlText;
  33. using Microsoft::WRL::Wrappers::HStringReference;
  34. namespace winui = ABI::Windows::UI;
  35. #define RETURN_IF_FAILED(hr) \
  36. do { \
  37. if (const HRESULT _hrTemp = hr; FAILED(_hrTemp)) { \
  38. return _hrTemp; \
  39. } \
  40. } while (false)
  41. #define REPORT_AND_RETURN_IF_FAILED(hr, msg) \
  42. do { \
  43. if (const HRESULT _hrTemp = hr; FAILED(_hrTemp)) { \
  44. std::string _err = \
  45. base::StrCat({msg, ", ERROR ", base::NumberToString(_hrTemp)}); \
  46. DebugLog(_err); \
  47. Notification::NotificationFailed(_err); \
  48. return _hrTemp; \
  49. } \
  50. } while (false)
  51. namespace electron {
  52. namespace {
  53. // This string needs to be max 16 characters to work on Windows 10 prior to
  54. // applying Creators Update (build 15063).
  55. constexpr wchar_t kGroup[] = L"Notifications";
  56. void DebugLog(std::string_view log_msg) {
  57. if (base::Environment::Create()->HasVar("ELECTRON_DEBUG_NOTIFICATIONS"))
  58. LOG(INFO) << log_msg;
  59. }
  60. std::wstring GetTag(const std::string& notification_id) {
  61. return base::NumberToWString(base::Hash(notification_id));
  62. }
  63. } // namespace
  64. // static
  65. ComPtr<winui::Notifications::IToastNotificationManagerStatics>
  66. WindowsToastNotification::toast_manager_;
  67. // static
  68. ComPtr<winui::Notifications::IToastNotifier>
  69. WindowsToastNotification::toast_notifier_;
  70. // static
  71. bool WindowsToastNotification::Initialize() {
  72. // Just initialize, don't care if it fails or already initialized.
  73. Windows::Foundation::Initialize(RO_INIT_MULTITHREADED);
  74. ScopedHString toast_manager_str(
  75. RuntimeClass_Windows_UI_Notifications_ToastNotificationManager);
  76. if (!toast_manager_str.success())
  77. return false;
  78. if (FAILED(Windows::Foundation::GetActivationFactory(toast_manager_str,
  79. &toast_manager_)))
  80. return false;
  81. if (IsRunningInDesktopBridge()) {
  82. // Ironically, the Desktop Bridge / UWP environment
  83. // requires us to not give Windows an appUserModelId.
  84. return SUCCEEDED(toast_manager_->CreateToastNotifier(&toast_notifier_));
  85. } else {
  86. ScopedHString app_id;
  87. if (!GetAppUserModelID(&app_id))
  88. return false;
  89. return SUCCEEDED(
  90. toast_manager_->CreateToastNotifierWithId(app_id, &toast_notifier_));
  91. }
  92. }
  93. WindowsToastNotification::WindowsToastNotification(
  94. NotificationDelegate* delegate,
  95. NotificationPresenter* presenter)
  96. : Notification(delegate, presenter) {}
  97. WindowsToastNotification::~WindowsToastNotification() {
  98. // Remove the notification on exit.
  99. if (toast_notification_) {
  100. RemoveCallbacks(toast_notification_.Get());
  101. }
  102. }
  103. void WindowsToastNotification::Show(const NotificationOptions& options) {
  104. if (SUCCEEDED(ShowInternal(options))) {
  105. DebugLog("Notification created");
  106. if (delegate())
  107. delegate()->NotificationDisplayed();
  108. }
  109. }
  110. void WindowsToastNotification::Remove() {
  111. DebugLog("Removing notification from action center");
  112. ComPtr<winui::Notifications::IToastNotificationManagerStatics2>
  113. toast_manager2;
  114. if (FAILED(toast_manager_.As(&toast_manager2)))
  115. return;
  116. ComPtr<winui::Notifications::IToastNotificationHistory> notification_history;
  117. if (FAILED(toast_manager2->get_History(&notification_history)))
  118. return;
  119. ScopedHString app_id;
  120. if (!GetAppUserModelID(&app_id))
  121. return;
  122. ScopedHString group(kGroup);
  123. ScopedHString tag(GetTag(notification_id()));
  124. notification_history->RemoveGroupedTagWithId(tag, group, app_id);
  125. }
  126. void WindowsToastNotification::Dismiss() {
  127. DebugLog("Hiding notification");
  128. toast_notifier_->Hide(toast_notification_.Get());
  129. }
  130. HRESULT WindowsToastNotification::ShowInternal(
  131. const NotificationOptions& options) {
  132. ComPtr<IXmlDocument> toast_xml;
  133. // The custom xml takes priority over the preset template.
  134. if (!options.toast_xml.empty()) {
  135. REPORT_AND_RETURN_IF_FAILED(
  136. XmlDocumentFromString(base::as_wcstr(options.toast_xml), &toast_xml),
  137. "XML: Invalid XML");
  138. } else {
  139. auto* presenter_win = static_cast<NotificationPresenterWin*>(presenter());
  140. std::wstring icon_path =
  141. presenter_win->SaveIconToFilesystem(options.icon, options.icon_url);
  142. REPORT_AND_RETURN_IF_FAILED(
  143. GetToastXml(toast_manager_.Get(), options.title, options.msg, icon_path,
  144. options.timeout_type, options.silent, &toast_xml),
  145. "XML: Failed to create XML document");
  146. }
  147. ScopedHString toast_str(
  148. RuntimeClass_Windows_UI_Notifications_ToastNotification);
  149. if (!toast_str.success()) {
  150. NotificationFailed("Creating ScopedHString failed");
  151. return E_FAIL;
  152. }
  153. ComPtr<winui::Notifications::IToastNotificationFactory> toast_factory;
  154. REPORT_AND_RETURN_IF_FAILED(
  155. Windows::Foundation::GetActivationFactory(toast_str, &toast_factory),
  156. "WinAPI: GetActivationFactory failed");
  157. REPORT_AND_RETURN_IF_FAILED(toast_factory->CreateToastNotification(
  158. toast_xml.Get(), &toast_notification_),
  159. "WinAPI: CreateToastNotification failed");
  160. ComPtr<winui::Notifications::IToastNotification2> toast2;
  161. REPORT_AND_RETURN_IF_FAILED(
  162. toast_notification_->QueryInterface(IID_PPV_ARGS(&toast2)),
  163. "WinAPI: Getting Notification interface failed");
  164. ScopedHString group(kGroup);
  165. REPORT_AND_RETURN_IF_FAILED(toast2->put_Group(group),
  166. "WinAPI: Setting group failed");
  167. ScopedHString tag(GetTag(notification_id()));
  168. REPORT_AND_RETURN_IF_FAILED(toast2->put_Tag(tag),
  169. "WinAPI: Setting tag failed");
  170. REPORT_AND_RETURN_IF_FAILED(SetupCallbacks(toast_notification_.Get()),
  171. "WinAPI: SetupCallbacks failed");
  172. REPORT_AND_RETURN_IF_FAILED(toast_notifier_->Show(toast_notification_.Get()),
  173. "WinAPI: Show failed");
  174. return S_OK;
  175. }
  176. HRESULT WindowsToastNotification::GetToastXml(
  177. winui::Notifications::IToastNotificationManagerStatics* toastManager,
  178. const std::u16string& title,
  179. const std::u16string& msg,
  180. const std::wstring& icon_path,
  181. const std::u16string& timeout_type,
  182. bool silent,
  183. IXmlDocument** toast_xml) {
  184. winui::Notifications::ToastTemplateType template_type;
  185. if (title.empty() || msg.empty()) {
  186. // Single line toast.
  187. template_type =
  188. icon_path.empty()
  189. ? winui::Notifications::ToastTemplateType_ToastText01
  190. : winui::Notifications::ToastTemplateType_ToastImageAndText01;
  191. REPORT_AND_RETURN_IF_FAILED(
  192. toast_manager_->GetTemplateContent(template_type, toast_xml),
  193. "XML: Fetching XML ToastImageAndText01 template failed");
  194. std::u16string toastMsg = title.empty() ? msg : title;
  195. // we can't create an empty notification
  196. toastMsg = toastMsg.empty() ? u"[no message]" : toastMsg;
  197. REPORT_AND_RETURN_IF_FAILED(
  198. SetXmlText(*toast_xml, toastMsg),
  199. "XML: Filling XML ToastImageAndText01 template failed");
  200. } else {
  201. // Title and body toast.
  202. template_type =
  203. icon_path.empty()
  204. ? winui::Notifications::ToastTemplateType_ToastText02
  205. : winui::Notifications::ToastTemplateType_ToastImageAndText02;
  206. REPORT_AND_RETURN_IF_FAILED(
  207. toastManager->GetTemplateContent(template_type, toast_xml),
  208. "XML: Fetching XML ToastImageAndText02 template failed");
  209. REPORT_AND_RETURN_IF_FAILED(
  210. SetXmlText(*toast_xml, title, msg),
  211. "XML: Filling XML ToastImageAndText02 template failed");
  212. }
  213. // Configure the toast's timeout settings
  214. if (timeout_type == u"never") {
  215. REPORT_AND_RETURN_IF_FAILED(
  216. (SetXmlScenarioReminder(*toast_xml)),
  217. "XML: Setting \"scenario\" option on notification failed");
  218. }
  219. // Configure the toast's notification sound
  220. if (silent) {
  221. REPORT_AND_RETURN_IF_FAILED(
  222. SetXmlAudioSilent(*toast_xml),
  223. "XML: Setting \"silent\" option on notification failed");
  224. }
  225. // Configure the toast's image
  226. if (!icon_path.empty()) {
  227. REPORT_AND_RETURN_IF_FAILED(
  228. SetXmlImage(*toast_xml, icon_path),
  229. "XML: Setting \"icon\" option on notification failed");
  230. }
  231. return S_OK;
  232. }
  233. HRESULT WindowsToastNotification::SetXmlScenarioReminder(IXmlDocument* doc) {
  234. ScopedHString tag(L"toast");
  235. if (!tag.success())
  236. return false;
  237. ComPtr<IXmlNodeList> node_list;
  238. RETURN_IF_FAILED(doc->GetElementsByTagName(tag, &node_list));
  239. // Check that root "toast" node exists
  240. ComPtr<IXmlNode> root;
  241. RETURN_IF_FAILED(node_list->Item(0, &root));
  242. // get attributes of root "toast" node
  243. ComPtr<IXmlNamedNodeMap> toast_attributes;
  244. RETURN_IF_FAILED(root->get_Attributes(&toast_attributes));
  245. ComPtr<IXmlAttribute> scenario_attribute;
  246. ScopedHString scenario_str(L"scenario");
  247. RETURN_IF_FAILED(doc->CreateAttribute(scenario_str, &scenario_attribute));
  248. ComPtr<IXmlNode> scenario_attribute_node;
  249. RETURN_IF_FAILED(scenario_attribute.As(&scenario_attribute_node));
  250. ScopedHString scenario_value(L"reminder");
  251. if (!scenario_value.success())
  252. return E_FAIL;
  253. ComPtr<IXmlText> scenario_text;
  254. RETURN_IF_FAILED(doc->CreateTextNode(scenario_value, &scenario_text));
  255. ComPtr<IXmlNode> scenario_node;
  256. RETURN_IF_FAILED(scenario_text.As(&scenario_node));
  257. ComPtr<IXmlNode> scenario_backup_node;
  258. RETURN_IF_FAILED(scenario_attribute_node->AppendChild(scenario_node.Get(),
  259. &scenario_backup_node));
  260. ComPtr<IXmlNode> scenario_attribute_pnode;
  261. RETURN_IF_FAILED(toast_attributes.Get()->SetNamedItem(
  262. scenario_attribute_node.Get(), &scenario_attribute_pnode));
  263. // Create "actions" wrapper
  264. ComPtr<IXmlElement> actions_wrapper_element;
  265. ScopedHString actions_wrapper_str(L"actions");
  266. RETURN_IF_FAILED(
  267. doc->CreateElement(actions_wrapper_str, &actions_wrapper_element));
  268. ComPtr<IXmlNode> actions_wrapper_node_tmp;
  269. RETURN_IF_FAILED(actions_wrapper_element.As(&actions_wrapper_node_tmp));
  270. // Append actions wrapper node to toast xml
  271. ComPtr<IXmlNode> actions_wrapper_node;
  272. RETURN_IF_FAILED(
  273. root->AppendChild(actions_wrapper_node_tmp.Get(), &actions_wrapper_node));
  274. ComPtr<IXmlNamedNodeMap> attributes_actions_wrapper;
  275. RETURN_IF_FAILED(
  276. actions_wrapper_node->get_Attributes(&attributes_actions_wrapper));
  277. // Add a "Dismiss" button
  278. // Create "action" tag
  279. ComPtr<IXmlElement> action_element;
  280. ScopedHString action_str(L"action");
  281. RETURN_IF_FAILED(doc->CreateElement(action_str, &action_element));
  282. ComPtr<IXmlNode> action_node_tmp;
  283. RETURN_IF_FAILED(action_element.As(&action_node_tmp));
  284. // Append action node to actions wrapper in toast xml
  285. ComPtr<IXmlNode> action_node;
  286. RETURN_IF_FAILED(
  287. actions_wrapper_node->AppendChild(action_node_tmp.Get(), &action_node));
  288. // Setup attributes for action
  289. ComPtr<IXmlNamedNodeMap> action_attributes;
  290. RETURN_IF_FAILED(action_node->get_Attributes(&action_attributes));
  291. // Create activationType attribute
  292. ComPtr<IXmlAttribute> activation_type_attribute;
  293. ScopedHString activation_type_str(L"activationType");
  294. RETURN_IF_FAILED(
  295. doc->CreateAttribute(activation_type_str, &activation_type_attribute));
  296. ComPtr<IXmlNode> activation_type_attribute_node;
  297. RETURN_IF_FAILED(
  298. activation_type_attribute.As(&activation_type_attribute_node));
  299. // Set activationType attribute to system
  300. ScopedHString activation_type_value(L"system");
  301. if (!activation_type_value.success())
  302. return E_FAIL;
  303. ComPtr<IXmlText> activation_type_text;
  304. RETURN_IF_FAILED(
  305. doc->CreateTextNode(activation_type_value, &activation_type_text));
  306. ComPtr<IXmlNode> activation_type_node;
  307. RETURN_IF_FAILED(activation_type_text.As(&activation_type_node));
  308. ComPtr<IXmlNode> activation_type_backup_node;
  309. RETURN_IF_FAILED(activation_type_attribute_node->AppendChild(
  310. activation_type_node.Get(), &activation_type_backup_node));
  311. // Add activation type to the action attributes
  312. ComPtr<IXmlNode> activation_type_attribute_pnode;
  313. RETURN_IF_FAILED(action_attributes.Get()->SetNamedItem(
  314. activation_type_attribute_node.Get(), &activation_type_attribute_pnode));
  315. // Create arguments attribute
  316. ComPtr<IXmlAttribute> arguments_attribute;
  317. ScopedHString arguments_str(L"arguments");
  318. RETURN_IF_FAILED(doc->CreateAttribute(arguments_str, &arguments_attribute));
  319. ComPtr<IXmlNode> arguments_attribute_node;
  320. RETURN_IF_FAILED(arguments_attribute.As(&arguments_attribute_node));
  321. // Set arguments attribute to dismiss
  322. ScopedHString arguments_value(L"dismiss");
  323. if (!arguments_value.success())
  324. return E_FAIL;
  325. ComPtr<IXmlText> arguments_text;
  326. RETURN_IF_FAILED(doc->CreateTextNode(arguments_value, &arguments_text));
  327. ComPtr<IXmlNode> arguments_node;
  328. RETURN_IF_FAILED(arguments_text.As(&arguments_node));
  329. ComPtr<IXmlNode> arguments_backup_node;
  330. RETURN_IF_FAILED(arguments_attribute_node->AppendChild(
  331. arguments_node.Get(), &arguments_backup_node));
  332. // Add arguments to the action attributes
  333. ComPtr<IXmlNode> arguments_attribute_pnode;
  334. RETURN_IF_FAILED(action_attributes.Get()->SetNamedItem(
  335. arguments_attribute_node.Get(), &arguments_attribute_pnode));
  336. // Create content attribute
  337. ComPtr<IXmlAttribute> content_attribute;
  338. ScopedHString content_str(L"content");
  339. RETURN_IF_FAILED(doc->CreateAttribute(content_str, &content_attribute));
  340. ComPtr<IXmlNode> content_attribute_node;
  341. RETURN_IF_FAILED(content_attribute.As(&content_attribute_node));
  342. // Set content attribute to Dismiss
  343. ScopedHString content_value(l10n_util::GetWideString(IDS_APP_CLOSE));
  344. if (!content_value.success())
  345. return E_FAIL;
  346. ComPtr<IXmlText> content_text;
  347. RETURN_IF_FAILED(doc->CreateTextNode(content_value, &content_text));
  348. ComPtr<IXmlNode> content_node;
  349. RETURN_IF_FAILED(content_text.As(&content_node));
  350. ComPtr<IXmlNode> content_backup_node;
  351. RETURN_IF_FAILED(content_attribute_node->AppendChild(content_node.Get(),
  352. &content_backup_node));
  353. // Add content to the action attributes
  354. ComPtr<IXmlNode> content_attribute_pnode;
  355. return action_attributes.Get()->SetNamedItem(content_attribute_node.Get(),
  356. &content_attribute_pnode);
  357. }
  358. HRESULT WindowsToastNotification::SetXmlAudioSilent(IXmlDocument* doc) {
  359. ScopedHString tag(L"toast");
  360. if (!tag.success())
  361. return E_FAIL;
  362. ComPtr<IXmlNodeList> node_list;
  363. RETURN_IF_FAILED(doc->GetElementsByTagName(tag, &node_list));
  364. ComPtr<IXmlNode> root;
  365. RETURN_IF_FAILED(node_list->Item(0, &root));
  366. ComPtr<IXmlElement> audio_element;
  367. ScopedHString audio_str(L"audio");
  368. RETURN_IF_FAILED(doc->CreateElement(audio_str, &audio_element));
  369. ComPtr<IXmlNode> audio_node_tmp;
  370. RETURN_IF_FAILED(audio_element.As(&audio_node_tmp));
  371. // Append audio node to toast xml
  372. ComPtr<IXmlNode> audio_node;
  373. RETURN_IF_FAILED(root->AppendChild(audio_node_tmp.Get(), &audio_node));
  374. // Create silent attribute
  375. ComPtr<IXmlNamedNodeMap> attributes;
  376. RETURN_IF_FAILED(audio_node->get_Attributes(&attributes));
  377. ComPtr<IXmlAttribute> silent_attribute;
  378. ScopedHString silent_str(L"silent");
  379. RETURN_IF_FAILED(doc->CreateAttribute(silent_str, &silent_attribute));
  380. ComPtr<IXmlNode> silent_attribute_node;
  381. RETURN_IF_FAILED(silent_attribute.As(&silent_attribute_node));
  382. // Set silent attribute to true
  383. ScopedHString silent_value(L"true");
  384. if (!silent_value.success())
  385. return E_FAIL;
  386. ComPtr<IXmlText> silent_text;
  387. RETURN_IF_FAILED(doc->CreateTextNode(silent_value, &silent_text));
  388. ComPtr<IXmlNode> silent_node;
  389. RETURN_IF_FAILED(silent_text.As(&silent_node));
  390. ComPtr<IXmlNode> child_node;
  391. RETURN_IF_FAILED(
  392. silent_attribute_node->AppendChild(silent_node.Get(), &child_node));
  393. ComPtr<IXmlNode> silent_attribute_pnode;
  394. return attributes.Get()->SetNamedItem(silent_attribute_node.Get(),
  395. &silent_attribute_pnode);
  396. }
  397. HRESULT WindowsToastNotification::SetXmlText(IXmlDocument* doc,
  398. const std::u16string& text) {
  399. ScopedHString tag;
  400. ComPtr<IXmlNodeList> node_list;
  401. RETURN_IF_FAILED(GetTextNodeList(&tag, doc, &node_list, 1));
  402. ComPtr<IXmlNode> node;
  403. RETURN_IF_FAILED(node_list->Item(0, &node));
  404. return AppendTextToXml(doc, node.Get(), text);
  405. }
  406. HRESULT WindowsToastNotification::SetXmlText(IXmlDocument* doc,
  407. const std::u16string& title,
  408. const std::u16string& body) {
  409. ScopedHString tag;
  410. ComPtr<IXmlNodeList> node_list;
  411. RETURN_IF_FAILED(GetTextNodeList(&tag, doc, &node_list, 2));
  412. ComPtr<IXmlNode> node;
  413. RETURN_IF_FAILED(node_list->Item(0, &node));
  414. RETURN_IF_FAILED(AppendTextToXml(doc, node.Get(), title));
  415. RETURN_IF_FAILED(node_list->Item(1, &node));
  416. return AppendTextToXml(doc, node.Get(), body);
  417. }
  418. HRESULT WindowsToastNotification::SetXmlImage(IXmlDocument* doc,
  419. const std::wstring& icon_path) {
  420. ScopedHString tag(L"image");
  421. if (!tag.success())
  422. return E_FAIL;
  423. ComPtr<IXmlNodeList> node_list;
  424. RETURN_IF_FAILED(doc->GetElementsByTagName(tag, &node_list));
  425. ComPtr<IXmlNode> image_node;
  426. RETURN_IF_FAILED(node_list->Item(0, &image_node));
  427. ComPtr<IXmlNamedNodeMap> attrs;
  428. RETURN_IF_FAILED(image_node->get_Attributes(&attrs));
  429. ScopedHString src(L"src");
  430. if (!src.success())
  431. return E_FAIL;
  432. ComPtr<IXmlNode> src_attr;
  433. RETURN_IF_FAILED(attrs->GetNamedItem(src, &src_attr));
  434. const ScopedHString img_path{icon_path};
  435. if (!img_path.success())
  436. return E_FAIL;
  437. ComPtr<IXmlText> src_text;
  438. RETURN_IF_FAILED(doc->CreateTextNode(img_path, &src_text));
  439. ComPtr<IXmlNode> src_node;
  440. RETURN_IF_FAILED(src_text.As(&src_node));
  441. ComPtr<IXmlNode> child_node;
  442. return src_attr->AppendChild(src_node.Get(), &child_node);
  443. }
  444. HRESULT WindowsToastNotification::GetTextNodeList(ScopedHString* tag,
  445. IXmlDocument* doc,
  446. IXmlNodeList** node_list,
  447. uint32_t req_length) {
  448. tag->Reset(L"text");
  449. if (!tag->success())
  450. return E_FAIL;
  451. RETURN_IF_FAILED(doc->GetElementsByTagName(*tag, node_list));
  452. uint32_t node_length;
  453. RETURN_IF_FAILED((*node_list)->get_Length(&node_length));
  454. return node_length >= req_length;
  455. }
  456. HRESULT WindowsToastNotification::AppendTextToXml(IXmlDocument* doc,
  457. IXmlNode* node,
  458. const std::u16string& text) {
  459. ScopedHString str(base::as_wcstr(text));
  460. if (!str.success())
  461. return E_FAIL;
  462. ComPtr<IXmlText> xml_text;
  463. RETURN_IF_FAILED(doc->CreateTextNode(str, &xml_text));
  464. ComPtr<IXmlNode> text_node;
  465. RETURN_IF_FAILED(xml_text.As(&text_node));
  466. ComPtr<IXmlNode> append_node;
  467. RETURN_IF_FAILED(node->AppendChild(text_node.Get(), &append_node));
  468. return S_OK;
  469. }
  470. HRESULT WindowsToastNotification::XmlDocumentFromString(
  471. const wchar_t* xmlString,
  472. IXmlDocument** doc) {
  473. ComPtr<IXmlDocument> xmlDoc;
  474. RETURN_IF_FAILED(Windows::Foundation::ActivateInstance(
  475. HStringReference(RuntimeClass_Windows_Data_Xml_Dom_XmlDocument).Get(),
  476. &xmlDoc));
  477. ComPtr<IXmlDocumentIO> docIO;
  478. RETURN_IF_FAILED(xmlDoc.As(&docIO));
  479. RETURN_IF_FAILED(docIO->LoadXml(HStringReference(xmlString).Get()));
  480. return xmlDoc.CopyTo(doc);
  481. }
  482. HRESULT WindowsToastNotification::SetupCallbacks(
  483. winui::Notifications::IToastNotification* toast) {
  484. event_handler_ = Make<ToastEventHandler>(this);
  485. RETURN_IF_FAILED(
  486. toast->add_Activated(event_handler_.Get(), &activated_token_));
  487. RETURN_IF_FAILED(
  488. toast->add_Dismissed(event_handler_.Get(), &dismissed_token_));
  489. RETURN_IF_FAILED(toast->add_Failed(event_handler_.Get(), &failed_token_));
  490. return S_OK;
  491. }
  492. bool WindowsToastNotification::RemoveCallbacks(
  493. winui::Notifications::IToastNotification* toast) {
  494. if (FAILED(toast->remove_Activated(activated_token_)))
  495. return false;
  496. if (FAILED(toast->remove_Dismissed(dismissed_token_)))
  497. return false;
  498. return SUCCEEDED(toast->remove_Failed(failed_token_));
  499. }
  500. /*
  501. / Toast Event Handler
  502. */
  503. ToastEventHandler::ToastEventHandler(Notification* notification)
  504. : notification_(notification->GetWeakPtr()) {}
  505. ToastEventHandler::~ToastEventHandler() = default;
  506. IFACEMETHODIMP ToastEventHandler::Invoke(
  507. winui::Notifications::IToastNotification* sender,
  508. IInspectable* args) {
  509. content::GetUIThreadTaskRunner({})->PostTask(
  510. FROM_HERE,
  511. base::BindOnce(&Notification::NotificationClicked, notification_));
  512. DebugLog("Notification clicked");
  513. return S_OK;
  514. }
  515. IFACEMETHODIMP ToastEventHandler::Invoke(
  516. winui::Notifications::IToastNotification* sender,
  517. winui::Notifications::IToastDismissedEventArgs* e) {
  518. content::GetUIThreadTaskRunner({})->PostTask(
  519. FROM_HERE, base::BindOnce(&Notification::NotificationDismissed,
  520. notification_, false));
  521. DebugLog("Notification dismissed");
  522. return S_OK;
  523. }
  524. IFACEMETHODIMP ToastEventHandler::Invoke(
  525. winui::Notifications::IToastNotification* sender,
  526. winui::Notifications::IToastFailedEventArgs* e) {
  527. HRESULT error;
  528. e->get_ErrorCode(&error);
  529. std::string errorMessage =
  530. "Notification failed. HRESULT:" + std::to_string(error);
  531. content::GetUIThreadTaskRunner({})->PostTask(
  532. FROM_HERE, base::BindOnce(&Notification::NotificationFailed,
  533. notification_, errorMessage));
  534. DebugLog(errorMessage);
  535. return S_OK;
  536. }
  537. } // namespace electron