chromium/ios/web/js_messaging/scoped_wk_script_message_handler.h

// Copyright 2021 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#ifndef IOS_WEB_JS_MESSAGING_SCOPED_WK_SCRIPT_MESSAGE_HANDLER_H_
#define IOS_WEB_JS_MESSAGING_SCOPED_WK_SCRIPT_MESSAGE_HANDLER_H_

#import <WebKit/WebKit.h>

#include "base/functional/callback.h"

namespace base {
class Value;
}  // namespace base

@class CRWScriptMessageHandler;
@class CRWScriptMessageHandlerWithReply;

// Callback to receive messages from JavaScript running in a webpage.
using ScriptMessageCallback =
    base::RepeatingCallback<void(WKScriptMessage* message)>;

// A block to be called with the result of processing the message from
// JavaScript.
// 1. Passing a non-nil NSString value to the `error_message` signals an error.
// No matter what value you pass to the `reply`. the Promise will be rejected
// with a JavaScript error object whose message property is set to that
// `error_message` string.
// 2. If the `error_message` is nil, the `reply` will be converted to its
// JavaScript equivalent and the Promise will be fulfilled with the resulting
// value.
//    a. If `reply` is nullptr, then the JavaScript resulting value is
//    `undefined`.
//    b. If `reply` is none type base::Value, then the JavaScript resulting
//    value is `null`.
using ScriptMessageReplyHandler = void (^)(const base::Value* reply,
                                           NSString* error_message);
// Callback to receive messages from JavaScript running in a webpage and
// replying to them asynchronously. The `reply_handler` can be called at most
// once. If the `reply_handler` is deallocated before it is called, the Promise
// will be rejected with a JavaScript Error object with an appropriate message
// indicating the handler was never called.
using ScriptMessageWithReplyCallback =
    base::RepeatingCallback<void(WKScriptMessage* message,
                                 ScriptMessageReplyHandler reply_handler)>;

// Instances of this class register and unregister itself as a
// WKUserContentController script message handler upon construction and
// deconstruction respectively.
class ScopedWKScriptMessageHandler {
 public:
  // Registers `script_handler_name` with `user_content_controller`. `callback`
  // will be called whenever JavaScript sends a post message to
  // `script_handler_name` within the page content world.
  // Ex: window.webkit.messageHandlers['script_handler_name'].postMessage(10);
  ScopedWKScriptMessageHandler(WKUserContentController* user_content_controller,
                               NSString* script_handler_name,
                               ScriptMessageCallback callback);

  // Registers `script_handler_name` with `user_content_controller` within
  // `content_world`. `callback` will be called whenever JavaScript
  // sends a post message to `script_handler_name` within `content_world`
  // Ex: window.webkit.messageHandlers['script_handler_name'].postMessage(10);
  ScopedWKScriptMessageHandler(WKUserContentController* user_content_controller,
                               NSString* script_handler_name,
                               WKContentWorld* content_world,
                               ScriptMessageCallback callback);

  // Registers `script_handler_name` with `user_content_controller` within
  // `content_world`. `callback` will be called whenever JavaScript
  // sends a post message to `script_handler_name` within `content_world`, and
  // it allows native to reply to JavaScript via `reply_handler` which is passed
  // to `callback`.
  //
  // Ex: let result = await
  // window.webkit.messageHandlers['script_handler_name'].postMessage("42");
  ScopedWKScriptMessageHandler(WKUserContentController* user_content_controller,
                               NSString* script_handler_name,
                               WKContentWorld* content_world,
                               ScriptMessageWithReplyCallback callback);

  ~ScopedWKScriptMessageHandler();

 private:
  // The content world associated with this feature. May be null which
  // represents the main world that the page content itself uses. (May also be
  // [WKContentWorld pageWorld] on iOS 14 and later.)
  WKContentWorld* content_world_ = nullptr;

  __weak WKUserContentController* user_content_controller_;
  NSString* script_handler_name_;

  // Called with messages sent from JavaScript if the constructor accepting a
  // `ScriptMessageCallback` was used, null otherwise.
  CRWScriptMessageHandler* script_message_handler_;
  // Called with messages sent from JavaScript if the constructor accepting a
  // `ScriptMessageWithReplyCallback` was used, null otherwise.
  CRWScriptMessageHandlerWithReply* script_message_handler_with_reply_;

  ScopedWKScriptMessageHandler(const ScopedWKScriptMessageHandler&) = delete;
  ScopedWKScriptMessageHandler& operator=(const ScopedWKScriptMessageHandler&) =
      delete;
};

#endif  // IOS_WEB_JS_MESSAGING_SCOPED_WK_SCRIPT_MESSAGE_HANDLER_H_