Question on using private API in WKWebView

Hi, thank you for reading the following questions:

UIWebView can intercept network requests by registering NSURLProtocol sub-type like NSURLProtocol registerClass:[RNCachingURLProtocol class]]. Therefore, local resource files (such as: js,css,png) can be modified or uploaded.

However, NSURLProtocol is not supported by WKWebView by default. If registered with a private API, WKWebView can intercept local resources in the way UIWebView does. The codes are as follows:

Class cls = NSClassFromString(@"WKBrowsingContextController");
SEL sel = NSSelectorFromString(@"registerSchemeForCustomProtocol:");
if ([cls respondsToSelector:sel]) {
[cls performSelector:sel withObject:@"http"];
[cls performSelector:sel withObject:@"https"];
}


Regard to this, I’d like to know:

1.Will there be any risk if I use the above API in WKWebView? Is there any chance that my App would be rejected by App Review?
2. If the above private API is risky, is there any better solutions for this problem?

Thanks again!

Replies

If it is Apple private, you will be caught during app review and submission.

1. Will there be any risk if I use the above API in WKWebView?

What iTen said but also…

This is not just about App Review; there’s also a significant binary compatibility risk associated with using private APIs. You don’t want to build a core part of your app’s functionality on an unstable foundation. If something changes in the OS and that foundation goes away, your app collapses and there’s no way to rebuild it.

2. If the above private API is risky, is there any better solutions for this problem?

iOS 11 has a new mechanism for implementing custom resource loading, namely WKURLSchemeHandler. You can learn more about this in WWDC 2017 Session 220 Customized Loading in WKWebView.

Whether this meets your needs depends on your specific requirements. Notably, WKURLSchemeHandler only lets you implement custom URL schemes, it does not let you override the built-in handling of the

http
and
https
scheme. But perhaps you can change the scheme, either on your origin server or via a user script.

Share and Enjoy

Quinn “The Eskimo!”
Apple Developer Relations, Developer Technical Support, Core OS/Hardware

let myEmail = "eskimo" + "1" + "@apple.com"

Thanks, Quinn. You said that I can refer to the new iOS 11 mechanism. However, I didn't find any detailed description on WKURLSchemeHandler either in Apple Forum or iOS 11 SDK. Is there any way that I can see the demo code or demo program of WKURLSchemeHandler?

Thanks for reading.

I’m not aware of any official sample code for this. However, the API is pretty straightforward and has good doc comments. That, combined with the overview from the WWDC session, should allow you to get started.

Give it a while and see what you get.

Share and Enjoy

Quinn “The Eskimo!”
Apple Developer Relations, Developer Technical Support, Core OS/Hardware

let myEmail = "eskimo" + "1" + "@apple.com"

Has anyone actually gotten a WKURLSchemeHandler to work? I'm calling


        self.wv.configuration.setURLSchemeHandler(MySchemeHandler(),
                                                  forURLScheme: "test")


But although my HTML tries to fetch data from a `test:1` URL, no code in MySchemeHandler is ever called, and in fact there is no evidence that the code worked at all: `self.wv.configuration.urlSchemeHandler(forURLScheme: "test")` is nil.


It seems like this feature, though much discussed in the video, might be largely aspirational.

But although my HTML tries to fetch data from a

test:1
URL, no code in
MySchemeHandler
is ever called …

I don’t have time to do an end-to-end test of this right now but but I definitely got further than you (-: Here’s what I did different:

  • I used

    x-qqqtest
    rather than
    test
    , just in case there’s some sort of built-in
    test
    scheme.
  • I used

    x-qqqtest://some/random/path
    as the URL to make it look more ‘real’.
  • I used

    self
    as my scheme handler, like so:
    override func loadView() {
        let config = WKWebViewConfiguration()
        config.setURLSchemeHandler(self, forURLScheme: "x-qqqtest")
        let webView = WKWebView(frame: CGRect.zero, configuration: config)
        webView.navigationDelegate = self
        self.view = webView
    }
    
    
    func webView(_ webView: WKWebView, start urlSchemeTask: WKURLSchemeTask) {
        NSLog("start called")
    }
    
    
    func webView(_ webView: WKWebView, stop urlSchemeTask: WKURLSchemeTask) {
        NSLog("stop called")
    }

    -

I suspect that the last point is key. It wouldn’t surprise me if you’re having problems because the web view configuration holds a weak reference to the scheme handler and you’re not holding it in memory yourself.

Share and Enjoy

Quinn “The Eskimo!”
Apple Developer Relations, Developer Technical Support, Core OS/Hardware

let myEmail = "eskimo" + "1" + "@apple.com"

No, the reference is a strong reference. (Indeed, it can easily cause a memory leak of your whole darned view controller if you are not careful.) The problem turns out to be that the start of the code has to be exactly what you did. But you'll notice that that is not what I did. You cannot say what I said:


self.wv.configuration.setURLSchemeHandler(MySchemeHandler(),  forURLScheme: "test")


Once the configuration has been assigned to (or born with) the web view, setURLSchemeHandler always fails. I regard that as a bug and have reported it. Now that WKWebView is an object in the Interface Builder Object library, it is wrong to expect that I will always be making the web view by hand.

I regard that as a bug and have reported it.

Fair enough. What was the bug number?

Share and Enjoy

Quinn “The Eskimo!”
Apple Developer Relations, Developer Technical Support, Core OS/Hardware

let myEmail = "eskimo" + "1" + "@apple.com"

That would be 34832253.

Accessing the configuration on an already-created WKWebView gives you a copy of the configuration the WKWebVIew was created with.

It does not change the WKWebView's setup after the fact.

There are two ways of using private API

1. Using Objective-C

In your application, create an Objective-C header file (Make sure you enable bridging header). In the Objective-C file, add this code
Code Block Objective-C
@interface WKPreferences ()
@end

Open this link https://github.com/WebKit/webkit/blob/master/Source/WebKit/UIProcess/API/Cocoa/WKPreferences.mm
In the link, you will find many private API's
Chose an API you want to use then add it to your Objective-C

For Example...
Code Block Objective-C
@interface WKPreferences ()
- (void)_setDeveloperExtrasEnabled:(BOOL)developerExtrasEnabled;
@end


Then you just do this
Code Block Swift
MyWebView.configuration.preferences._setDeveloperExtrasEnabled(true)



Before adding another private API, make sure you change this

Code Block
- (void)_setTelephoneNumberDetectionIsEnabled:(BOOL)telephoneNumberDetectionIsEnabled
{
_preferences->setTelephoneNumberParsingEnabled(telephoneNumberDetectionIsEnabled);
}


To this


Code Block Objective-C
-(void)_setTelephoneNumberDetectionIsEnabled:(BOOL)telephoneNumberDetectionIsEnabled;

2. Using the set value


Go to this link https://github.com/WebKit/webkit/blob/master/Source/WebKit/UIProcess/API/Cocoa/WKPreferences.mm
Chose a API that isn’t an Objective-C function
For example...
Code Block Swift
- (BOOL)_telephoneNumberDetectionIsEnabled
{
return _preferences->telephoneNumberParsingEnabled();
}

Find the title of the Objective-C bool
Which is
Code Block
_telephoneNumberDetectionIsEnabled

Remove the underscore then put it in the string below

Code Block Swift
mainWebView.configuration.preferences.setValue(true, forKey: "telephoneNumberDetectionIsEnabled")