| 1 | // Copyright 2013 The Flutter Authors. All rights reserved. |
| 2 | // Use of this source code is governed by a BSD-style license that can be |
| 3 | // found in the LICENSE file. |
| 4 | |
| 5 | #define RAPIDJSON_HAS_STDSTRING 1 |
| 6 | |
| 7 | #include "flutter/runtime/service_protocol.h" |
| 8 | |
| 9 | #include <cstring> |
| 10 | #include <sstream> |
| 11 | #include <string> |
| 12 | #include <utility> |
| 13 | #include <vector> |
| 14 | |
| 15 | #include "flutter/fml/posix_wrappers.h" |
| 16 | #include "flutter/fml/synchronization/waitable_event.h" |
| 17 | #include "rapidjson/stringbuffer.h" |
| 18 | #include "rapidjson/writer.h" |
| 19 | #include "third_party/dart/runtime/include/dart_tools_api.h" |
| 20 | |
| 21 | namespace flutter { |
| 22 | |
| 23 | const std::string_view ServiceProtocol::kScreenshotExtensionName = |
| 24 | "_flutter.screenshot" ; |
| 25 | const std::string_view ServiceProtocol::kScreenshotSkpExtensionName = |
| 26 | "_flutter.screenshotSkp" ; |
| 27 | const std::string_view ServiceProtocol::kRunInViewExtensionName = |
| 28 | "_flutter.runInView" ; |
| 29 | const std::string_view ServiceProtocol::kFlushUIThreadTasksExtensionName = |
| 30 | "_flutter.flushUIThreadTasks" ; |
| 31 | const std::string_view ServiceProtocol::kSetAssetBundlePathExtensionName = |
| 32 | "_flutter.setAssetBundlePath" ; |
| 33 | const std::string_view ServiceProtocol::kGetDisplayRefreshRateExtensionName = |
| 34 | "_flutter.getDisplayRefreshRate" ; |
| 35 | const std::string_view ServiceProtocol::kGetSkSLsExtensionName = |
| 36 | "_flutter.getSkSLs" ; |
| 37 | const std::string_view |
| 38 | ServiceProtocol::kEstimateRasterCacheMemoryExtensionName = |
| 39 | "_flutter.estimateRasterCacheMemory" ; |
| 40 | const std::string_view |
| 41 | ServiceProtocol::kRenderFrameWithRasterStatsExtensionName = |
| 42 | "_flutter.renderFrameWithRasterStats" ; |
| 43 | const std::string_view ServiceProtocol::kReloadAssetFonts = |
| 44 | "_flutter.reloadAssetFonts" ; |
| 45 | |
| 46 | static constexpr std::string_view kViewIdPrefx = "_flutterView/" ; |
| 47 | static constexpr std::string_view kListViewsExtensionName = |
| 48 | "_flutter.listViews" ; |
| 49 | |
| 50 | ServiceProtocol::ServiceProtocol() |
| 51 | : endpoints_({ |
| 52 | // Private |
| 53 | kListViewsExtensionName, |
| 54 | |
| 55 | // Public |
| 56 | kScreenshotExtensionName, |
| 57 | kScreenshotSkpExtensionName, |
| 58 | kRunInViewExtensionName, |
| 59 | kFlushUIThreadTasksExtensionName, |
| 60 | kSetAssetBundlePathExtensionName, |
| 61 | kGetDisplayRefreshRateExtensionName, |
| 62 | kGetSkSLsExtensionName, |
| 63 | kEstimateRasterCacheMemoryExtensionName, |
| 64 | kRenderFrameWithRasterStatsExtensionName, |
| 65 | kReloadAssetFonts, |
| 66 | }), |
| 67 | handlers_mutex_(fml::SharedMutex::Create()) {} |
| 68 | |
| 69 | ServiceProtocol::~ServiceProtocol() { |
| 70 | ToggleHooks(set: false); |
| 71 | } |
| 72 | |
| 73 | void ServiceProtocol::AddHandler(Handler* handler, |
| 74 | const Handler::Description& description) { |
| 75 | fml::UniqueLock lock(*handlers_mutex_); |
| 76 | handlers_.emplace(args&: handler, args: description); |
| 77 | } |
| 78 | |
| 79 | void ServiceProtocol::RemoveHandler(Handler* handler) { |
| 80 | fml::UniqueLock lock(*handlers_mutex_); |
| 81 | handlers_.erase(k: handler); |
| 82 | } |
| 83 | |
| 84 | void ServiceProtocol::SetHandlerDescription( |
| 85 | Handler* handler, |
| 86 | const Handler::Description& description) { |
| 87 | fml::SharedLock lock(*handlers_mutex_); |
| 88 | auto it = handlers_.find(k: handler); |
| 89 | if (it != handlers_.end()) { |
| 90 | it->second.Store(object: description); |
| 91 | } |
| 92 | } |
| 93 | |
| 94 | void ServiceProtocol::ToggleHooks(bool set) { |
| 95 | for (const auto& endpoint : endpoints_) { |
| 96 | Dart_RegisterIsolateServiceRequestCallback( |
| 97 | method: endpoint.data(), // method |
| 98 | callback: &ServiceProtocol::HandleMessage, // callback |
| 99 | user_data: set ? this : nullptr // user data |
| 100 | ); |
| 101 | } |
| 102 | } |
| 103 | |
| 104 | static void WriteServerErrorResponse(rapidjson::Document* document, |
| 105 | const char* message) { |
| 106 | document->SetObject(); |
| 107 | document->AddMember(name: "code" , value: -32000, allocator&: document->GetAllocator()); |
| 108 | rapidjson::Value message_value; |
| 109 | message_value.SetString(s: message, allocator&: document->GetAllocator()); |
| 110 | document->AddMember(name: "message" , value&: message_value, allocator&: document->GetAllocator()); |
| 111 | } |
| 112 | |
| 113 | bool ServiceProtocol::HandleMessage(const char* method, |
| 114 | const char** param_keys, |
| 115 | const char** param_values, |
| 116 | intptr_t num_params, |
| 117 | void* user_data, |
| 118 | const char** json_object) { |
| 119 | Handler::ServiceProtocolMap params; |
| 120 | for (intptr_t i = 0; i < num_params; i++) { |
| 121 | params[std::string_view{param_keys[i]}] = std::string_view{param_values[i]}; |
| 122 | } |
| 123 | |
| 124 | #ifndef NDEBUG |
| 125 | FML_DLOG(INFO) << "Service protcol method: " << method; |
| 126 | FML_DLOG(INFO) << "Arguments: " << params.size(); |
| 127 | for (intptr_t i = 0; i < num_params; i++) { |
| 128 | FML_DLOG(INFO) << " " << i + 1 << ": " << param_keys[i] << " = " |
| 129 | << param_values[i]; |
| 130 | } |
| 131 | #endif // NDEBUG |
| 132 | |
| 133 | rapidjson::Document document; |
| 134 | bool result = HandleMessage(method: std::string_view{method}, // |
| 135 | params, // |
| 136 | service_protocol: static_cast<ServiceProtocol*>(user_data), // |
| 137 | response: &document // |
| 138 | ); |
| 139 | rapidjson::StringBuffer buffer; |
| 140 | rapidjson::Writer<rapidjson::StringBuffer> writer(buffer); |
| 141 | document.Accept(handler&: writer); |
| 142 | *json_object = fml::strdup(str1: buffer.GetString()); |
| 143 | |
| 144 | #ifndef NDEBUG |
| 145 | FML_DLOG(INFO) << "Response: " << *json_object; |
| 146 | FML_DLOG(INFO) << "RPC Result: " << result; |
| 147 | #endif // NDEBUG |
| 148 | |
| 149 | return result; |
| 150 | } |
| 151 | |
| 152 | bool ServiceProtocol::HandleMessage(std::string_view method, |
| 153 | const Handler::ServiceProtocolMap& params, |
| 154 | ServiceProtocol* service_protocol, |
| 155 | rapidjson::Document* response) { |
| 156 | if (service_protocol == nullptr) { |
| 157 | WriteServerErrorResponse(document: response, message: "Service protocol unavailable." ); |
| 158 | return false; |
| 159 | } |
| 160 | |
| 161 | return service_protocol->HandleMessage(method, params, response); |
| 162 | } |
| 163 | |
| 164 | [[nodiscard]] static bool HandleMessageOnHandler( |
| 165 | ServiceProtocol::Handler* handler, |
| 166 | std::string_view method, |
| 167 | const ServiceProtocol::Handler::ServiceProtocolMap& params, |
| 168 | rapidjson::Document* document) { |
| 169 | FML_DCHECK(handler); |
| 170 | fml::AutoResetWaitableEvent latch; |
| 171 | bool result = false; |
| 172 | fml::TaskRunner::RunNowOrPostTask( |
| 173 | runner: handler->GetServiceProtocolHandlerTaskRunner(method), |
| 174 | task: [&latch, // |
| 175 | &result, // |
| 176 | &handler, // |
| 177 | &method, // |
| 178 | ¶ms, // |
| 179 | &document // |
| 180 | ]() { |
| 181 | result = |
| 182 | handler->HandleServiceProtocolMessage(method, params, response: document); |
| 183 | latch.Signal(); |
| 184 | }); |
| 185 | latch.Wait(); |
| 186 | return result; |
| 187 | } |
| 188 | |
| 189 | bool ServiceProtocol::HandleMessage(std::string_view method, |
| 190 | const Handler::ServiceProtocolMap& params, |
| 191 | rapidjson::Document* response) const { |
| 192 | if (method == kListViewsExtensionName) { |
| 193 | // So far, this is the only built-in method that does not forward to the |
| 194 | // dynamic set of handlers. |
| 195 | return HandleListViewsMethod(response); |
| 196 | } |
| 197 | |
| 198 | fml::SharedLock lock(*handlers_mutex_); |
| 199 | |
| 200 | if (handlers_.empty()) { |
| 201 | WriteServerErrorResponse(document: response, |
| 202 | message: "There are no running service protocol handlers." ); |
| 203 | return false; |
| 204 | } |
| 205 | |
| 206 | // Find the handler by its "viewId" in the params. |
| 207 | auto view_id_param_found = params.find(k: std::string_view{"viewId" }); |
| 208 | if (view_id_param_found != params.end()) { |
| 209 | auto* handler = reinterpret_cast<Handler*>(std::stoull( |
| 210 | str: view_id_param_found->second.data() + kViewIdPrefx.size(), idx: nullptr, base: 16)); |
| 211 | auto handler_found = handlers_.find(k: handler); |
| 212 | if (handler_found != handlers_.end()) { |
| 213 | return HandleMessageOnHandler(handler, method, params, document: response); |
| 214 | } |
| 215 | } |
| 216 | |
| 217 | // Handle legacy calls that do not specify a handler in their args. |
| 218 | // TODO(chinmaygarde): Deprecate these calls in the tools and remove these |
| 219 | // fallbacks. |
| 220 | if (method == kScreenshotExtensionName || |
| 221 | method == kScreenshotSkpExtensionName || |
| 222 | method == kFlushUIThreadTasksExtensionName) { |
| 223 | return HandleMessageOnHandler(handler: handlers_.begin()->first, method, params, |
| 224 | document: response); |
| 225 | } |
| 226 | |
| 227 | WriteServerErrorResponse( |
| 228 | document: response, |
| 229 | message: "Service protocol could not handle or find a handler for the " |
| 230 | "requested method." ); |
| 231 | return false; |
| 232 | } |
| 233 | |
| 234 | static std::string CreateFlutterViewID(intptr_t handler) { |
| 235 | std::stringstream stream; |
| 236 | stream << kViewIdPrefx << "0x" << std::hex << handler; |
| 237 | return stream.str(); |
| 238 | } |
| 239 | |
| 240 | static std::string CreateIsolateID(int64_t isolate) { |
| 241 | std::stringstream stream; |
| 242 | stream << "isolates/" << isolate; |
| 243 | return stream.str(); |
| 244 | } |
| 245 | |
| 246 | void ServiceProtocol::Handler::Description::Write( |
| 247 | Handler* handler, |
| 248 | rapidjson::Value& view, |
| 249 | rapidjson::MemoryPoolAllocator<>& allocator) const { |
| 250 | view.SetObject(); |
| 251 | view.AddMember(name: "type" , value: "FlutterView" , allocator); |
| 252 | view.AddMember(name: "id" , value: CreateFlutterViewID(handler: reinterpret_cast<intptr_t>(handler)), |
| 253 | allocator); |
| 254 | if (isolate_port != 0) { |
| 255 | rapidjson::Value isolate(rapidjson::Type::kObjectType); |
| 256 | { |
| 257 | isolate.AddMember(name: "type" , value: "@Isolate" , allocator); |
| 258 | isolate.AddMember(name: "fixedId" , value: true, allocator); |
| 259 | isolate.AddMember(name: "id" , value: CreateIsolateID(isolate: isolate_port), allocator); |
| 260 | isolate.AddMember(name: "name" , value: isolate_name, allocator); |
| 261 | isolate.AddMember(name: "number" , value: isolate_port, allocator); |
| 262 | } |
| 263 | view.AddMember(name: "isolate" , value&: isolate, allocator); |
| 264 | } |
| 265 | } |
| 266 | |
| 267 | bool ServiceProtocol::HandleListViewsMethod( |
| 268 | rapidjson::Document* response) const { |
| 269 | fml::SharedLock lock(*handlers_mutex_); |
| 270 | std::vector<std::pair<intptr_t, Handler::Description>> descriptions; |
| 271 | for (const auto& handler : handlers_) { |
| 272 | descriptions.emplace_back(args: reinterpret_cast<intptr_t>(handler.first), |
| 273 | args: handler.second.Load()); |
| 274 | } |
| 275 | |
| 276 | auto& allocator = response->GetAllocator(); |
| 277 | |
| 278 | // Construct the response objects. |
| 279 | response->SetObject(); |
| 280 | response->AddMember(name: "type" , value: "FlutterViewList" , allocator); |
| 281 | |
| 282 | rapidjson::Value viewsList(rapidjson::Type::kArrayType); |
| 283 | for (const auto& description : descriptions) { |
| 284 | rapidjson::Value view(rapidjson::Type::kObjectType); |
| 285 | description.second.Write(handler: reinterpret_cast<Handler*>(description.first), |
| 286 | view, allocator); |
| 287 | viewsList.PushBack(value&: view, allocator); |
| 288 | } |
| 289 | |
| 290 | response->AddMember(name: "views" , value&: viewsList, allocator); |
| 291 | |
| 292 | return true; |
| 293 | } |
| 294 | |
| 295 | } // namespace flutter |
| 296 | |