Students who are familiar with iOS/macOS Hybrid hybrid development should have the experience. Although WKWebView is a "new" component introduced by Apple as an alternative to UIWebView\WebView, most developers really "cannot love it". After all, for most domestic application developers, the so-called "advantages" of WKWebView may not be reflected in actual use, but the "pits" it brings are really not shallow.
At present, most of the WKWebView related materials that can be found in the community or online are relatively old and popular, and most of them are copy and paste. A small number of developers who actually practice and explore, perhaps due to time or energy, failed to elaborate on the problems and solutions. As a result, there are a lot of online WKWebView related materials, but the quality is not high; and many articles have problems such as unclear background explanation of the problem and lack of effective verification of the solution.
I have been engaged in the development of the terminal container field for many years, and have "confronted" WKWebView many times in the design of the production environment. At present, hybrid development is the standard configuration of modern App. On the one hand, it is a summary of the experience of such a long time. On the other hand, it is also hoped to provide some new perspectives or solutions for students who are still struggling, so we plan to combine part of the source code of WebKit. , I will share my understanding of this component and some problem solutions. This article attempts to explain 3 things:
- What are the typical problems in the use of WKWebView?
- Why do these problems occur?
- What are the solutions to these problems?
The network design of iOS and the design features of WKWebView can be checked through official information. But in order to better explain the problem later, let's focus on reviewing two basic knowledge points related to the follow-up content of the article:
- iOS network design and cookie management
- WKWebView multi-process model
iOS web design and cookie management
Cookie management is a part that is often involved in the process of hybrid development. In application development, we know that we can manage application cookies through NSHTTPCookie and NSHTTPCookieStorage. But at the system level, how cookies are managed and how they interact with the various modules of the network layer are vital to our subsequent analysis of the cookie problem in WKWebView.
According to official information, we know that the network-related modules under the iOS platform are roughly related as follows:
The modules from top to bottom are:
- WebKit: application layer, client App and WKWebView are in this layer;
- NSURL: It can be understood as an encapsulation and extension layer for the underlying CF interface, NSURLConnection, NSURLSession, etc. are in this layer;
- CFNetwork: The core implementation layer of the iOS network module is the most important part of the network layer design. Responsible for network protocol assembly, sending and receiving, and other major tasks, closely related to the CoreFoundation framework;
- BSD socket: socket service based on the underlying hardware interface.
CFNetwork is the core module of the entire network system, responsible for assembling requests, processing responses, etc.:
The core content includes:
- CFURLRequest: Including URL/header/body request information. CFURLRequest will be further converted into CFHTTPMessage;
- CFHTTPMessage: It is mainly the definition and conversion of HTTP protocol, which converts each request into standard HTTP format text;
- CFURLConnection: Mainly handles request tasks. Including pthread thread, CFRunloop, request queue management and so on. Provides APIs for start, cancel, etc. operations;
- CFHost: responsible for DNS, with functions such as CFHostStartInfoResolution, based on methods such as dns_async_start and getaddrinfo_async_start;
- CFURLCache/CFURLCredential/CFHTTPCookie: Processing cache/certificate/cookie related logic, there is a corresponding NS class.
From the above analysis, we can know the key information: iOS Cookie management related modules are in the CFNetwork layer. That is, the "set-cookie" field in the response of the request is consumed and processed in CFNetwork.
WKWebView multi-process model
Through official information, we know that a big change in WKWebView compared to UIWebView is the "multi-process model":
When WKWebView is running, the core module runs in an independent process, independent of the App process.
Many of the causes of various problems in the use of WKWebView have a lot to do with the multi-process operation mode.
Detailed explanation of the multi-process model
But what kind of multi-process is it? Let's use a simple diagram to illustrate:
- WKWebView(WebKit) contains 3 kinds of processes: UI Process, Networking Process, WebContent Process;
- UI Process: App process, some modules in WKWebView (WebKit) run in this process and will be responsible for starting other processes;
- Networking Process: the network module process, which is mainly responsible for the network request related functions in WKWebView; this process will only be started once in the App and shared among multiple WKWebView;
- WebContent Process: Web module process, mainly responsible for the operation of WebCore, JSCore related modules, is the core process of WKWebView. This process will be started multiple times in the App, and each WKWebView will have its own independent WebContent process;
- The various processes communicate through the CoreIPC process.
In general: in a client App, multiple WKWebView will share a UI process (shared with the App process), share a Networking process, and each WKWebView instance will share a WebContent process exclusively.
Regarding the startup rules of the WebContent Process and Networking Process, the official documents did not explain it clearly, and due to version iterations and other reasons, the documents are slightly different from the current latest rules. In order to avoid confusion and ambiguity, the following combined with the WebKit source code for a little analysis.
WebContent process startup rules
According to the official document description:
A WKProcessPool object represents a single process that WebKit uses to manage web content. To provide a more secure and stable experience, WebKit renders the content of web views in separate processes, rather than in your app’s process space. By default, WebKit gives each web view its own process space until it reaches an implementation-defined process limit. After that, web views with the same WKProcessPool object share the same web content process.
The rule is to give priority to creating a new process, and when the process goes online exceeds a certain threshold, it will be shared. It is controlled by maximumProcessCount inside WebKit. But this rule is only effective for systems before iOS13. For systems after iOS13, WKWebView will start a new WebContent Porcess every time an instance is created. The relevant implementation is as follows.
iOS 13 and later:
Networking process startup rules
Networking rules are relatively simple, ensuring that one case is started during the App lifecycle (it will be recreated after Crash). Related code:
Main problems and solutions
In the use of WKWebView in a production environment, apart from the relatively simple use and adaptation problems, there are 4 problems that are likely to cause confusion for developers and front-end students:
- Request proxy problem
- Cookie management issues
- Full screen adaptation problem
- WebContent process crashes
The following is an explanation of the causes of these 4 cases of problems, the solutions that can be tried, and the problems introduced under different solutions.
Request proxy problem
This should be the primary problem that hinders the rollout of WKWebView. The background of the problem is also relatively simple. It is not about the difficulty of technical implementation, but Apple officially does not want WKWebView requests to be intercepted by the application, which is called "for security". However, in actual use scenarios, we need to proxy WebView requests to meet business and performance requirements. Typical scenarios include offline packages, traffic monitoring, etc.
It is not officially supported and there are usage scenarios in the business. We can only try to solve it through "black magic". There are currently two solutions that are more applicable:
- Register the agent through [WKBrowsingContextController registerSchemeForCustomProtocol:], which is referred to as agent scheme 1 for convenience;
- Register through [WKWebViewConfiguration setURLSchemeHandler:forURLScheme:], which is referred to as proxy scheme 2 for convenience.
The current implementation methods of the two solutions have relatively rich materials and instructions, so I won't repeat them here.
Although these two solutions can "partially solve the problem" to some extent, they have relatively many side effects. How to choose in the production environment requires specific development students to choose. The following is a brief explanation by referring to "Agent Plan 1" and "Agent Plan 2", which can be used as a reference for everyone to choose.
Agent plan 1
This is the earliest WKWebView request proxy solution that can be used by iOS 9 and later applications (currently the latest is iOS 14). According to previous research and analysis, most apps in the industry that have agency needs adopt this scheme or a variant scheme.
1) Scheme ideas
Register http(s) to the m_registeredSchemes array of Networking through WKBrowsingContextController. For the Schemes in the array, when WebKit initiates a request, the request will be sent to the process where the App is located through WKCustomProtocol, and the App process will perform the sending.
Because when sending data from the Networking process to the App process, the Body part is intentionally stripped in WebKit (see WebCoreArgumentCodersMac.mm):
Therefore, some special processing needs to be done for the request that carries the body. The solution is to inject scripts into WKWebView to rewrite the request sending related methods in WebView. Before the request is sent, the body part is serialized and then passed to the App process for temporary storage through the bridge.
When the App process proxy WKWebView request, it splices the cached body as required according to the rules, and then sends it after completion.
2) Disadvantages of the scheme
Although this scheme has a wide range of applications, its drawbacks are also obvious. There are two main aspects:
(1) Problem 1: Cannot be targeted, only one size fits all
That is, if the App adopts this scheme, all the requests sent by its WKWebView instance need to be proxied. If there is no script or proxy execution in the WKWebView instance, it may cause problems such as failure to send the request and missing body to be sent, which are common in the WKWebView instance in some integrated second-party libraries and third-party libraries.
(2) Problem 2: It is difficult to guarantee the completeness of rewriting scripts
As the request sending logic needs to be rewritten in the JS layer, such as form submission, AJAX, Fetch and other interfaces, the quality of the rewritten interface directly determines the completeness of the solution. In addition, the original design of WKWebView has a lot of capabilities to be implemented at the C++ level, and only rewriting in JS cannot guarantee alignment. Currently known issues are:
- For synchronization requests, this solution cannot currently be supported;
- For streaming requests, such as upload scenarios, the current support is poor. It can only be sent after the full amount is read on the JS side;
- Unable to process the Fetch API Stream return value;
- When using the Form to submit content that contains a large block of data, it may be lost, crashed, etc.
Agency plan 2
This solution is implemented based on the [WKWebViewConfiguration setURLSchemeHandler:forURLScheme:] interface opened by Apple on iOS 11. For devices after iOS 11.3, this solution has good practicability (WebKit handles part of the body transfer problem).
1) Scheme ideas
- [WKWebViewConfiguration setURLSchemeHandler:forURLScheme:] can register a custom request scheme on the WKWebView instance. If the request sent by WKWebView matches the registered Scheme, it will proxy to the UI process (App process) to perform the sending action;
- WKWebView does not support registration of standard schemes such as http(s) by default, but there are "black magic" to bypass the restriction;
- When AJAX sends BLOB data, the body will also be lost. You can refer to the similar solution in proxy solution 1.
2) Solution advantages
The agency plan 2 has two huge advantages over plan 1:
- No one size fits all, the configuration is bound to the WKWebView instance: that is, the WKWebView instance that we need to process can be processed directionally, and the objects in the third party library can be completely unaffected, and the security is greatly improved;
- There is no need to rewrite all sending requests: In most cases, the body in the request can be carried to the App process, that is, we only need to deal with some exceptions in a targeted manner, and the robustness is greatly improved.
3) Disadvantages of the scheme
In addition to the system version restrictions of iOS 11.3, this solution also has many difficult problems in specific operations, mainly as follows:
(1) Problem 1: In the case of multi-image segmented download, there is a BUG in the processing timing inside WKWebView
Problem performance: When a large image is loaded in WKWebView and the large image data has fragmented returns, the internal timing processing abnormality of WKWebView may cause problems such as incomplete display of the image and incomplete image display. The specific description can be briefly combined with the image loading process in WebKit:
The problem arises in the execution order of step1, step2, and step3 above. Under abnormal circumstances, the execution order will occasionally be: step1 -> step3 -> step2, and step3 will no longer be triggered (allDataReceived), resulting in the final content of the picture not being rendered on the screen.
Solution: There is currently no effective solution. By configuring suppressesIncrementalRendering to YES, the problem can be alleviated to some extent, but it cannot be cured and has a slight impact on the experience.
(2) Problem 2: iOS 12 and below system system synchronizes AJAX and causes Crash
Problem manifestation: If a web page sends a sync request in WKWebView, it may cause the WebContent process to crash, and WKWebView will call back webViewWebContentProcessDidTerminate, which will cause problems such as a blank screen. This problem can clearly be a bug in WebKit, and there are related Fixes:
Bug1: WebURLSchemeHandlerProxy::loadSynchronously crash with sync request（2018-08-06 14:14 ）：https://bugs.webkit.org/show_bug.cgi?id=188358
Bug2: WKURLSchemeHandler crashes when sent errors with sync XHR（2019-06-20 01:20）：https://bugs.webkit.org/show_bug.cgi?id=199063
Solution: For Bug1, the processing is relatively simple, that is, some empty data is called back first before the network request callback error to avoid the problem; but for Bug2, there is currently no effective solution.
(3) Problem 3: SWAP under 301 request causes the page to fail to transition
Problem manifestation: If there is a 301 redirect in the page, the redirected page may not load, which may lead to problems such as abnormal page and white screen.
Solution: Close processSwapsOnNavigation and set it to NO (internal attribute).
In general, although the proxy plan 2 has great advantages over the proxy plan 1, the current usage of this plan is slightly lower than that of the proxy plan 1, due to version restrictions and other reasons. And compared with the proxy solution 1, the advantages and disadvantages of this solution are obvious, such as the problem that pictures cannot be displayed in the multi-picture fragmentation scenario, and no effective solution has been found at present. Whether Option 2 can be used in a production environment instead of Option 1 is still subject to the students' consideration.
First of all, based on your own understanding of this, try to explain what the problem of WKWebView uses Cookie is and the reason behind it. Since Apple has not open sourced all the code, many of the contents are based on their own understanding and inference, and cannot be guaranteed to be completely correct. Only some ideas and judgments are introduced for your reference when needed.
Cookie management strategy
According to the background introduction in the previous section, we know that iOS Cookie related content is managed by CFHTTPCookie, CFHTTPCookieStorage, etc. at the CFNetwork layer, and is part of the CFNetwork module. And for Session Cookie and Persistent Cookie, the system has different management strategies:
- Session Cookie: Saved in the memory, it takes effect during the process cycle. On the iOS mobile terminal, an App process corresponds to a Session, that is, Session Cookie can be shared within the process;
- Persistent Cookies: In addition to saving memory, these cookies are also persisted to disk and can be used multiple times. Local files are stored in the sandbox folder /Library/Cookies/Cookies.binarycookies; it is important to note that persistent cookies are not synchronized to Cookies.binarycookies immediately after they are generated. According to experience, there will be a 300ms ~ 3s delay.
WKWebView Cookie Issue
Based on the iOS Cookie management in the previous section, combined with the multi-process model, we can probably infer the App and WKWebView Cookie management model, as shown in the following diagram:
Note: WKHTTPCookieStore is for illustration, the Networking process is drawn. In actual situation, this module is scattered in WebContent, Networking and UI Process, and the parts in each process are bridged by IPC.
According to the above figure, two core points related to WKWebView Cookie can be guided:
1) What exactly is the WKWebView Cookie problem
- For "Session Cookie": App process and WKWebView process (WebContent + Networking) are completely isolated;
- For "persistent cookies": there is a time difference between the App process and the WKWebView process (WebContent + Networking).
2) The root cause of the WKWebView Cookie problem
- App process and Networking dual process design.
After understanding the WKWebView problem and the corresponding root cause, how to deal with this problem is relatively clear: according to whether the network request proxying WKWebView is used, we need different processing strategies.
- Scenario 1-WKWebView network request is not proxyed: Cookies are completely managed by the Networking process, and WKWebView can be self-closing. In most cases, the App process does not need to be aware. If you really need to be aware, you can choose JS bridging, forced persistence and other solutions according to the business scenario;
- Scenario 2-WKWebView network request has been proxyed: most of the cookies are managed by the App process, what synchronization strategy should be adopted at this time.
Since we did not use it in a production environment in Scenario 1, this article does not intend to make a risky analysis. The following mainly focuses on scene 2 to do a part of the analysis. Our core goal under scenario 2:
- For the cookies generated in the App process, it can be synchronized to the Networking process in time: mainly to solve the problem of how the JS side reads related cookies in time when there is a "Set-Cookie" in the Response;
- For cookies generated by JS in WebContent, they can be synchronized to the App process in time: mainly to solve the problem of how we can ensure that they can be carried normally in subsequent proxy network requests after cookies are generated on the JS side.
Before confirming the plan, we must first clarify a question: What are the client-side cookie sources?
For the App process, there are two cookie sources:
- Written through NSHTTPCookieStorage;
- It is written by "Set-Cookie" in the response header of the network request.
For the WebContent process, it is mainly written by JS through document.cookie (Set-Cookie will not take effect in the WKWebView process after the network proxy).
Secondly, we need to confirm what are the methods available for synchronization:
For systems after iOS 11, Apple has provided us with the WKHTTPCookieStore object to read, write, and monitor the cookies corresponding to WKWebView, which can be used directly.
For systems prior to iOS 11, it needs to be treated differently.
From the App process to the Networking process, the simple process is as follows:
- The first step is to make the Session Cookie persistent and save it temporarily (note that it needs to be identified for recovery);
- Step 2, call NSHTTPCookieStorage internal interface _saveCookies to trigger forced synchronization;
- The third step is to restore the temporarily saved Session Cookie to avoid pollution.
Since the Networking process does not generate cookies, what we need to do is to synchronize cookies from the WebContent process: the processing strategy is to rewrite the document.cookie method on the JS side, and when the JS modifies the cookies, the cookie is passed to the App process through the bridge.
After clarifying the problems, goals and available means, we can summarize the solutions for WKWebView Cookie related problems:
- For iOS 11 and later systems, we can read and write cookies through the interface of HOOK NSHTTPCookieStorage, and listen to the "Set-Cookie" keyword in network requests, and synchronize to WKWebView when the cookie changes in the App process; at the same time, provide cookiesDidChangeInCookieStore capability through WKHTTPCookieStore To monitor the changes of Cookie in WKWebView;
- For systems prior to iOS 11, the processing strategy is similar. But we need to use the NSHTTPCookieStorage interface to do forced synchronization, and we need to pay attention to restoring the SessionOnly property of the Cookie; at the same time, we need to perceive the changes of the Cookie in WKWebView by rewriting document.cookie on the JS side.
You must pay attention to the solution after adopting iOS 11, the operation of WKHTTPCookieStore will involve IPC communication. If the communication is too frequent and the communication data volume is too large, it will cause obvious performance problems. Extreme situations may cause the IPC module to be abnormal, and all WKWebView cannot be loaded. For example, in a typical scenario, if there are many requests for a page, each request carries "Set-Cookie", and the business is simple, every time all the cookies of the App process are synchronized to WKWebView, when there are too many cookies, there is a certain probability ( Brute force testing can be reproduced) triggering an IPC exception, causing all subsequent WKWebView instances to fail to load normally, and only the App killing process can be restored. It is recommended that when synchronizing cookies, try to synchronize the changed parts as needed.
Full screen adaptation problem
The problem of full-screen adaptation is relatively uncomplicated, but because of the different performance of WKWebView such as UIWebView, it is easy to cause some troubles.
The problem is that UIWebView and WKWebView have a slight difference in the front-end viewport-fit support performance: UIWebView has better support for viewport-fit, and its performance is basically consistent with the official documentation. But there is a hidden rule in WKWebView. If the height of the body in the Web page does not exceed the actual height of the WKWebView component, viewport-fit=cover may not take effect.
The solution is to avoid such situations in the page, such as configuring the body height to 100vh (or other similar solutions).
WebContent process crashes
This is a problem that has a low probability of occurrence, but lacks a universal and effective solution. We know that in WKWebView multi-process mode, if the WebContent process crashes due to various reasons, WKWebView will tell the developer through the webViewWebContentProcessDidTerminate callback. In general, we will reload the page through the reload method. At the same time, if the user's device memory is tight, the system may take the initiative to KILL WebContent process. That is, it may happen that the App process (foreground) is normal, but the WebContent crashes and the page reloads.
In most cases, entering this process does not necessarily cause trouble to user operations. However, if the memory shortage at this time is caused by the front-end triggering business, such as a form that evokes the camera to upload pictures, the impact of this process on users may be fatal. Even if we restore the page through WebView reload, the upload action performed by the user will be interrupted, causing an abnormality in the submission process and affecting the user's operation. And if the user equipment enters this state, in most cases, the user's re-operation will trigger the same process.
In this case, the user cannot perceive the root cause of the problem in time. The most intuitive reaction is: "The App has a bug!" Therefore, from the user's point of view, there is a lack of automatic recovery and handling of the problem.
At present, there is no effective and unified solution to this problem. One solution is to configure the client and front-end, aiming at the core and possible abnormal processes, and designing solutions oriented. Use end-side capabilities to persist data, use persistent data to restore the scene after similar exceptions occur, and try to ensure that the user's operation process is normal without the user's feelings.
The above are some typical problems and corresponding solutions we encountered in the use of WKWebView during the design and development of the terminal container. In general, the current uncoordinated state is mostly caused by the failure of the system platform to fully consider the demands of developers and the poor compatibility of component design with historical businesses. Of course, the current state is certainly not a reasonable state. In the future, whether it is the system platform side, the business side, or the developer, there will always be a compromise when the conflict cannot be coordinated. Before this time comes, I hope that the above summary can provide some help to students who are troubled by such problems.
Follow us, 3 mobile dry goods & practice for you to think about every week!