WebKit Bugzilla
New
Browse
Log In
×
Sign in with GitHub
or
Remember my login
Create Account
·
Forgot Password
Forgotten password account recovery
RESOLVED FIXED
252544
Initial ServiceWorkerWindowClient in a Home Screen web app launched to handle notificationclick handler is inert for a short period
https://bugs.webkit.org/show_bug.cgi?id=252544
Summary
Initial ServiceWorkerWindowClient in a Home Screen web app launched to handle...
lehu
Reported
2023-02-19 04:15:46 PST
Overview: I made a Home Screen web app, which register a service worker to handle the push events triggered by the WebPush. And in the notificationclick callback function, I try to open the web app calling the clients.openWindow() function. Then, I try to post some message with the postMessage method. In my web app's js, I have registered the "message" event handler for navigator.serviceWorker, which should be called when receive the message from the notificationclick callback. If the Home Screen web app is not open, it can be opened by touch the APNS message. However, the web app's js can not receive the posted message from the service worker. After it has been opened, the following push which will post message from the service worker will be received. Steps to Reproduce: 1) Upgrade iOS to 16.4 beta1 2) Open the following experimental features of Safari: a) Notifications b) Push API 3) Make a web app and add it to the Home Screen 4) Register Service Worker and handle the events In the service worker: async function openPushNotification(event) { event.notification.close(); event.waitUntil((async () => { let w = await clients.openWindow(event.notification.data); w.postMessage(event.notification.data); })()); } self.addEventListener("notificationclick", openPushNotification); In the web app: navigator.serviceWorker.addEventListener("message", (event) => { console.log(event.data); let wn = document.getElementById("web-notification"); wn.innerHTML = JSON.stringify(event.data, null, " "); }); 5) Close the Home Screen web app. 6) Click the APNS notification. Actual Results: The "message" event callback will not triggered if the web app is not opened. Expected Results: Every time we click the WebPush notification, which will open the Home Screen web app, it should always receive the post message sent by the notificationclick handler. Build Date & Hardware: iOS 16.4 (20E5212f) beta1 Additional Builds and Platforms: iPhone SE 3rd Additional Information: I have tested the Firefox 110 and Safari 16.3 on macOS 13.2.1, both of which support receiving the post message.
Attachments
Add attachment
proposed patch, testcase, etc.
Radar WebKit Bug Importer
Comment 1
2023-02-20 10:13:32 PST
<
rdar://problem/105684663
>
Brady Eidson
Comment 2
2023-03-06 22:51:36 PST
I tried this out from a test app to debug. It actually worked expected. I not only verified the behavior of the web content, but also that the relevant WebKit code was working as expected. One thing that tripped me up while trying to reproduce was some syntax errors that started out with direct copy-and-paste of your code. Have you verified via Web Inspector that your JS code is working as expected and not throwing any errors? If you're still having trouble with this, I think we'll need an actual end-to-end test to interact with - and not code snippets here - because I can verify at least one incarnation of the test works fine.
lehu
Comment 3
2023-03-06 23:16:34 PST
Hi, Brady, First, thank you for testing this issue. The code snippet may have syntax error. It is a little hard to paste all code needed to reproduce this bug. What I try hard to do is to demonstrate how the issue is, and the snippet is just a clue. However, I have finished a complete demo. And its URL is
https://taoshu.in/web/push-demo/
In the demo page, there is a form to display the subscription. And at the bottom of that page, there is a section named "Web Push Custom Data". Every time we push some message with some custom data and click the notification, the custom data will be displayed in that section. But if the Home Screen web app have not booted, click the notification will not let the bottom display custom data. And I also made a Push API. If you use httpie, here is the command needed to push some message: ``` http -f
https://taoshu.in/+/push
title=WebPush body="Ping from taoshu.in" data="娃哈哈😄" subs="web-push-subscription-data-in-json" ``` In the above command, the *data* argument is used to send custom data to web app. And the *subs* argument is used to set the web push subscription. You can just copy the subscription in the demo page form. Feel free to comment this bug if you have any problem. Thanks.
Brady Eidson
Comment 4
2023-03-07 10:47:15 PST
Inside `openPushNotification`, the Notification does have the data intact. So that's great. But through direct debugging I can also see that ServiceWorkerClient.postMessage is never actually called. Seems like some part of a promise chain or something is getting dropped in all of your async/await code, causing none of the postMessages to actually fire. I'll try copying your notificationclick handler verbatim over to mine and go from there.
Brady Eidson
Comment 5
2023-03-07 14:28:43 PST
(In reply to Brady Eidson from
comment #4
)
> Inside `openPushNotification`, the Notification does have the data intact. > So that's great. > > But through direct debugging I can also see that > ServiceWorkerClient.postMessage is never actually called. > > Seems like some part of a promise chain or something is getting dropped in > all of your async/await code, causing none of the postMessages to actually > fire. > > I'll try copying your notificationclick handler verbatim over to mine and go > from there.
postMessage is never getting called because the call to focus() results in a rejected promise, mostly aborting the rest of your SW code. I'm digging deeper in to why it rejects now, but you can also change your code to handle the rejection and then postMessage successfully.
Brady Eidson
Comment 6
2023-03-07 14:29:47 PST
Retitling: If a Home Screen Web App is backgrounded and tries to focus() a ServiceWorkerWindowClient in a notificationclick handler, the focus() promise rejects
lehu
Comment 7
2023-03-07 16:55:05 PST
It seems not relate to the focus() call. I have tried a) use try catch to wrap the focus() call b) delete the focus() call totally None of which works 😂
Brady Eidson
Comment 8
2023-03-07 18:29:26 PST
(In reply to lehu from
comment #7
)
> It seems not relate to the focus() call. I have tried > > a) use try catch to wrap the focus() call > b) delete the focus() call totally > > None of which works 😂
I guarantee through direct debugging and observation that the focus call is failing. You can wrap it and see that the promise rejects, instead of fulfills. *BUT*, the issue causing focus() to fail very well might be causing later things to fail.
Brady Eidson
Comment 9
2023-03-07 18:48:53 PST
Setting aside postMessage not working, focus() is also not working. Here's what happens: 1 - The app launches and starts a page load for the root URL of the web app -
https://taoshu.in/web/push-demo/
in this case. This happens in web content process A. 2 - That load has a ScriptExecutionContextIdentifier associated with it. Importantly, this identifier is what the eventual Document *will* have, but that document does not exist yet. 3 - The networking process is asked to load
https://taoshu.in/web/push-demo/
. As part of that resource load it is given the context identifier. So the networking process remembers "There is a window client for this URL with this identifier. 4 - The service worker is fired up in web content process B to handle the notification click. This happens right away, before the main page document has really started loading. 5 - In the notification click handler, the SW does a matchAll on clients looking for window clients 6 - That matchAll goes to the networking process, which knows about the eventual client. So it returns that UUID 7 - Back in web content B, the SW has the results of the matchAll and tries to call focus() on the window client. 8 - That focus call is routed to the networking process based on UUID, which knows that the document *should be* in Web Content process A. So it forwards the call there 9 - In Web Content process A, the focus() attempt tries to look up the Document for that UUID and fails. Remember from step 2, the eventual Document that will be created will have that UUID, but that Document doesn't exist yet. 10 - The failure is routed to Networking, which then routes to Web Content B, and then rejects the focus() promise. Whoops! 11 - Almost immediately after that rejection, data *does* come in for the main page document, therefore *actually creating the Document object in Web Content A*, ensuring that a future focus() call would actually work. Your experimenting suggests removing the focus call still causes failure. I'd need to see the exact code you changed it to to take a look. (Note: Please do *not* change the live site I'm debugging with now! Make a second copy and mention it if that is something you'd like to do) Whatever you changed it to may be affected by a very similar issue, or something different, but: the focus() issue is definitely real. (FWIW, I still have a private test with postMessage working just fine, but it's simply different than yours)
lehu
Comment 10
2023-03-07 19:01:24 PST
I will not change the live demo site since now. And here is the current code of
https://taoshu.in/web/push-demo/sw.js
, in which I just commented out three lines of code. function receivePushNotification(event) { console.log("[Service Worker] Push Received."); const { title, body, data } = event.data.json(); const options = { body: body, data: data, }; event.waitUntil(self.registration.showNotification(title, options)); } async function openPushNotification(event) { console.log("[Service Worker] Notification click Received.", event); event.notification.close(); event.waitUntil((async () => { const allClients = await clients.matchAll({ type: 'window' }); for (const client of allClients) { // if (!client.focused) { // await client.focus().catch(e => { console.log(e) }); // } client.postMessage(event.notification.data); return; } let url = event.target.registration.scope; let w = await clients.openWindow(url); w.postMessage(event.notification.data); })()); } self.addEventListener("push", receivePushNotification); self.addEventListener("notificationclick", openPushNotification);
Brady Eidson
Comment 11
2023-03-07 19:08:01 PST
Yup, this will run into the exact same issue as mentioned above. (In reply to lehu from
comment #10
)
> > async function openPushNotification(event) { > console.log("[Service Worker] Notification click Received.", event); > > event.notification.close(); > event.waitUntil((async () => { > const allClients = await clients.matchAll({ type: 'window' }); > > for (const client of allClients) { > // if (!client.focused) { > // await client.focus().catch(e => { console.log(e) }); > // } > client.postMessage(event.notification.data); > return; > } > ...
Because of the weird mismatch in Networking's idea of the truth with Web Content's idea of the truth, the matchAll() call returns a client... But that client doesn't *ACTUALLY* exist yet. It is "just about to exist" So it's a different way of triggering the exact same issue.
Brady Eidson
Comment 12
2023-03-07 19:08:25 PST
CC'ing Youenn and Chris (And I'll ping them on Slack)
Brady Eidson
Comment 13
2023-03-07 20:34:10 PST
Retitling: Initial ServiceWorkerWindowClient in a Home Screen web app launched to handle notificationclick handler is inert for a short period If your promise chain holds out long enough such that the document networking request actually gets a response, it will start working.
Brady Eidson
Comment 14
2023-03-07 20:35:48 PST
In my test case it's working because I'm postMessaging to a *new* client opened via clients.openWindow If I attempted to matchAll and reuse an existing window client, I would fall into this same trap.
Brady Eidson
Comment 15
2023-03-08 09:39:08 PST
With ServiceWorkerClient and ServiceWorkerWindowClient combined, there are 3 APIs we need to handle in this case: postMessage(), focus(), and navigate() All 3 of these fail because the context's Document isn't fully live yet. The navigate() steps -
https://w3c.github.io/ServiceWorker/#client-navigate
- clearly state: "If browsingContext’s associated document is not fully active, queue a task to reject promise with a TypeError" navigate() already fails today per the spec. focus() is a little trickier -
https://w3c.github.io/ServiceWorker/#client-focus
- As you have to cross reference with some other specs and concepts. But basically, since the document doesn't exist, focus will not be true after running focus steps, and therefore the promise will reject. So focus() also fails correctly today! Now we're back to postMessage(). We clearly intended to queue messages sent to a "not fully active" context and deliver them once it becomes active. And we're failing that case here. Let's fix.
lehu
Comment 16
2023-03-14 20:32:12 PDT
BTW, it seems iOS Safari does not play sound when showing notifications. Is it a intentional design? I have checked the Safari on macos, it does not allow play sound as well. Without the notification sound, it is had to make app like chat or social network based on standard web technologies. What a sad. PS I also wrote an article about web push on ios 16.4
https://taoshu.in/web/push-on-ios.html
Brady Eidson
Comment 17
2023-03-22 20:55:07 PDT
Pull request:
https://github.com/WebKit/WebKit/pull/11848
decademoon.bugzilla
Comment 18
2023-03-28 02:59:21 PDT
I am also experiencing a similar issue. In my case, if the app is closed and a notification is clicked, then it seems the service worker doesn't actually handle the notificationclick event at all and just opens a window at the service worker origin URL. In this case, I cannot open the Safari devtools for the service worker running on my phone, it simply isn't listed in the Develop menu under my phone submenu, which is really strange. If I open the app manually then the service worker appears in the menu as expected. So basically I have no way of debugging this.
Brady Eidson
Comment 19
2023-03-30 17:17:21 PDT
(In reply to decademoon.bugzilla from
comment #18
)
> I am also experiencing a similar issue. > > In my case, if the app is closed and a notification is clicked, then it > seems the service worker doesn't actually handle the notificationclick event > at all and just opens a window at the service worker origin URL. In this > case, I cannot open the Safari devtools for the service worker running on my > phone, it simply isn't listed in the Develop menu under my phone submenu, > which is really strange. If I open the app manually then the service worker > appears in the menu as expected. So basically I have no way of debugging > this.
This bug report (and incoming fix) are well understood. What you describe is almost certainly a different issue, and we need a new, clean bug report with steps to reproduce.
EWS
Comment 20
2023-04-07 09:42:39 PDT
Committed
262711@main
(6833b7d7f7be): <
https://commits.webkit.org/262711@main
> Reviewed commits have been landed. Closing PR #11848 and removing active labels.
EWS
Comment 21
2023-04-12 15:02:37 PDT
Committed
259548.634@safari-7615-branch
(013db85952ab): <
https://commits.webkit.org/259548.634@safari-7615-branch
> Reviewed commits have been landed. Closing PR #545 and removing active labels.
Peter
Comment 22
2023-06-07 05:08:55 PDT
Which iOs version is this fixed in?
youenn fablet
Comment 23
2023-06-07 06:03:25 PDT
(In reply to Peter from
comment #22
)
> Which iOs version is this fixed in?
Have you tried the latest iOS 16 and 17 betas?
Note
You need to
log in
before you can comment on or make changes to this bug.
Top of Page
Format For Printing
XML
Clone This Bug