Bug 198554

Summary: Unable to get cookies from WKWebView
Product: WebKit Reporter: Jeremy Schonfeld <jeremyschonfeld>
Component: New BugsAssignee: Nobody <webkit-unassigned>
Status: NEW ---    
Severity: Blocker CC: achristensen, beidson, cdumez, sihui_liu, webkit-bug-importer
Priority: P2 Keywords: InRadar
Version: Safari 12   
Hardware: iPhone / iPad   
OS: iOS 12   
Attachments:
Description Flags
Cookies shown in the safari web inspector after passing the login page in the iOS simulator
none
Xcode project demonstrating issue none

Description Jeremy Schonfeld 2019-06-04 20:20:43 PDT
Created attachment 371369 [details]
Cookies shown in the safari web inspector after passing the login page in the iOS simulator

I am currently using a WKWebView to authenticate a user via a website in my app. Ideally, the web view is presented, the user enters login information, the website responds with the proper authentication cookies, and then the app retrieves the cookies from the web view to use in later background URLSession requests. However, I am unable to get the cookies after login. I can clearly see through the Safari web inspector that the cookies appear after moving past the login page (see attached screenshot). However, I cannot retrieve the cookies via code. I tried setting the view controller as the WKHTTPCookieStoreObserver with "webView.configuration.websiteDataStore.httpCookieStore.add(self)" in viewDidLoad() of the view controller, but the delegate method "cookiesDidChange" is never called. Additionally, I attempt to call "webView.configuration.websiteDataStore.httpCookieStore.getAllCookies" in webView didFinish navigation, however the callback only returns the cookies half of the time seemingly randomly. The other half of the time, the callback returns an empty list or other cookies (not the authentication cookies I see in the web inspector). This issue is blocking me from adding a functioning login view to my app. I spoke to some of the WebKit developers (Chris and John) at the WWDC lab today.
Comment 1 Radar WebKit Bug Importer 2019-06-04 22:12:34 PDT
<rdar://problem/51428301>
Comment 2 Chris Dumez 2019-06-05 13:34:10 PDT
Seems like some kind of race. Brady/Alex, do we need to sync cookies in httpCookieStore.getAllCookies() to make sure we return up-to-date ones?
Comment 3 Alex Christensen 2019-06-05 13:54:29 PDT
websiteDataStore.httpCookieStore.getAllCookies does go to the network process and gets the cookies from the storage that the WKWebView is using.  It's possible a sub resource is setting the cookie after the main resource is done loading, in which case it's possible that getAllCookies would get the cookies between main and sub resource loading.  The cookiesDidChange should indeed be called, though, so something is strange here.  Could you attach a simple project that reproduces the issue?
Comment 4 Sihui Liu 2019-06-05 14:01:41 PDT
(In reply to Jeremy Schonfeld from comment #0)
> Created attachment 371369 [details]
> Cookies shown in the safari web inspector after passing the login page in
> the iOS simulator
> 
> I am currently using a WKWebView to authenticate a user via a website in my
> app. Ideally, the web view is presented, the user enters login information,
> the website responds with the proper authentication cookies, and then the
> app retrieves the cookies from the web view to use in later background
> URLSession requests. However, I am unable to get the cookies after login. I
> can clearly see through the Safari web inspector that the cookies appear
> after moving past the login page (see attached screenshot). However, I
> cannot retrieve the cookies via code. I tried setting the view controller as
> the WKHTTPCookieStoreObserver with
> "webView.configuration.websiteDataStore.httpCookieStore.add(self)" in
> viewDidLoad() of the view controller, but the delegate method
> "cookiesDidChange" is never called. Additionally, I attempt to call
> "webView.configuration.websiteDataStore.httpCookieStore.getAllCookies" in
> webView didFinish navigation, however the callback only returns the cookies
> half of the time seemingly randomly. The other half of the time, the
> callback returns an empty list or other cookies (not the authentication
> cookies I see in the web inspector). This issue is blocking me from adding a
> functioning login view to my app. I spoke to some of the WebKit developers
> (Chris and John) at the WWDC lab today.

Do you have web inspector open when you try to get all cookies from httpCookieStore?
Comment 5 Jeremy Schonfeld 2019-06-05 14:05:51 PDT
The issue occurs if the web inspector is open or if it is closed, so having the web inspector open while calling getAllCookies does not seem to influence the issue. I will make an example project to demonstrate the issue later today.
Comment 6 Chris Dumez 2019-06-05 14:12:13 PDT
(In reply to Alex Christensen from comment #3)
> websiteDataStore.httpCookieStore.getAllCookies does go to the network
> process and gets the cookies from the storage that the WKWebView is using. 

What does it mean "the storage that the WKWebView is using" ? iirc, Jeremy is using an ephemeral data store. If the cookies are set in the network process and then read from another process, isn't there a sync delay / race?

> It's possible a sub resource is setting the cookie after the main resource
> is done loading, in which case it's possible that getAllCookies would get
> the cookies between main and sub resource loading.

Jeremy, could you confirm if the Set-Cookie response if from the main resource load or for a sub-resource load later on?

> The cookiesDidChange
> should indeed be called, though, so something is strange here.  Could you
> attach a simple project that reproduces the issue?

For this part, I suspect a bug in the app because it should definitely get called.
A simple test project reproducing the issue would definitely help us a lot.


If I remember correctly, Jeremy said at WWDC that the issue may only reproduce when using the ephemeral data store. Jeremy, could you please confirm.
Comment 7 Jeremy Schonfeld 2019-06-05 21:24:52 PDT
I have copied the key parts of the app I am working with relating to the login view into a simple xcode project to demonstrate. While doing this, I have narrowed down the problem. The main view of the app has a separate WKWebView that displays announcements. When the app launches to the initial view controller (this main view controller) and the app sees the user has not logged in, it presents the login view controller on top of the main view controller. Then after login, the login view controller is dismissed and the WKWebView on the main view controller loads content while logged in. If I set the login view controller to be the initial view controller (with no WKWebView in a view controller underneath), the delegate works just fine. However, if there is a WKWebView in a view controller underneath the presented login view controller (simply instantiated, not having loaded any content), the delegate does not function. Looking at this sample project, is this a bug, or if not could you explain why this behavior occurs and how I should avoid this issue? I've attached a zip file containing the project to this bug report.
Comment 8 Jeremy Schonfeld 2019-06-05 21:25:45 PDT
Created attachment 371469 [details]
Xcode project demonstrating issue
Comment 9 Chris Dumez 2019-06-06 08:48:37 PDT
(In reply to Jeremy Schonfeld from comment #7)
> I have copied the key parts of the app I am working with relating to the
> login view into a simple xcode project to demonstrate. While doing this, I
> have narrowed down the problem. The main view of the app has a separate
> WKWebView that displays announcements. When the app launches to the initial
> view controller (this main view controller) and the app sees the user has
> not logged in, it presents the login view controller on top of the main view
> controller. Then after login, the login view controller is dismissed and the
> WKWebView on the main view controller loads content while logged in. If I
> set the login view controller to be the initial view controller (with no
> WKWebView in a view controller underneath), the delegate works just fine.
> However, if there is a WKWebView in a view controller underneath the
> presented login view controller (simply instantiated, not having loaded any
> content), the delegate does not function. Looking at this sample project, is
> this a bug, or if not could you explain why this behavior occurs and how I
> should avoid this issue? I've attached a zip file containing the project to
> this bug report.

Do you mean that the WKWebView is not visible during some period of time? If so, it is possible the WebView's child processes are getting suspended. On iOS, processes gets suspended aggressively and a view that is not visible is not allowed to keep "running" (i.e. its processes get suspended) shortly after load completes.
Comment 10 Jeremy Schonfeld 2019-06-06 09:20:26 PDT
There are two WKWebViews. One is the login web view that I mentioned that is visible the entire time. However I realized there is also a second web view that is hidden and not visible. The attached Xcode project demonstrates this. Is it possible that halting the processes of the hidden web view prevent the visible web view from functioning properly?
Comment 11 Chris Dumez 2019-06-06 09:23:31 PDT
(In reply to Jeremy Schonfeld from comment #10)
> There are two WKWebViews. One is the login web view that I mentioned that is
> visible the entire time. However I realized there is also a second web view
> that is hidden and not visible. The attached Xcode project demonstrates
> this. 

I am having trouble running your test app, it crashes here but I could be on a bad SDK.

> Is it possible that halting the processes of the hidden web view
> prevent the visible web view from functioning properly?

I doubt this is the issue.
Comment 12 Sihui Liu 2019-06-06 09:28:51 PDT
(In reply to Jeremy Schonfeld from comment #10)
> There are two WKWebViews. One is the login web view that I mentioned that is
> visible the entire time. However I realized there is also a second web view
> that is hidden and not visible. The attached Xcode project demonstrates
> this. Is it possible that halting the processes of the hidden web view
> prevent the visible web view from functioning properly?

Does this bug reproduce if you use same processPool for different web views? Maybe creating two web views using same process pool.
Comment 13 Sihui Liu 2019-06-06 11:07:29 PDT
(In reply to Sihui Liu from comment #12)
> (In reply to Jeremy Schonfeld from comment #10)
> > There are two WKWebViews. One is the login web view that I mentioned that is
> > visible the entire time. However I realized there is also a second web view
> > that is hidden and not visible. The attached Xcode project demonstrates
> > this. Is it possible that halting the processes of the hidden web view
> > prevent the visible web view from functioning properly?
> 
> Does this bug reproduce if you use same processPool for different web views?
> Maybe creating two web views using same process pool.

In the test app, a WKProcessPool is created but not used. It seems simply removing that line will make the cookie observer work as expected.
Comment 14 Jeremy Schonfeld 2019-06-06 20:15:33 PDT
It seems that creating the extra WKProcessPool (a line I had apparently accidentally left in) was causing the issue of the cookie delegate not getting called. After removing that line and instead creating one WKProcessPool that is shared by all WKWebViews in my app, the issue seems to be resolved. I can now reliably use cookiesDidChange to listen for the authentication cookies. Thank you for your help on this issue. I'm not sure if the unused WKProcessPool causing the delegate methods to never fire is still an underlying bug, or a behavior that I misunderstood, but my app is now functional with these changes.