iOS Source Parsing - WebView Javascript Bridge

Keywords: Javascript encoding JSON

brief introduction

A bridge mechanism for OC and JS interaction consists of three main classes: JS-side window. WebView Javascript Bridge, OC-side WebView Javascript Bridge and WebView Javascript BridgeBase. Bridge class supports JS calling OC method, OC calling JS method. JS calls OC by redirecting url and taking handlerName. OC calls JS by stringByEvaluating JavaScript FromString.

  1. OC side initialization, registration func:

    • A webview Javascript Bridge and a webview Javascript BridgeBase are generated by initialization method. The delegate of webview is changed to webview Javascript Bridge, and the _webview Delegate of webview Javascript Bridge is an external incoming webview Controller.

      + (instancetype)bridgeForWebView:(WVJB_WEBVIEW_TYPE*)webView webViewDelegate:(WVJB_WEBVIEW_DELEGATE_TYPE*)webViewDelegate handler:(WVJBHandler)messageHandler resourceBundle:(NSBundle*)bundle
      {
       WebViewJavascriptBridge* bridge = [[self alloc] init];
       [bridge _platformSpecificSetup:webView webViewDelegate:webViewDelegate handler:messageHandler resourceBundle:bundle];
       return bridge;
      }
      
      - (void) _platformSpecificSetup:(WVJB_WEBVIEW_TYPE*)webView webViewDelegate:(id<UIWebViewDelegate>)webViewDelegate handler:(WVJBHandler)messageHandler resourceBundle:(NSBundle*)bundle 
      {
       _webView = webView;
       _webView.delegate = self;
       _webViewDelegate = webViewDelegate;
       _base = [[WebViewJavascriptBridgeBase alloc] initWithHandler:(WVJBHandler)messageHandler resourceBundle:(NSBundle*)bundle];
       _base.delegate = self;
      }
    • MessageageHandler is the default handler. When JS calls OC, the default messageHandler is called when OC's block cannot be found according to the method name, or OC is called through the send() method.

    • messageHandlers are key-value pairs that store OC registration methods and corresponding method names. startupMessageQueue is an array that stores the JS method message invoked by OC before the JS-side WebView JavascriptBridge. js. TXT script is executed.

      -(id)initWithHandler:(WVJBHandler)messageHandler resourceBundle:(NSBundle*)bundle
      {
       self = [super init];
       _resourceBundle = bundle;
       self.messageHandler = messageHandler;
       self.messageHandlers = [NSMutableDictionary dictionary];
       self.startupMessageQueue = [NSMutableArray array];
       self.responseCallbacks = [NSMutableDictionary dictionary];
       _uniqueId = 0;
       return(self);
      }
    • Register the OC method and call registerHandler:name handler:(WVJBHandler)handler to save to the message handlers key value.

      - (void)registerHandler:(NSString *)handlerName handler:(WVJBHandler)handler {
       _base.messageHandlers[handlerName] = [handler copy];
      }
  2. JS-side initialization and registration of func

    • Loading JS scripts

      After the webView is loaded, enter the webViewDidFinishLoad callback, call injectJavascriptFile, and execute the WebView JavascriptBridge. JS script. And batch processing of message in startup MessageQueue.

      - (void)injectJavascriptFile:(BOOL)shouldInject {
           ...
           NSString *filePath = [bundle pathForResource:@"WebViewJavascriptBridge.js" ofType:@"txt"];
           NSString *js = [NSString stringWithContentsOfFile:filePath encoding:NSUTF8StringEncoding error:nil];
           [self _evaluateJavascript:js];
           [self dispatchStartUpMessageQueue];
      }
      
      - (void)dispatchStartUpMessageQueue {
       if (self.startupMessageQueue) {
           for (id queuedMessage in self.startupMessageQueue) {
               [self _dispatchMessage:queuedMessage];
           }
           self.startupMessageQueue = nil;
       }
      }
    • The WebView JavascriptBridge. JS script creates the window. WebView JavascriptBridge object. _ handleMessageFromObjC is the entry for OC to invoke JS.

      window.WebViewJavascriptBridge = {
      init: init,
       send: send,
       registerHandler: registerHandler,
       callHandler: callHandler,
       _fetchQueue: _fetchQueue,
       _handleMessageFromObjC: _handleMessageFromObjC
      }
    • At the same time, create some variables, receiveMessageQueue is an array, storing the message message queue of OC calling JS before init method call. messageHandlers are key-value pairs that store JS-side registration. sendMessageQueue is a Message queue that stores the method that JS calls OC.

      var messagingIframe
      var sendMessageQueue = []
      var receiveMessageQueue = []
      var messageHandlers = {}
    • Register the JS response method and call the registerHandler method to register.

      function registerHandler(handlerName, handler) {
      messageHandlers[handlerName] = handler
      }
    • The init method is initialized, and messageHandler is the initialized callback block, which handles the default logic, just like OC's messageHandler function. At the same time, batch processing of messages in receiveMessageQueue.

      function init(messageHandler) {
      WebViewJavascriptBridge._messageHandler = messageHandler
      var receivedMessages = receiveMessageQueue
      receiveMessageQueue = null
      for (var i=0; i<receivedMessages.length; i++) {
              _dispatchMessageFromObjC(receivedMessages[i])
      }
      }
  3. OC calls JS func:

    • Call the callHandler:handlerName data:data responseCallback:responseCallback method or send:data responseCallback:responseCallback method to call the JS method. The sendData:data responseCallback:responseCallback handlerName:handlerName method of WebView JavascriptBridgeBase is called internally.

      - (void)sendData:(id)data responseCallback:(WVJBResponseCallback)responseCallback handlerName:(NSString*)handlerName {
       NSMutableDictionary* message = [NSMutableDictionary dictionary];
       if (data) {
           message[@"data"] = data;
       }
       if (responseCallback) {
           NSString* callbackId = [NSString stringWithFormat:@"objc_cb_%ld", ++_uniqueId];
           self.responseCallbacks[callbackId] = [responseCallback copy];
           message[@"callbackId"] = callbackId;
       }
       if (handlerName) {
           message[@"handlerName"] = handlerName;
       }
       [self _queueMessage:message];
      }
    • If responseCallback, a responseCallback is created and maintained to the responseCallbacks key pair. Wrap a Message object, including data, handlerName and callbackId, and send it together. If WebView JavascriptBridge. JS is not loaded, add it to the startup MessageQueue queue. If loaded, it is sent to the window. WebView JavascriptBridge object.

      - (void)_queueMessage:(WVJBMessage*)message {
       if (self.startupMessageQueue) {
           [self.startupMessageQueue addObject:message];
       } else {
           [self _dispatchMessage:message];
       }
      }
      - (void)_dispatchMessage:(WVJBMessage*)message {
          ...
          NSString* javascriptCommand = [NSString stringWithFormat:@"WebViewJavascriptBridge._handleMessageFromObjC('%@');", messageJSON];
      [self _evaluateJavascript:javascriptCommand];
      }
    • Execute the _handleMessageFromObjC method. If the WebView JavascriptBridge is not init initialized, it is added to the receiveMessageQueue queue, otherwise dispatchMessageFromObjC is executed.

      function _handleMessageFromObjC(messageJSON) {
       if (receiveMessageQueue) {
           receiveMessageQueue.push(messageJSON)
       } else {
           _dispatchMessageFromObjC(messageJSON)
       }
      }
    • dispatchMessageFromObjC, if there is callBackId, creates a callback func, calls back OC through doSend, and if there is no handler Name, calls the default messageHandler to pass data.

      function _dispatchMessageFromObjC(messageJSON) {
      if (message.callbackId) {
          var callbackResponseId = message.callbackId
          responseCallback = function(responseData) {
              _doSend({ responseId:callbackResponseId, responseData:responseData })
          }
      }
      var handler = WebViewJavascriptBridge._messageHandler
      if (message.handlerName) {
          handler = messageHandlers[message.handlerName]
      }
      handler(message.data, responseCallback)
      }
    • To handle callbacks, the js method calls responseCallback(), passes in responseId and responseData, and calls the doSend method. The doSend method first builds a Message, puts it in the sendMessageQueue, and then redirects the scheme://message.

      function _doSend(message, responseCallback) {
      ...
       sendMessageQueue.push(message)
       messagingIframe.src = CUSTOM_PROTOCOL_SCHEME + '://' + QUEUE_HAS_MESSAGE
      }
    • The WebView Javascript Bridge on the OC side intercepts the url, gets the message in the sendMessageQueue array, and executes the flushMessageQueue:message method.

      - (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType {
       ...
       NSString *messageQueueString = [self _evaluateJavascript:[_base webViewJavascriptFetchQueyCommand]];
       [_base flushMessageQueue:messageQueueString];
       ...
      }
    • In the flushMessageQueue:message method, if there is a responseId, the OC callback method is extracted from the _responseCallbacks key pair by responseId, and the responseData is passed to the callback method.

      - (void)flushMessageQueue:(NSString *)messageQueueString{
      for (WVJBMessage* message in messages) {
      NSString* responseId = message[@"responseId"];
          if (responseId) {
              WVJBResponseCallback responseCallback = _responseCallbacks[responseId];
               responseCallback(message[@"responseData"]);
           }
      }
      }
  4. JS calls OC func:

    • JS calls send and callHandler, and then calls the doSend() method

      _doSend({ handlerName:handlerName, data:data }, responseCallback)
    • _ In the doSend method, if there is a callback response Callback, the callback response Callbacks are maintained and stored. Add message in sendMessageQueue. Then redirect Url.

      function _doSend(message, responseCallback) {
      if (responseCallback) {
          var callbackId = 'cb_'+(uniqueId++)+'_'+new Date().getTime()
           responseCallbacks[callbackId] = responseCallback
           message['callbackId'] = callbackId
       }
       sendMessageQueue.push(message)
       messagingIframe.src = CUSTOM_PROTOCOL_SCHEME + '://' + QUEUE_HAS_MESSAGE
      }
    • ShouStartLoadWithRequest: Method intercepts the url, gets the message queue through WebView JavascriptBridge. _fetchQueue (), and then calls the flushMessageQueue method to process the message.

      - (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType {
      NSString *messageQueueString = [self _evaluateJavascript:[_base webViewJavascriptFetchQueyCommand]];
      [_base flushMessageQueue:messageQueueString];
       return NO;
      }
    • FlushMessageQueue: The messageQueue method, if there is a callbackId, calls the _queueMessage method to pass callback data and responseId. If there is handlerName, the corresponding OC method is invoked, otherwise the default method messageHandler is invoked.

      - (void)flushMessageQueue:(NSString *)messageQueueString{
      NSString* callbackId = message[@"callbackId"];
      if (callbackId) {
          responseCallback = ^(id responseData) {
              if (responseData == nil) {
                  responseData = [NSNull null];
               }
               WVJBMessage* msg = @{ @"responseId":callbackId, @"responseData":responseData };
              [self _queueMessage:msg];
           };
      }
      WVJBHandler handler;
      if (message[@"handlerName"]) {
          handler = self.messageHandlers[message[@"handlerName"]];
       } else {
          handler = self.messageHandler;
       }
      handler(message[@"data"], responseCallback);
      }
    • _ The queueMessage method calls the dispatchMessage method, followed by the WebViewJavascriptBridge.handleMessageFromObjC method.

      - (void)_dispatchMessage:(WVJBMessage*)message {
      NSString* javascriptCommand = [NSString stringWithFormat:@"WebViewJavascriptBridge._handleMessageFromObjC('%@');", messageJSON];
      [self _evaluateJavascript:javascriptCommand];
      }
    • _ handleMessageFromObjC calls the dispatchMessageFromObjC (message JSON) method. If the message has responseId, it finds the JS callback method through responseId and responseCallbacks and executes it.

      function _dispatchMessageFromObjC(messageJSON) {
      if (message.responseId) {
          responseCallback = responseCallbacks[message.responseId]
           if (!responseCallback) { return; }
           responseCallback(message.responseData)
           delete responseCallbacks[message.responseId]
       }
      }

Ending

WebView Javascript Bridge provides a very lightweight way of thinking about the interaction between native and js.

Posted by OLG on Fri, 12 Apr 2019 15:06:33 -0700