Remove rapidjson from TextInputModel (#18397)

This allows this class to be shared with the Linux plugin.
Also move client_id outside this class as was noted in a TODO.
diff --git a/shell/platform/common/cpp/text_input_model.cc b/shell/platform/common/cpp/text_input_model.cc
index b8377ed..0a92dc7 100644
--- a/shell/platform/common/cpp/text_input_model.cc
+++ b/shell/platform/common/cpp/text_input_model.cc
@@ -8,26 +8,6 @@
 #include <codecvt>
 #include <locale>
 
-// TODO(awdavies): Need to fix this regarding issue #47.
-static constexpr char kComposingBaseKey[] = "composingBase";
-
-static constexpr char kComposingExtentKey[] = "composingExtent";
-
-static constexpr char kSelectionAffinityKey[] = "selectionAffinity";
-static constexpr char kAffinityDownstream[] = "TextAffinity.downstream";
-
-static constexpr char kSelectionBaseKey[] = "selectionBase";
-static constexpr char kSelectionExtentKey[] = "selectionExtent";
-
-static constexpr char kSelectionIsDirectionalKey[] = "selectionIsDirectional";
-
-static constexpr char kTextKey[] = "text";
-
-// Input client configuration keys.
-static constexpr char kTextInputAction[] = "inputAction";
-static constexpr char kTextInputType[] = "inputType";
-static constexpr char kTextInputTypeName[] = "name";
-
 #if defined(_MSC_VER)
 // TODO(naifu): This temporary code is to solve link error.(VS2015/2017)
 // https://social.msdn.microsoft.com/Forums/vstudio/en-US/8f40dcd8-c67f-4eba-9134-a19b9178e481/vs-2015-rc-linker-stdcodecvt-error
@@ -49,28 +29,12 @@
 
 }  // namespace
 
-TextInputModel::TextInputModel(int client_id, const rapidjson::Value& config)
-    : client_id_(client_id),
+TextInputModel::TextInputModel(const std::string& input_type,
+                               const std::string& input_action)
+    : input_type_(input_type),
+      input_action_(input_action),
       selection_base_(text_.begin()),
-      selection_extent_(text_.begin()) {
-  // TODO: Improve error handling during refactoring; this is just minimal
-  // checking to avoid asserts since RapidJSON is stricter than jsoncpp.
-  if (config.IsObject()) {
-    auto input_action = config.FindMember(kTextInputAction);
-    if (input_action != config.MemberEnd() && input_action->value.IsString()) {
-      input_action_ = input_action->value.GetString();
-    }
-    auto input_type_info = config.FindMember(kTextInputType);
-    if (input_type_info != config.MemberEnd() &&
-        input_type_info->value.IsObject()) {
-      auto input_type = input_type_info->value.FindMember(kTextInputTypeName);
-      if (input_type != input_type_info->value.MemberEnd() &&
-          input_type->value.IsString()) {
-        input_type_ = input_type->value.GetString();
-      }
-    }
-  }
-}
+      selection_extent_(text_.begin()) {}
 
 TextInputModel::~TextInputModel() = default;
 
@@ -192,34 +156,10 @@
   return false;
 }
 
-std::unique_ptr<rapidjson::Document> TextInputModel::GetState() const {
-  // TODO(stuartmorgan): Move client_id out up to the plugin so that this
-  // function just returns the editing state.
-  auto args = std::make_unique<rapidjson::Document>(rapidjson::kArrayType);
-  auto& allocator = args->GetAllocator();
-  args->PushBack(client_id_, allocator);
-
-  rapidjson::Value editing_state(rapidjson::kObjectType);
-  // TODO(awdavies): Most of these are hard-coded for now.
-  editing_state.AddMember(kComposingBaseKey, -1, allocator);
-  editing_state.AddMember(kComposingExtentKey, -1, allocator);
-  editing_state.AddMember(kSelectionAffinityKey, kAffinityDownstream,
-                          allocator);
-  editing_state.AddMember(kSelectionBaseKey,
-                          static_cast<int>(selection_base_ - text_.begin()),
-                          allocator);
-  editing_state.AddMember(kSelectionExtentKey,
-                          static_cast<int>(selection_extent_ - text_.begin()),
-                          allocator);
-  editing_state.AddMember(kSelectionIsDirectionalKey, false, allocator);
+std::string TextInputModel::GetText() const {
   std::wstring_convert<std::codecvt_utf8_utf16<char16_t>, char16_t>
       utf8_converter;
-  editing_state.AddMember(
-      kTextKey,
-      rapidjson::Value(utf8_converter.to_bytes(text_), allocator).Move(),
-      allocator);
-  args->PushBack(editing_state, allocator);
-  return args;
+  return utf8_converter.to_bytes(text_);
 }
 
 }  // namespace flutter
