IOS Wkwebview those pits

Source: Internet
Author: User

Lead

Wkwebview is a new generation of WebView components introduced by Apple on WWDC 2014 to replace the cumbersome, memory-leaking uiwebview of UIKit. Wkwebview has the advantage of 60fps scrolling refresh rate and the same JavaScript engine as Safari.

A simple adaptation method this article does not repeat, mainly for the adaptation of the Wkwebview process filled with pits and good to solve the technical problems.

1, Wkwebview white screen problem

Wkwebview boasts a faster loading speed and lower memory footprint, but in fact Wkwebview is a multi-process component, Network Loading and UI Rendering executed in other processes. When we first adapted to the Wkwebview, we were also surprised that the APP process memory consumption dropped significantly after opening the Wkwebview, but careful observation revealed that the memory footprint of the other process would increase. In some complex pages rendered with WebGL, using Wkwebview overall memory footprint (App Process memory + other process memory) is not necessarily much less than UIWebView.

In the UIWebView when the memory consumption is too large, the APP process will crash, and in Wkwebview when the overall memory consumption is relatively large, webcontent Process will crash, resulting in white screen phenomenon. Load the following test link in Wkwebview to stabilize the white screen phenomenon:

Http://people.mozilla.org/~rnewman/fennec/mem.html

This time Wkwebview.url will become nil, simple reload refresh operation has been invalidated, for some long-standing H5 page impact is relatively large.

Our Final solution is to:

A, with the help of Wknavigtiondelegate

IOS 9 has a new callback function after Wknavigtiondelegate:

- (void)webViewWebContentProcessDidTerminate:(WKWebView *)webView API_AVAILABLE(macosx(10.11), ios(9.0));

When the Wkwebview overall memory consumption is too large, the page is about to white screen, the system will call the above callback function, we execute in the function [webView reload] (this time webview.url value is not nil) to solve the white screen problem. In some high memory consumption of the page may frequently refresh the current page, the H5 side to do the appropriate adaptation operation.

B. Detect if Webview.title is empty

