Skip to content

Commit df752eb

Browse files
Merge pull request #5465 from MicrosoftEdge/user/chetanpandey/ServiceWorkerSettingAPI
API Review: Service Worker Post Message Setting
2 parents cdaf832 + 10261a1 commit df752eb

File tree

1 file changed

+347
-0
lines changed

1 file changed

+347
-0
lines changed
Lines changed: 347 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,347 @@
1+
Service Worker PostMessage Setting
2+
===
3+
# Background
4+
5+
This API provides a setting to expose the webview2 specific JS APIs on service
6+
worker script.
7+
8+
# Description
9+
10+
We propose adding the `AreWebViewScriptApisEnabledForServiceWorkers` setting
11+
API to control the exposure of WebView2-specific JavaScript APIs in service
12+
worker scripts. When enabled, developers can use WebView2's service worker
13+
postmessage APIs to communicate directly between service worker scripts and
14+
the WebView2 host.
15+
16+
Previously, WebView2-specific JavaScript APIs were only exposed to service
17+
worker scripts when developers subscribed to the `ServiceWorkerRegistered`
18+
event. This approach was unreliable because developers could obtain service
19+
worker registrations through the `GetServiceWorkerRegistrations` API and
20+
attempt to use service worker postmessage APIs, which would fail since the
21+
JavaScript APIs were not exposed.
22+
23+
# Examples
24+
25+
## Win32 C++
26+
27+
```cpp
28+
void ToggleServiceWorkerJsApiSetting()
29+
{
30+
// Unregister every service worker so that for all the newly installed service worker
31+
// the new settings can be applied.
32+
m_webView->ExecuteScript(
33+
L"navigator.serviceWorker.getRegistrations().then(function(registrations) {"
34+
L" for(let registration of registrations) {"
35+
L" registration.unregister();"
36+
L" }"
37+
L"});",
38+
nullptr);
39+
40+
wil::com_ptr<ICoreWebView2Settings> webViewSettings;
41+
CHECK_FAILURE(m_webView->get_Settings(&webViewSettings));
42+
auto webViewSettingsStaging =
43+
webViewSettings.try_query<ICoreWebView2StagingSettings>();
44+
45+
if (webViewSettingsStaging)
46+
{
47+
// Toggle the service worker post message setting.
48+
BOOL isEnabled;
49+
CHECK_FAILURE(webViewSettingsStaging->get_IsWebViewScriptApisForServiceWorkerEnabled(&isEnabled));
50+
CHECK_FAILURE(webViewSettingsStaging->put_IsWebViewScriptApisForServiceWorkerEnabled(!isEnabled));
51+
52+
MessageBox(
53+
reinterpret_cast<HWND>(m_appWindow.Id().Value),
54+
(std::wstring(L"Service Worker JS API setting has been ") +
55+
(!isEnabled ? L"enabled." : L"disabled."))
56+
.c_str(),
57+
L"Service Worker JS API Setting", MB_OK);
58+
}
59+
}
60+
61+
void SetupEventsOnServiceWorker(
62+
wil::com_ptr<ICoreWebView2ExperimentalServiceWorker> serviceWorker)
63+
{
64+
serviceWorker->add_WebMessageReceived(
65+
Callback<ICoreWebView2ExperimentalServiceWorkerWebMessageReceivedEventHandler>(
66+
[this](
67+
ICoreWebView2ExperimentalServiceWorker* sender,
68+
ICoreWebView2WebMessageReceivedEventArgs* args) -> HRESULT
69+
{
70+
71+
wil::unique_cotaskmem_string messageRaw;
72+
CHECK_FAILURE(args->TryGetWebMessageAsString(&messageRaw));
73+
std::wstring messageFromWorker = messageRaw.get();
74+
75+
std::wstringstream message{};
76+
message << L"Message: " << std::endl << messageFromWorker << std::endl;
77+
m_appWindow->AsyncMessageBox(message.str(), L"Message from Service Worker");
78+
79+
return S_OK;
80+
})
81+
.Get(),
82+
nullptr);
83+
}
84+
85+
void SetUpEventsAndNavigate()
86+
{
87+
// Setup WebMessageReceived event to receive message from main thread.
88+
m_webView->add_WebMessageReceived(
89+
Callback<ICoreWebView2WebMessageReceivedEventHandler>(
90+
[this](ICoreWebView2* sender, ICoreWebView2WebMessageReceivedEventArgs* args) -> HRESULT
91+
{
92+
wil::unique_cotaskmem_string message;
93+
CHECK_FAILURE(args->TryGetWebMessageAsString(&message));
94+
95+
std::wstring msgStr = message.get();
96+
if (msgStr == L"MessageFromMainThread")
97+
{
98+
std::wstringstream message{};
99+
message << L"Message: " << std::endl << L"Service Worker Message from Main thread" << std::endl;
100+
m_appWindow->AsyncMessageBox(message.str(), L"Message from Service Worker (relayed by Main Thread)");
101+
}
102+
return S_OK;
103+
})
104+
.Get(),
105+
&m_webMessageReceivedToken);
106+
107+
// Get ServiceWorkerManager from profile and setup events to listen to service worker post messages.
108+
auto webView2_13 = m_webView.try_query<ICoreWebView2_13>();
109+
CHECK_FEATURE_RETURN_EMPTY(webView2_13);
110+
111+
wil::com_ptr<ICoreWebView2Profile> webView2Profile;
112+
CHECK_FAILURE(webView2_13->get_Profile(&webView2Profile));
113+
auto webViewExperimentalProfile13 =
114+
webView2Profile.try_query<ICoreWebView2ExperimentalProfile13>();
115+
CHECK_FEATURE_RETURN_EMPTY(webViewExperimentalProfile13);
116+
CHECK_FAILURE(
117+
webViewExperimentalProfile13->get_ServiceWorkerManager(&m_serviceWorkerManager));
118+
119+
CHECK_FAILURE(m_serviceWorkerManager->add_ServiceWorkerRegistered(
120+
Callback<ICoreWebView2ExperimentalServiceWorkerRegisteredEventHandler>(
121+
[this](
122+
ICoreWebView2ExperimentalServiceWorkerManager* sender,
123+
ICoreWebView2ExperimentalServiceWorkerRegisteredEventArgs* args)
124+
{
125+
wil::com_ptr<ICoreWebView2ExperimentalServiceWorkerRegistration>
126+
serviceWorkerRegistration;
127+
CHECK_FAILURE(args->get_ServiceWorkerRegistration(&serviceWorkerRegistration));
128+
129+
if (serviceWorkerRegistration)
130+
{
131+
wil::com_ptr<ICoreWebView2ExperimentalServiceWorker> serviceWorker;
132+
CHECK_FAILURE(
133+
serviceWorkerRegistration->get_ActiveServiceWorker(&serviceWorker));
134+
135+
if (serviceWorker)
136+
{
137+
SetupEventsOnServiceWorker(serviceWorker);
138+
}
139+
else
140+
{
141+
CHECK_FAILURE(serviceWorkerRegistration->add_ServiceWorkerActivated(
142+
Callback<
143+
ICoreWebView2ExperimentalServiceWorkerActivatedEventHandler>(
144+
[this](
145+
ICoreWebView2ExperimentalServiceWorkerRegistration* sender,
146+
ICoreWebView2ExperimentalServiceWorkerActivatedEventArgs*
147+
args) -> HRESULT
148+
{
149+
wil::com_ptr<ICoreWebView2ExperimentalServiceWorker>
150+
serviceWorker;
151+
CHECK_FAILURE(
152+
args->get_ActiveServiceWorker(&serviceWorker));
153+
SetupEventsOnServiceWorker(serviceWorker);
154+
155+
return S_OK;
156+
})
157+
.Get(),
158+
nullptr));
159+
}
160+
}
161+
162+
return S_OK;
163+
})
164+
.Get(),
165+
&m_serviceWorkerRegisteredToken));
166+
167+
// Navigate to index.html which will register a new service worker and
168+
// check if chrome and webview objects are available in service worker script.
169+
m_sampleUri = m_appWindow->GetLocalUri(L"index.html");
170+
CHECK_FAILURE(m_webView->Navigate(m_sampleUri.c_str()));
171+
}
172+
```
173+
174+
**index.html**:
175+
```html
176+
<!DOCTYPE html>
177+
<html lang="en">
178+
<head>
179+
<meta charset="UTF-8">
180+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
181+
<title>Service Worker Post Message Setting</title>
182+
<script>
183+
"use strict";
184+
185+
// Register the service worker
186+
navigator.serviceWorker.register("service_worker.js").then(function(registration) {
187+
188+
// Wait for the service worker to be ready
189+
return navigator.serviceWorker.ready;
190+
}).then(function(registration) {
191+
const serviceWorker = registration.active;
192+
193+
// Post a message to the service worker with CHECK_CHROME_WEBVIEW command
194+
if (serviceWorker) {
195+
serviceWorker.postMessage({command: 'CHECK_CHROME_WEBVIEW'});
196+
console.log("Message posted to service worker");
197+
}
198+
}).catch(function(error) {
199+
console.error("Service worker registration or messaging failed:", error);
200+
});
201+
202+
// Listen to messages from the service worker
203+
// Messages sent via event.source.postMessage() are received here
204+
navigator.serviceWorker.addEventListener('message', function(event) {
205+
console.log("Message received from service worker:", event.data);
206+
207+
if (event.data === 'chromeWebViewNotAvailable') {
208+
self.chrome.webview.postMessage('MessageFromMainThread');
209+
}
210+
});
211+
</script>
212+
</head>
213+
<body>
214+
<h1>Service Worker Post Message Setting</h1>
215+
<p>This page registers a service worker, posts a message to it, and listens for responses.</p>
216+
</body>
217+
</html>
218+
```
219+
220+
**service_worker.js**
221+
```js
222+
self.addEventListener('message', (event) => {
223+
if(self.chrome && self.chrome.webview) {
224+
event.source.postMessage('chromeWebViewAvailable');
225+
// When self.chrome.webview is available, message can be directly posted
226+
// to service worker object on host.
227+
self.chrome.webview.postMessage('Service Worker Message directly from service worker thread');
228+
} else {
229+
// When self.chrome.webview is not available, message can be posted back
230+
// to main thread, which can then forward it to host.
231+
event.source.postMessage('chromeWebViewNotAvailable');
232+
}
233+
});
234+
```
235+
236+
## C#/.NET
237+
238+
```c#
239+
private void ToggleServiceWorkerJsApiSetting()
240+
{
241+
// Unregister every service worker so that for all the newly installed service worker
242+
// the new settings can be applied.
243+
_ = await webView.CoreWebView2.ExecuteScriptAsync(
244+
@"navigator.serviceWorker.getRegistrations().then(function(registrations) {
245+
for(let registration of registrations) {
246+
registration.unregister();
247+
}
248+
});");
249+
250+
// Toggle the service worker post message setting.
251+
var settings = webView.CoreWebView2.Settings;
252+
settings.AreWebViewScriptApisEnabledForServiceWorkers = !settings.AreWebViewScriptApisEnabledForServiceWorkers;
253+
254+
MessageBox.Show(this,
255+
$"AreWebViewScriptApisEnabledForServiceWorkers is now set to: {settings.AreWebViewScriptApisEnabledForServiceWorkers}",
256+
"Service Worker JS API Setting", MessageBoxButtons.OK, MessageBoxIcon.Information);
257+
}
258+
259+
private void SetupEventsOnServiceWorker(CoreWebView2ServiceWorker serviceWorker)
260+
{
261+
serviceWorker.WebMessageReceived += (sender, args) =>
262+
{
263+
string messageFromWorker = args.TryGetWebMessageAsString();
264+
MessageBox.Show(this, $"Message: \n{messageFromWorker}", "Message from Service Worker");
265+
};
266+
}
267+
268+
private void SetUpEventsAndNavigate()
269+
{
270+
// Setup WebMessageReceived event to receive message from main thread.
271+
webView.CoreWebView2.WebMessageReceived += (sender, args) =>
272+
{
273+
string message = args.TryGetWebMessageAsString();
274+
if (message == "MessageFromMainThread")
275+
{
276+
MessageBox.Show(this,
277+
"Message: \nService Worker Message from Main thread",
278+
"Message from Service Worker (relayed by Main Thread)");
279+
}
280+
};
281+
282+
// Get ServiceWorkerManager from profile and setup events to listen to service worker post messages.
283+
var serviceWorkerManager = webView.CoreWebView2.Profile.ServiceWorkerManager;
284+
285+
serviceWorkerManager.ServiceWorkerRegistered += (sender, args) =>
286+
{
287+
var serviceWorkerRegistration = args.ServiceWorkerRegistration;
288+
289+
if (serviceWorkerRegistration != null)
290+
{
291+
var serviceWorker = serviceWorkerRegistration.ActiveServiceWorker;
292+
293+
if (serviceWorker != null)
294+
{
295+
SetupEventsOnServiceWorker(serviceWorker);
296+
}
297+
else
298+
{
299+
serviceWorkerRegistration.ServiceWorkerActivated += (s, e) =>
300+
{
301+
SetupEventsOnServiceWorker(e.ActiveServiceWorker);
302+
};
303+
}
304+
}
305+
};
306+
307+
// Navigate to index.html which will register a new service worker and
308+
// check if chrome and webview objects are available in service worker script.
309+
sampleUri = GetLocalUri("index.html");
310+
webView.CoreWebView2.Navigate(sampleUri);
311+
}
312+
313+
```
314+
315+
**index.html** and **service_worker.js**: Same as the Win32 C++ example above.
316+
317+
# API Details
318+
319+
## Win32 C++
320+
```cpp
321+
interface ICoreWebView2Settings10 : ICoreWebView2Settings9 {
322+
/// Gets the `AreWebViewScriptApisEnabledForServiceWorkers` property.
323+
[propget] HRESULT AreWebViewScriptApisEnabledForServiceWorkers([out, retval] BOOL* value);
324+
325+
/// Enables or disables webview2 specific Service Worker JS APIs in the WebView2.
326+
/// When set to `TRUE`, chrome and webview objects are available in Service Workers .
327+
/// chrome.webview exposes APIs to interact with the WebView from Service Workers.
328+
/// The default value is `FALSE`.
329+
/// When enabled, this setting takes effect for all the newly installed Service Workers.
330+
[propput] HRESULT AreWebViewScriptApisEnabledForServiceWorkers([in] BOOL value)
331+
}
332+
333+
```
334+
335+
## .NET/C#
336+
```c#
337+
namespace Microsoft.Web.WebView2.Core
338+
{
339+
runtimeclass CoreWebView2Settings
340+
{
341+
[interface_name("Microsoft.Web.WebView2.Core.ICoreWebView2Settings10")]
342+
{
343+
Boolean AreWebViewScriptApisEnabledForServiceWorkers { get; set; };
344+
}
345+
}
346+
}
347+
```

0 commit comments

Comments
 (0)