diff --git a/shell/platform/common/cpp/text_input_model.h b/shell/platform/common/cpp/text_input_model.h
index 5aeda0a..ac6cf8d 100644
--- a/shell/platform/common/cpp/text_input_model.h
+++ b/shell/platform/common/cpp/text_input_model.h
@@ -8,15 +8,14 @@
 #include <memory>
 #include <string>
 
-#include "rapidjson/document.h"
-
 namespace flutter {
 // Handles underlying text input state, using a simple ASCII model.
 //
 // Ignores special states like "insert mode" for now.
 class TextInputModel {
  public:
-  TextInputModel(int client_id, const rapidjson::Value& config);
+  TextInputModel(const std::string& input_type,
+                 const std::string& input_action);
   virtual ~TextInputModel();
 
   // Attempts to set the text state.
@@ -78,11 +77,18 @@
   // Returns true if the cursor could be moved.
   void MoveCursorToEnd();
 
-  // Returns the state in the form of a platform message.
-  std::unique_ptr<rapidjson::Document> GetState() const;
+  // Get the current text
+  std::string GetText() const;
 
-  // Id of the text input client.
-  int client_id() const { return client_id_; }
+  // The position of the cursor
+  int selection_base() const {
+    return static_cast<int>(selection_base_ - text_.begin());
+  }
+
+  // The end of the selection
+  int selection_extent() const {
+    return static_cast<int>(selection_extent_ - text_.begin());
+  }
 
   // Keyboard type of the client. See available options:
   // https://docs.flutter.io/flutter/services/TextInputType-class.html
@@ -96,7 +102,6 @@
   void DeleteSelected();
 
   std::u16string text_;
-  int client_id_;
   std::string input_type_;
   std::string input_action_;
   std::u16string::iterator selection_base_;
diff --git a/shell/platform/glfw/text_input_plugin.cc b/shell/platform/glfw/text_input_plugin.cc
index f95d0d0..484c6c8 100644
--- a/shell/platform/glfw/text_input_plugin.cc
+++ b/shell/platform/glfw/text_input_plugin.cc
@@ -21,9 +21,16 @@
     "TextInputClient.updateEditingState";
 static constexpr char kPerformActionMethod[] = "TextInputClient.performAction";
 
+static constexpr char kTextInputAction[] = "inputAction";
+static constexpr char kTextInputType[] = "inputType";
+static constexpr char kTextInputTypeName[] = "name";
+static constexpr char kComposingBaseKey[] = "composingBase";
+static constexpr char kComposingExtentKey[] = "composingExtent";
+static constexpr char kSelectionAffinityKey[] = "selectionAffinity";
+static constexpr char kAffinityDownstream[] = "TextAffinity.downstream";
 static constexpr char kSelectionBaseKey[] = "selectionBase";
 static constexpr char kSelectionExtentKey[] = "selectionExtent";
-
+static constexpr char kSelectionIsDirectionalKey[] = "selectionIsDirectional";
 static constexpr char kTextKey[] = "text";
 
 static constexpr char kChannelName[] = "flutter/textinput";
@@ -133,8 +140,25 @@
       result->Error(kBadArgumentError,
                     "Could not set client, missing arguments.");
     }
-    int client_id = client_id_json.GetInt();
-    active_model_ = std::make_unique<TextInputModel>(client_id, client_config);
+    client_id_ = client_id_json.GetInt();
+    std::string input_action;
+    auto input_action_json = client_config.FindMember(kTextInputAction);
+    if (input_action_json != client_config.MemberEnd() &&
+        input_action_json->value.IsString()) {
+      input_action = input_action_json->value.GetString();
+    }
+    std::string input_type;
+    auto input_type_info_json = client_config.FindMember(kTextInputType);
+    if (input_type_info_json != client_config.MemberEnd() &&
+        input_type_info_json->value.IsObject()) {
+      auto input_type_json =
+          input_type_info_json->value.FindMember(kTextInputTypeName);
+      if (input_type_json != input_type_info_json->value.MemberEnd() &&
+          input_type_json->value.IsString()) {
+        input_type = input_type_json->value.GetString();
+      }
+    }
+    active_model_ = std::make_unique<TextInputModel>(input_type, input_action);
   } else if (method.compare(kSetEditingStateMethod) == 0) {
     if (!method_call.arguments() || method_call.arguments()->IsNull()) {
       result->Error(kBadArgumentError, "Method invoked without args");
@@ -176,7 +200,24 @@
 }
 
 void TextInputPlugin::SendStateUpdate(const TextInputModel& model) {
-  channel_->InvokeMethod(kUpdateEditingStateMethod, model.GetState());
+  auto args = std::make_unique<rapidjson::Document>(rapidjson::kArrayType);
+  auto& allocator = args->GetAllocator();
+  args->PushBack(client_id_, allocator);
+
+  rapidjson::Value editing_state(rapidjson::kObjectType);
+  editing_state.AddMember(kComposingBaseKey, -1, allocator);
+  editing_state.AddMember(kComposingExtentKey, -1, allocator);
+  editing_state.AddMember(kSelectionAffinityKey, kAffinityDownstream,
+                          allocator);
+  editing_state.AddMember(kSelectionBaseKey, model.selection_base(), allocator);
+  editing_state.AddMember(kSelectionExtentKey, model.selection_extent(),
+                          allocator);
+  editing_state.AddMember(kSelectionIsDirectionalKey, false, allocator);
+  editing_state.AddMember(
+      kTextKey, rapidjson::Value(model.GetText(), allocator).Move(), allocator);
+  args->PushBack(editing_state, allocator);
+
+  channel_->InvokeMethod(kUpdateEditingStateMethod, std::move(args));
 }
 
 void TextInputPlugin::EnterPressed(TextInputModel* model) {
@@ -186,7 +227,7 @@
   }
   auto args = std::make_unique<rapidjson::Document>(rapidjson::kArrayType);
   auto& allocator = args->GetAllocator();
-  args->PushBack(model->client_id(), allocator);
+  args->PushBack(client_id_, allocator);
   args->PushBack(rapidjson::Value(model->input_action(), allocator).Move(),
                  allocator);
 
diff --git a/shell/platform/glfw/text_input_plugin.h b/shell/platform/glfw/text_input_plugin.h
index c3e6673..00c9a73 100644
--- a/shell/platform/glfw/text_input_plugin.h
+++ b/shell/platform/glfw/text_input_plugin.h
@@ -14,6 +14,8 @@
 #include "flutter/shell/platform/glfw/keyboard_hook_handler.h"
 #include "flutter/shell/platform/glfw/public/flutter_glfw.h"
 
+#include "rapidjson/document.h"
+
 namespace flutter {
 
 // Implements a text input plugin.
@@ -50,6 +52,9 @@
   // The MethodChannel used for communication with the Flutter engine.
   std::unique_ptr<flutter::MethodChannel<rapidjson::Document>> channel_;
 
+  // The active client id.
+  int client_id_;
+
   // The active model. nullptr if not set.
   std::unique_ptr<TextInputModel> active_model_;
 };
diff --git a/shell/platform/windows/text_input_plugin.cc b/shell/platform/windows/text_input_plugin.cc
index be34dee..36f6389 100644
--- a/shell/platform/windows/text_input_plugin.cc
+++ b/shell/platform/windows/text_input_plugin.cc
@@ -23,9 +23,16 @@
     "TextInputClient.updateEditingState";
 static constexpr char kPerformActionMethod[] = "TextInputClient.performAction";
 
+static constexpr char kTextInputAction[] = "inputAction";
+static constexpr char kTextInputType[] = "inputType";
+static constexpr char kTextInputTypeName[] = "name";
+static constexpr char kComposingBaseKey[] = "composingBase";
+static constexpr char kComposingExtentKey[] = "composingExtent";
+static constexpr char kSelectionAffinityKey[] = "selectionAffinity";
+static constexpr char kAffinityDownstream[] = "TextAffinity.downstream";
 static constexpr char kSelectionBaseKey[] = "selectionBase";
 static constexpr char kSelectionExtentKey[] = "selectionExtent";
-
+static constexpr char kSelectionIsDirectionalKey[] = "selectionIsDirectional";
 static constexpr char kTextKey[] = "text";
 
 static constexpr char kChannelName[] = "flutter/textinput";
@@ -135,8 +142,25 @@
                     "Could not set client, missing arguments.");
       return;
     }