Not all H5 page white screen call the above callback function, for example, recently encountered in a high memory consumption of the H5 page present system camera, after taking pictures to return to the original page when the white screen phenomenon (the photo process consumes a lot of memory, resulting in memory tension, webcontent The Process is suspended by the system, but the callback function above is not called. In the Wkwebview white screen, another phenomenon is that webview.titile will be empty, so you can detect webview.title when viewwillappear is empty to reload the page.

Combined with the above two methods can solve the overwhelming majority of white screen problems.

2. Wkwebview Cookie problem

Cookie problem is a big short board of Wkwebview at present

2.1. Wkwebview Cookie Storage

The industry generally believes that Wkwebview owns its own private storage and does not deposit cookies into the standard cookie container nshttpcookiestorage .

Practice found that Wkwebview instances will actually store cookies in Nshttpcookiestorage, but the storage time is delayed, on iOS 8, when the page jumps, the current page of the cookie is written to Nshttpcookiestorage, While on IOS 10, JS execution Document.cookie or server Set-cookie injected cookie will quickly sync to Nshttpcookiestorage, FireFox engineers have suggested through reset wkprocess Pool to trigger the cookie synchronization to Nshttpcookiestorage, the practice discovery does not work, and may cause the current page session cookie loss and so on.

the problem with Wkwebview cookies is that requests initiated by Wkwebview do not automatically bring in cookies stored in nshttpcookiestorage containers.

For example, a Cookie is stored in the Nshttpcookiestorage:

name=Nicholas;value=test;domain=y.qq.com;expires=Sat, 02 May 2019 23:38:25 GMT;

The request http://y.qq.com is automatically brought with the cookie:nicholas=test by the UIWebView initiating the request;
The request header does not automatically take Cookie:nicholas=test with the Wkwebview initiating the request http://y.qq.com.

2.2, Wkprocesspool

The Apple Developer documentation defines Wkprocesspool as: A Wkprocesspool object represents a pool of Web Content process. By having all Wkwebview share the same Wkprocesspool instance, you can implement the sharing of cookies (session cookie and persistent cookie) data between multiple Wkwebview . However, the Wkwebview Wkprocesspool instance will be reset after the app kill process restarts, resulting in the loss of cookies, session cookie data in Wkprocesspool, and the inability to localize Wkprocesspool instances at this time. 。

2.3, workaround

Since many H5 businesses rely on cookies for sign-on verification, and Wkwebview requests do not automatically carry cookies, the main solution now is:

A, Wkwebview loadrequest, set a cookie in the request header to resolve the problem that the first request cookie does not take;



[webView loadRequest:request];
b, through Document.cookie set up a cookie to resolve subsequent pages (same domain) Ajax, iframe request cookie problem;

注意:document.cookie()无法跨域设置 cookie



[userContentController addUserScript:cookieScript];

This scenario does not solve the cookie problem of the 302 request, for example, the first request is www.a.com, we use a cookie in the request header to resolve the cookie problem, then page 302 jumps to www.b.com, this time WW W.b.com this request may not be accessible because it is not carrying a cookie. Of course, the callback function is called before each page jump:

- (void)webView:(WKWebView *)webView decidePolicyForNavigationAction:(WKNavigationAction *)navigationAction decisionHandler:(void (^)(WKNavigationActionPolicy))decisionHandler;

You can intercept 302 requests in the callback function, copy the request, bring a cookie in the request header, and re-loadrequest. However, this approach still cannot solve the Cookie problem of cross-domain request of page iframe, after all-[wkwebview loadrequest:] Only suitable for loading mainFrame requests.

3, Wkwebview nsurlprotocol problem

Wkwebview executes a network request in a process separate from the app process, requesting that the data not go through the main process, and therefore, using nsurlprotocol directly on Wkwebview cannot intercept the request. Apple's Open source WebKit2 source exposes the private API:

+ [WKBrowsingContextController registerSchemeForCustomProtocol:]

After registering for HTTP (s) scheme, Wkwebview will be able to intercept HTTP (s) requests using Nsurlprotocol:

Class cls = NSClassFromString(@"WKBrowsingContextController”); SEL sel = NSSelectorFromString(@"registerSchemeForCustomProtocol:"); if ([(id)cls respondsToSelector:sel]) {            // 注册http(s) scheme, 把 http和https请求交给 NSURLProtocol处理            [(id)cls performSelector:sel withObject:@"http"];            [(id)cls performSelector:sel withObject:@"https"]; }

But there are now two serious flaws in this scenario:

A, POST request body data is emptied

Because Wkwebview executes network requests in a standalone process. Once the HTTP (s) scheme is registered, the network request will be sent to the APP process from net process so that the Nsurlprotocol can intercept the network request. In Webkit2 's design, the MessageQueue is used for communication between processes, and the Network process encode the request into a Message and then sends it to the APP process via IPC. For performance reasons, the Httpbody and Httpbodystream fields were discarded when encode.

Reference Apple Source:

https://github.com/WebKit/webkit/blob/fe39539b83d28751e86077b173abd5b7872ce3f9/Source/WebKit2/Shared/mac/ Webcoreargumentcodersmac.mm#l61-l88 (copy linked to browser open)

and bug report:

https://bugs.webkit.org/show_bug.cgi?id=138169 (copy linked to browser open)

Therefore, if HTTP (s) scheme is registered via Registerschemeforcustomprotocol, all HTTP (s) requests initiated by Wkwebview are passed through the IPC to the main process Nsurlprotocol Processing, which causes the POST request body to be emptied ;

B. Insufficient support for ATS

Test discovery Once the ATS switch is turned on: Allowarbitrary Loads option is set to No, and HTTP (s) scheme is registered via Registerschemeforcustomprotocol, All HTTP network requests initiated by Wkwebview will be blocked (even if the Allow arbitrary Loads in Web Content option is set to Yes);

Wkwebview can register customscheme, such as dynamic://, so requests that want to use offline functionality without using a post can initiate requests through customscheme, such as dynamic:// www.dynamicalbumlocalimage.com/, then intercepts the request and loads the offline data nsurlprotocol the app process. Insufficient: The request using the Post method is still not applicable, and requires the H5-side modification request scheme and the CSP rules;

4, Wkwebview loadrequest problem

The body data is lost on the POST request initiated via Loadrequest on Wkwebview:

//同样是由于进程间通信性能问题,HTTPBody字段被丢弃
[request setHTTPMethod:@"POST"];[request setHTTPBody:[@"bodyData" dataUsingEncoding:NSUTF8StringEncoding]];[wkwebview loadRequest: request];
Workaround:

If you want to-[wkwebview Loadrequest:] Load the POST request Request1: Http://h5.qzone.qq.com/mqzone/index, the following steps can be implemented:

    1. Replace request scheme, generate a new POST request Request2: post://h5.qzone.qq.com/mqzone/index, and copy the Request1 body field to Request2 head ER (WebKit does not discard header fields);

    2. Load a new POST request Request2 by-[wkwebview Loadrequest:];

    3. Through +[wkbrowsingcontextcontroller Registerschemeforcustomprotocol:] Registration scheme: post://;

    4. Register Nsurlprotocol intercept Request Post://h5.qzone.qq.com/mqzone/index , replace request scheme, generate a new request REQUEST3:/ http H5.qzone.qq.com/mqzone/index, copy the Body field of the REQUEST2 header into the body of request3, and use nsurlconnection to load request3, and finally through NSU Rlprotocolclient will load the results back to Wkwebview;

5. Wkwebview Page Style issues

During the Wkwebview adaptation process, we found that some H5 page element positions were shifted downward or stretched to deform , following the discovery of the main H5 page height value anomalies:

a. The Space H5 page has transparent navigation, transparent navigation drop-down refresh, full screen and so on, so before webView the entire start from (0, 0) layout, adjust webView.scrollView.contentInset to adapt to the special navigation bar requirements. In the Wkwebview on the adjustment of the Contentinset will be feedback to webView.scrollView.contentSize.height the change, such as setting webView.scrollView.contentInset.top = a , then contentSize.height the value will increase a, resulting in H5 page length increase, page element position downward offset;

The solution: Adjust the Wkwebview layout to avoid adjustments webView.scrollView.contentInset . In fact, even if the value is not recommended for direct adjustment on UIWebView webView.scrollView.contentInset , it does pose some strange problems. If you have to adjust contentinset in some special cases, you can get the H5 page back to normal in the following way:

/**设置contentInset值后通过调整webView.frame让页面恢复正常显示  
webView.scrollView.contentInset = UIEdgeInsetsMake(a, 0, 0, 0); webView.frame = CGRectMake(webView.frame.origin.x, webView.frame.origin.y, webView.frame.size.width, webView.frame.size.height - a);

b. When accessing now live, we found that on IOS 9, the page was stretched and wkwebview, and the result was that the value was inaccurate window.innerHeight (a very large value was returned on Wkwebview), and H5 window.innerHeight To set the page height, which causes the overall page to be stretched. By looking at the relevant data, the bug was found only on several system versions of IOS 9, and Apple later fix the bug. Our final solution is to delay calling Window.innerheight

setTimeout(function(){height = window.innerHeight},0);

Or

Use shrink-to-fit meta-tag <meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no, maximum-scale=1, shrink-to-fit=no">
6, Wkwebview screenshot problem

Space Play Bar H5 games have the function of screen sharing, Wkwebview under the-[calayer Renderincontext:] To achieve the mode of screen failure, you need to achieve the screenshot function by the following means:

@implementation UIView (ImageSnapshot) - (UIImage*)imageSnapshot {     UIGraphicsBeginImageContextWithOptions(self.bounds.size,YES,self.contentScaleFactor);     [self drawViewHierarchyInRect:self.bounds afterScreenUpdates:YES];     UIImage* newImage = UIGraphicsGetImageFromCurrentImageContext();     UIGraphicsEndImageContext();     return newImage; } @end

However, this approach still does not solve the WebGL page screenshot problem, the author has searched the Apple document, studied the webKit2 source of the screenshot of the private API, still did not find the right solution, and found that Safari and Chrome both full-scale switch to Wkwebview Browser also has the same problem: the screenshot of the WebGL page is either blank or black . Helpless, we can only agree to a JS interface, so that the game developers to implement the interface, specifically through the canvas getImageData() method to obtain the image data after the return Base64 format data, the client when needed, call this JS interface to get Base64 String and converted to UIImage.

7, Wkwebview crash problem

Wkwebview volume, the external network has added some crash, of which the main stack of crash is as follows:



Completion handler passed to -[QZWebController webView:runJavaScriptAlertPanelWithMessage:initiatedByFrame:completionHandler:] was not called

is mainly window.alert() caused by the JS call function, from the crash stack can be seen as the Wkwebview callback function:

+ (void) presentAlertOnController:(nonnull UIViewController*)parentController title:(nullable NSString*)title message:(nullable NSString *)message handler:(nonnull void (^)())completionHandler;

The Completionhandler is not called cause. When we adapt the wkwebview, we need to implement the callback function ourselves, in window.alert() order to adjust the alert box, our initial implementation is this:

- (void)webView:(WKWebView *)webView runJavaScriptAlertPanelWithMessage:(NSString *)message initiatedByFrame:(WKFrameInfo *)frame completionHandler:(void (^)(void))completionHandler {     UIAlertController *alertController = [UIAlertController alertControllerWithTitle:@"" message:message preferredStyle:UIAlertControllerStyleAlert];     [alertController addAction:[UIAlertAction actionWithTitle:@"确认" style:UIAlertActionStyleCancel handler:^(UIAlertAction *action) { completionHandler(); }]];     [self presentViewController:alertController animated:YES completion:^{}]; }

If the Wkwebview exit, JS just execute window.alert() , alert box may not bounce out, Completionhandler finally not executed, resulting in crash; another situation is in the Wkwebview an open, JS on the execution window.alert() , This time because the Wkwebview uiviewcontroller (push or present) animation has not ended, the alert box may not be able to bounce, Completionhandler was not executed, resulting in crash. Our final implementation is roughly the same:

 -(void) WebView: (Wkwebview *) WebView runjavascriptalertpanelwithmessage: (NSString *) message Initiatedbyframe: (Wkframeinfo *) frame Completionhandler: (void (^) (void)) Completionhandler {  &NBSP;IF (/*  Uiviewcontroller of Wkwebview has finish push or present animation*/) {       completionhandler ();         return;    }    uialertcontroller *alertcontroller = [Uialertcontroller alertcontrollerwithtitle:@ ""  Message:message Preferredstyle:uialertcontrollerstylealert];    [alertcontroller addaction:[uialertaction actionwithtitle:@ "Confirm" style:uialertactionstylecancel Handler  : ^ (uialertaction *action) {Completionhandler ();}]];    if (/*uiviewcontroller of Wkwebview is visible*/)        [self Presentviewcontroller:ale  Rtcontroller Animated:yes completion:^{}];    else        completionhandler (); }

