[iOS] ViewServices started by StoreKitUIService may get suspended unexpectedly
[WebKit-https.git] / Source / WebKit / UIProcess / ApplicationStateTracker.mm
1 /*
2  * Copyright (C) 2015 Apple Inc. All rights reserved.
3  *
4  * Redistribution and use in source and binary forms, with or without
5  * modification, are permitted provided that the following conditions
6  * are met:
7  * 1. Redistributions of source code must retain the above copyright
8  *    notice, this list of conditions and the following disclaimer.
9  * 2. Redistributions in binary form must reproduce the above copyright
10  *    notice, this list of conditions and the following disclaimer in the
11  *    documentation and/or other materials provided with the distribution.
12  *
13  * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
14  * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
15  * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
16  * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
17  * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
18  * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
19  * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
20  * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
21  * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
22  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
23  * THE POSSIBILITY OF SUCH DAMAGE.
24  */
25
26 #import "config.h"
27 #import "ApplicationStateTracker.h"
28
29 #if PLATFORM(IOS)
30
31 #import "AssertionServicesSPI.h"
32 #import "SandboxUtilities.h"
33 #import "UIKitSPI.h"
34 #import <wtf/ObjcRuntimeExtras.h>
35 #import <wtf/spi/cocoa/SecuritySPI.h>
36
37 @interface UIWindow (WKDetails)
38 - (BOOL)_isHostedInAnotherProcess;
39 @end
40
41 namespace WebKit {
42
43
44 enum class ApplicationType {
45     Application,
46     ViewService,
47     Extension,
48 };
49
50 static ApplicationType applicationType(UIWindow *window)
51 {
52     ASSERT(window);
53
54     if (_UIApplicationIsExtension())
55         return ApplicationType::Extension;
56
57     if (processHasEntitlement(@"com.apple.UIKit.vends-view-services") && window._isHostedInAnotherProcess)
58         return ApplicationType::ViewService;
59
60     return ApplicationType::Application;
61 }
62
63 static bool isBackgroundState(BKSApplicationState state)
64 {
65     switch (state) {
66     case BKSApplicationStateBackgroundRunning:
67     case BKSApplicationStateBackgroundTaskSuspended:
68         return true;
69
70     default:
71         return false;
72     }
73 }
74
75 ApplicationStateTracker::ApplicationStateTracker(UIView *view, SEL didEnterBackgroundSelector, SEL didCreateWindowContextSelector, SEL didFinishSnapshottingAfterEnteringBackgroundSelector, SEL willEnterForegroundSelector)
76     : m_view(view)
77     , m_didEnterBackgroundSelector(didEnterBackgroundSelector)
78     , m_didCreateWindowContextSelector(didCreateWindowContextSelector)
79     , m_didFinishSnapshottingAfterEnteringBackgroundSelector(didFinishSnapshottingAfterEnteringBackgroundSelector)
80     , m_willEnterForegroundSelector(willEnterForegroundSelector)
81     , m_isInBackground(true)
82     , m_weakPtrFactory(this)
83     , m_didEnterBackgroundObserver(nullptr)
84     , m_didCreateWindowContextObserver(nullptr)
85     , m_didFinishSnapshottingAfterEnteringBackgroundObserver(nullptr)
86     , m_willEnterForegroundObserver(nullptr)
87 {
88     ASSERT([m_view.get() respondsToSelector:m_didEnterBackgroundSelector]);
89     ASSERT([m_view.get() respondsToSelector:m_didCreateWindowContextSelector]);
90     ASSERT([m_view.get() respondsToSelector:m_didFinishSnapshottingAfterEnteringBackgroundSelector]);
91     ASSERT([m_view.get() respondsToSelector:m_willEnterForegroundSelector]);
92
93     UIWindow *window = [m_view.get() window];
94     ASSERT(window);
95
96     NSNotificationCenter *notificationCenter = [NSNotificationCenter defaultCenter];
97     auto weakThis = m_weakPtrFactory.createWeakPtr();
98     m_didCreateWindowContextObserver = [notificationCenter addObserverForName:@"_UIWindowDidCreateWindowContextNotification" object:window queue:nil usingBlock:[weakThis](NSNotification *) {
99         auto applicationStateTracker = weakThis.get();
100         if (!applicationStateTracker)
101             return;
102         applicationStateTracker->applicationDidCreateWindowContext();
103     }];
104
105     m_didFinishSnapshottingAfterEnteringBackgroundObserver = [notificationCenter addObserverForName:@"_UIWindowWillDestroyWindowContextNotification" object:window queue:nil usingBlock:[weakThis](NSNotification *) {
106         auto applicationStateTracker = weakThis.get();
107         if (!applicationStateTracker)
108             return;
109         applicationStateTracker->applicationDidFinishSnapshottingAfterEnteringBackground();
110     }];
111
112     switch (applicationType(window)) {
113     case ApplicationType::Application: {
114         UIApplication *application = [UIApplication sharedApplication];
115
116         m_isInBackground = application.applicationState == UIApplicationStateBackground;
117
118         m_didEnterBackgroundObserver = [notificationCenter addObserverForName:UIApplicationDidEnterBackgroundNotification object:application queue:nil usingBlock:[this](NSNotification *) {
119             applicationDidEnterBackground();
120         }];
121
122         m_willEnterForegroundObserver = [notificationCenter addObserverForName:UIApplicationWillEnterForegroundNotification object:application queue:nil usingBlock:[this](NSNotification *) {
123             applicationWillEnterForeground();
124         }];
125         break;
126     }
127
128     case ApplicationType::ViewService: {
129         UIViewController *serviceViewController = nil;
130
131         for (UIView *view = m_view.get().get(); view; view = view.superview) {
132             UIViewController *viewController = [UIViewController viewControllerForView:view];
133
134             if (viewController._hostProcessIdentifier) {
135                 serviceViewController = viewController;
136                 break;
137             }
138         }
139
140         ASSERT(serviceViewController);
141
142         pid_t applicationPID = serviceViewController._hostProcessIdentifier;
143         ASSERT(applicationPID);
144
145         auto applicationStateMonitor = adoptNS([[BKSApplicationStateMonitor alloc] init]);
146         m_isInBackground = isBackgroundState([applicationStateMonitor mostElevatedApplicationStateForPID:applicationPID]);
147         [applicationStateMonitor invalidate];
148
149         // Workaround for <rdar://problem/34028921>. If the host application is StoreKitUIService then it is also a ViewService
150         // and is always in the background. We need to treat StoreKitUIService as foreground for the purpose of process suspension
151         // or its ViewServices will get suspended.
152         if ([serviceViewController._hostApplicationBundleIdentifier isEqualToString:@"com.apple.ios.StoreKitUIService"])
153             m_isInBackground = false;
154
155         m_didEnterBackgroundObserver = [notificationCenter addObserverForName:@"_UIViewServiceHostDidEnterBackgroundNotification" object:serviceViewController queue:nil usingBlock:[this](NSNotification *) {
156             applicationDidEnterBackground();
157         }];
158         m_willEnterForegroundObserver = [notificationCenter addObserverForName:@"_UIViewServiceHostWillEnterForegroundNotification" object:serviceViewController queue:nil usingBlock:[this](NSNotification *) {
159             applicationWillEnterForeground();
160         }];
161
162         break;
163     }
164
165     case ApplicationType::Extension: {
166         m_applicationStateMonitor = adoptNS([[BKSApplicationStateMonitor alloc] init]);
167
168         m_isInBackground = isBackgroundState([m_applicationStateMonitor mostElevatedApplicationStateForPID:getpid()]);
169
170         [m_applicationStateMonitor setHandler:[weakThis](NSDictionary *userInfo) {
171             pid_t pid = [userInfo[BKSApplicationStateProcessIDKey] integerValue];
172             if (pid != getpid())
173                 return;
174
175             BKSApplicationState newState = (BKSApplicationState)[userInfo[BKSApplicationStateMostElevatedStateForProcessIDKey] unsignedIntValue];
176             bool newInBackground = isBackgroundState(newState);
177
178             dispatch_async(dispatch_get_main_queue(), [weakThis, newInBackground] {
179                 auto applicationStateTracker = weakThis.get();
180                 if (!applicationStateTracker)
181                     return;
182
183                 if (!applicationStateTracker->m_isInBackground && newInBackground)
184                     applicationStateTracker->applicationDidEnterBackground();
185                 else if (applicationStateTracker->m_isInBackground && !newInBackground)
186                     applicationStateTracker->applicationWillEnterForeground();
187             });
188         }];
189     }
190     }
191 }
192
193 ApplicationStateTracker::~ApplicationStateTracker()
194 {
195     if (m_applicationStateMonitor) {
196         [m_applicationStateMonitor invalidate];
197         return;
198     }
199
200     NSNotificationCenter *notificationCenter = [NSNotificationCenter defaultCenter];
201     [notificationCenter removeObserver:m_didEnterBackgroundObserver];
202     [notificationCenter removeObserver:m_didCreateWindowContextObserver];
203     [notificationCenter removeObserver:m_didFinishSnapshottingAfterEnteringBackgroundObserver];
204     [notificationCenter removeObserver:m_willEnterForegroundObserver];
205 }
206
207 void ApplicationStateTracker::applicationDidEnterBackground()
208 {
209     m_isInBackground = true;
210
211     if (auto view = m_view.get())
212         wtfObjcMsgSend<void>(view.get(), m_didEnterBackgroundSelector);
213 }
214
215 void ApplicationStateTracker::applicationDidCreateWindowContext()
216 {
217     if (auto view = m_view.get())
218         wtfObjcMsgSend<void>(view.get(), m_didCreateWindowContextSelector);
219 }
220
221 void ApplicationStateTracker::applicationDidFinishSnapshottingAfterEnteringBackground()
222 {
223     if (auto view = m_view.get())
224         wtfObjcMsgSend<void>(view.get(), m_didFinishSnapshottingAfterEnteringBackgroundSelector);
225 }
226
227 void ApplicationStateTracker::applicationWillEnterForeground()
228 {
229     m_isInBackground = false;
230
231     if (auto view = m_view.get())
232         wtfObjcMsgSend<void>(view.get(), m_willEnterForegroundSelector);
233 }
234
235 }
236
237 #endif