-    int client_id = client_id_json.GetInt();
-    active_model_ = std::make_unique<TextInputModel>(client_id, client_config);
+    client_id_ = client_id_json.GetInt();
+    std::string input_action;
+    auto input_action_json = client_config.FindMember(kTextInputAction);
+    if (input_action_json != client_config.MemberEnd() &&
+        input_action_json->value.IsString()) {
+      input_action = input_action_json->value.GetString();
+    }
+    std::string input_type;
+    auto input_type_info_json = client_config.FindMember(kTextInputType);
+    if (input_type_info_json != client_config.MemberEnd() &&
+        input_type_info_json->value.IsObject()) {
+      auto input_type_json =
+          input_type_info_json->value.FindMember(kTextInputTypeName);
+      if (input_type_json != input_type_info_json->value.MemberEnd() &&
+          input_type_json->value.IsString()) {
+        input_type = input_type_json->value.GetString();
+      }
+    }
+    active_model_ = std::make_unique<TextInputModel>(input_type, input_action);
   } else if (method.compare(kSetEditingStateMethod) == 0) {
     if (!method_call.arguments() || method_call.arguments()->IsNull()) {
       result->Error(kBadArgumentError, "Method invoked without args");
@@ -178,7 +202,24 @@
 }
 
 void TextInputPlugin::SendStateUpdate(const TextInputModel& model) {
-  channel_->InvokeMethod(kUpdateEditingStateMethod, model.GetState());
+  auto args = std::make_unique<rapidjson::Document>(rapidjson::kArrayType);
+  auto& allocator = args->GetAllocator();
+  args->PushBack(client_id_, allocator);
+
+  rapidjson::Value editing_state(rapidjson::kObjectType);
+  editing_state.AddMember(kComposingBaseKey, -1, allocator);
+  editing_state.AddMember(kComposingExtentKey, -1, allocator);
+  editing_state.AddMember(kSelectionAffinityKey, kAffinityDownstream,
+                          allocator);
+  editing_state.AddMember(kSelectionBaseKey, model.selection_base(), allocator);
+  editing_state.AddMember(kSelectionExtentKey, model.selection_extent(),
+                          allocator);
+  editing_state.AddMember(kSelectionIsDirectionalKey, false, allocator);
+  editing_state.AddMember(
+      kTextKey, rapidjson::Value(model.GetText(), allocator).Move(), allocator);
+  args->PushBack(editing_state, allocator);
+
+  channel_->InvokeMethod(kUpdateEditingStateMethod, std::move(args));
 }
 
 void TextInputPlugin::EnterPressed(TextInputModel* model) {
@@ -188,7 +229,7 @@
   }
   auto args = std::make_unique<rapidjson::Document>(rapidjson::kArrayType);
   auto& allocator = args->GetAllocator();
-  args->PushBack(model->client_id(), allocator);
+  args->PushBack(client_id_, allocator);
   args->PushBack(rapidjson::Value(model->input_action(), allocator).Move(),
                  allocator);
 
diff --git a/shell/platform/windows/text_input_plugin.h b/shell/platform/windows/text_input_plugin.h
index 44179f8..9000da7 100644
--- a/shell/platform/windows/text_input_plugin.h
+++ b/shell/platform/windows/text_input_plugin.h
@@ -10,6 +10,7 @@
 
 #include "flutter/shell/platform/common/cpp/client_wrapper/include/flutter/binary_messenger.h"
 #include "flutter/shell/platform/common/cpp/client_wrapper/include/flutter/method_channel.h"
+#include "flutter/shell/platform/common/cpp/json_method_codec.h"
 #include "flutter/shell/platform/common/cpp/text_input_model.h"
 #include "flutter/shell/platform/windows/keyboard_hook_handler.h"
 #include "flutter/shell/platform/windows/public/flutter_windows.h"
@@ -53,6 +54,9 @@
   // The MethodChannel used for communication with the Flutter engine.
   std::unique_ptr<flutter::MethodChannel<rapidjson::Document>> channel_;
 
+  // The active client id.
+  int client_id_;
+
   // The active model. nullptr if not set.
   std::unique_ptr<TextInputModel> active_model_;
 };