diff --git a/docs/api/notification.md b/docs/api/notification.md index 7d3930c8afbc..dbf855171e35 100644 --- a/docs/api/notification.md +++ b/docs/api/notification.md @@ -29,9 +29,9 @@ Returns `Boolean` - Whether or not desktop notifications are supported on the cu ### `new Notification([options])` * `options` Object (optional) - * `title` String - A title for the notification, which will be shown at the top of the notification window when it is shown. + * `title` String (optional) - A title for the notification, which will be shown 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 - The body text of the notification, which will be displayed below the title or subtitle. + * `body` String (optional) - The body text of the notification, which will be displayed below the title or subtitle. * `silent` Boolean (optional) - Whether or not to emit an OS notification noise when showing the notification. * `icon` (String | [NativeImage](native-image.md)) (optional) - An icon to use in the notification. * `hasReply` Boolean (optional) _macOS_ - Whether or not to add an inline reply option to the notification. @@ -41,6 +41,7 @@ Returns `Boolean` - Whether or not desktop notifications are supported on the cu * `urgency` String (optional) _Linux_ - The urgency level of the notification. Can be 'normal', 'critical', or 'low'. * `actions` [NotificationAction[]](structures/notification-action.md) (optional) _macOS_ - Actions to add to the notification. Please read the available actions and limitations in the `NotificationAction` documentation. * `closeButtonText` String (optional) _macOS_ - A custom title for the close button of an alert. An empty string will cause the default localized text to be used. + * `toastXml` String (optional) _Windows_ - A custom description of the Notification on Windows superseding all properties above. Provides full customization of design and behavior of the notification. ### Instance Events @@ -94,6 +95,15 @@ Returns: * `event` Event * `index` Number - The index of the action that was activated. +#### Event: 'failed' _Windows_ + +Returns: + +* `event` Event +* `error` String - The error encountered during execution of the `show()` method. + +Emitted when an error is encountered while creating and showing the native notification. + ### Instance Methods Objects created with `new Notification` have the following instance methods: @@ -162,6 +172,10 @@ If `timeoutType` is set to 'never', the notification never expires. It stays ope A [`NotificationAction[]`](structures/notification-action.md) property representing the actions of the notification. +#### `notification.toastXml` _Windows_ + +A `String` property representing the custom Toast XML of the notification. + ### Playing Sounds On macOS, you can specify the name of the sound you'd like to play when the diff --git a/shell/browser/api/electron_api_notification.cc b/shell/browser/api/electron_api_notification.cc index f7bf4f973bad..501daa43232d 100644 --- a/shell/browser/api/electron_api_notification.cc +++ b/shell/browser/api/electron_api_notification.cc @@ -72,6 +72,7 @@ Notification::Notification(gin::Arguments* args) { opts.Get("actions", &actions_); opts.Get("sound", &sound_); opts.Get("closeButtonText", &close_button_text_); + opts.Get("toastXml", &toast_xml_); } } @@ -135,6 +136,10 @@ base::string16 Notification::GetCloseButtonText() const { return close_button_text_; } +base::string16 Notification::GetToastXml() const { + return toast_xml_; +} + // Setters void Notification::SetTitle(const base::string16& new_title) { title_ = new_title; @@ -181,6 +186,10 @@ void Notification::SetCloseButtonText(const base::string16& text) { close_button_text_ = text; } +void Notification::SetToastXml(const base::string16& new_toast_xml) { + toast_xml_ = new_toast_xml; +} + void Notification::NotificationAction(int index) { Emit("action", index); } @@ -197,6 +206,10 @@ void Notification::NotificationDisplayed() { Emit("show"); } +void Notification::NotificationFailed(const std::string& error) { + Emit("failed", error); +} + void Notification::NotificationDestroyed() {} void Notification::NotificationClosed() { @@ -231,6 +244,7 @@ void Notification::Show() { options.sound = sound_; options.close_button_text = close_button_text_; options.urgency = urgency_; + options.toast_xml = toast_xml_; notification_->Show(options); } } @@ -265,6 +279,8 @@ v8::Local Notification::FillObjectTemplate( &Notification::SetActions) .SetProperty("closeButtonText", &Notification::GetCloseButtonText, &Notification::SetCloseButtonText) + .SetProperty("toastXml", &Notification::GetToastXml, + &Notification::SetToastXml) .Build(); } diff --git a/shell/browser/api/electron_api_notification.h b/shell/browser/api/electron_api_notification.h index 18b3bff41c38..fd4e0f497b94 100644 --- a/shell/browser/api/electron_api_notification.h +++ b/shell/browser/api/electron_api_notification.h @@ -52,6 +52,7 @@ class Notification : public gin::Wrappable, void NotificationDisplayed() override; void NotificationDestroyed() override; void NotificationClosed() override; + void NotificationFailed(const std::string& error) override; // gin::Wrappable static gin::WrapperInfo kWrapperInfo; @@ -75,6 +76,7 @@ class Notification : public gin::Wrappable, base::string16 GetSound() const; std::vector GetActions() const; base::string16 GetCloseButtonText() const; + base::string16 GetToastXml() const; // Prop Setters void SetTitle(const base::string16& new_title); @@ -88,6 +90,7 @@ class Notification : public gin::Wrappable, void SetSound(const base::string16& sound); void SetActions(const std::vector& actions); void SetCloseButtonText(const base::string16& text); + void SetToastXml(const base::string16& text); private: base::string16 title_; @@ -104,6 +107,7 @@ class Notification : public gin::Wrappable, base::string16 urgency_; std::vector actions_; base::string16 close_button_text_; + base::string16 toast_xml_; electron::NotificationPresenter* presenter_; diff --git a/shell/browser/notifications/notification.cc b/shell/browser/notifications/notification.cc index 62c4441b0a43..a7e9a3b44be6 100644 --- a/shell/browser/notifications/notification.cc +++ b/shell/browser/notifications/notification.cc @@ -33,9 +33,9 @@ void Notification::NotificationDismissed() { Destroy(); } -void Notification::NotificationFailed() { +void Notification::NotificationFailed(const std::string& error) { if (delegate()) - delegate()->NotificationFailed(); + delegate()->NotificationFailed(error); Destroy(); } diff --git a/shell/browser/notifications/notification.h b/shell/browser/notifications/notification.h index 1a465bdbef88..e75ca2f4da75 100644 --- a/shell/browser/notifications/notification.h +++ b/shell/browser/notifications/notification.h @@ -38,6 +38,7 @@ struct NotificationOptions { base::string16 urgency; // Linux std::vector actions; base::string16 close_button_text; + base::string16 toast_xml; NotificationOptions(); ~NotificationOptions(); @@ -56,7 +57,7 @@ class Notification { // Should be called by derived classes. void NotificationClicked(); void NotificationDismissed(); - void NotificationFailed(); + void NotificationFailed(const std::string& error = ""); // delete this. void Destroy(); diff --git a/shell/browser/notifications/notification_delegate.h b/shell/browser/notifications/notification_delegate.h index 1ef0dc805521..4f52279c71d4 100644 --- a/shell/browser/notifications/notification_delegate.h +++ b/shell/browser/notifications/notification_delegate.h @@ -15,7 +15,7 @@ class NotificationDelegate { virtual void NotificationDestroyed() {} // Failed to send the notification. - virtual void NotificationFailed() {} + virtual void NotificationFailed(const std::string& error) {} // Notification was replied to virtual void NotificationReplied(const std::string& reply) {} diff --git a/shell/browser/notifications/win/windows_toast_notification.cc b/shell/browser/notifications/win/windows_toast_notification.cc index b5ca4dc62853..fec97c986720 100644 --- a/shell/browser/notifications/win/windows_toast_notification.cc +++ b/shell/browser/notifications/win/windows_toast_notification.cc @@ -9,6 +9,7 @@ #include "shell/browser/notifications/win/windows_toast_notification.h" #include +#include #include #include "base/environment.h" @@ -23,11 +24,33 @@ using ABI::Windows::Data::Xml::Dom::IXmlAttribute; using ABI::Windows::Data::Xml::Dom::IXmlDocument; +using ABI::Windows::Data::Xml::Dom::IXmlDocumentIO; using ABI::Windows::Data::Xml::Dom::IXmlElement; using ABI::Windows::Data::Xml::Dom::IXmlNamedNodeMap; using ABI::Windows::Data::Xml::Dom::IXmlNode; using ABI::Windows::Data::Xml::Dom::IXmlNodeList; using ABI::Windows::Data::Xml::Dom::IXmlText; +using Microsoft::WRL::Wrappers::HStringReference; + +#define RETURN_IF_FAILED(hr) \ + do { \ + HRESULT _hrTemp = hr; \ + if (FAILED(_hrTemp)) { \ + return _hrTemp; \ + } \ + } while (false) +#define REPORT_AND_RETURN_IF_FAILED(hr, msg) \ + do { \ + HRESULT _hrTemp = hr; \ + std::string _msgTemp = msg; \ + if (FAILED(_hrTemp)) { \ + std::string _err = _msgTemp + ",ERROR " + std::to_string(_hrTemp); \ + if (IsDebuggingNotifications()) \ + LOG(INFO) << _err; \ + Notification::NotificationFailed(_err); \ + return _hrTemp; \ + } \ + } while (false) namespace electron { @@ -36,7 +59,6 @@ namespace { bool IsDebuggingNotifications() { return base::Environment::Create()->HasVar("ELECTRON_DEBUG_NOTIFICATIONS"); } - } // namespace // static @@ -83,59 +105,17 @@ WindowsToastNotification::~WindowsToastNotification() { // Remove the notification on exit. if (toast_notification_) { RemoveCallbacks(toast_notification_.Get()); - Dismiss(); } } void WindowsToastNotification::Show(const NotificationOptions& options) { - auto* presenter_win = static_cast(presenter()); - std::wstring icon_path = - presenter_win->SaveIconToFilesystem(options.icon, options.icon_url); + if (SUCCEEDED(ShowInternal(options))) { + if (IsDebuggingNotifications()) + LOG(INFO) << "Notification created"; - ComPtr toast_xml; - if (FAILED(GetToastXml(toast_manager_.Get(), options.title, options.msg, - icon_path, options.timeout_type, options.silent, - &toast_xml))) { - NotificationFailed(); - return; + if (delegate()) + delegate()->NotificationDisplayed(); } - - ScopedHString toast_str( - RuntimeClass_Windows_UI_Notifications_ToastNotification); - if (!toast_str.success()) { - NotificationFailed(); - return; - } - - ComPtr - toast_factory; - if (FAILED(Windows::Foundation::GetActivationFactory(toast_str, - &toast_factory))) { - NotificationFailed(); - return; - } - - if (FAILED(toast_factory->CreateToastNotification(toast_xml.Get(), - &toast_notification_))) { - NotificationFailed(); - return; - } - - if (!SetupCallbacks(toast_notification_.Get())) { - NotificationFailed(); - return; - } - - if (FAILED(toast_notifier_->Show(toast_notification_.Get()))) { - NotificationFailed(); - return; - } - - if (IsDebuggingNotifications()) - LOG(INFO) << "Notification created"; - - if (delegate()) - delegate()->NotificationDisplayed(); } void WindowsToastNotification::Dismiss() { @@ -144,7 +124,50 @@ void WindowsToastNotification::Dismiss() { toast_notifier_->Hide(toast_notification_.Get()); } -bool WindowsToastNotification::GetToastXml( +HRESULT WindowsToastNotification::ShowInternal( + const NotificationOptions& options) { + ComPtr toast_xml; + // The custom xml takes priority over the preset template. + if (!options.toast_xml.empty()) { + REPORT_AND_RETURN_IF_FAILED( + XmlDocumentFromString(options.toast_xml.c_str(), &toast_xml), + "XML: Invalid XML"); + } else { + auto* presenter_win = static_cast(presenter()); + std::wstring icon_path = + presenter_win->SaveIconToFilesystem(options.icon, options.icon_url); + REPORT_AND_RETURN_IF_FAILED( + GetToastXml(toast_manager_.Get(), options.title, options.msg, icon_path, + options.timeout_type, options.silent, &toast_xml), + "XML: Failed to create XML document"); + } + + ScopedHString toast_str( + RuntimeClass_Windows_UI_Notifications_ToastNotification); + if (!toast_str.success()) { + NotificationFailed("Creating ScopedHString failed"); + return E_FAIL; + } + + ComPtr + toast_factory; + REPORT_AND_RETURN_IF_FAILED( + Windows::Foundation::GetActivationFactory(toast_str, &toast_factory), + "WinAPI: GetActivationFactory failed"); + + REPORT_AND_RETURN_IF_FAILED(toast_factory->CreateToastNotification( + toast_xml.Get(), &toast_notification_), + "WinAPI: CreateToastNotification failed"); + + REPORT_AND_RETURN_IF_FAILED(SetupCallbacks(toast_notification_.Get()), + "WinAPI: SetupCallbacks failed"); + + REPORT_AND_RETURN_IF_FAILED(toast_notifier_->Show(toast_notification_.Get()), + "WinAPI: Show failed"); + return S_OK; +} + +HRESULT WindowsToastNotification::GetToastXml( ABI::Windows::UI::Notifications::IToastNotificationManagerStatics* toastManager, const std::wstring& title, @@ -161,10 +184,15 @@ bool WindowsToastNotification::GetToastXml( ? ABI::Windows::UI::Notifications::ToastTemplateType_ToastText01 : ABI::Windows::UI::Notifications:: ToastTemplateType_ToastImageAndText01; - if (FAILED(toast_manager_->GetTemplateContent(template_type, toast_xml))) - return false; - if (!SetXmlText(*toast_xml, title.empty() ? msg : title)) - return false; + REPORT_AND_RETURN_IF_FAILED( + toast_manager_->GetTemplateContent(template_type, toast_xml), + "XML: Fetching XML ToastImageAndText01 template failed"); + std::wstring toastMsg = title.empty() ? msg : title; + // we can't create an empty notification + toastMsg = toastMsg.empty() ? L"[no message]" : toastMsg; + REPORT_AND_RETURN_IF_FAILED( + SetXmlText(*toast_xml, toastMsg), + "XML: Filling XML ToastImageAndText01 template failed"); } else { // Title and body toast. template_type = @@ -172,284 +200,256 @@ bool WindowsToastNotification::GetToastXml( ? ABI::Windows::UI::Notifications::ToastTemplateType_ToastText02 : ABI::Windows::UI::Notifications:: ToastTemplateType_ToastImageAndText02; - if (FAILED(toastManager->GetTemplateContent(template_type, toast_xml))) { - if (IsDebuggingNotifications()) - LOG(INFO) << "Fetching XML template failed"; - return false; - } - - if (!SetXmlText(*toast_xml, title, msg)) { - if (IsDebuggingNotifications()) - LOG(INFO) << "Setting text fields on template failed"; - return false; - } + REPORT_AND_RETURN_IF_FAILED( + toastManager->GetTemplateContent(template_type, toast_xml), + "XML: Fetching XML ToastImageAndText02 template failed"); + REPORT_AND_RETURN_IF_FAILED( + SetXmlText(*toast_xml, title, msg), + "XML: Filling XML ToastImageAndText02 template failed"); } // Configure the toast's timeout settings if (timeout_type == base::ASCIIToUTF16("never")) { - if (FAILED(SetXmlScenarioReminder(*toast_xml))) { - if (IsDebuggingNotifications()) - LOG(INFO) << "Setting \"scenario\" option on notification failed"; - return false; - } + REPORT_AND_RETURN_IF_FAILED( + (SetXmlScenarioReminder(*toast_xml)), + "XML: Setting \"scenario\" option on notification failed"); } // Configure the toast's notification sound if (silent) { - if (FAILED(SetXmlAudioSilent(*toast_xml))) { - if (IsDebuggingNotifications()) { - LOG(INFO) << "Setting \"silent\" option on notification failed"; - } - - return false; - } + REPORT_AND_RETURN_IF_FAILED( + SetXmlAudioSilent(*toast_xml), + "XML: Setting \"silent\" option on notification failed"); } // Configure the toast's image - if (!icon_path.empty()) - return SetXmlImage(*toast_xml, icon_path); + if (!icon_path.empty()) { + REPORT_AND_RETURN_IF_FAILED( + SetXmlImage(*toast_xml, icon_path), + "XML: Setting \"icon\" option on notification failed"); + } - return true; + return S_OK; } -bool WindowsToastNotification::SetXmlScenarioReminder(IXmlDocument* doc) { +HRESULT WindowsToastNotification::SetXmlScenarioReminder(IXmlDocument* doc) { ScopedHString tag(L"toast"); if (!tag.success()) return false; ComPtr node_list; - if (FAILED(doc->GetElementsByTagName(tag, &node_list))) - return false; + RETURN_IF_FAILED(doc->GetElementsByTagName(tag, &node_list)); // Check that root "toast" node exists ComPtr root; - if (FAILED(node_list->Item(0, &root))) - return false; + RETURN_IF_FAILED(node_list->Item(0, &root)); // get attributes of root "toast" node ComPtr attributes; - if (FAILED(root->get_Attributes(&attributes))) - return false; + RETURN_IF_FAILED(root->get_Attributes(&attributes)); ComPtr scenario_attribute; ScopedHString scenario_str(L"scenario"); - if (FAILED(doc->CreateAttribute(scenario_str, &scenario_attribute))) - return false; + RETURN_IF_FAILED(doc->CreateAttribute(scenario_str, &scenario_attribute)); ComPtr scenario_attribute_node; - if (FAILED(scenario_attribute.As(&scenario_attribute_node))) - return false; + RETURN_IF_FAILED(scenario_attribute.As(&scenario_attribute_node)); ScopedHString scenario_value(L"reminder"); if (!scenario_value.success()) - return false; + return E_FAIL; ComPtr scenario_text; - if (FAILED(doc->CreateTextNode(scenario_value, &scenario_text))) - return false; + RETURN_IF_FAILED(doc->CreateTextNode(scenario_value, &scenario_text)); ComPtr scenario_node; - if (FAILED(scenario_text.As(&scenario_node))) - return false; + RETURN_IF_FAILED(scenario_text.As(&scenario_node)); ComPtr child_node; - if (FAILED(scenario_attribute_node->AppendChild(scenario_node.Get(), - &child_node))) - return false; + RETURN_IF_FAILED( + scenario_attribute_node->AppendChild(scenario_node.Get(), &child_node)); ComPtr scenario_attribute_pnode; - return SUCCEEDED(attributes.Get()->SetNamedItem(scenario_attribute_node.Get(), - &scenario_attribute_pnode)); + return attributes.Get()->SetNamedItem(scenario_attribute_node.Get(), + &scenario_attribute_pnode); } -bool WindowsToastNotification::SetXmlAudioSilent(IXmlDocument* doc) { +HRESULT WindowsToastNotification::SetXmlAudioSilent(IXmlDocument* doc) { ScopedHString tag(L"toast"); if (!tag.success()) - return false; + return E_FAIL; ComPtr node_list; - if (FAILED(doc->GetElementsByTagName(tag, &node_list))) - return false; + RETURN_IF_FAILED(doc->GetElementsByTagName(tag, &node_list)); ComPtr root; - if (FAILED(node_list->Item(0, &root))) - return false; + RETURN_IF_FAILED(node_list->Item(0, &root)); ComPtr audio_element; ScopedHString audio_str(L"audio"); - if (FAILED(doc->CreateElement(audio_str, &audio_element))) - return false; + RETURN_IF_FAILED(doc->CreateElement(audio_str, &audio_element)); ComPtr audio_node_tmp; - if (FAILED(audio_element.As(&audio_node_tmp))) - return false; + RETURN_IF_FAILED(audio_element.As(&audio_node_tmp)); // Append audio node to toast xml ComPtr audio_node; - if (FAILED(root->AppendChild(audio_node_tmp.Get(), &audio_node))) - return false; + RETURN_IF_FAILED(root->AppendChild(audio_node_tmp.Get(), &audio_node)); // Create silent attribute ComPtr attributes; - if (FAILED(audio_node->get_Attributes(&attributes))) - return false; + RETURN_IF_FAILED(audio_node->get_Attributes(&attributes)); ComPtr silent_attribute; ScopedHString silent_str(L"silent"); - if (FAILED(doc->CreateAttribute(silent_str, &silent_attribute))) - return false; + RETURN_IF_FAILED(doc->CreateAttribute(silent_str, &silent_attribute)); ComPtr silent_attribute_node; - if (FAILED(silent_attribute.As(&silent_attribute_node))) - return false; + RETURN_IF_FAILED(silent_attribute.As(&silent_attribute_node)); // Set silent attribute to true ScopedHString silent_value(L"true"); if (!silent_value.success()) - return false; + return E_FAIL; ComPtr silent_text; - if (FAILED(doc->CreateTextNode(silent_value, &silent_text))) - return false; + RETURN_IF_FAILED(doc->CreateTextNode(silent_value, &silent_text)); ComPtr silent_node; - if (FAILED(silent_text.As(&silent_node))) - return false; + RETURN_IF_FAILED(silent_text.As(&silent_node)); ComPtr child_node; - if (FAILED( - silent_attribute_node->AppendChild(silent_node.Get(), &child_node))) - return false; + RETURN_IF_FAILED( + silent_attribute_node->AppendChild(silent_node.Get(), &child_node)); ComPtr silent_attribute_pnode; - return SUCCEEDED(attributes.Get()->SetNamedItem(silent_attribute_node.Get(), - &silent_attribute_pnode)); + return attributes.Get()->SetNamedItem(silent_attribute_node.Get(), + &silent_attribute_pnode); } -bool WindowsToastNotification::SetXmlText(IXmlDocument* doc, - const std::wstring& text) { +HRESULT WindowsToastNotification::SetXmlText(IXmlDocument* doc, + const std::wstring& text) { ScopedHString tag; ComPtr node_list; - if (!GetTextNodeList(&tag, doc, &node_list, 1)) - return false; + RETURN_IF_FAILED(GetTextNodeList(&tag, doc, &node_list, 1)); ComPtr node; - if (FAILED(node_list->Item(0, &node))) - return false; + RETURN_IF_FAILED(node_list->Item(0, &node)); return AppendTextToXml(doc, node.Get(), text); } -bool WindowsToastNotification::SetXmlText(IXmlDocument* doc, - const std::wstring& title, - const std::wstring& body) { +HRESULT WindowsToastNotification::SetXmlText(IXmlDocument* doc, + const std::wstring& title, + const std::wstring& body) { ScopedHString tag; ComPtr node_list; - if (!GetTextNodeList(&tag, doc, &node_list, 2)) - return false; + RETURN_IF_FAILED(GetTextNodeList(&tag, doc, &node_list, 2)); ComPtr node; - if (FAILED(node_list->Item(0, &node))) - return false; - - if (!AppendTextToXml(doc, node.Get(), title)) - return false; - - if (FAILED(node_list->Item(1, &node))) - return false; + RETURN_IF_FAILED(node_list->Item(0, &node)); + RETURN_IF_FAILED(AppendTextToXml(doc, node.Get(), title)); + RETURN_IF_FAILED(node_list->Item(1, &node)); return AppendTextToXml(doc, node.Get(), body); } -bool WindowsToastNotification::SetXmlImage(IXmlDocument* doc, - const std::wstring& icon_path) { +HRESULT WindowsToastNotification::SetXmlImage(IXmlDocument* doc, + const std::wstring& icon_path) { ScopedHString tag(L"image"); if (!tag.success()) - return false; + return E_FAIL; ComPtr node_list; - if (FAILED(doc->GetElementsByTagName(tag, &node_list))) - return false; + RETURN_IF_FAILED(doc->GetElementsByTagName(tag, &node_list)); ComPtr image_node; - if (FAILED(node_list->Item(0, &image_node))) - return false; + RETURN_IF_FAILED(node_list->Item(0, &image_node)); ComPtr attrs; - if (FAILED(image_node->get_Attributes(&attrs))) - return false; + RETURN_IF_FAILED(image_node->get_Attributes(&attrs)); ScopedHString src(L"src"); if (!src.success()) - return false; + return E_FAIL; ComPtr src_attr; - if (FAILED(attrs->GetNamedItem(src, &src_attr))) - return false; + RETURN_IF_FAILED(attrs->GetNamedItem(src, &src_attr)); ScopedHString img_path(icon_path.c_str()); if (!img_path.success()) - return false; + return E_FAIL; ComPtr src_text; - if (FAILED(doc->CreateTextNode(img_path, &src_text))) - return false; + RETURN_IF_FAILED(doc->CreateTextNode(img_path, &src_text)); ComPtr src_node; - if (FAILED(src_text.As(&src_node))) - return false; + RETURN_IF_FAILED(src_text.As(&src_node)); ComPtr child_node; - return SUCCEEDED(src_attr->AppendChild(src_node.Get(), &child_node)); + return src_attr->AppendChild(src_node.Get(), &child_node); } -bool WindowsToastNotification::GetTextNodeList(ScopedHString* tag, - IXmlDocument* doc, - IXmlNodeList** node_list, - uint32_t req_length) { +HRESULT WindowsToastNotification::GetTextNodeList(ScopedHString* tag, + IXmlDocument* doc, + IXmlNodeList** node_list, + uint32_t req_length) { tag->Reset(L"text"); if (!tag->success()) - return false; + return E_FAIL; - if (FAILED(doc->GetElementsByTagName(*tag, node_list))) - return false; + RETURN_IF_FAILED(doc->GetElementsByTagName(*tag, node_list)); uint32_t node_length; - if (FAILED((*node_list)->get_Length(&node_length))) - return false; + RETURN_IF_FAILED((*node_list)->get_Length(&node_length)); return node_length >= req_length; } -bool WindowsToastNotification::AppendTextToXml(IXmlDocument* doc, - IXmlNode* node, - const std::wstring& text) { +HRESULT WindowsToastNotification::AppendTextToXml(IXmlDocument* doc, + IXmlNode* node, + const std::wstring& text) { ScopedHString str(text); if (!str.success()) - return false; + return E_FAIL; ComPtr xml_text; - if (FAILED(doc->CreateTextNode(str, &xml_text))) - return false; + RETURN_IF_FAILED(doc->CreateTextNode(str, &xml_text)); ComPtr text_node; - if (FAILED(xml_text.As(&text_node))) - return false; + RETURN_IF_FAILED(xml_text.As(&text_node)); ComPtr append_node; - return SUCCEEDED(node->AppendChild(text_node.Get(), &append_node)); + RETURN_IF_FAILED(node->AppendChild(text_node.Get(), &append_node)); + + return S_OK; } -bool WindowsToastNotification::SetupCallbacks( +HRESULT WindowsToastNotification::XmlDocumentFromString( + const wchar_t* xmlString, + IXmlDocument** doc) { + ComPtr xmlDoc; + RETURN_IF_FAILED(Windows::Foundation::ActivateInstance( + HStringReference(RuntimeClass_Windows_Data_Xml_Dom_XmlDocument).Get(), + &xmlDoc)); + + ComPtr docIO; + RETURN_IF_FAILED(xmlDoc.As(&docIO)); + + RETURN_IF_FAILED(docIO->LoadXml(HStringReference(xmlString).Get())); + + return xmlDoc.CopyTo(doc); +} + +HRESULT WindowsToastNotification::SetupCallbacks( ABI::Windows::UI::Notifications::IToastNotification* toast) { event_handler_ = Make(this); - if (FAILED(toast->add_Activated(event_handler_.Get(), &activated_token_))) - return false; - - if (FAILED(toast->add_Dismissed(event_handler_.Get(), &dismissed_token_))) - return false; - - return SUCCEEDED(toast->add_Failed(event_handler_.Get(), &failed_token_)); + RETURN_IF_FAILED( + toast->add_Activated(event_handler_.Get(), &activated_token_)); + RETURN_IF_FAILED( + toast->add_Dismissed(event_handler_.Get(), &dismissed_token_)); + RETURN_IF_FAILED(toast->add_Failed(event_handler_.Get(), &failed_token_)); + return S_OK; } bool WindowsToastNotification::RemoveCallbacks( @@ -498,11 +498,15 @@ IFACEMETHODIMP ToastEventHandler::Invoke( IFACEMETHODIMP ToastEventHandler::Invoke( ABI::Windows::UI::Notifications::IToastNotification* sender, ABI::Windows::UI::Notifications::IToastFailedEventArgs* e) { - base::PostTask( - FROM_HERE, {content::BrowserThread::UI}, - base::BindOnce(&Notification::NotificationFailed, notification_)); + HRESULT error; + e->get_ErrorCode(&error); + std::string errorMessage = + "Notification failed. HRESULT:" + std::to_string(error); + base::PostTask(FROM_HERE, {content::BrowserThread::UI}, + base::BindOnce(&Notification::NotificationFailed, + notification_, errorMessage)); if (IsDebuggingNotifications()) - LOG(INFO) << "Notification failed"; + LOG(INFO) << errorMessage; return S_OK; } diff --git a/shell/browser/notifications/win/windows_toast_notification.h b/shell/browser/notifications/win/windows_toast_notification.h index eed2e2bed5d0..769e3280da69 100644 --- a/shell/browser/notifications/win/windows_toast_notification.h +++ b/shell/browser/notifications/win/windows_toast_notification.h @@ -57,7 +57,8 @@ class WindowsToastNotification : public Notification { private: friend class ToastEventHandler; - bool GetToastXml( + HRESULT ShowInternal(const NotificationOptions& options); + HRESULT GetToastXml( ABI::Windows::UI::Notifications::IToastNotificationManagerStatics* toastManager, const std::wstring& title, @@ -66,23 +67,27 @@ class WindowsToastNotification : public Notification { const std::wstring& timeout_type, const bool silent, ABI::Windows::Data::Xml::Dom::IXmlDocument** toastXml); - bool SetXmlAudioSilent(ABI::Windows::Data::Xml::Dom::IXmlDocument* doc); - bool SetXmlScenarioReminder(ABI::Windows::Data::Xml::Dom::IXmlDocument* doc); - bool SetXmlText(ABI::Windows::Data::Xml::Dom::IXmlDocument* doc, - const std::wstring& text); - bool SetXmlText(ABI::Windows::Data::Xml::Dom::IXmlDocument* doc, - const std::wstring& title, - const std::wstring& body); - bool SetXmlImage(ABI::Windows::Data::Xml::Dom::IXmlDocument* doc, - const std::wstring& icon_path); - bool GetTextNodeList(ScopedHString* tag, - ABI::Windows::Data::Xml::Dom::IXmlDocument* doc, - ABI::Windows::Data::Xml::Dom::IXmlNodeList** nodeList, - uint32_t reqLength); - bool AppendTextToXml(ABI::Windows::Data::Xml::Dom::IXmlDocument* doc, - ABI::Windows::Data::Xml::Dom::IXmlNode* node, - const std::wstring& text); - bool SetupCallbacks( + HRESULT SetXmlAudioSilent(ABI::Windows::Data::Xml::Dom::IXmlDocument* doc); + HRESULT SetXmlScenarioReminder( + ABI::Windows::Data::Xml::Dom::IXmlDocument* doc); + HRESULT SetXmlText(ABI::Windows::Data::Xml::Dom::IXmlDocument* doc, + const std::wstring& text); + HRESULT SetXmlText(ABI::Windows::Data::Xml::Dom::IXmlDocument* doc, + const std::wstring& title, + const std::wstring& body); + HRESULT SetXmlImage(ABI::Windows::Data::Xml::Dom::IXmlDocument* doc, + const std::wstring& icon_path); + HRESULT GetTextNodeList(ScopedHString* tag, + ABI::Windows::Data::Xml::Dom::IXmlDocument* doc, + ABI::Windows::Data::Xml::Dom::IXmlNodeList** nodeList, + uint32_t reqLength); + HRESULT AppendTextToXml(ABI::Windows::Data::Xml::Dom::IXmlDocument* doc, + ABI::Windows::Data::Xml::Dom::IXmlNode* node, + const std::wstring& text); + HRESULT XmlDocumentFromString( + const wchar_t* xmlString, + ABI::Windows::Data::Xml::Dom::IXmlDocument** doc); + HRESULT SetupCallbacks( ABI::Windows::UI::Notifications::IToastNotification* toast); bool RemoveCallbacks( ABI::Windows::UI::Notifications::IToastNotification* toast); diff --git a/spec-main/api-notification-spec.ts b/spec-main/api-notification-spec.ts index 0ec2861963c6..c4c96ae5c469 100644 --- a/spec-main/api-notification-spec.ts +++ b/spec-main/api-notification-spec.ts @@ -108,6 +108,14 @@ describe('Notification module', () => { n.close(); }); + ifit(process.platform === 'win32')('inits, gets and sets custom xml', () => { + const n = new Notification({ + toastXml: '' + }); + + expect(n.toastXml).to.equal(''); + }); + ifit(process.platform === 'darwin')('emits show and close events', async () => { const n = new Notification({ title: 'test notification', @@ -126,5 +134,16 @@ describe('Notification module', () => { } }); + ifit(process.platform === 'win32')('emits failed event', async () => { + const n = new Notification({ + toastXml: 'not xml' + }); + { + const e = emittedOnce(n, 'failed'); + n.show(); + await e; + } + }); + // TODO(sethlu): Find way to test init with notification icon? });