beefc7bc1f3ba39f977b7a34432ba6a010de85f6
[WebKit.git] / Source / WebKit / WebProcess / cocoa / WebProcessCocoa.mm
1 /*
2  * Copyright (C) 2010-2017 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 "WebProcess.h"
28 #import "WebProcessCocoa.h"
29
30 #import "LegacyCustomProtocolManager.h"
31 #import "Logging.h"
32 #import "ObjCObjectGraph.h"
33 #import <runtime/ConfigFile.h>
34 #import "SandboxExtension.h"
35 #import "SandboxInitializationParameters.h"
36 #import "SecItemShim.h"
37 #import "SessionTracker.h"
38 #import "WKAPICast.h"
39 #import "WKBrowsingContextHandleInternal.h"
40 #import "WKFullKeyboardAccessWatcher.h"
41 #import "WKTypeRefWrapper.h"
42 #import "WKWebProcessPlugInBrowserContextControllerInternal.h"
43 #import "WebFrame.h"
44 #import "WebInspector.h"
45 #import "WebPage.h"
46 #import "WebProcessCreationParameters.h"
47 #import "WebProcessProxyMessages.h"
48 #import "WebsiteDataStoreParameters.h"
49 #import <JavaScriptCore/Options.h>
50 #import <WebCore/AXObjectCache.h>
51 #import <WebCore/CFNetworkSPI.h>
52 #import <WebCore/CPUMonitor.h>
53 #import <WebCore/FileSystem.h>
54 #import <WebCore/FontCache.h>
55 #import <WebCore/FontCascade.h>
56 #import <WebCore/LocalizedStrings.h>
57 #import <WebCore/MemoryRelease.h>
58 #import <WebCore/NSAccessibilitySPI.h>
59 #import <WebCore/PerformanceLogging.h>
60 #import <WebCore/QuartzCoreSPI.h>
61 #import <WebCore/RuntimeApplicationChecks.h>
62 #import <WebCore/WebCoreNSURLExtras.h>
63 #import <WebCore/pthreadSPI.h>
64 #import <WebKitSystemInterface.h>
65 #import <algorithm>
66 #import <dispatch/dispatch.h>
67 #import <objc/runtime.h>
68 #import <stdio.h>
69
70 #if PLATFORM(IOS)
71 #import "CelestialSPI.h"
72 #import <WebCore/GraphicsServicesSPI.h>
73 #import <wtf/SoftLinking.h>
74 #endif
75
76 #if USE(OS_STATE)
77 #import <os/state_private.h>
78 #endif
79
80 #if PLATFORM(IOS)
81 SOFT_LINK_PRIVATE_FRAMEWORK_OPTIONAL(Celestial)
82
83 SOFT_LINK_CLASS_OPTIONAL(Celestial, AVSystemController)
84
85 SOFT_LINK_CONSTANT_MAY_FAIL(Celestial, AVSystemController_PIDToInheritApplicationStateFrom, NSString *)
86
87 #define AVSystemController_PIDToInheritApplicationStateFrom getAVSystemController_PIDToInheritApplicationStateFrom()
88 #endif
89
90 using namespace WebCore;
91
92 namespace WebKit {
93
94 #if PLATFORM(MAC)
95 static const Seconds cpuMonitoringInterval { 8_min };
96 #endif
97
98 void WebProcess::platformSetCacheModel(CacheModel)
99 {
100 }
101
102 #if USE(APPKIT)
103 static id NSApplicationAccessibilityFocusedUIElement(NSApplication*, SEL)
104 {
105     WebPage* page = WebProcess::singleton().focusedWebPage();
106     if (!page || !page->accessibilityRemoteObject())
107         return 0;
108
109     return [page->accessibilityRemoteObject() accessibilityFocusedUIElement];
110 }
111 #endif
112
113 void WebProcess::platformInitializeWebProcess(WebProcessCreationParameters&& parameters)
114 {
115     WebCore::setApplicationBundleIdentifier(parameters.uiProcessBundleIdentifier);
116     SessionTracker::setIdentifierBase(parameters.uiProcessBundleIdentifier);
117
118 #if ENABLE(SANDBOX_EXTENSIONS)
119     SandboxExtension::consumePermanently(parameters.uiProcessBundleResourcePathExtensionHandle);
120     SandboxExtension::consumePermanently(parameters.webSQLDatabaseDirectoryExtensionHandle);
121     SandboxExtension::consumePermanently(parameters.applicationCacheDirectoryExtensionHandle);
122     SandboxExtension::consumePermanently(parameters.mediaCacheDirectoryExtensionHandle);
123     SandboxExtension::consumePermanently(parameters.mediaKeyStorageDirectoryExtensionHandle);
124     SandboxExtension::consumePermanently(parameters.javaScriptConfigurationDirectoryExtensionHandle);
125 #if ENABLE(MEDIA_STREAM)
126     SandboxExtension::consumePermanently(parameters.audioCaptureExtensionHandle);
127 #endif
128 #if PLATFORM(IOS)
129     SandboxExtension::consumePermanently(parameters.cookieStorageDirectoryExtensionHandle);
130     SandboxExtension::consumePermanently(parameters.containerCachesDirectoryExtensionHandle);
131     SandboxExtension::consumePermanently(parameters.containerTemporaryDirectoryExtensionHandle);
132 #endif
133 #endif
134
135     if (!parameters.javaScriptConfigurationDirectory.isEmpty()) {
136         String javaScriptConfigFile = parameters.javaScriptConfigurationDirectory + "/JSC.config";
137         JSC::processConfigFile(javaScriptConfigFile.latin1().data(), "com.apple.WebKit.WebContent", parameters.uiProcessBundleIdentifier.latin1().data());
138     }
139
140 #if PLATFORM(MAC)
141     setSharedHTTPCookieStorage(parameters.uiProcessCookieStorageIdentifier);
142 #endif
143
144     auto urlCache = adoptNS([[NSURLCache alloc] initWithMemoryCapacity:0 diskCapacity:0 diskPath:nil]);
145     [NSURLCache setSharedURLCache:urlCache.get()];
146
147 #if PLATFORM(MAC)
148     WebCore::FontCache::setFontWhitelist(parameters.fontWhitelist);
149 #endif
150
151     m_compositingRenderServerPort = WTFMove(parameters.acceleratedCompositingPort);
152
153     WebCore::registerMemoryReleaseNotifyCallbacks();
154     MemoryPressureHandler::ReliefLogger::setLoggingEnabled(parameters.shouldEnableMemoryPressureReliefLogging);
155
156     setEnhancedAccessibility(parameters.accessibilityEnhancedUserInterfaceEnabled);
157
158 #if USE(APPKIT)
159     [[NSUserDefaults standardUserDefaults] registerDefaults:@{ @"NSApplicationCrashOnExceptions" : @YES }];
160
161     // rdar://9118639 accessibilityFocusedUIElement in NSApplication defaults to use the keyWindow. Since there's
162     // no window in WK2, NSApplication needs to use the focused page's focused element.
163     Method methodToPatch = class_getInstanceMethod([NSApplication class], @selector(accessibilityFocusedUIElement));
164     method_setImplementation(methodToPatch, (IMP)NSApplicationAccessibilityFocusedUIElement);
165 #endif
166     _CFNetworkSetATSContext(parameters.networkATSContext.get());
167
168 #if TARGET_OS_IPHONE
169     // Priority decay on iOS 9 is impacting page load time so we fix the priority of the WebProcess' main thread (rdar://problem/22003112).
170     pthread_set_fixedpriority_self();
171 #endif
172
173 #if PLATFORM(IOS)
174     if (canLoadAVSystemController_PIDToInheritApplicationStateFrom()) {
175         pid_t pid = WebCore::presentingApplicationPID();
176         NSError *error = nil;
177         [[getAVSystemControllerClass() sharedAVSystemController] setAttribute:@(pid) forKey:AVSystemController_PIDToInheritApplicationStateFrom error:&error];
178         if (error)
179             WTFLogAlways("Failed to set up PID proxying: %s", [[error localizedDescription] UTF8String]);
180     }
181 #endif
182 }
183
184 void WebProcess::initializeProcessName(const ChildProcessInitializationParameters& parameters)
185 {
186 #if !PLATFORM(IOS)
187     NSString *applicationName;
188     if (parameters.extraInitializationData.get(ASCIILiteral("inspector-process")) == "1")
189         applicationName = [NSString stringWithFormat:WEB_UI_STRING("%@ Web Inspector", "Visible name of Web Inspector's web process. The argument is the application name."), (NSString *)parameters.uiProcessName];
190     else
191         applicationName = [NSString stringWithFormat:WEB_UI_STRING("%@ Web Content", "Visible name of the web process. The argument is the application name."), (NSString *)parameters.uiProcessName];
192     WKSetVisibleApplicationName((CFStringRef)applicationName);
193 #endif
194 }
195
196 static void registerWithAccessibility()
197 {
198 #if USE(APPKIT)
199     [NSAccessibilityRemoteUIElement setRemoteUIApp:YES];
200 #endif
201 #if PLATFORM(IOS)
202     NSString *accessibilityBundlePath = [(NSString *)GSSystemRootDirectory() stringByAppendingString:@"/System/Library/AccessibilityBundles/WebProcessLoader.axbundle"];
203     NSError *error = nil;
204     if (![[NSBundle bundleWithPath:accessibilityBundlePath] loadAndReturnError:&error])
205         LOG_ERROR("Failed to load accessibility bundle at %@: %@", accessibilityBundlePath, error);
206 #endif
207 }
208
209 #if USE(OS_STATE)
210 void WebProcess::registerWithStateDumper()
211 {
212     os_state_add_handler(dispatch_get_main_queue(), ^(os_state_hints_t hints) {
213
214         @autoreleasepool {
215             os_state_data_t os_state = nil;
216
217             // Only gather state on faults and sysdiagnose. It's overkill for
218             // general error messages.
219             if (hints->osh_api == OS_STATE_API_ERROR)
220                 return os_state;
221
222             // Create a dictionary to contain the collected state. This
223             // dictionary will be serialized and passed back to os_state.
224             auto stateDict = adoptNS([[NSMutableDictionary alloc] init]);
225
226             {
227                 auto memoryUsageStats = adoptNS([[NSMutableDictionary alloc] init]);
228                 for (auto& it : PerformanceLogging::memoryUsageStatistics(ShouldIncludeExpensiveComputations::Yes)) {
229                     auto keyString = adoptNS([[NSString alloc] initWithUTF8String:it.key]);
230                     [memoryUsageStats setObject:@(it.value) forKey:keyString.get()];
231                 }
232                 [stateDict setObject:memoryUsageStats.get() forKey:@"Memory Usage Stats"];
233             }
234
235             {
236                 auto jsObjectCounts = adoptNS([[NSMutableDictionary alloc] init]);
237                 for (auto& it : PerformanceLogging::javaScriptObjectCounts()) {
238                     auto keyString = adoptNS([[NSString alloc] initWithUTF8String:it.key]);
239                     [jsObjectCounts setObject:@(it.value) forKey:keyString.get()];
240                 }
241                 [stateDict setObject:jsObjectCounts.get() forKey:@"JavaScript Object Counts"];
242             }
243
244             auto pageLoadTimes = adoptNS([[NSMutableArray alloc] init]);
245             for (auto& page : m_pageMap.values()) {
246                 if (page->usesEphemeralSession())
247                     continue;
248
249                 NSDate* date = [NSDate dateWithTimeIntervalSince1970:std::chrono::system_clock::to_time_t(page->loadCommitTime())];
250                 [pageLoadTimes addObject:date];
251             }
252
253             // Adding an empty array to the process state may provide an
254             // indication of the existance of private sessions, which we'd like
255             // to hide, so don't add empty arrays.
256             if ([pageLoadTimes count])
257                 [stateDict setObject:pageLoadTimes.get() forKey:@"Page Load Times"];
258
259             // --- Possibly add other state here as other entries in the dictionary. ---
260
261             // Submitting an empty process state object may provide an
262             // indication of the existance of private sessions, which we'd like
263             // to hide, so don't return empty dictionaries.
264             if (![stateDict count])
265                 return os_state;
266
267             // Serialize the accumulated process state so that we can put the
268             // result in an os_state_data_t structure.
269             NSError* error = nil;
270             NSData* data = [NSPropertyListSerialization dataWithPropertyList:stateDict.get() format:NSPropertyListBinaryFormat_v1_0 options:0 error:&error];
271
272             if (!data) {
273                 ASSERT(data);
274                 return os_state;
275             }
276
277             size_t neededSize = OS_STATE_DATA_SIZE_NEEDED(data.length);
278             os_state = (os_state_data_t)malloc(neededSize);
279             if (os_state) {
280                 memset(os_state, 0, neededSize);
281                 os_state->osd_type = OS_STATE_DATA_SERIALIZED_NSCF_OBJECT;
282                 os_state->osd_data_size = data.length;
283                 strlcpy(os_state->osd_title, "WebContent state", sizeof(os_state->osd_title));
284                 memcpy(os_state->osd_data, data.bytes, data.length);
285             }
286
287             return os_state;
288         }
289     });
290 }
291 #endif
292
293 void WebProcess::platformInitializeProcess(const ChildProcessInitializationParameters&)
294 {
295     registerWithAccessibility();
296
297 #if USE(OS_STATE)
298     registerWithStateDumper();
299 #endif
300
301 #if ENABLE(SEC_ITEM_SHIM)
302     initializeSecItemShim(*this);
303 #endif
304 }
305
306 #if USE(APPKIT)
307 void WebProcess::stopRunLoop()
308 {
309     ChildProcess::stopNSAppRunLoop();
310 }
311 #endif
312
313 void WebProcess::platformTerminate()
314 {
315 }
316
317 RetainPtr<CFDataRef> WebProcess::sourceApplicationAuditData() const
318 {
319 #if PLATFORM(IOS)
320     audit_token_t auditToken;
321     ASSERT(parentProcessConnection());
322     if (!parentProcessConnection() || !parentProcessConnection()->getAuditToken(auditToken))
323         return nullptr;
324     return adoptCF(CFDataCreate(nullptr, (const UInt8*)&auditToken, sizeof(auditToken)));
325 #else
326     return nullptr;
327 #endif
328 }
329
330 void WebProcess::initializeSandbox(const ChildProcessInitializationParameters& parameters, SandboxInitializationParameters& sandboxParameters)
331 {
332 #if ENABLE(WEB_PROCESS_SANDBOX)
333 #if ENABLE(MANUAL_SANDBOXING)
334     // Need to override the default, because service has a different bundle ID.
335 #if WK_API_ENABLED
336     NSBundle *webKit2Bundle = [NSBundle bundleForClass:NSClassFromString(@"WKWebView")];
337 #else
338     NSBundle *webKit2Bundle = [NSBundle bundleForClass:NSClassFromString(@"WKView")];
339 #endif
340 #if PLATFORM(IOS)
341     sandboxParameters.setOverrideSandboxProfilePath([webKit2Bundle pathForResource:@"com.apple.WebKit.WebContent" ofType:@"sb"]);
342 #else
343     sandboxParameters.setOverrideSandboxProfilePath([webKit2Bundle pathForResource:@"com.apple.WebProcess" ofType:@"sb"]);
344 #endif
345     ChildProcess::initializeSandbox(parameters, sandboxParameters);
346 #endif
347 #else
348     UNUSED_PARAM(parameters);
349     UNUSED_PARAM(sandboxParameters);
350 #endif
351 }
352
353 #if PLATFORM(MAC)
354
355 static NSURL *origin(WebPage& page)
356 {
357     WebFrame* mainFrame = page.mainWebFrame();
358     if (!mainFrame)
359         return nil;
360
361     URL mainFrameURL(URL(), mainFrame->url());
362     Ref<SecurityOrigin> mainFrameOrigin = SecurityOrigin::create(mainFrameURL);
363     String mainFrameOriginString;
364     if (!mainFrameOrigin->isUnique())
365         mainFrameOriginString = mainFrameOrigin->toRawString();
366     else
367         mainFrameOriginString = makeString(mainFrameURL.protocol(), ':'); // toRawString() is not supposed to work with unique origins, and would just return "://".
368
369     // +[NSURL URLWithString:] returns nil when its argument is malformed. It's unclear when we would have a malformed URL here,
370     // but it happens in practice according to <rdar://problem/14173389>. Leaving an assertion in to catch a reproducible case.
371     ASSERT([NSURL URLWithString:mainFrameOriginString]);
372
373     return [NSURL URLWithString:mainFrameOriginString];
374 }
375
376 #endif
377
378 void WebProcess::updateActivePages()
379 {
380 #if PLATFORM(MAC)
381     auto activePageURLs = adoptNS([[NSMutableArray alloc] init]);
382
383     for (auto& page : m_pageMap.values()) {
384         if (page->usesEphemeralSession())
385             continue;
386
387         if (NSURL *originAsURL = origin(*page))
388             [activePageURLs addObject:userVisibleString(originAsURL)];
389     }
390
391     dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), [activePageURLs] {
392         WKSetApplicationInformationItem(CFSTR("LSActivePageUserVisibleOriginsKey"), (__bridge CFArrayRef)activePageURLs.get());
393     });
394 #endif
395 }
396
397 void WebProcess::updateCPULimit()
398 {
399 #if PLATFORM(MAC)
400     std::optional<double> cpuLimit;
401
402     // Use the largest limit among all pages in this process.
403     for (auto& page : m_pageMap.values()) {
404         auto pageCPULimit = page->cpuLimit();
405         if (!pageCPULimit) {
406             cpuLimit = std::nullopt;
407             break;
408         }
409         if (!cpuLimit || pageCPULimit > cpuLimit.value())
410             cpuLimit = pageCPULimit;
411     }
412
413     if (m_cpuLimit == cpuLimit)
414         return;
415
416     m_cpuLimit = cpuLimit;
417     updateCPUMonitorState(CPUMonitorUpdateReason::LimitHasChanged);
418 #endif
419 }
420
421 void WebProcess::updateCPUMonitorState(CPUMonitorUpdateReason reason)
422 {
423 #if PLATFORM(MAC)
424     if (!m_cpuLimit) {
425         if (m_cpuMonitor)
426             m_cpuMonitor->setCPULimit(std::nullopt);
427         return;
428     }
429
430     if (!m_cpuMonitor) {
431         m_cpuMonitor = std::make_unique<CPUMonitor>(cpuMonitoringInterval, [this](double cpuUsage) {
432             RELEASE_LOG(PerformanceLogging, "%p - WebProcess exceeded CPU limit of %.1f%% (was using %.1f%%) hasVisiblePages? %d", this, m_cpuLimit.value() * 100, cpuUsage * 100, hasVisibleWebPage());
433             parentProcessConnection()->send(Messages::WebProcessProxy::DidExceedCPULimit(), 0);
434         });
435     } else if (reason == CPUMonitorUpdateReason::VisibilityHasChanged) {
436         // If the visibility has changed, stop the CPU monitor before setting its limit. This is needed because the CPU usage can vary wildly based on visibility and we would
437         // not want to report that a process has exceeded its background CPU limit even though most of the CPU time was used while the process was visible.
438         m_cpuMonitor->setCPULimit(std::nullopt);
439     }
440     m_cpuMonitor->setCPULimit(m_cpuLimit.value());
441 #else
442     UNUSED_PARAM(reason);
443 #endif
444 }
445
446 RefPtr<ObjCObjectGraph> WebProcess::transformHandlesToObjects(ObjCObjectGraph& objectGraph)
447 {
448     struct Transformer final : ObjCObjectGraph::Transformer {
449         Transformer(WebProcess& webProcess)
450             : m_webProcess(webProcess)
451         {
452         }
453
454         bool shouldTransformObject(id object) const override
455         {
456 #if WK_API_ENABLED
457             if (dynamic_objc_cast<WKBrowsingContextHandle>(object))
458                 return true;
459
460             if (dynamic_objc_cast<WKTypeRefWrapper>(object))
461                 return true;
462 #endif
463             return false;
464         }
465
466         RetainPtr<id> transformObject(id object) const override
467         {
468 #if WK_API_ENABLED
469             if (auto* handle = dynamic_objc_cast<WKBrowsingContextHandle>(object)) {
470                 if (auto* webPage = m_webProcess.webPage(handle._pageID))
471                     return wrapper(*webPage);
472
473                 return [NSNull null];
474             }
475
476             if (auto* wrapper = dynamic_objc_cast<WKTypeRefWrapper>(object))
477                 return adoptNS([[WKTypeRefWrapper alloc] initWithObject:toAPI(m_webProcess.transformHandlesToObjects(toImpl(wrapper.object)).get())]);
478 #endif
479             return object;
480         }
481
482         WebProcess& m_webProcess;
483     };
484
485     return ObjCObjectGraph::create(ObjCObjectGraph::transform(objectGraph.rootObject(), Transformer(*this)).get());
486 }
487
488 RefPtr<ObjCObjectGraph> WebProcess::transformObjectsToHandles(ObjCObjectGraph& objectGraph)
489 {
490     struct Transformer final : ObjCObjectGraph::Transformer {
491         bool shouldTransformObject(id object) const override
492         {
493 #if WK_API_ENABLED
494             if (dynamic_objc_cast<WKWebProcessPlugInBrowserContextController>(object))
495                 return true;
496
497             if (dynamic_objc_cast<WKTypeRefWrapper>(object))
498                 return true;
499 #endif
500
501             return false;
502         }
503
504         RetainPtr<id> transformObject(id object) const override
505         {
506 #if WK_API_ENABLED
507             if (auto* controller = dynamic_objc_cast<WKWebProcessPlugInBrowserContextController>(object))
508                 return controller.handle;
509
510             if (auto* wrapper = dynamic_objc_cast<WKTypeRefWrapper>(object))
511                 return adoptNS([[WKTypeRefWrapper alloc] initWithObject:toAPI(transformObjectsToHandles(toImpl(wrapper.object)).get())]);
512 #endif
513             return object;
514         }
515     };
516
517     return ObjCObjectGraph::create(ObjCObjectGraph::transform(objectGraph.rootObject(), Transformer()).get());
518 }
519
520 void WebProcess::destroyRenderingResources()
521 {
522 #if !RELEASE_LOG_DISABLED
523     double startTime = monotonicallyIncreasingTime();
524 #endif
525     CABackingStoreCollectBlocking();
526 #if !RELEASE_LOG_DISABLED
527     double endTime = monotonicallyIncreasingTime();
528 #endif
529     RELEASE_LOG(ProcessSuspension, "%p - WebProcess::destroyRenderingResources() took %.2fms", this, (endTime - startTime) * 1000.0);
530 }
531
532 // FIXME: This should live somewhere else, and it should have the implementation in line instead of calling out to WKSI.
533 void _WKSetCrashReportApplicationSpecificInformation(NSString *infoString)
534 {
535     return WKSetCrashReportApplicationSpecificInformation((__bridge CFStringRef)infoString);
536 }
537
538 } // namespace WebKit