From 1c1d3d0b72d4d675fced247b0157d3bc2cf6828c Mon Sep 17 00:00:00 2001 From: Jessica Chen Date: Fri, 9 Dec 2022 12:34:54 -0800 Subject: [PATCH 01/17] Add Web Notification API Spec draft --- specs/WebNotification.md | 610 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 610 insertions(+) create mode 100644 specs/WebNotification.md diff --git a/specs/WebNotification.md b/specs/WebNotification.md new file mode 100644 index 000000000..5ac762b81 --- /dev/null +++ b/specs/WebNotification.md @@ -0,0 +1,610 @@ +Web Notification APIs +=== + +# Background + +The WebView2 team is adding support for Web notifications. End developers should +be able to handle notification permission requests, and further listen to +`NotificationReceived` events to optionally handle the notifications themselves. +The `NotificationReceived` events are raised on `CorebWebView2` and +`CoreWebView2Profile` object respectively for non-persistent and persistent +notifications respectively. + +The `NotificationReceived` event on `CoreWebView2` and `CoreWebView2Profile` let you intercept the web non-persistent and persistent notifications. The host can use the `Notification` property on the `NotificationReceivedEventArgs` to construct an notification matching the look and feel of the host app. The host can also use such information to decide to show or not show a particular notification. The host can `GetDeferral` or set the `Handled` property on the `NotificationReceivedEventArgs` to handle the event at a later time or let WebView2 know if the notification has been handled. By default, if the `NotificationReceived` event is not handled by the host, the web notification will be displayed using the default notification UI provided by WebView2 Runtime. + +# Examples + +## Handle PermissionRequested event + +This can be achieved with the existing `PermissionRequested` events. +`PermissionRequested` event used to not be raised for `PermissionKind.Notification`. +Now, `PermissionRequested` events are raised for `PermissionKind.Notification`, and `PermissionState` needs to be set `Allow` explicitly to allow such permission requests. `PermissionState.Default` for `PermissionKind.Notification` is considered denied. + +### C# Sample +```csharp +IDictionary<(string, CoreWebView2PermissionKind, bool), bool> _cachedPermissions = + new Dictionary<(string, CoreWebView2PermissionKind, bool), bool>(); +... +void WebView_PermissionRequested(object sender, CoreWebView2PermissionRequestedEventArgs args) +{ + CoreWebView2Deferral deferral = args.GetDeferral(); + System.Threading.SynchronizationContext.Current.Post((_) => + { + using (deferral) + { + (string, CoreWebView2PermissionKind, bool) cachedKey = (args.Uri, args.PermissionKind, args.IsUserInitiated); + if (_cachedPermissions.ContainsKey(cachedKey)) + { + args.State = _cachedPermissions[cachedKey] + ? CoreWebView2PermissionState.Allow + : CoreWebView2PermissionState.Deny; + } + else + { + string message = "An iframe has requested device permission for "; + message += NameOfPermissionKind(args.PermissionKind) + " to the website at "; + message += args.Uri + ".\n\nDo you want to grant permission?\n"; + message += args.IsUserInitiated ? "This request came from a user gesture." : "This request did not come from a user gesture."; + var selection = MessageBox.Show( + message, "Permission Request", MessageBoxButton.YesNoCancel); + switch (selection) + { + case MessageBoxResult.Yes: + args.State = CoreWebView2PermissionState.Allow; + break; + case MessageBoxResult.No: + args.State = CoreWebView2PermissionState.Deny; + break; + case MessageBoxResult.Cancel: + args.State = CoreWebView2PermissionState.Default; + break; + } + } + } + }, null); +} +``` + +### C++ Sample +```cpp +... +{ +CHECK_FAILURE(m_webView->add_PermissionRequested( + Callback( + this, &SettingsComponent::OnPermissionRequested) + .Get(), + &m_permissionRequestedToken)); +} +... +HRESULT SettingsComponent::OnPermissionRequested( + ICoreWebView2* sender, ICoreWebView2PermissionRequestedEventArgs* args) +{ + // Obtain a deferral for the event so that the CoreWebView2 + // doesn't examine the properties we set on the event args until + // after we call the Complete method asynchronously later. + wil::com_ptr deferral; + CHECK_FAILURE(args->GetDeferral(&deferral)); + + // Do the rest asynchronously, to avoid calling MessageBox in an event handler. + m_appWindow->RunAsync([this, deferral, args] { + COREWEBVIEW2_PERMISSION_KIND kind = COREWEBVIEW2_PERMISSION_KIND_UNKNOWN_PERMISSION; + BOOL userInitiated = FALSE; + wil::unique_cotaskmem_string uri; + CHECK_FAILURE(args->get_PermissionKind(&kind)); + CHECK_FAILURE(args->get_IsUserInitiated(&userInitiated)); + CHECK_FAILURE(args->get_Uri(&uri)); + + COREWEBVIEW2_PERMISSION_STATE state; + + auto cached_key = std::make_tuple(std::wstring(uri.get()), kind, userInitiated); + auto cached_permission = m_cached_permissions.find(cached_key); + if (cached_permission != m_cached_permissions.end()) + { + state = + (cached_permission->second ? COREWEBVIEW2_PERMISSION_STATE_ALLOW + : COREWEBVIEW2_PERMISSION_STATE_DENY); + } + else + { + std::wstring message = L"An iframe has requested device permission for "; + message += SettingsComponent::NameOfPermissionKind(kind); + message += L" to the website at "; + message += uri.get(); + message += L"?\n\n"; + message += L"Do you want to grant permission?\n"; + message += + (userInitiated ? L"This request came from a user gesture." + : L"This request did not come from a user gesture."); + + int response = MessageBox( + nullptr, message.c_str(), L"Permission Request", + MB_YESNOCANCEL | MB_ICONWARNING); + switch (response) + { + case IDYES: + m_cached_permissions[cached_key] = true; + state = COREWEBVIEW2_PERMISSION_STATE_ALLOW; + break; + case IDNO: + m_cached_permissions[cached_key] = false; + state = COREWEBVIEW2_PERMISSION_STATE_DENY; + break; + default: + state = COREWEBVIEW2_PERMISSION_STATE_DEFAULT; + break; + } + } + + CHECK_FAILURE(args->put_State(state)); + CHECK_FAILURE(deferral->Complete()); + }); + return S_OK; +} + +``` + +## Filter Notifications from a specific doamin and send local toast + +Learn more about sending a local toast [Send a local toast notification from a C# app - Windows apps | Microsoft Learn](https://learn.microsoft.com/windows/apps/design/shell/tiles-and-notifications/send-local-toast?tabs=uwp). + +### C# Sample +```csharp +using Microsoft.Toolkit.Uwp.Notifications; +... +void WebView_NotificationReceived(object sender, CoreWebView2NotificationReceivedEventArgs args) +{ + CoreWebView2Deferral deferral = args.GetDeferral(); + var notification = args.Notification; + + // Show notification with local MessageBox + string message = "Received notification from " + args.Uri + " with body " + notification.Body; + MessageBox.Show(message); + + // Requires Microsoft.Toolkit.Uwp.Notifications NuGet package version 7.0 or greater + var toastContent = new ToastContentBuilder() + .AddArgument("conversationId", m_id) + .AddText("Notification sent from " + args.Uri +":\n") + .AddText(notification.Body); + toastContent.Show(); + // See + // https://learn.microsoft.com/windows/apps/design/shell/tiles-and-notifications/send-local-toast?tabs=uwp + // for how to handle toast activation + + notification.Show(); + notification.Close(); + args.Handled = true; +} +``` + +### C++ Sample +```cpp +... +{ + //! [NotificationReceived] + // Register a handler for the NotificationReceived event. + CHECK_FAILURE(m_webView2_17->add_NotificationReceived( + Callback( + [this]( + ICoreWebView2* sender, + ICoreWebView2NotificationReceivedEventArgs* args) -> HRESULT + { + // Block notifications from specific URIs and set Handled to + // false so the the default notification UI will not be + // shown by WebView2 either. + wil::unique_cotaskmem_string uri; + CHECK_FAILURE(args->get_Uri(&uri); + if (ShouldBlockUri(uri.get())) + { + CHECK_FAILURE(args->put_Handled(FALSE)); + } + ShowNotification(args); + return S_OK; + }) + .Get(), + &m_notificationReceivedToken)); + //! [NotificationReceived] + } +... +//! [OnNotificationReceived] +void SettingsComponent::ShowNotification( + ICoreWebView2NotificationReceivedEventArgs* args) +{ + AppWindow* appWindow = m_appWindow; + + // Obtain a deferral for the event so that the CoreWebView2 + // does not examine the properties we set on the event args and + // after we call the Complete method asynchronously later. + wil::com_ptr deferral; + CHECK_FAILURE(args->GetDeferral(&deferral)); + + wil::com_ptr eventArgs = args; + + appWindow->RunAsync( + [this, eventArgs, deferral] + { + wil::com_ptr notification; + CHECK_FAILURE(eventArgs->get_Notification(¬ification)); + + wil::unique_cotaskmem_string uri; + CHECK_FAILURE(eventArgs->get_Uri(&uri)); + wil::unique_cotaskmem_string title; + CHECK_FAILURE(notification->get_Title(&title)); + wil::unique_cotaskmem_string body; + CHECK_FAILURE(notification->get_Body(&body)); + + std::wstring message = + L"The page at " + std::wstring(uri.get()) + L" sends you an + notification:\n\n"; + message += body.get(); + notification->Show(); + int response = MessageBox(nullptr, message.c_str(), title.get(), MB_OKCANCEL); + (response == IDOK) ? notification->Click() : notification->Close(); + + deferral->Complete(); + }); +} +//! [OnNotificationReceived] +``` + +# API Details + +```cpp +/// Specifies the text direction of the notification. +[v1_enum] +typedef enum COREWEBVIEW2_NOTIFICATION_DIRECTION_KINDS { + /// Indicates that the notification text direction adopts the browser's language setting behavior. + COREWEBVIEW2_NOTIFICATION_DIRECTION_KINDS_AUTO, + + /// Indicates that the notification text is left-to-right. + COREWEBVIEW2_NOTIFICATION_DIRECTION_KINDS_LTR, + + /// Indicates that the notification text is right-to-left. + COREWEBVIEW2_NOTIFICATION_DIRECTION_KINDS_RTL, +} COREWEBVIEW2_NOTIFICATION_DIRECTION_KINDS; + +/// This is the notification action for interacting with the notification. +typedef struct COREWEBVIEW2_NOTIFICATION_ACTION { + /// A string identifying a user action to be displayed on the notification. + LPWSTR Action; + + /// A string containing action text to be shown to the user. + LPWSTR Title; + + /// A string containing the URL of an icon to display with the action. + LPWSTR Icon; +} COREWEBVIEW2_NOTIFICATION_ACTION; + +/// This is the ICoreWebView2Profile3 interface that manages WebView2 Web +/// Notification functionality. +[uuid(51B49A68-BA2D-4188-821E-B13339CD96EE), object, pointer_default(unique)] +interface ICoreWebView2Profile3 : IUnknown { + /// Add an event handler for the `NotificationReceived` event for persistent + /// notifications. + HRESULT add_NotificationReceived( + [in] ICoreWebView2PersistentNotificationReceivedEventHandler* eventHandler, + [out] EventRegistrationToken* token); + + /// Remove an event handler previously added with `add_NotificationReceived`. + HRESULT remove_NotificationReceived([in] EventRegistrationToken token); +} + +/// This is the ICoreWebView2_17 interface that manages WebView2 Web +/// Notification functionality. +[uuid(AA02BF32-E098-4F16-99F6-3FC182A1EC30), object, pointer_default(unique)] +interface ICoreWebView2_17 : ICoreWebView2_16 { + /// Add an event handler for the `NotificationReceived` event for + /// non-persistent notifications. + HRESULT add_NotificationReceived( + [in] ICoreWebView2NotificationReceivedEventHandler* eventHandler, + [out] EventRegistrationToken* token); + + /// Remove an event handler previously added with `add_NotificationReceived`. + HRESULT remove_NotificationReceived( + [in] EventRegistrationToken token); +} + +/// An event handler for the `NotificationReceived` event. +[uuid(4E0C46D6-B06F-4FFC-A11B-14C6A9B19BA5), object, pointer_default(unique)] +interface ICoreWebView2NotificationReceivedEventHandler : IUnknown { + /// Provides the event args for the corresponding event. + HRESULT Invoke( + [in] ICoreWebView2* sender, + [in] ICoreWebView2NotificationReceivedEventArgs* args); +} + +/// An event handler for the `NotificationReceived` event for persistent notifications. +[uuid(4843DC48-C1EF-4B0F-872E-F7AA6BC80175), object, pointer_default(unique)] +interface ICoreWebView2PersistentNotificationReceivedEventHandler : IUnknown { + /// Provides the event args for the corresponding event. + HRESULT Invoke( + [in] ICoreWebView2Profile* sender, + [in] ICoreWebView2NotificationReceivedEventArgs* args); +} + +/// Event args for the `NotificationReceived` event. +/// \snippet SettingsComponent.cpp NotificationReceived +/// \snippet SettingsComponent.cpp OnNotificationReceived +[uuid(5E8983CA-19A0-4140-BF2E-D7B9B707DDCC), object, pointer_default(unique)] +interface ICoreWebView2NotificationReceivedEventArgs : IUnknown { + /// The origin of the web content that sends the notification. + /// + /// The caller must free the returned string with `CoTaskMemFree`. See + /// [API Conventions](/microsoft-edge/webview2/concepts/win32-api-conventions#strings). + [propget] HRESULT Uri([out, retval] LPWSTR* uri); + + /// The notification that was received. End developers can access the + /// properties on the Notification object to show their own notification. + [propget] HRESULT Notification([out, retval] ICoreWebView2Notification** notification); + + /// Sets whether the `NotificationReceived` event is handled by the host after + /// the event handler completes or if there is a deferral then after the + /// deferral is completed. + /// + /// If `Handled` is set to TRUE then WebView will not display the notification + /// with the default UI, and the host will be responsible for handling the + /// notification and let the web content know that the notification has been + /// displayed, clicked, or closed. If after the event handler or deferral + /// completes `Handled` is set to FALSE then WebView will display the default + /// notification. Note that if `Show` has been called on the `Notification` + /// object, WebView will not display the default notification regardless of + /// the Handled property. The default value is FALSE. + [propput] HRESULT Handled([in] BOOL value); + + /// Gets whether the `NotificationReceived` event is handled by host. + [propget] HRESULT Handled([out, retval] BOOL* value); + + /// Returns an `ICoreWebView2Deferral` object. Use this operation to complete + /// the event at a later time. + HRESULT GetDeferral([out, retval] ICoreWebView2Deferral** deferral); +} + +/// An event handler for the `Closed` event. +[uuid(6A0B4DE9-8CBE-4211-BAEF-AC037B1A72DE), object, pointer_default(unique)] +interface ICoreWebView2NotificationClosedEventHandler : IUnknown { + /// Provides the event args for the corresponding event. + HRESULT Invoke( + [in] ICoreWebView2Notification* sender, + [in] IUnknown* args); +} + +/// A collection of notification actions. +[uuid(89D8907E-18C7-458B-A970-F91F645E4C43), object, pointer_default(unique)] +interface ICoreWebView2NotificationActionCollection : IUnknown { + /// The number of notification actions contained in the + /// ICoreWebView2NotificationActionCollection. + [propget] HRESULT Count([out, retval] UINT* count); + + /// Gets the notification action at the given index. + HRESULT GetValueAtIndex([in] UINT index, + [out, retval] COREWEBVIEW2_NOTIFICATION_ACTION* value); +} + +/// A collection of unsigned long integers. +[uuid(974A91F8-A309-4376-BC70-7537D686533B), object, pointer_default(unique)] +interface ICoreWebView2UnsignedLongCollection : IUnknown { + /// The number of unsigned long integers contained in the + /// ICoreWebView2UnsignedLongCollection. + [propget] HRESULT Count([out, retval] UINT* count); + + /// Gets the unsigned long integer at the given index. + HRESULT GetValueAtIndex([in] UINT index, [out, retval] UINT64* value); +} + +/// This is the ICoreWebView2Notification that represents a [HTML Notification +/// object](https://developer.mozilla.org/docs/Web/API/Notification). +[uuid(E3F43572-2930-42EB-BD90-CAC16DE6D942), object, pointer_default(unique)] +interface ICoreWebView2Notification : IUnknown { + /// Add an event handler for the `Closed` event. + /// This event is raised when the notification is closed by the web code. + HRESULT add_Closed( + [in] ICoreWebView2NotificationClosedEventHandler* eventHandler, + [out] EventRegistrationToken* token); + + /// Remove an event handler previously added with `add_Closed`. + HRESULT remove_Closed( + [in] EventRegistrationToken token); + + /// The host may run this to report the notification has been displayed. + /// The NotificationReceived event is considered handled regardless of the + /// Handled property of the NotifificationReceivedEventArgs if the host has + /// run Show(). + HRESULT Show(); + + /// The host may run this to report the notification has been clicked. Use + /// `ClickWithAction` to specify an action to activate a persistent + /// notification. This will no-op if `Show` is not run or `Close` has been + /// run. + HRESULT Click(); + + /// The host may run this to report the persistent notification has been + /// activated with a given action. The action index corresponds to the index + /// in NotificationActionCollection. This returns `E_INVALIDARG` if an invalid + /// action index is provided. Use `Click` to activate an non-persistent + /// notification. This will no-op if `Show` is not run or `Close` has been + /// run. + HRESULT ClickWithAction([in] UINT actionIndex); + + /// The host may run this to report the notification was dismissed. + /// This will no-op if `Show` is not run or `Click` has been run. + HRESULT Close(); + + /// The body string of the notification as specified in the constructor's + /// options parameter. + /// The default value is an empty string. + /// + /// The caller must free the returned string with `CoTaskMemFree`. See + /// [API Conventions](/microsoft-edge/webview2/concepts/win32-api-conventions#strings). + [propget] HRESULT Body ([out, retval] LPWSTR* body); + + /// Returns an IDataObject that represents a structured clone of the + /// notification's data. + /// Returns `null` if the optional Notification property does not exist. + [propget] HRESULT Data ([out, retval] IDataObject** data); + + /// The text direction of the notification as specified in the constructor's + /// options parameter. + /// The default value is `COREWEBVIEW2_NOTIFICATION_DIRECTION_KINDS_AUTO`. + [propget] HRESULT Direction ([out, retval] COREWEBVIEW2_NOTIFICATION_DIRECTION_KINDS* value); + + /// The language code of the notification as specified in the constructor's + /// options parameter. It is in the format of + /// `language-country` where `language` is the 2-letter code from [ISO + /// 639](https://www.iso.org/iso-639-language-codes.html) and `country` is the + /// 2-letter code from [ISO 3166](https://www.iso.org/standard/72482.html). + /// The default value is an empty string. + /// + /// The caller must free the returned string with `CoTaskMemFree`. See + /// [API Conventions](/microsoft-edge/webview2/concepts/win32-api-conventions#strings). + [propget] HRESULT Language ([out, retval] LPWSTR* language); + + /// The ID of the notification (if any) as specified in the constructor's + /// options parameter. + /// The default value is an empty string. + /// + /// The caller must free the returned string with `CoTaskMemFree`. See + /// [API Conventions](/microsoft-edge/webview2/concepts/win32-api-conventions#strings). + [propget] HRESULT Tag ([out, retval] LPWSTR* tag); + + /// The URL of the image used as an icon of the notification as specified in + /// the constructor's options parameter. + /// Returns `null` if the optional Notification property does not exist. + /// + /// The caller must free the returned string with `CoTaskMemFree`. See + /// [API Conventions](/microsoft-edge/webview2/concepts/win32-api-conventions#strings). + [propget] HRESULT Icon ([out, retval] LPWSTR* icon); + + /// The title of the notification as specified in the first parameter of the + /// constructor. + /// + /// The caller must free the returned string with `CoTaskMemFree`. See + /// [API Conventions](/microsoft-edge/webview2/concepts/win32-api-conventions#strings). + [propget] HRESULT Title ([out, retval] LPWSTR* title); + + + /// The actions available for users to choose from for interacting with the + /// notification. Note that actions are only supported for persistent notifications. + /// Returns `null` if the optional Notification property does not exist. + [propget] HRESULT Actions([out, retval] ICoreWebView2NotificationActionCollection** actions); + + /// The URL of the image used to represent the notification when there is not + /// enough space to display the notification itself. + /// Returns `null` if the optional Notification property does not exist. + /// + /// The caller must free the returned string with `CoTaskMemFree`. See + /// [API Conventions](/microsoft-edge/webview2/concepts/win32-api-conventions#strings). + [propget] HRESULT Badge([out, retval] LPWSTR* badge); + + /// The URL of an image to be displayed as part of the notification. + /// Returns `null` if the optional Notification property does not exist. + /// + /// The caller must free the returned string with `CoTaskMemFree`. See + /// [API Conventions](/microsoft-edge/webview2/concepts/win32-api-conventions#strings). + [propget] HRESULT Image([out, retval] LPWSTR* image); + + /// Specifies whether the user should be notified after a new notification + /// replaces an old one. + /// The default value is `FALSE`. + [propget] HRESULT Renotify([out, retval] BOOL* renotify); + + /// A boolean value indicating that a notification should remain active until + /// the user clicks or dismisses it, rather than closing automatically. + /// The default value is `FALSE`. + [propget] HRESULT RequireInteraction([out, retval] BOOL* requireInteraction); + + /// Specifies whether the notification should be silent — i.e., no sounds or + /// vibrations should be issued, regardless of the device settings. + /// The default value is `FALSE`. + [propget] HRESULT Silent([out, retval] BOOL* silent); + + /// Specifies the time at which a notification is created or applicable (past, + /// present, or future). + /// Returns `null` if the optional Notification property does not exist. + [propget] HRESULT Timestamp([out, retval] double* timestamp); + + /// Specifies a vibration pattern for devices with vibration hardware to emit. + /// Returns `null` if the optional Notification property does not exist. + [propget] HRESULT Vibrate([out, retval] ICoreWebView2UnsignedLongCollection** vibrationPattern); +} +``` + +```csharp +namespace Microsoft.Web.WebView2.Core +{ + runtimeclass CoreWebView2NotificationReceivedEventArgs; + runtimeclass CoreWebView2Notification; + struct CoreWebView2NotificationAction + { + String Action; + String title; + String Icon; + + }; + struct CoreWebView2NotificationAction + { + String Action; + String title; + String Icon; + + }; + runtimeclass CoreWebView2Profile + { + ... + [interface_name("Microsoft.Web.WebView2.Core.ICoreWebView2Profile3")] + { + // ICoreWebView2Profile3 members + event Windows.Foundation.TypedEventHandler NotificationReceived; + } + ... + } + runtimeclass CoreWebView2 + { + ... + [interface_name("Microsoft.Web.WebView2.Core.ICoreWebView2_17")] + { + // ICoreWebView2_17 members + event Windows.Foundation.TypedEventHandler NotificationReceived; + + + + } + ... + } + runtimeclass CoreWebView2NotificationReceivedEventArgs + { + // ICoreWebView2NotificationReceivedEventArgs members + String Uri { get; }; + CoreWebView2Notification Notification { get; }; + Boolean Handled { get; set; }; + Windows.Foundation.Deferral GetDeferral(); + } + + runtimeclass CoreWebView2Notification + { + // ICoreWebView2Notification members + String Body { get; }; + CoreWebView2NotificationDirectionKinds Direction { get; }; + String Language { get; }; + String Tag { get; }; + String Icon { get; }; + String Title { get; }; + IVectorView Actions { get; }; + String Badge { get; }; + String Image { get; }; + Boolean Renotify { get; }; + Boolean RequireInteraction { get; }; + Boolean Silent { get; }; + Double Timestamp { get; }; + IVectorView Vibrate { get; }; + // TODO: What should the proper data type be for + // CoreWebView2Notification.Data? We use IDataObject for COM/C++. + // Data { get; }; + + event Windows.Foundation.TypedEventHandler Closed; + + void Show(); + void Click(); + void Click(UInt32 actionIndex); + void Close(); + } +} +``` From 952a757064a5fe7256e5ad17c87504975477ee04 Mon Sep 17 00:00:00 2001 From: Jessica Chen <55165503+peiche-jessica@users.noreply.github.com> Date: Mon, 12 Dec 2022 16:20:19 -0800 Subject: [PATCH 02/17] Apply suggestions from code review Co-authored-by: David Risney --- specs/WebNotification.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/specs/WebNotification.md b/specs/WebNotification.md index 5ac762b81..aee142530 100644 --- a/specs/WebNotification.md +++ b/specs/WebNotification.md @@ -330,7 +330,7 @@ interface ICoreWebView2NotificationReceivedEventArgs : IUnknown { /// /// The caller must free the returned string with `CoTaskMemFree`. See /// [API Conventions](/microsoft-edge/webview2/concepts/win32-api-conventions#strings). - [propget] HRESULT Uri([out, retval] LPWSTR* uri); + [propget] HRESULT Uri([out, retval] LPWSTR* value); /// The notification that was received. End developers can access the /// properties on the Notification object to show their own notification. @@ -342,10 +342,10 @@ interface ICoreWebView2NotificationReceivedEventArgs : IUnknown { /// /// If `Handled` is set to TRUE then WebView will not display the notification /// with the default UI, and the host will be responsible for handling the - /// notification and let the web content know that the notification has been + /// notification and for letting the web content know that the notification has been /// displayed, clicked, or closed. If after the event handler or deferral /// completes `Handled` is set to FALSE then WebView will display the default - /// notification. Note that if `Show` has been called on the `Notification` + /// notification UI. Note that if `Show` has been called on the `Notification` /// object, WebView will not display the default notification regardless of /// the Handled property. The default value is FALSE. [propput] HRESULT Handled([in] BOOL value); @@ -478,7 +478,7 @@ interface ICoreWebView2Notification : IUnknown { /// /// The caller must free the returned string with `CoTaskMemFree`. See /// [API Conventions](/microsoft-edge/webview2/concepts/win32-api-conventions#strings). - [propget] HRESULT Title ([out, retval] LPWSTR* title); + [propget] HRESULT Title([out, retval] LPWSTR* title); /// The actions available for users to choose from for interacting with the From e03a2149bdc0fd85cace9cc89f05d7aed1b6a21f Mon Sep 17 00:00:00 2001 From: Jessica Chen Date: Mon, 12 Dec 2022 16:45:20 -0800 Subject: [PATCH 03/17] Address initial feedback part 1 --- specs/WebNotification.md | 129 ++++++++++++++++++++------------------- 1 file changed, 65 insertions(+), 64 deletions(-) diff --git a/specs/WebNotification.md b/specs/WebNotification.md index aee142530..30f59e49a 100644 --- a/specs/WebNotification.md +++ b/specs/WebNotification.md @@ -10,7 +10,17 @@ The `NotificationReceived` events are raised on `CorebWebView2` and `CoreWebView2Profile` object respectively for non-persistent and persistent notifications respectively. -The `NotificationReceived` event on `CoreWebView2` and `CoreWebView2Profile` let you intercept the web non-persistent and persistent notifications. The host can use the `Notification` property on the `NotificationReceivedEventArgs` to construct an notification matching the look and feel of the host app. The host can also use such information to decide to show or not show a particular notification. The host can `GetDeferral` or set the `Handled` property on the `NotificationReceivedEventArgs` to handle the event at a later time or let WebView2 know if the notification has been handled. By default, if the `NotificationReceived` event is not handled by the host, the web notification will be displayed using the default notification UI provided by WebView2 Runtime. +The `NotificationReceived` event on `CoreWebView2` and `CoreWebView2Profile` let +you intercept the web non-persistent and persistent notifications. The host can +use the `Notification` property on the `NotificationReceivedEventArgs` to +construct an notification matching the look and feel of the host app. The host +can also use such information to decide to show or not show a particular +notification. The host can `GetDeferral` or set the `Handled` property on the +`NotificationReceivedEventArgs` to handle the event at a later time or let +WebView2 know if the notification has been handled. By default, if the +`NotificationReceived` event is not handled by the host, the web notification +will be displayed using the default notification UI provided by WebView2 +Runtime. # Examples @@ -251,28 +261,16 @@ void SettingsComponent::ShowNotification( ```cpp /// Specifies the text direction of the notification. [v1_enum] -typedef enum COREWEBVIEW2_NOTIFICATION_DIRECTION_KINDS { +typedef enum COREWEBVIEW2_TEXT_DIRECTION_KINDS { /// Indicates that the notification text direction adopts the browser's language setting behavior. - COREWEBVIEW2_NOTIFICATION_DIRECTION_KINDS_AUTO, + COREWEBVIEW2_TEXT_DIRECTION_KINDS_DEFAULT, /// Indicates that the notification text is left-to-right. - COREWEBVIEW2_NOTIFICATION_DIRECTION_KINDS_LTR, + COREWEBVIEW2_TEXT_DIRECTION_KINDS_LEFT_TO_RIGHT, /// Indicates that the notification text is right-to-left. - COREWEBVIEW2_NOTIFICATION_DIRECTION_KINDS_RTL, -} COREWEBVIEW2_NOTIFICATION_DIRECTION_KINDS; - -/// This is the notification action for interacting with the notification. -typedef struct COREWEBVIEW2_NOTIFICATION_ACTION { - /// A string identifying a user action to be displayed on the notification. - LPWSTR Action; - - /// A string containing action text to be shown to the user. - LPWSTR Title; - - /// A string containing the URL of an icon to display with the action. - LPWSTR Icon; -} COREWEBVIEW2_NOTIFICATION_ACTION; + COREWEBVIEW2_TEXT_DIRECTION_KINDS_RIGHT_TO_LEFT, +} COREWEBVIEW2_TEXT_DIRECTION_KINDS; /// This is the ICoreWebView2Profile3 interface that manages WebView2 Web /// Notification functionality. @@ -334,7 +332,7 @@ interface ICoreWebView2NotificationReceivedEventArgs : IUnknown { /// The notification that was received. End developers can access the /// properties on the Notification object to show their own notification. - [propget] HRESULT Notification([out, retval] ICoreWebView2Notification** notification); + [propget] HRESULT Notification([out, retval] ICoreWebView2Notification** value); /// Sets whether the `NotificationReceived` event is handled by the host after /// the event handler completes or if there is a deferral then after the @@ -367,16 +365,29 @@ interface ICoreWebView2NotificationClosedEventHandler : IUnknown { [in] IUnknown* args); } +/// This is the notification for interacting with the notification. +[uuid(07DD3067-2B86-47F6-AB96-D74825C2DA41), object, pointer_default(unique)] +interface ICoreWebView2NotificationAction : IUnknown { + /// A string identifying a user action to be displayed on the notification. + [propget] HRESULT Action([out, retval] LPWSTR* value); + + /// A string containing action text to be shown to the user. + [propget] HRESULT Title([out, retval] LPWSTR* value); + + /// A string containing the URI of an icon to display with the action. + [propget] HRESULT IconUri([out, retval] LPWSTR* value); +} + /// A collection of notification actions. [uuid(89D8907E-18C7-458B-A970-F91F645E4C43), object, pointer_default(unique)] -interface ICoreWebView2NotificationActionCollection : IUnknown { +interface ICoreWebView2NotificationActionCollectionView : IUnknown { /// The number of notification actions contained in the - /// ICoreWebView2NotificationActionCollection. - [propget] HRESULT Count([out, retval] UINT* count); + /// ICoreWebView2NotificationActionCollectionView. + [propget] HRESULT Count([out, retval] UINT* value); /// Gets the notification action at the given index. HRESULT GetValueAtIndex([in] UINT index, - [out, retval] COREWEBVIEW2_NOTIFICATION_ACTION* value); + [out, retval] ICoreWebView2NotificationAction** value); } /// A collection of unsigned long integers. @@ -384,7 +395,7 @@ interface ICoreWebView2NotificationActionCollection : IUnknown { interface ICoreWebView2UnsignedLongCollection : IUnknown { /// The number of unsigned long integers contained in the /// ICoreWebView2UnsignedLongCollection. - [propget] HRESULT Count([out, retval] UINT* count); + [propget] HRESULT Count([out, retval] UINT* value); /// Gets the unsigned long integer at the given index. HRESULT GetValueAtIndex([in] UINT index, [out, retval] UINT64* value); @@ -418,7 +429,7 @@ interface ICoreWebView2Notification : IUnknown { /// The host may run this to report the persistent notification has been /// activated with a given action. The action index corresponds to the index - /// in NotificationActionCollection. This returns `E_INVALIDARG` if an invalid + /// in NotificationActionCollectionView. This returns `E_INVALIDARG` if an invalid /// action index is provided. Use `Click` to activate an non-persistent /// notification. This will no-op if `Show` is not run or `Close` has been /// run. @@ -434,17 +445,17 @@ interface ICoreWebView2Notification : IUnknown { /// /// The caller must free the returned string with `CoTaskMemFree`. See /// [API Conventions](/microsoft-edge/webview2/concepts/win32-api-conventions#strings). - [propget] HRESULT Body ([out, retval] LPWSTR* body); + [propget] HRESULT Body([out, retval] LPWSTR* value); /// Returns an IDataObject that represents a structured clone of the /// notification's data. /// Returns `null` if the optional Notification property does not exist. - [propget] HRESULT Data ([out, retval] IDataObject** data); + [propget] HRESULT Data([out, retval] IDataObject** value); /// The text direction of the notification as specified in the constructor's /// options parameter. - /// The default value is `COREWEBVIEW2_NOTIFICATION_DIRECTION_KINDS_AUTO`. - [propget] HRESULT Direction ([out, retval] COREWEBVIEW2_NOTIFICATION_DIRECTION_KINDS* value); + /// The default value is `COREWEBVIEW2_TEXT_DIRECTION_KINDS_DEFAULT`. + [propget] HRESULT Direction([out, retval] COREWEBVIEW2_TEXT_DIRECTION_KINDS* value); /// The language code of the notification as specified in the constructor's /// options parameter. It is in the format of @@ -455,7 +466,7 @@ interface ICoreWebView2Notification : IUnknown { /// /// The caller must free the returned string with `CoTaskMemFree`. See /// [API Conventions](/microsoft-edge/webview2/concepts/win32-api-conventions#strings). - [propget] HRESULT Language ([out, retval] LPWSTR* language); + [propget] HRESULT Language([out, retval] LPWSTR* value); /// The ID of the notification (if any) as specified in the constructor's /// options parameter. @@ -463,67 +474,67 @@ interface ICoreWebView2Notification : IUnknown { /// /// The caller must free the returned string with `CoTaskMemFree`. See /// [API Conventions](/microsoft-edge/webview2/concepts/win32-api-conventions#strings). - [propget] HRESULT Tag ([out, retval] LPWSTR* tag); + [propget] HRESULT Tag([out, retval] LPWSTR* value); - /// The URL of the image used as an icon of the notification as specified in + /// The URI of the image used as an icon of the notification as specified in /// the constructor's options parameter. /// Returns `null` if the optional Notification property does not exist. /// /// The caller must free the returned string with `CoTaskMemFree`. See /// [API Conventions](/microsoft-edge/webview2/concepts/win32-api-conventions#strings). - [propget] HRESULT Icon ([out, retval] LPWSTR* icon); + [propget] HRESULT IconUri([out, retval] LPWSTR* value); /// The title of the notification as specified in the first parameter of the /// constructor. /// /// The caller must free the returned string with `CoTaskMemFree`. See /// [API Conventions](/microsoft-edge/webview2/concepts/win32-api-conventions#strings). - [propget] HRESULT Title([out, retval] LPWSTR* title); + [propget] HRESULT Title([out, retval] LPWSTR* value); /// The actions available for users to choose from for interacting with the /// notification. Note that actions are only supported for persistent notifications. - /// Returns `null` if the optional Notification property does not exist. - [propget] HRESULT Actions([out, retval] ICoreWebView2NotificationActionCollection** actions); + /// An empty NotificationActionCollectionView is returned if no notification actions. + [propget] HRESULT Actions([out, retval] ICoreWebView2NotificationActionCollectionView** value); - /// The URL of the image used to represent the notification when there is not + /// The URI of the image used to represent the notification when there is not /// enough space to display the notification itself. /// Returns `null` if the optional Notification property does not exist. /// /// The caller must free the returned string with `CoTaskMemFree`. See /// [API Conventions](/microsoft-edge/webview2/concepts/win32-api-conventions#strings). - [propget] HRESULT Badge([out, retval] LPWSTR* badge); + [propget] HRESULT BadgeUri([out, retval] LPWSTR* value); - /// The URL of an image to be displayed as part of the notification. + /// The URI of an image to be displayed as part of the notification. /// Returns `null` if the optional Notification property does not exist. /// /// The caller must free the returned string with `CoTaskMemFree`. See /// [API Conventions](/microsoft-edge/webview2/concepts/win32-api-conventions#strings). - [propget] HRESULT Image([out, retval] LPWSTR* image); + [propget] HRESULT ImageUri([out, retval] LPWSTR* value); /// Specifies whether the user should be notified after a new notification /// replaces an old one. /// The default value is `FALSE`. - [propget] HRESULT Renotify([out, retval] BOOL* renotify); + [propget] HRESULT Renotify([out, retval] BOOL* value); /// A boolean value indicating that a notification should remain active until /// the user clicks or dismisses it, rather than closing automatically. /// The default value is `FALSE`. - [propget] HRESULT RequireInteraction([out, retval] BOOL* requireInteraction); + [propget] HRESULT RequireInteraction([out, retval] BOOL* value); /// Specifies whether the notification should be silent — i.e., no sounds or /// vibrations should be issued, regardless of the device settings. /// The default value is `FALSE`. - [propget] HRESULT Silent([out, retval] BOOL* silent); + [propget] HRESULT Silent([out, retval] BOOL* value); /// Specifies the time at which a notification is created or applicable (past, /// present, or future). /// Returns `null` if the optional Notification property does not exist. - [propget] HRESULT Timestamp([out, retval] double* timestamp); + [propget] HRESULT Timestamp([out, retval] double* value); /// Specifies a vibration pattern for devices with vibration hardware to emit. /// Returns `null` if the optional Notification property does not exist. - [propget] HRESULT Vibrate([out, retval] ICoreWebView2UnsignedLongCollection** vibrationPattern); + [propget] HRESULT Vibrate([out, retval] ICoreWebView2UnsignedLongCollection** value); } ``` @@ -532,20 +543,13 @@ namespace Microsoft.Web.WebView2.Core { runtimeclass CoreWebView2NotificationReceivedEventArgs; runtimeclass CoreWebView2Notification; - struct CoreWebView2NotificationAction + runtimeclass CoreWebView2NotificationAction { - String Action; - String title; - String Icon; - - }; - struct CoreWebView2NotificationAction - { - String Action; - String title; - String Icon; - - }; + // ICoreWebView2NotificationAction members + String Action { get; }; + String Title { get; }; + String IconUri { get; }; + } runtimeclass CoreWebView2Profile { ... @@ -563,9 +567,6 @@ namespace Microsoft.Web.WebView2.Core { // ICoreWebView2_17 members event Windows.Foundation.TypedEventHandler NotificationReceived; - - - } ... } @@ -585,11 +586,11 @@ namespace Microsoft.Web.WebView2.Core CoreWebView2NotificationDirectionKinds Direction { get; }; String Language { get; }; String Tag { get; }; - String Icon { get; }; + String IconUri { get; }; String Title { get; }; IVectorView Actions { get; }; - String Badge { get; }; - String Image { get; }; + String BadgeUri { get; }; + String ImageUri { get; }; Boolean Renotify { get; }; Boolean RequireInteraction { get; }; Boolean Silent { get; }; From cbd9d614b9340e2cd267e58cd01f4922823eb019 Mon Sep 17 00:00:00 2001 From: Jessica Chen Date: Mon, 12 Dec 2022 16:50:04 -0800 Subject: [PATCH 04/17] Add enum CoreWebView2TextDirectionKinds to c# API details --- specs/WebNotification.md | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/specs/WebNotification.md b/specs/WebNotification.md index 30f59e49a..2239c79bd 100644 --- a/specs/WebNotification.md +++ b/specs/WebNotification.md @@ -541,8 +541,12 @@ interface ICoreWebView2Notification : IUnknown { ```csharp namespace Microsoft.Web.WebView2.Core { - runtimeclass CoreWebView2NotificationReceivedEventArgs; - runtimeclass CoreWebView2Notification; + enum CoreWebView2TextDirectionKinds + { + Default = 0, + LeftToRight = 1, + RightToLeft = 2, + }; runtimeclass CoreWebView2NotificationAction { // ICoreWebView2NotificationAction members From 5e22e5f85dc748bc9f991e712c1beb57503c1299 Mon Sep 17 00:00:00 2001 From: Jessica Chen Date: Wed, 4 Jan 2023 13:34:35 -0800 Subject: [PATCH 05/17] Report* and CloseRequested --- specs/WebNotification.md | 59 ++++++++++++++++++++-------------------- 1 file changed, 30 insertions(+), 29 deletions(-) diff --git a/specs/WebNotification.md b/specs/WebNotification.md index 2239c79bd..a687b9151 100644 --- a/specs/WebNotification.md +++ b/specs/WebNotification.md @@ -180,8 +180,8 @@ void WebView_NotificationReceived(object sender, CoreWebView2NotificationReceive // https://learn.microsoft.com/windows/apps/design/shell/tiles-and-notifications/send-local-toast?tabs=uwp // for how to handle toast activation - notification.Show(); - notification.Close(); + notification.ReportShown(); + notification.ReportClosed(); args.Handled = true; } ``` @@ -246,9 +246,9 @@ void SettingsComponent::ShowNotification( L"The page at " + std::wstring(uri.get()) + L" sends you an notification:\n\n"; message += body.get(); - notification->Show(); + notification->ReportShown(); int response = MessageBox(nullptr, message.c_str(), title.get(), MB_OKCANCEL); - (response == IDOK) ? notification->Click() : notification->Close(); + (response == IDOK) ? notification->ReportClicked() : notification->ReportClosed(); deferral->Complete(); }); @@ -343,7 +343,7 @@ interface ICoreWebView2NotificationReceivedEventArgs : IUnknown { /// notification and for letting the web content know that the notification has been /// displayed, clicked, or closed. If after the event handler or deferral /// completes `Handled` is set to FALSE then WebView will display the default - /// notification UI. Note that if `Show` has been called on the `Notification` + /// notification UI. Note that if `ReportShown` has been called on the `Notification` /// object, WebView will not display the default notification regardless of /// the Handled property. The default value is FALSE. [propput] HRESULT Handled([in] BOOL value); @@ -356,9 +356,9 @@ interface ICoreWebView2NotificationReceivedEventArgs : IUnknown { HRESULT GetDeferral([out, retval] ICoreWebView2Deferral** deferral); } -/// An event handler for the `Closed` event. +/// An event handler for the `CloseRequested` event. [uuid(6A0B4DE9-8CBE-4211-BAEF-AC037B1A72DE), object, pointer_default(unique)] -interface ICoreWebView2NotificationClosedEventHandler : IUnknown { +interface ICoreWebView2NotificationCloseRequestedEventHandler : IUnknown { /// Provides the event args for the corresponding event. HRESULT Invoke( [in] ICoreWebView2Notification* sender, @@ -405,39 +405,40 @@ interface ICoreWebView2UnsignedLongCollection : IUnknown { /// object](https://developer.mozilla.org/docs/Web/API/Notification). [uuid(E3F43572-2930-42EB-BD90-CAC16DE6D942), object, pointer_default(unique)] interface ICoreWebView2Notification : IUnknown { - /// Add an event handler for the `Closed` event. - /// This event is raised when the notification is closed by the web code. - HRESULT add_Closed( - [in] ICoreWebView2NotificationClosedEventHandler* eventHandler, + /// Add an event handler for the `CloseRequested` event. + /// This event is raised when the notification is closed by the web code, such as + /// through `notification.close()`. + HRESULT add_CloseRequested( + [in] ICoreWebView2NotificationCloseRequestedEventHandler* eventHandler, [out] EventRegistrationToken* token); - /// Remove an event handler previously added with `add_Closed`. - HRESULT remove_Closed( + /// Remove an event handler previously added with `add_CloseRequested`. + HRESULT remove_CloseRequested( [in] EventRegistrationToken token); /// The host may run this to report the notification has been displayed. /// The NotificationReceived event is considered handled regardless of the - /// Handled property of the NotifificationReceivedEventArgs if the host has - /// run Show(). - HRESULT Show(); + /// Handled property of the NotificationReceivedEventArgs if the host has + /// run ReportShown(). + HRESULT ReportShown(); /// The host may run this to report the notification has been clicked. Use - /// `ClickWithAction` to specify an action to activate a persistent - /// notification. This will no-op if `Show` is not run or `Close` has been + /// `ReportClickedWithAction` to specify an action to activate a persistent + /// notification. This will no-op if `ReportShown` is not run or `ReportClosed` has been /// run. - HRESULT Click(); + HRESULT ReportClicked(); /// The host may run this to report the persistent notification has been /// activated with a given action. The action index corresponds to the index /// in NotificationActionCollectionView. This returns `E_INVALIDARG` if an invalid - /// action index is provided. Use `Click` to activate an non-persistent - /// notification. This will no-op if `Show` is not run or `Close` has been + /// action index is provided. Use `ReportClicked` to activate an non-persistent + /// notification. This will no-op if `ReportShown` is not run or `ReportClosed` has been /// run. - HRESULT ClickWithAction([in] UINT actionIndex); + HRESULT ReportClickedWithAction([in] UINT actionIndex); /// The host may run this to report the notification was dismissed. - /// This will no-op if `Show` is not run or `Click` has been run. - HRESULT Close(); + /// This will no-op if `ReportShown` is not run or `ReportClicked` has been run. + HRESULT ReportClosed(); /// The body string of the notification as specified in the constructor's /// options parameter. @@ -604,12 +605,12 @@ namespace Microsoft.Web.WebView2.Core // CoreWebView2Notification.Data? We use IDataObject for COM/C++. // Data { get; }; - event Windows.Foundation.TypedEventHandler Closed; + event Windows.Foundation.TypedEventHandler CloseRequested; - void Show(); - void Click(); - void Click(UInt32 actionIndex); - void Close(); + void ReportShown(); + void ReportClicked(); + void ReportClicked(UInt32 actionIndex); + void ReportClosed(); } } ``` From 0b9a618c5340014f3849b4e7d2701ed3e6bef749 Mon Sep 17 00:00:00 2001 From: Jessica Chen Date: Fri, 6 Jan 2023 10:27:43 -0800 Subject: [PATCH 06/17] Persistent -> ProfileNotificationReceivedEventHandler --- specs/WebNotification.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/specs/WebNotification.md b/specs/WebNotification.md index a687b9151..e0b437ead 100644 --- a/specs/WebNotification.md +++ b/specs/WebNotification.md @@ -279,7 +279,7 @@ interface ICoreWebView2Profile3 : IUnknown { /// Add an event handler for the `NotificationReceived` event for persistent /// notifications. HRESULT add_NotificationReceived( - [in] ICoreWebView2PersistentNotificationReceivedEventHandler* eventHandler, + [in] ICoreWebView2ProfileNotificationReceivedEventHandler* eventHandler, [out] EventRegistrationToken* token); /// Remove an event handler previously added with `add_NotificationReceived`. @@ -312,7 +312,7 @@ interface ICoreWebView2NotificationReceivedEventHandler : IUnknown { /// An event handler for the `NotificationReceived` event for persistent notifications. [uuid(4843DC48-C1EF-4B0F-872E-F7AA6BC80175), object, pointer_default(unique)] -interface ICoreWebView2PersistentNotificationReceivedEventHandler : IUnknown { +interface ICoreWebView2ProfileNotificationReceivedEventHandler : IUnknown { /// Provides the event args for the corresponding event. HRESULT Invoke( [in] ICoreWebView2Profile* sender, From 61d7ae7c65c3d8ef4e3ee84a54e2343d4ea9c9bf Mon Sep 17 00:00:00 2001 From: Jessica Chen Date: Fri, 6 Jan 2023 10:28:27 -0800 Subject: [PATCH 07/17] Address initial feedback part 2 --- specs/WebNotification.md | 33 +++++++++++++++++++++++---------- 1 file changed, 23 insertions(+), 10 deletions(-) diff --git a/specs/WebNotification.md b/specs/WebNotification.md index e0b437ead..295a453cd 100644 --- a/specs/WebNotification.md +++ b/specs/WebNotification.md @@ -3,12 +3,17 @@ Web Notification APIs # Background -The WebView2 team is adding support for Web notifications. End developers should -be able to handle notification permission requests, and further listen to -`NotificationReceived` events to optionally handle the notifications themselves. -The `NotificationReceived` events are raised on `CorebWebView2` and -`CoreWebView2Profile` object respectively for non-persistent and persistent -notifications respectively. +The WebView2 team is adding support for web notifications including +non-persistent notifications and persistent notifications. A non-persistent +notification is a notification without an associated service worker +registration. A persistent notification is a notification with an associated +service worker registration. + +You should be able to handle notification permission requests, and +further listen to `NotificationReceived` events to optionally handle the +notifications themselves. The `NotificationReceived` events are raised on +`CorebWebView2` and `CoreWebView2Profile` object respectively for non-persistent +and persistent notifications respectively. The `NotificationReceived` event on `CoreWebView2` and `CoreWebView2Profile` let you intercept the web non-persistent and persistent notifications. The host can @@ -330,7 +335,7 @@ interface ICoreWebView2NotificationReceivedEventArgs : IUnknown { /// [API Conventions](/microsoft-edge/webview2/concepts/win32-api-conventions#strings). [propget] HRESULT Uri([out, retval] LPWSTR* value); - /// The notification that was received. End developers can access the + /// The notification that was received. You can access the /// properties on the Notification object to show their own notification. [propget] HRESULT Notification([out, retval] ICoreWebView2Notification** value); @@ -494,8 +499,11 @@ interface ICoreWebView2Notification : IUnknown { /// The actions available for users to choose from for interacting with the - /// notification. Note that actions are only supported for persistent notifications. - /// An empty NotificationActionCollectionView is returned if no notification actions. + /// notification. An empty NotificationActionCollectionView is returned if no + /// notification actions are specified. Note that actions are only applicable + /// for persistent notifications according to the web standard, and an empty + /// NotificationActionCollectionView will always be returned for + /// non-persistent notifications. [propget] HRESULT Actions([out, retval] ICoreWebView2NotificationActionCollectionView** value); /// The URI of the image used to represent the notification when there is not @@ -534,7 +542,12 @@ interface ICoreWebView2Notification : IUnknown { [propget] HRESULT Timestamp([out, retval] double* value); /// Specifies a vibration pattern for devices with vibration hardware to emit. - /// Returns `null` if the optional Notification property does not exist. + /// The vibration pattern can be represented by an array of integers + /// describing a pattern of vibrations and pauses. See [Vibation + /// API](https://developer.mozilla.org/docs/Web/API/Vibration_API) for more + /// information. + /// An empty UnsignedLongCollection is returned if no vibration patterns are + /// specified. [propget] HRESULT Vibrate([out, retval] ICoreWebView2UnsignedLongCollection** value); } ``` From c61f653495f2433543035e717fe7dfbf0ee5ed32 Mon Sep 17 00:00:00 2001 From: Jessica Chen Date: Fri, 6 Jan 2023 15:30:28 -0800 Subject: [PATCH 08/17] Update ref doc --- specs/WebNotification.md | 66 +++++++++++++++++++++++++--------------- 1 file changed, 42 insertions(+), 24 deletions(-) diff --git a/specs/WebNotification.md b/specs/WebNotification.md index 295a453cd..16c8ecdac 100644 --- a/specs/WebNotification.md +++ b/specs/WebNotification.md @@ -445,8 +445,7 @@ interface ICoreWebView2Notification : IUnknown { /// This will no-op if `ReportShown` is not run or `ReportClicked` has been run. HRESULT ReportClosed(); - /// The body string of the notification as specified in the constructor's - /// options parameter. + /// A string representing the body text of the notification. /// The default value is an empty string. /// /// The caller must free the returned string with `CoTaskMemFree`. See @@ -458,40 +457,46 @@ interface ICoreWebView2Notification : IUnknown { /// Returns `null` if the optional Notification property does not exist. [propget] HRESULT Data([out, retval] IDataObject** value); - /// The text direction of the notification as specified in the constructor's - /// options parameter. + /// The text direction in which to display the notification. + /// This corresponds to + /// [Notification.dir](https://developer.mozilla.org/docs/Web/API/Notification/dir) + /// DOM API. /// The default value is `COREWEBVIEW2_TEXT_DIRECTION_KINDS_DEFAULT`. [propget] HRESULT Direction([out, retval] COREWEBVIEW2_TEXT_DIRECTION_KINDS* value); - /// The language code of the notification as specified in the constructor's - /// options parameter. It is in the format of - /// `language-country` where `language` is the 2-letter code from [ISO - /// 639](https://www.iso.org/iso-639-language-codes.html) and `country` is the - /// 2-letter code from [ISO 3166](https://www.iso.org/standard/72482.html). + /// The notification's language, as intended to be specified using a string + /// representing a language tag according to + /// [BCP47](https://datatracker.ietf.org/doc/html/rfc5646). Note that no + /// validation is performed on this property and it can be any string the + /// notification sender specifies. + /// This corresponds to + /// [Notification.lang](https://developer.mozilla.org/docs/Web/API/Notification/lang) + /// DOM API. /// The default value is an empty string. /// /// The caller must free the returned string with `CoTaskMemFree`. See /// [API Conventions](/microsoft-edge/webview2/concepts/win32-api-conventions#strings). [propget] HRESULT Language([out, retval] LPWSTR* value); - /// The ID of the notification (if any) as specified in the constructor's - /// options parameter. + /// A string representing an identifying tag for the notification. + /// This corresponds to + /// [Notification.tag](https://developer.mozilla.org/docs/Web/API/Notification/tag) + /// DOM API. /// The default value is an empty string. /// /// The caller must free the returned string with `CoTaskMemFree`. See /// [API Conventions](/microsoft-edge/webview2/concepts/win32-api-conventions#strings). [propget] HRESULT Tag([out, retval] LPWSTR* value); - /// The URI of the image used as an icon of the notification as specified in - /// the constructor's options parameter. - /// Returns `null` if the optional Notification property does not exist. + /// A string containing the URI of an icon to be displayed in the + /// notification. + /// The default value is an empty string. /// /// The caller must free the returned string with `CoTaskMemFree`. See /// [API Conventions](/microsoft-edge/webview2/concepts/win32-api-conventions#strings). [propget] HRESULT IconUri([out, retval] LPWSTR* value); - /// The title of the notification as specified in the first parameter of the - /// constructor. + /// The title of the notification. /// /// The caller must free the returned string with `CoTaskMemFree`. See /// [API Conventions](/microsoft-edge/webview2/concepts/win32-api-conventions#strings). @@ -506,16 +511,18 @@ interface ICoreWebView2Notification : IUnknown { /// non-persistent notifications. [propget] HRESULT Actions([out, retval] ICoreWebView2NotificationActionCollectionView** value); - /// The URI of the image used to represent the notification when there is not - /// enough space to display the notification itself. - /// Returns `null` if the optional Notification property does not exist. + /// A string containing the URI of the image used to represent the + /// notification when there isn't enough space to display the notification + /// itself. + /// The default value is an empty string. /// /// The caller must free the returned string with `CoTaskMemFree`. See /// [API Conventions](/microsoft-edge/webview2/concepts/win32-api-conventions#strings). [propget] HRESULT BadgeUri([out, retval] LPWSTR* value); - /// The URI of an image to be displayed as part of the notification. - /// Returns `null` if the optional Notification property does not exist. + /// A string containing the URI of an image to be displayed in the + /// notification. + /// The default value is an empty string. /// /// The caller must free the returned string with `CoTaskMemFree`. See /// [API Conventions](/microsoft-edge/webview2/concepts/win32-api-conventions#strings). @@ -523,29 +530,40 @@ interface ICoreWebView2Notification : IUnknown { /// Specifies whether the user should be notified after a new notification /// replaces an old one. + /// This corresponds to + /// [Notification.renotify](https://developer.mozilla.org/docs/Web/API/Notification/renotify) + /// DOM API. /// The default value is `FALSE`. [propget] HRESULT Renotify([out, retval] BOOL* value); /// A boolean value indicating that a notification should remain active until /// the user clicks or dismisses it, rather than closing automatically. + /// This corresponds to + /// [Notification.requireInteraction](https://developer.mozilla.org/docs/Web/API/Notification/requireInteraction) + /// DOM API. /// The default value is `FALSE`. [propget] HRESULT RequireInteraction([out, retval] BOOL* value); /// Specifies whether the notification should be silent — i.e., no sounds or /// vibrations should be issued, regardless of the device settings. + /// This corresponds to + /// [Notification.silent](https://developer.mozilla.org/docs/Web/API/Notification/silent) + /// DOM API. /// The default value is `FALSE`. [propget] HRESULT Silent([out, retval] BOOL* value); /// Specifies the time at which a notification is created or applicable (past, - /// present, or future). - /// Returns `null` if the optional Notification property does not exist. + /// present, or future) as the number of milliseconds since the UNIX epoch. [propget] HRESULT Timestamp([out, retval] double* value); /// Specifies a vibration pattern for devices with vibration hardware to emit. /// The vibration pattern can be represented by an array of integers - /// describing a pattern of vibrations and pauses. See [Vibation + /// describing a pattern of vibrations and pauses. See [Vibration /// API](https://developer.mozilla.org/docs/Web/API/Vibration_API) for more /// information. + /// This corresponds to + /// [Notification.vibrate](https://developer.mozilla.org/docs/Web/API/Notification/vibrate) + /// DOM API. /// An empty UnsignedLongCollection is returned if no vibration patterns are /// specified. [propget] HRESULT Vibrate([out, retval] ICoreWebView2UnsignedLongCollection** value); From b8395cb0badd44722ef1554d1f308565dea10931 Mon Sep 17 00:00:00 2001 From: Jessica Chen Date: Mon, 9 Jan 2023 10:48:18 -0800 Subject: [PATCH 09/17] Add DataAsJson and TryGetDataAsString --- specs/WebNotification.md | 30 +++++++++++++++++++++++------- 1 file changed, 23 insertions(+), 7 deletions(-) diff --git a/specs/WebNotification.md b/specs/WebNotification.md index 16c8ecdac..ccc695813 100644 --- a/specs/WebNotification.md +++ b/specs/WebNotification.md @@ -452,10 +452,27 @@ interface ICoreWebView2Notification : IUnknown { /// [API Conventions](/microsoft-edge/webview2/concepts/win32-api-conventions#strings). [propget] HRESULT Body([out, retval] LPWSTR* value); - /// Returns an IDataObject that represents a structured clone of the - /// notification's data. - /// Returns `null` if the optional Notification property does not exist. - [propget] HRESULT Data([out, retval] IDataObject** value); + /// Returns a JSON string representing the notification data. + /// [Notification.data](https://developer.mozilla.org/docs/Web/API/Notification/data) + /// DOM API is arbitrary data the notification sender wants associated with + /// the notification and can be of any data type. + /// The default value is an empty string. + /// + /// The caller must free the returned string with `CoTaskMemFree`. See + /// [API Conventions](/microsoft-edge/webview2/concepts/win32-api-conventions#strings). + [propget] HRESULT DataAsJson([out, retval] LPWSTR* value); + + /// If the notification data is a string + /// type, this method returns the value of that string. If the notification data + /// is some other kind of JavaScript type this method fails with `E_INVALIDARG`. + /// [Notification.data](https://developer.mozilla.org/docs/Web/API/Notification/data) + /// DOM API is arbitrary data the notification sender wants associated with + /// the notification and can be of any data type. + /// The default value is an empty string. + /// + /// The caller must free the returned string with `CoTaskMemFree`. See + /// [API Conventions](/microsoft-edge/webview2/concepts/win32-api-conventions#strings). + HRESULT TryGetDataAsString([out, retval] LPWSTR* value); /// The text direction in which to display the notification. /// This corresponds to @@ -632,9 +649,8 @@ namespace Microsoft.Web.WebView2.Core Boolean Silent { get; }; Double Timestamp { get; }; IVectorView Vibrate { get; }; - // TODO: What should the proper data type be for - // CoreWebView2Notification.Data? We use IDataObject for COM/C++. - // Data { get; }; + String DataAsJson { get; }; + String TryGetDataAsString(); event Windows.Foundation.TypedEventHandler CloseRequested; From 88418d00d46557c91531722e73eefc68c7a96fa9 Mon Sep 17 00:00:00 2001 From: Jessica Chen Date: Mon, 9 Jan 2023 15:54:25 -0800 Subject: [PATCH 10/17] Add links to Notification.actions --- specs/WebNotification.md | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/specs/WebNotification.md b/specs/WebNotification.md index ccc695813..a9949a692 100644 --- a/specs/WebNotification.md +++ b/specs/WebNotification.md @@ -374,12 +374,21 @@ interface ICoreWebView2NotificationCloseRequestedEventHandler : IUnknown { [uuid(07DD3067-2B86-47F6-AB96-D74825C2DA41), object, pointer_default(unique)] interface ICoreWebView2NotificationAction : IUnknown { /// A string identifying a user action to be displayed on the notification. + /// This corresponds to the + /// [action](https://developer.mozilla.org/docs/Web/API/Notification/actions) + /// member of a notification action object. [propget] HRESULT Action([out, retval] LPWSTR* value); /// A string containing action text to be shown to the user. + /// This corresponds to the + /// [title](https://developer.mozilla.org/docs/Web/API/Notification/actions) + /// member of a notification action object. [propget] HRESULT Title([out, retval] LPWSTR* value); /// A string containing the URI of an icon to display with the action. + /// This corresponds to the + /// [icon](https://developer.mozilla.org/docs/Web/API/Notification/actions) + /// member of a notification action object. [propget] HRESULT IconUri([out, retval] LPWSTR* value); } From 0b6a3aa0dbf987173a06fc7dc950bd25f2e79a1e Mon Sep 17 00:00:00 2001 From: Jessica Chen Date: Mon, 9 Jan 2023 23:31:21 -0800 Subject: [PATCH 11/17] Apply API name suggestion --- specs/WebNotification.md | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/specs/WebNotification.md b/specs/WebNotification.md index a9949a692..4f1e0411c 100644 --- a/specs/WebNotification.md +++ b/specs/WebNotification.md @@ -329,11 +329,12 @@ interface ICoreWebView2ProfileNotificationReceivedEventHandler : IUnknown { /// \snippet SettingsComponent.cpp OnNotificationReceived [uuid(5E8983CA-19A0-4140-BF2E-D7B9B707DDCC), object, pointer_default(unique)] interface ICoreWebView2NotificationReceivedEventArgs : IUnknown { - /// The origin of the web content that sends the notification. + /// The origin of the web content that sends the notification, such as + /// `https://example.com/` or `https://www.example.com/`. /// /// The caller must free the returned string with `CoTaskMemFree`. See /// [API Conventions](/microsoft-edge/webview2/concepts/win32-api-conventions#strings). - [propget] HRESULT Uri([out, retval] LPWSTR* value); + [propget] HRESULT SenderOrigin([out, retval] LPWSTR* value); /// The notification that was received. You can access the /// properties on the Notification object to show their own notification. @@ -552,7 +553,7 @@ interface ICoreWebView2Notification : IUnknown { /// /// The caller must free the returned string with `CoTaskMemFree`. See /// [API Conventions](/microsoft-edge/webview2/concepts/win32-api-conventions#strings). - [propget] HRESULT ImageUri([out, retval] LPWSTR* value); + [propget] HRESULT BodyImageUri([out, retval] LPWSTR* value); /// Specifies whether the user should be notified after a new notification /// replaces an old one. @@ -560,7 +561,7 @@ interface ICoreWebView2Notification : IUnknown { /// [Notification.renotify](https://developer.mozilla.org/docs/Web/API/Notification/renotify) /// DOM API. /// The default value is `FALSE`. - [propget] HRESULT Renotify([out, retval] BOOL* value); + [propget] HRESULT ShouldRenotify([out, retval] BOOL* value); /// A boolean value indicating that a notification should remain active until /// the user clicks or dismisses it, rather than closing automatically. @@ -568,7 +569,7 @@ interface ICoreWebView2Notification : IUnknown { /// [Notification.requireInteraction](https://developer.mozilla.org/docs/Web/API/Notification/requireInteraction) /// DOM API. /// The default value is `FALSE`. - [propget] HRESULT RequireInteraction([out, retval] BOOL* value); + [propget] HRESULT RequiresInteraction([out, retval] BOOL* value); /// Specifies whether the notification should be silent — i.e., no sounds or /// vibrations should be issued, regardless of the device settings. @@ -576,7 +577,7 @@ interface ICoreWebView2Notification : IUnknown { /// [Notification.silent](https://developer.mozilla.org/docs/Web/API/Notification/silent) /// DOM API. /// The default value is `FALSE`. - [propget] HRESULT Silent([out, retval] BOOL* value); + [propget] HRESULT IsSilent([out, retval] BOOL* value); /// Specifies the time at which a notification is created or applicable (past, /// present, or future) as the number of milliseconds since the UNIX epoch. @@ -592,7 +593,7 @@ interface ICoreWebView2Notification : IUnknown { /// DOM API. /// An empty UnsignedLongCollection is returned if no vibration patterns are /// specified. - [propget] HRESULT Vibrate([out, retval] ICoreWebView2UnsignedLongCollection** value); + [propget] HRESULT VibrationPattern([out, retval] ICoreWebView2UnsignedLongCollection** value); } ``` From 25207b430a22003d6693cde22afea29e2156cc49 Mon Sep 17 00:00:00 2001 From: Jessica Chen Date: Tue, 10 Jan 2023 00:04:14 -0800 Subject: [PATCH 12/17] Clarify Handled and ReportXXX methods --- specs/WebNotification.md | 60 ++++++++++++++++++++++++++-------------- 1 file changed, 39 insertions(+), 21 deletions(-) diff --git a/specs/WebNotification.md b/specs/WebNotification.md index 4f1e0411c..7aa7937e0 100644 --- a/specs/WebNotification.md +++ b/specs/WebNotification.md @@ -346,12 +346,14 @@ interface ICoreWebView2NotificationReceivedEventArgs : IUnknown { /// /// If `Handled` is set to TRUE then WebView will not display the notification /// with the default UI, and the host will be responsible for handling the - /// notification and for letting the web content know that the notification has been - /// displayed, clicked, or closed. If after the event handler or deferral - /// completes `Handled` is set to FALSE then WebView will display the default - /// notification UI. Note that if `ReportShown` has been called on the `Notification` - /// object, WebView will not display the default notification regardless of - /// the Handled property. The default value is FALSE. + /// notification and for letting the web content know that the notification + /// has been displayed, clicked, or closed. You should set `Handled` to `TRUE` + /// before you call `ReportShown`, `ReportClicked`, `ReportClickedWithAction` + /// and `ReportClosed`, otherwise they will fail with `E_ABORT`. If after the + /// event handler or deferral completes `Handled` is set to FALSE then WebView + /// will display the default notification UI. Note that you cannot un-handle + /// this event once you have set `Handled` to be `TRUE`. The default value is + /// FALSE. [propput] HRESULT Handled([in] BOOL value); /// Gets whether the `NotificationReceived` event is handled by host. @@ -431,28 +433,44 @@ interface ICoreWebView2Notification : IUnknown { HRESULT remove_CloseRequested( [in] EventRegistrationToken token); - /// The host may run this to report the notification has been displayed. - /// The NotificationReceived event is considered handled regardless of the - /// Handled property of the NotificationReceivedEventArgs if the host has - /// run ReportShown(). + /// The host may run this to report the notification has been displayed and it + /// will cause the [show](https://developer.mozilla.org/docs/Web/API/Notification/show_event) + /// event to be raised for non-persistent notifications. + /// You should only run this if you are handling the `NotificationReceived` + /// event. Returns `E_ABORT` if `Handled` is `FALSE` when this is called. HRESULT ReportShown(); - /// The host may run this to report the notification has been clicked. Use - /// `ReportClickedWithAction` to specify an action to activate a persistent - /// notification. This will no-op if `ReportShown` is not run or `ReportClosed` has been - /// run. + /// The host may run this to report the notification has been clicked, and it + /// will cause the + /// [click](https://developer.mozilla.org/docs/Web/API/Notification/click_event) + /// event to be raised for non-persistent notifications and the + /// [notificationclick](https://developer.mozilla.org/docs/Web/API/ServiceWorkerGlobalScope/notificationclick_event) + /// event for persistent notifications. Use `ReportClickedWithAction` to specify an + /// action to activate a persistent notification. + /// You should only run this if you are handling the `NotificationReceived` + /// event. Returns `E_ABORT` if `Handled` is `FALSE` or `ReportShown` has not + /// been run when this is called. HRESULT ReportClicked(); /// The host may run this to report the persistent notification has been - /// activated with a given action. The action index corresponds to the index - /// in NotificationActionCollectionView. This returns `E_INVALIDARG` if an invalid - /// action index is provided. Use `ReportClicked` to activate an non-persistent - /// notification. This will no-op if `ReportShown` is not run or `ReportClosed` has been - /// run. + /// activated with a given action, and it will cause the + /// [notificationclick](https://developer.mozilla.org/docs/Web/API/ServiceWorkerGlobalScope/notificationclick_event) + /// event to be raised. The action index corresponds to the index in + /// NotificationActionCollectionView. You should only run this if you are + /// handling the `NotificationReceived` event. Returns `E_ABORT` if `Handled` + /// is `FALSE` or `ReportShown` has not been run when this is called. Returns + /// `E_INVALIDARG` if an invalid action index is provided. Use `ReportClicked` + /// to activate an non-persistent notification. HRESULT ReportClickedWithAction([in] UINT actionIndex); - /// The host may run this to report the notification was dismissed. - /// This will no-op if `ReportShown` is not run or `ReportClicked` has been run. + /// The host may run this to report the notification was dismissed, and it + /// will cause the + /// [close](https://developer.mozilla.org/docs/Web/API/Notification/close_event) + /// event to be raised for non-persistent notifications and the + /// [notificationclose](https://developer.mozilla.org/docs/Web/API/ServiceWorkerGlobalScope/notificationclose_event) + /// event for persistent notifications. You should only run this if you are + /// handling the `NotificationReceived` event. Returns `E_ABORT` if `Handled` + /// is `FALSE` or `ReportShown` has not been run when this is called. HRESULT ReportClosed(); /// A string representing the body text of the notification. From 8ef88d31c5b92e7b787c6f0dd7250ded2d25b4ef Mon Sep 17 00:00:00 2001 From: Jessica Chen Date: Tue, 10 Jan 2023 00:32:47 -0800 Subject: [PATCH 13/17] Clarify deferral info --- specs/WebNotification.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/specs/WebNotification.md b/specs/WebNotification.md index 7aa7937e0..2115141d7 100644 --- a/specs/WebNotification.md +++ b/specs/WebNotification.md @@ -283,6 +283,10 @@ typedef enum COREWEBVIEW2_TEXT_DIRECTION_KINDS { interface ICoreWebView2Profile3 : IUnknown { /// Add an event handler for the `NotificationReceived` event for persistent /// notifications. + /// + /// If a deferral is not taken on the event args, the subsequent scripts are + /// blocked until the event handler returns. If a deferral is taken, the + /// scripts are blocked until the deferral is completed. HRESULT add_NotificationReceived( [in] ICoreWebView2ProfileNotificationReceivedEventHandler* eventHandler, [out] EventRegistrationToken* token); @@ -297,6 +301,10 @@ interface ICoreWebView2Profile3 : IUnknown { interface ICoreWebView2_17 : ICoreWebView2_16 { /// Add an event handler for the `NotificationReceived` event for /// non-persistent notifications. + /// + /// If a deferral is not taken on the event args, the subsequent scripts are + /// blocked until the event handler returns. If a deferral is taken, the + /// scripts are blocked until the deferral is completed. HRESULT add_NotificationReceived( [in] ICoreWebView2NotificationReceivedEventHandler* eventHandler, [out] EventRegistrationToken* token); From 4908aaf631af2392e48f6fe59ad7e18a8c851422 Mon Sep 17 00:00:00 2001 From: Jessica Chen Date: Tue, 10 Jan 2023 00:54:30 -0800 Subject: [PATCH 14/17] Fix sample code --- specs/WebNotification.md | 74 +++++++++++++++++++++++----------------- 1 file changed, 43 insertions(+), 31 deletions(-) diff --git a/specs/WebNotification.md b/specs/WebNotification.md index 2115141d7..09637f32f 100644 --- a/specs/WebNotification.md +++ b/specs/WebNotification.md @@ -32,8 +32,11 @@ Runtime. ## Handle PermissionRequested event This can be achieved with the existing `PermissionRequested` events. -`PermissionRequested` event used to not be raised for `PermissionKind.Notification`. -Now, `PermissionRequested` events are raised for `PermissionKind.Notification`, and `PermissionState` needs to be set `Allow` explicitly to allow such permission requests. `PermissionState.Default` for `PermissionKind.Notification` is considered denied. +`PermissionRequested` event used to not be raised for +`PermissionKind.Notification`. Now, `PermissionRequested` events are raised for +`PermissionKind.Notification`, and `PermissionState` needs to be set `Allow` +explicitly to allow such permission requests. `PermissionState.Default` for +`PermissionKind.Notification` is considered denied. ### C# Sample ```csharp @@ -66,9 +69,11 @@ void WebView_PermissionRequested(object sender, CoreWebView2PermissionRequestedE { case MessageBoxResult.Yes: args.State = CoreWebView2PermissionState.Allow; + _cachedPermissions[cachedKey] = true; break; case MessageBoxResult.No: args.State = CoreWebView2PermissionState.Deny; + _cachedPermissions[cachedKey] = false; break; case MessageBoxResult.Cancel: args.State = CoreWebView2PermissionState.Default; @@ -169,11 +174,15 @@ using Microsoft.Toolkit.Uwp.Notifications; void WebView_NotificationReceived(object sender, CoreWebView2NotificationReceivedEventArgs args) { CoreWebView2Deferral deferral = args.GetDeferral(); + args.Handled = true; var notification = args.Notification; // Show notification with local MessageBox - string message = "Received notification from " + args.Uri + " with body " + notification.Body; - MessageBox.Show(message); + System.Threading.SynchronizationContext.Current.Post((_) => + { + string message = "Received notification from " + args.Uri + " with body " + notification.Body; + MessageBox.Show(message); + } // Requires Microsoft.Toolkit.Uwp.Notifications NuGet package version 7.0 or greater var toastContent = new ToastContentBuilder() @@ -183,15 +192,25 @@ void WebView_NotificationReceived(object sender, CoreWebView2NotificationReceive toastContent.Show(); // See // https://learn.microsoft.com/windows/apps/design/shell/tiles-and-notifications/send-local-toast?tabs=uwp - // for how to handle toast activation + // for how to handle toast activation. + // Call ReportShown after showing the toast notification to raise + // the DOM notification.show event. + // args.Handled has been set to true before we call Report... methods. notification.ReportShown(); + + // During the toast notification activation handling, we may call + // ReportClicked and/or ReportClose accordingly. notification.ReportClosed(); - args.Handled = true; } ``` ### C++ Sample + +`Microsoft.Toolkit.Uwp.Notifications ` package does not work with non-UWP Win32 +app. Hence in the sample code we use native MessageBox and handle notifications +accordingly. + ```cpp ... { @@ -203,16 +222,21 @@ void WebView_NotificationReceived(object sender, CoreWebView2NotificationReceive ICoreWebView2* sender, ICoreWebView2NotificationReceivedEventArgs* args) -> HRESULT { - // Block notifications from specific URIs and set Handled to - // false so the the default notification UI will not be - // shown by WebView2 either. - wil::unique_cotaskmem_string uri; - CHECK_FAILURE(args->get_Uri(&uri); - if (ShouldBlockUri(uri.get())) + // Setting Handled to TRUE so the the default notification UI will not be + // shown by WebView2 as we are handling the notification ourselves. + // Block notifications from specific origins and return directly without showing notifications. + CHECK_FAILURE(args->put_Handled(TRUE)); + wil::unique_cotaskmem_string origin; + CHECK_FAILURE(args->get_SenderOrigin(&origin)); + if (ShouldBlockOrigin(origin.get())) { - CHECK_FAILURE(args->put_Handled(FALSE)); + return S_OK; } - ShowNotification(args); + + wil::com_ptr notification; + CHECK_FAILURE(args->get_Notification(¬ification)); + + ShowNotification(notification); return S_OK; }) .Get(), @@ -222,40 +246,28 @@ void WebView_NotificationReceived(object sender, CoreWebView2NotificationReceive ... //! [OnNotificationReceived] void SettingsComponent::ShowNotification( - ICoreWebView2NotificationReceivedEventArgs* args) + ICoreWebView2Notification* notification) { AppWindow* appWindow = m_appWindow; - // Obtain a deferral for the event so that the CoreWebView2 - // does not examine the properties we set on the event args and - // after we call the Complete method asynchronously later. - wil::com_ptr deferral; - CHECK_FAILURE(args->GetDeferral(&deferral)); - - wil::com_ptr eventArgs = args; - appWindow->RunAsync( - [this, eventArgs, deferral] + [this, notification] { - wil::com_ptr notification; - CHECK_FAILURE(eventArgs->get_Notification(¬ification)); - wil::unique_cotaskmem_string uri; - CHECK_FAILURE(eventArgs->get_Uri(&uri)); + wil::unique_cotaskmem_string origin; + CHECK_FAILURE(eventArgs->get_SenderOrigin(&origin)); wil::unique_cotaskmem_string title; CHECK_FAILURE(notification->get_Title(&title)); wil::unique_cotaskmem_string body; CHECK_FAILURE(notification->get_Body(&body)); std::wstring message = - L"The page at " + std::wstring(uri.get()) + L" sends you an + L"The page from " + std::wstring(origin.get()) + L" sends you an notification:\n\n"; message += body.get(); notification->ReportShown(); int response = MessageBox(nullptr, message.c_str(), title.get(), MB_OKCANCEL); (response == IDOK) ? notification->ReportClicked() : notification->ReportClosed(); - - deferral->Complete(); }); } //! [OnNotificationReceived] From 993fe9f4284011646c0db20f48f74b80ced1942b Mon Sep 17 00:00:00 2001 From: Jessica Chen Date: Tue, 10 Jan 2023 01:23:33 -0800 Subject: [PATCH 15/17] Make ReportClicked and ReportClosed async --- specs/WebNotification.md | 24 ++++++++++++++++++------ 1 file changed, 18 insertions(+), 6 deletions(-) diff --git a/specs/WebNotification.md b/specs/WebNotification.md index 09637f32f..d8e002af5 100644 --- a/specs/WebNotification.md +++ b/specs/WebNotification.md @@ -438,6 +438,18 @@ interface ICoreWebView2UnsignedLongCollection : IUnknown { HRESULT GetValueAtIndex([in] UINT index, [out, retval] UINT64* value); } +/// The caller implements this interface to receive the result of reporting a notification clicked. +[uuid(1013C0D5-5F0C-4BF8-BA52-9D76AFA20E83), object, pointer_default(unique)] +interface ICoreWebView2NotificationReportClickedCompletedHandler : IUnknown { + HRESULT Invoke([in] HRESULT errorCode); +} + +/// The caller implements this interface to receive the result of reporting a notification closed. +[uuid(D8B63F74-1D78-4B4C-ACA9-7CB63FDFD74C), object, pointer_default(unique)] +interface ICoreWebView2NotificationReportClosedCompletedHandler : IUnknown { + HRESULT Invoke([in] HRESULT errorCode); +} + /// This is the ICoreWebView2Notification that represents a [HTML Notification /// object](https://developer.mozilla.org/docs/Web/API/Notification). [uuid(E3F43572-2930-42EB-BD90-CAC16DE6D942), object, pointer_default(unique)] @@ -470,7 +482,7 @@ interface ICoreWebView2Notification : IUnknown { /// You should only run this if you are handling the `NotificationReceived` /// event. Returns `E_ABORT` if `Handled` is `FALSE` or `ReportShown` has not /// been run when this is called. - HRESULT ReportClicked(); + HRESULT ReportClicked([in] ICoreWebView2NotificationReportClickedCompletedHandler* handler); /// The host may run this to report the persistent notification has been /// activated with a given action, and it will cause the @@ -481,7 +493,7 @@ interface ICoreWebView2Notification : IUnknown { /// is `FALSE` or `ReportShown` has not been run when this is called. Returns /// `E_INVALIDARG` if an invalid action index is provided. Use `ReportClicked` /// to activate an non-persistent notification. - HRESULT ReportClickedWithAction([in] UINT actionIndex); + HRESULT ReportClickedWithAction([in] UINT actionIndex, [in] ICoreWebView2NotificationReportClickedCompletedHandler* handler); /// The host may run this to report the notification was dismissed, and it /// will cause the @@ -491,7 +503,7 @@ interface ICoreWebView2Notification : IUnknown { /// event for persistent notifications. You should only run this if you are /// handling the `NotificationReceived` event. Returns `E_ABORT` if `Handled` /// is `FALSE` or `ReportShown` has not been run when this is called. - HRESULT ReportClosed(); + HRESULT ReportClosed([in] ICoreWebView2NotificationReportClosedCompletedHandler* handler); /// A string representing the body text of the notification. /// The default value is an empty string. @@ -703,9 +715,9 @@ namespace Microsoft.Web.WebView2.Core event Windows.Foundation.TypedEventHandler CloseRequested; void ReportShown(); - void ReportClicked(); - void ReportClicked(UInt32 actionIndex); - void ReportClosed(); + Windows.Foundation.IAsyncAction ReportClickedAsync(); + Windows.Foundation.IAsyncAction ReportClickedAsync(UInt32 actionIndex); + Windows.Foundation.IAsyncAction ReportClosedAsync(); } } ``` From 320baf0418624c33d657db424509d68f8796bb11 Mon Sep 17 00:00:00 2001 From: Jessica Chen Date: Wed, 1 Feb 2023 01:26:14 -0800 Subject: [PATCH 16/17] Address feedback --- specs/WebNotification.md | 69 +++++++++++++++++++++++++++++----------- 1 file changed, 50 insertions(+), 19 deletions(-) diff --git a/specs/WebNotification.md b/specs/WebNotification.md index d8e002af5..53aafc6ef 100644 --- a/specs/WebNotification.md +++ b/specs/WebNotification.md @@ -12,7 +12,7 @@ service worker registration. You should be able to handle notification permission requests, and further listen to `NotificationReceived` events to optionally handle the notifications themselves. The `NotificationReceived` events are raised on -`CorebWebView2` and `CoreWebView2Profile` object respectively for non-persistent +`CorebWebView2` and `CoreWebView2Profile` object for non-persistent and persistent notifications respectively. The `NotificationReceived` event on `CoreWebView2` and `CoreWebView2Profile` let @@ -96,6 +96,9 @@ CHECK_FAILURE(m_webView->add_PermissionRequested( &m_permissionRequestedToken)); } ... +std::map, bool> + m_cachedPermissions; + HRESULT SettingsComponent::OnPermissionRequested( ICoreWebView2* sender, ICoreWebView2PermissionRequestedEventArgs* args) { @@ -116,12 +119,12 @@ HRESULT SettingsComponent::OnPermissionRequested( COREWEBVIEW2_PERMISSION_STATE state; - auto cached_key = std::make_tuple(std::wstring(uri.get()), kind, userInitiated); - auto cached_permission = m_cached_permissions.find(cached_key); - if (cached_permission != m_cached_permissions.end()) + auto cachedKey = std::make_tuple(std::wstring(uri.get()), kind, userInitiated); + auto cachedPermission = m_cachedPermissions.find(cachedKey); + if (cachedPermission != m_cachedPermissions.end()) { state = - (cached_permission->second ? COREWEBVIEW2_PERMISSION_STATE_ALLOW + (cachedPermission->second ? COREWEBVIEW2_PERMISSION_STATE_ALLOW : COREWEBVIEW2_PERMISSION_STATE_DENY); } else @@ -142,11 +145,11 @@ HRESULT SettingsComponent::OnPermissionRequested( switch (response) { case IDYES: - m_cached_permissions[cached_key] = true; + m_cachedPermissions[cachedKey] = true; state = COREWEBVIEW2_PERMISSION_STATE_ALLOW; break; case IDNO: - m_cached_permissions[cached_key] = false; + m_cachedPermissions[cachedKey] = false; state = COREWEBVIEW2_PERMISSION_STATE_DENY; break; default: @@ -165,15 +168,28 @@ HRESULT SettingsComponent::OnPermissionRequested( ## Filter Notifications from a specific doamin and send local toast -Learn more about sending a local toast [Send a local toast notification from a C# app - Windows apps | Microsoft Learn](https://learn.microsoft.com/windows/apps/design/shell/tiles-and-notifications/send-local-toast?tabs=uwp). +This sample uses custom app UI to display toast notifications. It does so using +`Microsoft.Toolkit.Uwp.Notifications` nuget package. For an unpackaged C# WinApp +SDK based app, you can handle the toast notification activation in your app's +startup code such as in `App.xaml.cs`, and then dispatch it to the WebView2 +notification object. Note that the notification object will be gone along with +WebView2. If your app is closed when the toast notification is activated, the +notification object should be gone and you should not try to dispatch the +activation. + +For UWP and desktop packaged apps, you can learn more about via [Send a local +toast notification from a C# app - Windows apps | Microsoft +Learn](https://learn.microsoft.com/windows/apps/design/shell/tiles-and-notifications/send-local-toast?tabs=uwp). ### C# Sample + ```csharp using Microsoft.Toolkit.Uwp.Notifications; ... void WebView_NotificationReceived(object sender, CoreWebView2NotificationReceivedEventArgs args) { - CoreWebView2Deferral deferral = args.GetDeferral(); + using (args.GetDeferral()) + { args.Handled = true; var notification = args.Notification; @@ -190,24 +206,37 @@ void WebView_NotificationReceived(object sender, CoreWebView2NotificationReceive .AddText("Notification sent from " + args.Uri +":\n") .AddText(notification.Body); toastContent.Show(); - // See - // https://learn.microsoft.com/windows/apps/design/shell/tiles-and-notifications/send-local-toast?tabs=uwp - // for how to handle toast activation. // Call ReportShown after showing the toast notification to raise // the DOM notification.show event. // args.Handled has been set to true before we call Report... methods. notification.ReportShown(); + // Handle toast activation for C# unpackaged app. + // Listen to notification activation + ToastNotificationManagerCompat.OnActivated += toastArgs => + { + // Obtain the arguments from the notification + ToastArguments args = ToastArguments.Parse(toastArgs.Argument); + + // Obtain any user input (text boxes, menu selections) from the notification + ValueSet userInput = toastArgs.UserInput; + + // Need to dispatch to UI thread if performing UI operations + Application.Current.Dispatcher.Invoke(delegate + { // During the toast notification activation handling, we may call // ReportClicked and/or ReportClose accordingly. notification.ReportClosed(); + }); + }; +} } ``` ### C++ Sample -`Microsoft.Toolkit.Uwp.Notifications ` package does not work with non-UWP Win32 +`Microsoft.Toolkit.Uwp.Notifications` package does not work with non-UWP Win32 app. Hence in the sample code we use native MessageBox and handle notifications accordingly. @@ -296,9 +325,10 @@ interface ICoreWebView2Profile3 : IUnknown { /// Add an event handler for the `NotificationReceived` event for persistent /// notifications. /// - /// If a deferral is not taken on the event args, the subsequent scripts are - /// blocked until the event handler returns. If a deferral is taken, the - /// scripts are blocked until the deferral is completed. + /// If a deferral is not taken on the event args, the subsequent scripts after + /// the DOM notification creation call (i.e. `Notification()`) are blocked + /// until the event handler returns. If a deferral is taken, the scripts are + /// blocked until the deferral is completed. HRESULT add_NotificationReceived( [in] ICoreWebView2ProfileNotificationReceivedEventHandler* eventHandler, [out] EventRegistrationToken* token); @@ -314,9 +344,10 @@ interface ICoreWebView2_17 : ICoreWebView2_16 { /// Add an event handler for the `NotificationReceived` event for /// non-persistent notifications. /// - /// If a deferral is not taken on the event args, the subsequent scripts are - /// blocked until the event handler returns. If a deferral is taken, the - /// scripts are blocked until the deferral is completed. + /// If a deferral is not taken on the event args, the subsequent scripts after + /// the DOM notification creation call (i.e. `Notification()`) are blocked + /// until the event handler returns. If a deferral is taken, the scripts are + /// blocked until the deferral is completed. HRESULT add_NotificationReceived( [in] ICoreWebView2NotificationReceivedEventHandler* eventHandler, [out] EventRegistrationToken* token); From 16ba2bce1865c862aeb4810bb0fde678ead3a82d Mon Sep 17 00:00:00 2001 From: Jessica Chen Date: Fri, 3 Feb 2023 11:07:28 -0800 Subject: [PATCH 17/17] Update sample --- specs/WebNotification.md | 11 ++--------- 1 file changed, 2 insertions(+), 9 deletions(-) diff --git a/specs/WebNotification.md b/specs/WebNotification.md index 53aafc6ef..9f7395792 100644 --- a/specs/WebNotification.md +++ b/specs/WebNotification.md @@ -39,6 +39,7 @@ explicitly to allow such permission requests. `PermissionState.Default` for `PermissionKind.Notification` is considered denied. ### C# Sample + ```csharp IDictionary<(string, CoreWebView2PermissionKind, bool), bool> _cachedPermissions = new Dictionary<(string, CoreWebView2PermissionKind, bool), bool>(); @@ -86,16 +87,8 @@ void WebView_PermissionRequested(object sender, CoreWebView2PermissionRequestedE ``` ### C++ Sample + ```cpp -... -{ -CHECK_FAILURE(m_webView->add_PermissionRequested( - Callback( - this, &SettingsComponent::OnPermissionRequested) - .Get(), - &m_permissionRequestedToken)); -} -... std::map, bool> m_cachedPermissions;