WKUserContentController: subsequent add/remove/add of script message handler results in the handler not being called from JavaScript

tl;dr


do

WKUserContentController* ucc = self.webView.configuration.userContentController; 
[ucc addScriptMessageHandler:self name:@"serverTimeCallbackHandler"]; // add
[ucc removeScriptMessageHandlerForName:@"serverTimeCallbackHandler"]; // remove
[ucc addScriptMessageHandler:self name:@"serverTimeCallbackHandler"]; // add


...and

window.webkit.messageHandlers.serverTimeCallbackHandler.postMessage(<messageBody>)
will not be called from JavaScript.

description


I faced with issue while using

WebKit.framework
. It is either framework's fault or inappropriate usage of it.

I have a

.js
file which interacts with a webpage. At the end of its interaction, the script calls
window.webkit.messageHandlers.<name>.postMessage(<messageBody>)

    (function() {
        var timeZoneCoockieName = 'cTz';

        function getServerTimeWithTimeZone() {
            PageMethods.GetServerTime(function(time) {
           var serverTime = time;

            var timezone = getCookie(timeZoneCoockieName);

            var message = {'time': serverTime, 'timezone': timezone};

            window.webkit.messageHandlers.serverTimeCallbackHandler.postMessage(message);
            });
        }

        function getCookie(name) {
            var value = "; " + document.cookie;
            var parts = value.split("; " + name + "=");
            if (parts.length == 2) return parts.pop().split(";").shift();
        }

        getServerTimeWithTimeZone();
    })();


Basically, the script calls a page method, to retrieve time from the server and looks up for time zone stored in cookies upon the callback.

The native component is designed to wrap this call and expose convenient API to retrieve date/time zone info.

    - (void)getServerTimeWithCompletion:((^)(id, NSError *))completion {
        // Adding same message handler twice, leads to an exception.
        // Removing message handler with the same name, just in case.
        [self.webView.configuration.userContentController removeScriptMessageHandlerForName:@"serverTimeCallbackHandler"];
        [self.webView.configuration.userContentController addScriptMessageHandler:self name:@"serverTimeCallbackHandler"];

        self.getServerTimeCompleteBlock = completion;

        NSString * jsPath = [[NSBundle mainBundle] pathForResource:@"GetServerTime" ofType:@"js"];
        if (![jsPath isBlank]) {
            NSError* encodingError = nil;
            NSString* jsContent = [NSString stringWithContentsOfFile:jsPath
                                                            encoding:NSUTF8StringEncoding
                                                               error:&encodingError];
            if (!encodingError) {
                [self.webView evaluateJavaScript:jsContent completionHandler:  ^(id _Nullable result, NSError * _Nullable evalError) {
                    if (evalError) {
                        if (completion) { completion(nil, evalError); }
                    }
                 }];
            } else {
                if (completion) { completion(nil, encodingError); }
            }
        }
        else {
            if (completion) { completion(nil, nil); }
        }
    }


Now, if this method is called twice by a user, second time the handler is not called from JavaScript.

Web Debugger attached to the app's web view, logs the error:

InvalidAccessError: DOM Exception 15: A parameter or an operation was not supported by the underlying object.

WKUserContentController: subsequent add/remove/add of script message handler results in the handler not being called from JavaScript
 
 
Q