Make sure that the Completionhandler can be executed in both cases, eliminating the crash of the Crash,wkwebview bottom confirm box of the Wkwebview pop-up box is similar to the solution to alert.

Another crash occurs before the Wkwebview exits:

 -[WKWebView evaluateJavaScript: completionHandler:]

Executes the JS code in the case. When Wkwebview exits and is released, it completionHandler turns into a wild pointer, and JavaScript core executes the JS code, which is called when the JavaScript core is finished, completionHandler() resulting in crash. This crash only occurs on IOS 8 system, reference Apple Open Source, in IOS9 and later system Apple has fixed this bug, mainly to completionHandler block do copy (refer:https://trac.webkit.org/ changeset/179160); For iOS 8 systems, you can prevent Completionhandler from being released prematurely by retain Wkwebview in Completionhandler. We finally hook up this system method with Methodswizzle:

+ (void) load {      [self jr_swizzleMethod:NSSelectorFromString(@"evaluateJavaScript:completionHandler:") withMethod:@selector(altEvaluateJavaScript:completionHandler:) error:nil]; } /*  * fix: WKWebView crashes on deallocation if it has pending JavaScript evaluation  
- (void)altEvaluateJavaScript:(NSString *)javaScriptString completionHandler:(void (^)(id, NSError *))completionHandler {    id strongSelf = self;    [self altEvaluateJavaScript:javaScriptString completionHandler:^(id r, NSError *e) {        [strongSelf title];        if (completionHandler) {            completionHandler(r, e);        }    }]; }
8, other issues 8.1, video auto-play

Wkwebview need to set WKWebViewConfiguration.mediaPlaybackRequiresUserAction whether to allow AutoPlay, but be sure to set it before Wkwebview is initialized, and the settings are not valid after Wkwebview initialization.

8.2, GoBack API issues

Wkwebview-[wkwebview GoBack], back to the previous page will not trigger the window.onload() function, JS will not be executed.

8.3. Page scrolling Rate

Wkwebview need to scrollView delegate adjust the scrolling rate by:

- (void)scrollViewWillBeginDragging:(UIScrollView *)scrollView {     scrollView.decelerationRate = UIScrollViewDecelerationRateNormal;}
9. Conclusion

This article summarizes some of the pits that have been trampled on the Wkwebview. Although Wkwebview pits more, but relative uiwebview in memory consumption, stability still have a great advantage. Although Apple's progress on Wkwebview is too slow, it is believed that Wkwebview is the future.

IOS Wkwebview those pits

Related Article

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.