WebKit源码十分庞大,漫无目的去看代码,可能会傻傻搞不清,所以需要带着问题调试源码
Q0.WKWebview生命周期是什么
WKWebView的代理是WKNavigationDelegate,查看其接口代码:
// 决定是否继续加载webview
// 可以立即调用,或者异步调用decisionHandler(WKNavigationActionPolicyAllow) 允许加载
- (void)webView:(WKWebView *)webView decidePolicyForNavigationAction:(WKNavigationAction *)navigationAction decisionHandler:(void (^)(WKNavigationActionPolicy))decisionHandler WK_SWIFT_ASYNC(3);
- (void)webView:(WKWebView *)webView decidePolicyForNavigationAction:(WKNavigationAction *)navigationAction preferences:(WKWebpagePreferences *)preferences decisionHandler:(void (^)(WKNavigationActionPolicy, WKWebpagePreferences *))decisionHandler WK_SWIFT_ASYNC(4) WK_API_AVAILABLE(macos(10.15), ios(13.0));
// 获得http header数据后会回调,是否继续加载webview
- (void)webView:(WKWebView *)webView decidePolicyForNavigationResponse:(WKNavigationResponse *)navigationResponse decisionHandler:(void (^)(WKNavigationResponsePolicy))decisionHandler WK_SWIFT_ASYNC(3);
// webview中开始加载的时候,真正发出请求时回调
- (void)webView:(WKWebView *)webView didStartProvisionalNavigation:(null_unspecified WKNavigation *)navigation;
// 重定向时调用
- (void)webView:(WKWebView *)webView didReceiveServerRedirectForProvisionalNavigation:(null_unspecified WKNavigation *)navigation;
// 开始加载数据时出错
- (void)webView:(WKWebView *)webView didFailProvisionalNavigation:(null_unspecified WKNavigation *)navigation withError:(NSError *)error;
// 获取http body并回调
- (void)webView:(WKWebView *)webView didCommitNavigation:(null_unspecified WKNavigation *)navigation;
// 页面加载完成时调用
- (void)webView:(WKWebView *)webView didFinishNavigation:(null_unspecified WKNavigation *)navigation;
// 跳转失败时调用
- (void)webView:(WKWebView *)webView didFailNavigation:(null_unspecified WKNavigation *)navigation withError:(NSError *)error;
// 证书校验
- (void)webView:(WKWebView *)webView didReceiveAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition disposition, NSURLCredential * _Nullable credential))completionHandler WK_SWIFT_ASYNC_NAME(webView(_:respondTo:));
// webview进程终止时触发
- (void)webViewWebContentProcessDidTerminate:(WKWebView *)webView WK_API_AVAILABLE(macos(10.11), ios(9.0));
// 使用已弃用的TLS(Transport Layer Security )版本建立网络连接时调用
- (void)webView:(WKWebView *)webView authenticationChallenge:(NSURLAuthenticationChallenge *)challenge shouldAllowDeprecatedTLS:(void (^)(BOOL))decisionHandler WK_SWIFT_ASYNC_NAME(webView(_:shouldAllowDeprecatedTLSFor:)) WK_SWIFT_ASYNC(3) WK_API_AVAILABLE(macos(11.0), ios(14.0));
// web操作变成下载
- (void)webView:(WKWebView *)webView navigationAction:(WKNavigationAction *)navigationAction didBecomeDownload:(WKDownload *)download WK_API_AVAILABLE(macos(11.3), ios(14.5));
// web响应变成下载
- (void)webView:(WKWebView *)webView navigationResponse:(WKNavigationResponse *)navigationResponse didBecomeDownload:(WKDownload *)download WK_API_AVAILABLE(macos(11.3), ios(14.5));
写一个简单的demo,在WKNavigationDelegate回调上加上日志,结果如下:
---func---: decidePolicyForNavigationAction decisionHandler
---func---: didStartProvisionalNavigation
---func---: decidePolicyForNavigationResponse decisionHandler
---func---: didCommitNavigation
---func---: didFinishNavigation
主要流程为:
1.判断是否可以加载
2.开始加载
3.判断回报是否可以加载
4.接收数据
5.接收完成
Q1.WebKit是什么?
WebKit is an open-source Web browser engine. It’s a framework in macOS and iOS, and used by many first party and third party applications including Safari, Mail, Notes, Books, News, and App Store.
The WebKit codebase is mostly written in C++ with bits of C and assembly, primarily in JavaScriptCore, and some Objective-C to integrate with Cocoa platforms.
It primarily consists of the following components, each inside its own directory in Source:
- bmalloc - WebKit’s malloc implementation as a bump pointer allocator. It provides an important security feature, called IsoHeap, which segregates each type of object into its own page to prevent type confusion attacks upon use-after-free.
- WTF - Stands for Web Template Framework. WebKit’s template library. The rest of the WebKit codebase is built using this template library in addition to, and often in place of, similar class templates in the C++ standard library. It contains common container classes such as Vector, HashMap (unordered), HashSet, and smart pointer types such as Ref, RefPtr, and WeakPtr used throughout the rest of WebKit.
- JavaScriptCore - WebKit’s JavaScript engine; often abbreviated as JSC. JSC parses JavaScript and generates byte code, which is then executed by one of the following four tiers. Many tiers are needed to balance between compilation time and execution time. Also see Phil's blog post about Speculation in JavaScriptCore.
- Interpreter - This tier reads and executes instructions in byte code in C++.
- Baseline JIT - The first Just In Time compiler tier serves as the profiler as well as a significant speed up from the interpreter.
- DFG JIT - Data Flow Graph Just In Time compiler uses the data flow analysis to generate optimized machine code.
- FTL JIT - Faster than Light Just In Time compiler which uses B3 backend. It’s the fastest tier of JSC. JavaScriptCode also implements JavaScriptCore API for macOS and iOS applications.
-
WebCore - The largest component of WebKit, this layer implements most of the Web APIs and their behaviors. Most importantly, this component implements HTML, XML, and CSS parsers and implements HTML, SVG, and MathML elements as well as CSS. It also implements CSS JIT, the only Just In Time compiler for CSS in existence. It works with a few tree data structures:
- Document Object Model - This is the tree data structure we create from parsing HTML.
- Render Tree - This tree represents the visual representation of each element in DOM tree computed from CSS and also stores the geometric layout information of each element.
WebCore/PAL and WebCore/platform - Whilst technically a part of WebCore, this is a platform abstraction layer for WebCore so that the rest of WebCore code can remain platform independent / agnostic across all the platforms WebKit can run on: macOS, iOS, Windows, Linux, etc... Historically, most of this code resided in WebCore/platform. There is an ongoing multi-year project to slowly migrate code to PAL as we remove the reverse dependencies to WebCore.
WebKitLegacy (a.k.a. WebKit1) - This layer interfaces WebCore with the rest of operating systems in single process and implements WebView on macOS and UIWebView on iOS.
-
WebKit (a.k.a. WebKit2) - This layer implements the multi-process architecture of WebKit, and implements WKWebView on macOS and iOS. WebKit’s multi-process architecture consists of the following processes:
- UI process - This is the application process. e.g. Safari and Mail
- WebContent process - This process loads & runs code loaded from websites. Each tab in Safari typically has its own WebContent process. This is important to keep each tab responsive and protect websites from one another.
- Networking process - This process is responsible for handling network requests as well as storage management. All WebContent processes in a single session (default vs. private browsing) share a single networking session in the networking process.
WebInspector / WebDriver - WebKit’s developer tool & automation tool for Web developers.
Q2.在safari上输入now.qq.com到展示NOW直播主页的过程中,到底发生了些什么?
// 首先是初始化WKWebview
- (WKWebView *)createWebView
{
WKWebViewConfiguration *configuration = [[WKWebViewConfiguration alloc] init];
configuration.preferences._mockCaptureDevicesEnabled = YES;
WKWebView *webView = [[WKWebView alloc] initWithFrame:self.webViewContainer.bounds configuration:configuration];
webView.navigationDelegate = self;
webView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
[webView addObserver:self forKeyPath:@"title" options:NSKeyValueObservingOptionNew context:TitleContext];
return webView;
}
代码中初始化的时候引入了一个WKWebViewConfiguration的类,点进去查看,其注释为WebView属性的集合,其中有个WKUserContentController类型的属性,后面会用到。
先看wkwebview初始化的时候做了些什么:
- (instancetype)initWithFrame:(CGRect)frame configuration:(WKWebViewConfiguration *)configuration
{
// @interface WKWebView : NSView
if (!(self = [super initWithFrame:frame]))
return nil;
[self _initializeWithConfiguration:configuration];
return self;
}
- (void)_initializeWithConfiguration:(WKWebViewConfiguration *)configuration
{
// 判断configuration合法性
if (!configuration)
[NSException raise:NSInvalidArgumentException format:@"Configuration cannot be nil"];
if (!configuration.websiteDataStore)
[NSException raise:NSInvalidArgumentException format:@"Configuration websiteDataStore cannot be nil"];
_configuration = adoptNS([configuration copy]);
// TODO: 这里初始化的时候是nil,需要看下什么情况下会赋值
if (WKWebView *relatedWebView = [_configuration _relatedWebView]) {
WKProcessPool *processPool = [_configuration processPool];
WKProcessPool *relatedWebViewProcessPool = [relatedWebView->_configuration processPool];
if (processPool && processPool != relatedWebViewProcessPool)
[NSException raise:NSInvalidArgumentException format:@"Related web view %@ has process pool %@ but configuration specifies a different process pool %@", relatedWebView, relatedWebViewProcessPool, configuration.processPool];
if ([relatedWebView->_configuration websiteDataStore] != [_configuration websiteDataStore] && linkedOnOrAfterSDKWithBehavior(SDKAlignedBehavior::ExceptionsForRelatedWebViewsUsingDifferentDataStores))
[NSException raise:NSInvalidArgumentException format:@"Related web view %@ has data store %@ but configuration specifies a different data store %@", relatedWebView, [relatedWebView->_configuration websiteDataStore], [_configuration websiteDataStore]];
[_configuration setProcessPool:relatedWebViewProcessPool];
}
validate(_configuration.get());
// 这里引入了一个pageConfiguration
WebKit::WebProcessPool& processPool = *[_configuration processPool]->_processPool;
auto pageConfiguration = [configuration copyPageConfiguration];
pageConfiguration->setProcessPool(&processPool);
[self _setupPageConfiguration:pageConfiguration];
_usePlatformFindUI = YES;
#if PLATFORM(IOS_FAMILY)
// 初始化一些属性
_obscuredInsetEdgesAffectedBySafeArea = UIRectEdgeTop | UIRectEdgeLeft | UIRectEdgeRight;
_allowsViewportShrinkToFit = defaultAllowsViewportShrinkToFit;
_allowsLinkPreview = linkedOnOrAfterSDKWithBehavior(SDKAlignedBehavior::LinkPreviewEnabledByDefault);
_findInteractionEnabled = NO;
_needsToPresentLockdownModeMessage = YES;
auto fastClickingEnabled = []() {
if (NSNumber *enabledValue = [[NSUserDefaults standardUserDefaults] objectForKey:@"WebKitFastClickingDisabled"])
return enabledValue.boolValue;
return defaultFastClickingEnabled;
};
_fastClickingIsDisabled = fastClickingEnabled();
_dragInteractionPolicy = _WKDragInteractionPolicyDefault;
// 创建WKContentView
_contentView = adoptNS([[WKContentView alloc] initWithFrame:self.bounds processPool:processPool configuration:pageConfiguration.copyRef() webView:self]);
// 创建page
_page = [_contentView page];
// 添加到page管理类
[[_configuration _contentProviderRegistry] addPage:*_page];
// @interface WKWebView (WKViewInternalIOS) 分类实现
// 初始化WKScrollView
[self _setupScrollAndContentViews];
// 设置contentView和scrollView的背景色
if (!self.opaque || !pageConfiguration->drawsBackground())
[self _setOpaqueInternal:NO];
else
[self _updateScrollViewBackground];
// frame变化之后更新
[self _frameOrBoundsChanged];
// 注册一些常用事件,ex:_keyboardWillShow,_windowDidRotate
[self _registerForNotifications];
_page->contentSizeCategoryDidChange([self _contentSizeCategory]);
auto notificationName = adoptNS([[NSString alloc] initWithCString:kGSEventHardwareKeyboardAvailabilityChangedNotification encoding:NSUTF8StringEncoding]);
auto notificationBehavior = static_cast(CFNotificationSuspensionBehaviorCoalesce | _CFNotificationObserverIsObjC);
CFNotificationCenterAddObserver(CFNotificationCenterGetDarwinNotifyCenter(), (__bridge const void *)(self), hardwareKeyboardAvailabilityChangedCallback, (__bridge CFStringRef)notificationName.get(), nullptr, notificationBehavior);
#endif // PLATFORM(IOS_FAMILY)
#if ENABLE(META_VIEWPORT)
_page->setForceAlwaysUserScalable([_configuration ignoresViewportScaleLimits]);
#endif
// 下面都是为_page设置一些参数,至此webview初始化完成
// 此时的操作都还在我们的app进程中
if (NSString *applicationNameForUserAgent = configuration.applicationNameForUserAgent)
_page->setApplicationNameForUserAgent(applicationNameForUserAgent);
_page->setApplicationNameForDesktopUserAgent(configuration._applicationNameForDesktopUserAgent);
_navigationState = makeUnique(self);
_page->setNavigationClient(_navigationState->createNavigationClient());
_uiDelegate = makeUnique(self);
_page->setFindClient(makeUnique(self));
_page->setDiagnosticLoggingClient(makeUnique(self));
_iconLoadingDelegate = makeUnique(self);
_resourceLoadDelegate = makeUnique(self);
for (auto& pair : pageConfiguration->urlSchemeHandlers())
_page->setURLSchemeHandlerForScheme(pair.value.get(), pair.key);
_page->setCocoaView(self);
[WebViewVisualIdentificationOverlay installForWebViewIfNeeded:self kind:@"WKWebView" deprecated:NO];
#if PLATFORM(IOS_FAMILY)
auto timeNow = MonotonicTime::now();
_timeOfRequestForVisibleContentRectUpdate = timeNow;
_timeOfLastVisibleContentRectUpdate = timeNow;
_timeOfFirstVisibleContentRectUpdateWithPendingCommit = timeNow;
#endif
}
WKWebview的创建过程中,会先创建WKContentView,在WKContentView中通过WebProcessPool::createWebPage创建出WebProcessProxy对象,再执行process->createWebPage方法创建WebPageProxy(这里创建的process的时候会将它添加到WebProcessPool的Vector> m_processes变量中去):
1.WKContentView:
2.WebPageProxy
3.WebProcessPool
4.WebProcessProxy
// WKContentView.mm
- (instancetype)initWithFrame:(CGRect)frame processPool:(NakedRef)processPool configuration:(Ref&&)configuration webView:(WKWebView *)webView
{
if (!(self = [super initWithFrame:frame webView:webView]))
return nil;
WebKit::InitializeWebKit2();
_pageClient = makeUnique(self, webView);
_webView = webView;
return [self _commonInitializationWithProcessPool:processPool configuration:WTFMove(configuration)];
}
- (instancetype)_commonInitializationWithProcessPool:(WebKit::WebProcessPool&)processPool configuration:(Ref&&)configuration
{
...
// 创建WebPageProxy
_page = processPool.createWebPage(*_pageClient, WTFMove(configuration));
// WebPageProxy初始化参数的时候也会调用send方法
_page->initializeWebPage();
...
}
void WebPageProxy::initializeWebPage()
{
...
// 这里将create web page的ipc消息拼接到了m_pendingMessages中
send(Messages::WebProcess::CreateWebPage(m_webPageID, creationParameters(m_process, *m_drawingArea)), 0);
...
}
// WebProcess.cpp
void WebProcess::createWebPage(PageIdentifier pageID, WebPageCreationParameters&& parameters)
{
// It is necessary to check for page existence here since during a window.open() (or targeted
// link) the WebPage gets created both in the synchronous handler and through the normal way.
auto result = m_pageMap.add(pageID, nullptr);
if (result.isNewEntry) {
ASSERT(!result.iterator->value);
// 创建webpage
auto page = WebPage::create(pageID, WTFMove(parameters));
result.iterator->value = page.ptr();
#if ENABLE(GPU_PROCESS)
if (m_gpuProcessConnection)
page->gpuProcessConnectionDidBecomeAvailable(*m_gpuProcessConnection);
#endif
// Balanced by an enableTermination in removeWebPage.
disableTermination();
updateCPULimit();
#if OS(LINUX)
RealTimeThreads::singleton().setEnabled(hasVisibleWebPage());
#endif
} else
result.iterator->value->reinitializeWebPage(WTFMove(parameters));
ASSERT(result.iterator->value);
}
Ref WebPage::create(PageIdentifier pageID, WebPageCreationParameters&& parameters)
{
auto page = adoptRef(*new WebPage(pageID, WTFMove(parameters)));
if (WebProcess::singleton().injectedBundle())
WebProcess::singleton().injectedBundle()->didCreatePage(page.ptr());
#if HAVE(SANDBOX_STATE_FLAGS)
// This call is not meant to actually read a preference, but is only here to trigger a sandbox rule in the
// WebContent process, which will toggle a sandbox variable used to determine if the WebContent process
// has finished launching. This call should be replaced with proper API when available.
CFPreferencesGetAppIntegerValue(CFSTR("key"), CFSTR("com.apple.WebKit.WebContent.Launch"), nullptr);
#endif
return page;
}
webpage创建完成之后,回到WebPageProxy的代码继续看
// WebProcessPool.cpp
Ref WebProcessPool::createWebPage(PageClient& pageClient, Ref&& pageConfiguration)
{
...
RefPtr process;
auto captivePortalMode = pageConfiguration->captivePortalModeEnabled() ? WebProcessProxy::CaptivePortalMode::Enabled : WebProcessProxy::CaptivePortalMode::Disabled;
auto* relatedPage = pageConfiguration->relatedPage();
if (relatedPage && !relatedPage->isClosed()) {
// Sharing processes, e.g. when creating the page via window.open().
process = &pageConfiguration->relatedPage()->ensureRunningProcess();
// We do not support several WebsiteDataStores sharing a single process.
ASSERT(process->isDummyProcessProxy() || pageConfiguration->websiteDataStore() == process->websiteDataStore());
ASSERT(&pageConfiguration->relatedPage()->websiteDataStore() == pageConfiguration->websiteDataStore());
} else if (!m_isDelayedWebProcessLaunchDisabled) {
// In the common case, we delay process launch until something is actually loaded in the page.
process = dummyProcessProxy(pageConfiguration->websiteDataStore()->sessionID());
if (!process) {
process = WebProcessProxy::create(*this, pageConfiguration->websiteDataStore(), captivePortalMode, WebProcessProxy::IsPrewarmed::No, CrossOriginMode::Shared, WebProcessProxy::ShouldLaunchProcess::No);
m_dummyProcessProxies.add(pageConfiguration->websiteDataStore()->sessionID(), *process);
m_processes.append(*process);
}
} else
// 关键代码:当进程没有注册的时候,创建一个新的
process = processForRegistrableDomain(*pageConfiguration->websiteDataStore(), { }, captivePortalMode);
RefPtr userContentController = pageConfiguration->userContentController();
ASSERT(process);
auto page = process->createWebPage(pageClient, WTFMove(pageConfiguration));
if (!m_remoteWorkerPreferences) {
m_remoteWorkerPreferences = page->preferencesStore();
for (auto& workerProcess : remoteWorkerProcesses())
workerProcess.updateRemoteWorkerPreferencesStore(*m_remoteWorkerPreferences);
}
if (userContentController)
m_userContentControllerForRemoteWorkers = userContentController;
bool enableProcessSwapOnCrossSiteNavigation = page->preferences().processSwapOnCrossSiteNavigationEnabled();
#if PLATFORM(IOS_FAMILY)
if (WebCore::IOSApplication::isFirefox() && !linkedOnOrAfterSDKWithBehavior(SDKAlignedBehavior::ProcessSwapOnCrossSiteNavigation))
enableProcessSwapOnCrossSiteNavigation = false;
#endif
bool wasProcessSwappingOnNavigationEnabled = m_configuration->processSwapsOnNavigation();
m_configuration->setProcessSwapsOnNavigationFromExperimentalFeatures(enableProcessSwapOnCrossSiteNavigation);
if (wasProcessSwappingOnNavigationEnabled != m_configuration->processSwapsOnNavigation())
m_webProcessCache->updateCapacity(*this);
#if ENABLE(GPU_PROCESS)
if (auto* gpuProcess = GPUProcessProxy::singletonIfCreated()) {
gpuProcess->updatePreferences(*process);
gpuProcess->updateScreenPropertiesIfNeeded();
}
#endif
return page;
}
Ref WebProcessPool::processForRegistrableDomain(WebsiteDataStore& websiteDataStore, const RegistrableDomain& registrableDomain, WebProcessProxy::CaptivePortalMode captivePortalMode)
{
// 一些出错分支的条件判断
...
// 读取配置,是否使用同一个web进程
if (usesSingleWebProcess()) {
#if PLATFORM(COCOA)
bool mustMatchDataStore = WebKit::WebsiteDataStore::defaultDataStoreExists() && &websiteDataStore != WebKit::WebsiteDataStore::defaultDataStore().ptr();
#else
bool mustMatchDataStore = false;
#endif
for (auto& process : m_processes) {
if (process.ptr() == m_prewarmedProcess.get() || process->isDummyProcessProxy())
continue;
#if ENABLE(SERVICE_WORKER)
if (process->isRunningServiceWorkers())
continue;
#endif
if (mustMatchDataStore && process->websiteDataStore() != &websiteDataStore)
continue;
return process;
}
}
// 创建新的进程
return createNewWebProcess(&websiteDataStore, captivePortalMode);
}
Ref WebProcessPool::createNewWebProcess(WebsiteDataStore* websiteDataStore, WebProcessProxy::CaptivePortalMode captivePortalMode, WebProcessProxy::IsPrewarmed isPrewarmed, CrossOriginMode crossOriginMode)
{
#if PLATFORM(COCOA)
m_tccPreferenceEnabled = doesAppHaveITPEnabled();
// TODO:
if (websiteDataStore && !websiteDataStore->isItpStateExplicitlySet())
websiteDataStore->setResourceLoadStatisticsEnabled(m_tccPreferenceEnabled);
#endif
// 创建新的进程,在调试窗口可以看到,在这行代码之后,WebContent进程被创建
auto processProxy = WebProcessProxy::create(*this, websiteDataStore, captivePortalMode, isPrewarmed, crossOriginMode);
// 使用websiteDataStore初始化它
// 在初始化的时候会调用process.send(Messages::WebProcess::InitializeWebProcess(parameters), 0)
// 此时webkit处于State::Launching状态,将初始化的信息加入m_pendingMessages
initializeNewWebProcess(processProxy, websiteDataStore, isPrewarmed);
m_processes.append(processProxy.copyRef());
return processProxy;
}
// WebProcessProxy.cpp
Ref WebProcessProxy::create(WebProcessPool& processPool, WebsiteDataStore* websiteDataStore, CaptivePortalMode captivePortalMode, IsPrewarmed isPrewarmed, CrossOriginMode crossOriginMode, ShouldLaunchProcess shouldLaunchProcess)
{
auto proxy = adoptRef(*new WebProcessProxy(processPool, websiteDataStore, isPrewarmed, crossOriginMode, captivePortalMode));
// 进程池通过LRU算法管理
if (shouldLaunchProcess == ShouldLaunchProcess::Yes) {
if (liveProcessesLRU().size() >= s_maxProcessCount) {
for (auto& processPool : WebProcessPool::allProcessPools())
processPool->webProcessCache().clear();
if (liveProcessesLRU().size() >= s_maxProcessCount)
liveProcessesLRU().first()->requestTermination(ProcessTerminationReason::ExceededProcessCountLimit);
}
ASSERT(liveProcessesLRU().size() < s_maxProcessCount);
liveProcessesLRU().add(proxy.ptr());
// 执行父类AuxiliaryProcessProxy的connect方法
proxy->connect();
}
return proxy;
}
// AuxiliaryProcessProxy.cpp
void AuxiliaryProcessProxy::connect()
{
ASSERT(!m_processLauncher);
m_processStart = MonotonicTime::now();
ProcessLauncher::LaunchOptions launchOptions;
getLaunchOptions(launchOptions);
m_processLauncher = ProcessLauncher::create(this, WTFMove(launchOptions));
}
// ProcessLauncher.cpp
static Ref create(Client* client, LaunchOptions&& launchOptions)
{
return adoptRef(*new ProcessLauncher(client, WTFMove(launchOptions)));
}
ProcessLauncher::ProcessLauncher(Client* client, LaunchOptions&& launchOptions)
: m_client(client)
, m_launchOptions(WTFMove(launchOptions))
{
tracePoint(ProcessLaunchStart);
launchProcess();
}
// ProcessLauncherCocoa.mm
void ProcessLauncher::launchProcess()
{
ASSERT(!m_xpcConnection);
const char* name;
if (!m_launchOptions.customWebContentServiceBundleIdentifier.isNull())
name = m_launchOptions.customWebContentServiceBundleIdentifier.data();
else
name = serviceName(m_launchOptions, m_client);
m_xpcConnection = adoptOSObject(xpc_connection_create(name, nullptr));
uuid_t uuid;
uuid_generate(uuid);
xpc_connection_set_oneshot_instance(m_xpcConnection.get(), uuid);
// 初始化配置
...
xpc_connection_set_bootstrap(m_xpcConnection.get(), initializationMessage.get());
if (shouldLeakBoost(m_launchOptions)) {
auto preBootstrapMessage = adoptOSObject(xpc_dictionary_create(nullptr, nullptr, 0));
xpc_dictionary_set_string(preBootstrapMessage.get(), "message-name", "pre-bootstrap");
xpc_connection_send_message(m_xpcConnection.get(), preBootstrapMessage.get());
}
// 端口回调设置
...
auto bootstrapMessage = adoptOSObject(xpc_dictionary_create(nullptr, nullptr, 0));
// bootstrapMessage参数设置
...
auto sdkBehaviors = sdkAlignedBehaviors();
xpc_dictionary_set_data(bootstrapMessage.get(), "client-sdk-aligned-behaviors", sdkBehaviors.storage(), sdkBehaviors.storageLengthInBytes());
auto extraInitializationData = adoptOSObject(xpc_dictionary_create(nullptr, nullptr, 0));
for (const auto& keyValuePair : m_launchOptions.extraInitializationData)
xpc_dictionary_set_string(extraInitializationData.get(), keyValuePair.key.utf8().data(), keyValuePair.value.utf8().data());
xpc_dictionary_set_value(bootstrapMessage.get(), "extra-initialization-data", extraInitializationData.get());
// 出错的回调
auto errorHandlerImpl = [weakProcessLauncher = WeakPtr { *this }, listeningPort, logName = CString(name)] (xpc_object_t event) {
// 一些错误判断,后面是出错之后的处理
...
// We failed to launch. Release the send right.
deallocateSendRightSafely(listeningPort);
// And the receive right.
mach_port_mod_refs(mach_task_self(), listeningPort, MACH_PORT_RIGHT_RECEIVE, -1);
if (processLauncher->m_xpcConnection)
xpc_connection_cancel(processLauncher->m_xpcConnection.get());
processLauncher->m_xpcConnection = nullptr;
processLauncher->didFinishLaunchingProcess(0, IPC::Connection::Identifier());
};
// 事件回调
auto eventHandler = [errorHandlerImpl = WTFMove(errorHandlerImpl), eventHandler = m_client->xpcEventHandler()] (xpc_object_t event) mutable {
if (!event || xpc_get_type(event) == XPC_TYPE_ERROR) {
RunLoop::main().dispatch([errorHandlerImpl = WTFMove(errorHandlerImpl), event = OSObjectPtr(event)] {
errorHandlerImpl(event.get());
});
return;
}
if (eventHandler) {
RunLoop::main().dispatch([eventHandler = eventHandler, event = OSObjectPtr(event)] {
eventHandler->handleXPCEvent(event.get());
});
}
};
xpc_connection_set_event_handler(m_xpcConnection.get(), eventHandler);
xpc_connection_resume(m_xpcConnection.get());
if (UNLIKELY(m_launchOptions.shouldMakeProcessLaunchFailForTesting)) {
eventHandler(nullptr);
return;
}
// 引入计数器
ref();
// xpc发消息并关联进程,这行代码执行后WebKit.WebContent进程被创建,并执行初始化
xpc_connection_send_message_with_reply(m_xpcConnection.get(), bootstrapMessage.get(), dispatch_get_main_queue(), ^(xpc_object_t reply) {
// Errors are handled in the event handler.
// It is possible for this block to be called after the error event handler, in which case we're no longer
// launching and we already took care of cleaning things up.
if (isLaunching() && xpc_get_type(reply) != XPC_TYPE_ERROR) {
ASSERT(xpc_get_type(reply) == XPC_TYPE_DICTIONARY);
ASSERT(!strcmp(xpc_dictionary_get_string(reply, "message-name"), "process-finished-launching"));
#if ASSERT_ENABLED
mach_port_urefs_t sendRightCount = 0;
mach_port_get_refs(mach_task_self(), listeningPort, MACH_PORT_RIGHT_SEND, &sendRightCount);
ASSERT(sendRightCount >= 1);
#endif
deallocateSendRightSafely(listeningPort);
if (!m_xpcConnection) {
// The process was terminated.
didFinishLaunchingProcess(0, IPC::Connection::Identifier());
return;
}
// The process has finished launching, grab the pid from the connection.
pid_t processIdentifier = xpc_connection_get_pid(m_xpcConnection.get());
didFinishLaunchingProcess(processIdentifier, IPC::Connection::Identifier(listeningPort, m_xpcConnection));
m_xpcConnection = nullptr;
}
deref();
});
}
上面引入了XPC,官方解释:
The XPC Services API provides a lightweight mechanism for basic interprocess communication at the libSystem level. It allows you to create lightweight helper tools, called XPC services, that perform work on behalf of your app.
在WKMain.mm中执行WebKit::XPCServiceMain(argc, argv)方法:
int XPCServiceMain(int, const char**)
{
...
// 调用WTF初始化主线程
WTF::initializeMainThread();
auto bootstrap = adoptOSObject(xpc_copy_bootstrap());
if (bootstrap) {
#if PLATFORM(IOS_FAMILY)
auto containerEnvironmentVariables = xpc_dictionary_get_value(bootstrap.get(), "ContainerEnvironmentVariables");
xpc_dictionary_apply(containerEnvironmentVariables, ^(const char *key, xpc_object_t value) {
setenv(key, xpc_string_get_string_ptr(value), 1);
return true;
});
#endif
...
}
xpc_main(XPCServiceEventHandler);
return 0;
}
上面的部分后面再补齐,先回到wkwebivew的加载流程,从下面这行代码开始::
[webView loadRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:@"https://now.qq.com/"]]];
点击跳转WebKit源码中的WKWebView的loadrequest方法,加上断点。
#define THROW_IF_SUSPENDED if (UNLIKELY(_page && _page->isSuspended())) \
[NSException raise:NSInternalInconsistencyException format:@"The WKWebView is suspended"]
- (WKNavigation *)loadRequest:(NSURLRequest *)request
{
// 这里是判断是否有page(WebKit::WebPageProxy*)以及page是否停止
THROW_IF_SUSPENDED;
if (_page->isServiceWorkerPage())
[NSException raise:NSInternalInconsistencyException format:@"The WKWebView was used to load a service worker"];
return wrapper(_page->loadRequest(request));
}
// 在WebCore中Page.h
// m_isServiceWorkerPage在执行loadData/loadServiceWorker的时候为true
bool isServiceWorkerPage() const { return m_isServiceWorkerPage; }
接下来到WebPageProxy查看loadRequest方法
RefPtr WebPageProxy::loadRequest(ResourceRequest&& request, ShouldOpenExternalURLsPolicy shouldOpenExternalURLsPolicy, API::Object* userData)
{
if (m_isClosed)
return nullptr;
WEBPAGEPROXY_RELEASE_LOG(Loading, "loadRequest:");
if (!hasRunningProcess())
launchProcess(RegistrableDomain { request.url() }, ProcessLaunchReason::InitialProcess);
// 创建了Ref这个,后面看下是干什么用的
auto navigation = m_navigationState->createLoadRequestNavigation(ResourceRequest(request), m_backForwardList->currentItem());
if (shouldForceForegroundPriorityForClientNavigation())
navigation->setClientNavigationActivity(process().throttler().foregroundActivity("Client navigation"_s));
#if PLATFORM(COCOA)
setLastNavigationWasAppInitiated(request);
#endif
loadRequestWithNavigationShared(m_process.copyRef(), m_webPageID, navigation.get(), WTFMove(request), shouldOpenExternalURLsPolicy, userData, ShouldTreatAsContinuingLoad::No, isNavigatingToAppBoundDomain());
return navigation;
}
void WebPageProxy::loadRequestWithNavigationShared(Ref&& process, WebCore::PageIdentifier webPageID, API::Navigation& navigation, ResourceRequest&& request, ShouldOpenExternalURLsPolicy shouldOpenExternalURLsPolicy, API::Object* userData, ShouldTreatAsContinuingLoad shouldTreatAsContinuingLoad, std::optional isNavigatingToAppBoundDomain, std::optional&& websitePolicies, std::optional existingNetworkResourceLoadIdentifierToResume)
{
ASSERT(!m_isClosed);
WEBPAGEPROXY_RELEASE_LOG(Loading, "loadRequestWithNavigationShared:");
auto transaction = m_pageLoadState.transaction();
auto url = request.url();
if (shouldTreatAsContinuingLoad == ShouldTreatAsContinuingLoad::No)
m_pageLoadState.setPendingAPIRequest(transaction, { navigation.navigationID(), url.string() });
// 构造用来IPC通信的结构体,有encode和decode方法,这里赋值
LoadParameters loadParameters;
loadParameters.navigationID = navigation.navigationID();
loadParameters.request = WTFMove(request);
loadParameters.shouldOpenExternalURLsPolicy = shouldOpenExternalURLsPolicy;
loadParameters.userData = UserData(process->transformObjectsToHandles(userData).get());
loadParameters.shouldTreatAsContinuingLoad = shouldTreatAsContinuingLoad;
loadParameters.websitePolicies = WTFMove(websitePolicies);
loadParameters.lockHistory = navigation.lockHistory();
loadParameters.lockBackForwardList = navigation.lockBackForwardList();
loadParameters.clientRedirectSourceForHistory = navigation.clientRedirectSourceForHistory();
loadParameters.effectiveSandboxFlags = navigation.effectiveSandboxFlags();
loadParameters.isNavigatingToAppBoundDomain = isNavigatingToAppBoundDomain;
loadParameters.existingNetworkResourceLoadIdentifierToResume = existingNetworkResourceLoadIdentifierToResume;
#if ENABLE(PUBLIC_SUFFIX_LIST)
loadParameters.topPrivatelyControlledDomain = WebCore::topPrivatelyControlledDomain(loadParameters.request.url().host().toString());
#endif
// 经过一堆判断之后初始化了sandboxExtensionHandle
maybeInitializeSandboxExtensionHandle(process, url, m_pageLoadState.resourceDirectoryURL(), loadParameters.sandboxExtensionHandle);
addPlatformLoadParameters(process, loadParameters);
if (shouldTreatAsContinuingLoad == ShouldTreatAsContinuingLoad::No)
// 给NetworkProcess发送ipc消息
preconnectTo(url, predictedUserAgentForRequest(loadParameters.request));
navigation.setIsLoadedWithNavigationShared(true);
process->markProcessAsRecentlyUsed();
// 这里打包数据之后发送出去
if (!process->isLaunching() || !url.isLocalFile())
process->send(Messages::WebPage::LoadRequest(loadParameters), webPageID);
else
process->send(Messages::WebPage::LoadRequestWaitingForProcessLaunch(loadParameters, m_pageLoadState.resourceDirectoryURL(), m_identifier, true), webPageID);
process->startResponsivenessTimer();
}
void NetworkProcessProxy::preconnectTo(PAL::SessionID sessionID, WebPageProxyIdentifier webPageProxyID, WebCore::PageIdentifier webPageID, const URL& url, const String& userAgent, WebCore::StoredCredentialsPolicy storedCredentialsPolicy, std::optional isNavigatingToAppBoundDomain, LastNavigationWasAppInitiated lastNavigationWasAppInitiated)
{
if (!url.isValid() || !url.protocolIsInHTTPFamily())
return;
send(Messages::NetworkProcess::PreconnectTo(sessionID, webPageProxyID, webPageID, url, userAgent, storedCredentialsPolicy, isNavigatingToAppBoundDomain, lastNavigationWasAppInitiated), 0);
}
// WebProcessProxy中并未实现send方法,send方法在其父类AuxiliaryProcessProxy中
template
bool AuxiliaryProcessProxy::send(T&& message, uint64_t destinationID, OptionSet sendOptions)
{
static_assert(!T::isSync, "Async message expected");
auto encoder = makeUniqueRef(T::name(), destinationID);
encoder.get() << message.arguments();
return sendMessage(WTFMove(encoder), sendOptions);
}
bool AuxiliaryProcessProxy::sendMessage(UniqueRef&& encoder, OptionSet sendOptions, std::optional, uint64_t>>&& asyncReplyInfo, ShouldStartProcessThrottlerActivity shouldStartProcessThrottlerActivity)
{
// FIXME: We should turn this into a RELEASE_ASSERT().
ASSERT(isMainRunLoop());
if (!isMainRunLoop()) {
callOnMainRunLoop([protectedThis = Ref { *this }, encoder = WTFMove(encoder), sendOptions, asyncReplyInfo = WTFMove(asyncReplyInfo), shouldStartProcessThrottlerActivity]() mutable {
protectedThis->sendMessage(WTFMove(encoder), sendOptions, WTFMove(asyncReplyInfo), shouldStartProcessThrottlerActivity);
});
return true;
}
if (asyncReplyInfo && canSendMessage() && shouldStartProcessThrottlerActivity == ShouldStartProcessThrottlerActivity::Yes) {
auto completionHandler = std::exchange(asyncReplyInfo->first, nullptr);
asyncReplyInfo->first = [activity = throttler().backgroundActivity({ }), completionHandler = WTFMove(completionHandler)](IPC::Decoder* decoder) mutable {
completionHandler(decoder);
};
}
switch (state()) {
// 在此之前,初始化WKContentView,WebPageProxy,WebProcessProxy等等,以及setDelegate,addUserScript,addScriptMessageHandler等等都会一直往m_pendingMessages里面塞消息
case State::Launching:
// If we're waiting for the child process to launch, we need to stash away the messages so we can send them once we have a connection.
m_pendingMessages.append({ WTFMove(encoder), sendOptions, WTFMove(asyncReplyInfo) });
return true;
case State::Running:
if (asyncReplyInfo)
IPC::addAsyncReplyHandler(*connection(), asyncReplyInfo->second, std::exchange(asyncReplyInfo->first, nullptr));
if (connection()->sendMessage(WTFMove(encoder), sendOptions))
return true;
break;
case State::Terminated:
break;
}
if (asyncReplyInfo && asyncReplyInfo->first) {
RunLoop::current().dispatch([completionHandler = WTFMove(asyncReplyInfo->first)]() mutable {
completionHandler(nullptr);
});
}
return false;
}
AuxiliaryProcessProxy::State AuxiliaryProcessProxy::state() const
{
if (m_processLauncher && m_processLauncher->isLaunching())
return AuxiliaryProcessProxy::State::Launching;
if (!m_connection)
return AuxiliaryProcessProxy::State::Terminated;
return AuxiliaryProcessProxy::State::Running;
}
在loadrequest的最后依旧是在m_pendingMessages中添加消息,从state()方法可以看到,当m_processLauncher不在isLaunching状态时,且m_connection存在,代表进入running状态,m_connection赋值的地方,看着是不是很眼熟,这个就是前面xpc调用xpc_connection_send_message_with_reply方法中回调的方法,loadrequest之后,等待xpc的回调成功之后,再将保存的数据发送过去:
void AuxiliaryProcessProxy::didFinishLaunching(ProcessLauncher*, IPC::Connection::Identifier connectionIdentifier)
{
ASSERT(!m_connection);
ASSERT(isMainRunLoop());
auto launchTime = MonotonicTime::now() - m_processStart;
if (launchTime > 1_s)
RELEASE_LOG_FAULT(Process, "%s process (%p) took %f seconds to launch", processName().characters(), this, launchTime.value());
if (!IPC::Connection::identifierIsValid(connectionIdentifier))
return;
m_connection = IPC::Connection::createServerConnection(connectionIdentifier, *this);
connectionWillOpen(*m_connection);
m_connection->open();
for (auto&& pendingMessage : std::exchange(m_pendingMessages, { })) {
if (!shouldSendPendingMessage(pendingMessage))
continue;
if (pendingMessage.asyncReplyInfo)
IPC::addAsyncReplyHandler(*connection(), pendingMessage.asyncReplyInfo->second, WTFMove(pendingMessage.asyncReplyInfo->first));
m_connection->sendMessage(WTFMove(pendingMessage.encoder), pendingMessage.sendOptions);
}
}
回到WebProcessProxy的send方法上,它发送了Messages::WebPage::LoadRequest消息,找到接受消息的地方,在WebPage上,此时来到了webcontent进程
// WebPage.cpp
void WebPage::loadRequest(LoadParameters&& loadParameters)
{
// 初始化
...
corePage()->userInputBridge().loadRequest(WTFMove(frameLoadRequest));
...
}
// UserInputBridge.cpp
void UserInputBridge::loadRequest(FrameLoadRequest&& request, InputSource)
{
#if ENABLE(WEB_AUTHN)
m_page.authenticatorCoordinator().resetUserGestureRequirement();
#endif
Ref(m_page.mainFrame())->loader().load(WTFMove(request));
}
上面引入Frame与FrameLoader类