| // Copyright 2013 The Flutter Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #define RAPIDJSON_HAS_STDSTRING 1 |
| |
| #include "flutter/runtime/service_protocol.h" |
| |
| #include <cstring> |
| #include <sstream> |
| #include <string> |
| #include <utility> |
| #include <vector> |
| |
| #include "flutter/fml/posix_wrappers.h" |
| #include "flutter/fml/synchronization/waitable_event.h" |
| #include "rapidjson/stringbuffer.h" |
| #include "rapidjson/writer.h" |
| #include "third_party/dart/runtime/include/dart_tools_api.h" |
| |
| namespace flutter { |
| |
| const std::string_view ServiceProtocol::kScreenshotExtensionName = |
| "_flutter.screenshot"; |
| const std::string_view ServiceProtocol::kScreenshotSkpExtensionName = |
| "_flutter.screenshotSkp"; |
| const std::string_view ServiceProtocol::kRunInViewExtensionName = |
| "_flutter.runInView"; |
| const std::string_view ServiceProtocol::kFlushUIThreadTasksExtensionName = |
| "_flutter.flushUIThreadTasks"; |
| const std::string_view ServiceProtocol::kSetAssetBundlePathExtensionName = |
| "_flutter.setAssetBundlePath"; |
| const std::string_view ServiceProtocol::kGetDisplayRefreshRateExtensionName = |
| "_flutter.getDisplayRefreshRate"; |
| const std::string_view ServiceProtocol::kGetSkSLsExtensionName = |
| "_flutter.getSkSLs"; |
| const std::string_view |
| ServiceProtocol::kEstimateRasterCacheMemoryExtensionName = |
| "_flutter.estimateRasterCacheMemory"; |
| const std::string_view |
| ServiceProtocol::kRenderFrameWithRasterStatsExtensionName = |
| "_flutter.renderFrameWithRasterStats"; |
| const std::string_view ServiceProtocol::kReloadAssetFonts = |
| "_flutter.reloadAssetFonts"; |
| |
| static constexpr std::string_view kViewIdPrefx = "_flutterView/"; |
| static constexpr std::string_view kListViewsExtensionName = |
| "_flutter.listViews"; |
| |
| ServiceProtocol::ServiceProtocol() |
| : endpoints_({ |
| // Private |
| kListViewsExtensionName, |
| |
| // Public |
| kScreenshotExtensionName, |
| kScreenshotSkpExtensionName, |
| kRunInViewExtensionName, |
| kFlushUIThreadTasksExtensionName, |
| kSetAssetBundlePathExtensionName, |
| kGetDisplayRefreshRateExtensionName, |
| kGetSkSLsExtensionName, |
| kEstimateRasterCacheMemoryExtensionName, |
| kRenderFrameWithRasterStatsExtensionName, |
| kReloadAssetFonts, |
| }), |
| handlers_mutex_(fml::SharedMutex::Create()) {} |
| |
| ServiceProtocol::~ServiceProtocol() { |
| ToggleHooks(false); |
| } |
| |
| void ServiceProtocol::AddHandler(Handler* handler, |
| const Handler::Description& description) { |
| fml::UniqueLock lock(*handlers_mutex_); |
| handlers_.emplace(handler, description); |
| } |
| |
| void ServiceProtocol::RemoveHandler(Handler* handler) { |
| fml::UniqueLock lock(*handlers_mutex_); |
| handlers_.erase(handler); |
| } |
| |
| void ServiceProtocol::SetHandlerDescription( |
| Handler* handler, |
| const Handler::Description& description) { |
| fml::SharedLock lock(*handlers_mutex_); |
| auto it = handlers_.find(handler); |
| if (it != handlers_.end()) { |
| it->second.Store(description); |
| } |
| } |
| |
| void ServiceProtocol::ToggleHooks(bool set) { |
| for (const auto& endpoint : endpoints_) { |
| Dart_RegisterIsolateServiceRequestCallback( |
| endpoint.data(), // method |
| &ServiceProtocol::HandleMessage, // callback |
| set ? this : nullptr // user data |
| ); |
| } |
| } |
| |
| static void WriteServerErrorResponse(rapidjson::Document* document, |
| const char* message) { |
| document->SetObject(); |
| document->AddMember("code", -32000, document->GetAllocator()); |
| rapidjson::Value message_value; |
| message_value.SetString(message, document->GetAllocator()); |
| document->AddMember("message", message_value, document->GetAllocator()); |
| } |
| |
| bool ServiceProtocol::HandleMessage(const char* method, |
| const char** param_keys, |
| const char** param_values, |
| intptr_t num_params, |
| void* user_data, |
| const char** json_object) { |
| Handler::ServiceProtocolMap params; |
| for (intptr_t i = 0; i < num_params; i++) { |
| params[std::string_view{param_keys[i]}] = std::string_view{param_values[i]}; |
| } |
| |
| #ifndef NDEBUG |
| FML_DLOG(INFO) << "Service protcol method: " << method; |
| FML_DLOG(INFO) << "Arguments: " << params.size(); |
| for (intptr_t i = 0; i < num_params; i++) { |
| FML_DLOG(INFO) << " " << i + 1 << ": " << param_keys[i] << " = " |
| << param_values[i]; |
| } |
| #endif // NDEBUG |
| |
| rapidjson::Document document; |
| bool result = HandleMessage(std::string_view{method}, // |
| params, // |
| static_cast<ServiceProtocol*>(user_data), // |
| &document // |
| ); |
| rapidjson::StringBuffer buffer; |
| rapidjson::Writer<rapidjson::StringBuffer> writer(buffer); |
| document.Accept(writer); |
| *json_object = fml::strdup(buffer.GetString()); |
| |
| #ifndef NDEBUG |
| FML_DLOG(INFO) << "Response: " << *json_object; |
| FML_DLOG(INFO) << "RPC Result: " << result; |
| #endif // NDEBUG |
| |
| return result; |
| } |
| |
| bool ServiceProtocol::HandleMessage(std::string_view method, |
| const Handler::ServiceProtocolMap& params, |
| ServiceProtocol* service_protocol, |
| rapidjson::Document* response) { |
| if (service_protocol == nullptr) { |
| WriteServerErrorResponse(response, "Service protocol unavailable."); |
| return false; |
| } |
| |
| return service_protocol->HandleMessage(method, params, response); |
| } |
| |
| [[nodiscard]] static bool HandleMessageOnHandler( |
| ServiceProtocol::Handler* handler, |
| std::string_view method, |
| const ServiceProtocol::Handler::ServiceProtocolMap& params, |
| rapidjson::Document* document) { |
| FML_DCHECK(handler); |
| fml::AutoResetWaitableEvent latch; |
| bool result = false; |
| fml::TaskRunner::RunNowOrPostTask( |
| handler->GetServiceProtocolHandlerTaskRunner(method), |
| [&latch, // |
| &result, // |
| &handler, // |
| &method, // |
| ¶ms, // |
| &document // |
| ]() { |
| result = |
| handler->HandleServiceProtocolMessage(method, params, document); |
| latch.Signal(); |
| }); |
| latch.Wait(); |
| return result; |
| } |
| |
| bool ServiceProtocol::HandleMessage(std::string_view method, |
| const Handler::ServiceProtocolMap& params, |
| rapidjson::Document* response) const { |
| if (method == kListViewsExtensionName) { |
| // So far, this is the only built-in method that does not forward to the |
| // dynamic set of handlers. |
| return HandleListViewsMethod(response); |
| } |
| |
| fml::SharedLock lock(*handlers_mutex_); |
| |
| if (handlers_.empty()) { |
| WriteServerErrorResponse(response, |
| "There are no running service protocol handlers."); |
| return false; |
| } |
| |
| // Find the handler by its "viewId" in the params. |
| auto view_id_param_found = params.find(std::string_view{"viewId"}); |
| if (view_id_param_found != params.end()) { |
| auto* handler = reinterpret_cast<Handler*>(std::stoull( |
| view_id_param_found->second.data() + kViewIdPrefx.size(), nullptr, 16)); |
| auto handler_found = handlers_.find(handler); |
| if (handler_found != handlers_.end()) { |
| return HandleMessageOnHandler(handler, method, params, response); |
| } |
| } |
| |
| // Handle legacy calls that do not specify a handler in their args. |
| // TODO(chinmaygarde): Deprecate these calls in the tools and remove these |
| // fallbacks. |
| if (method == kScreenshotExtensionName || |
| method == kScreenshotSkpExtensionName || |
| method == kFlushUIThreadTasksExtensionName) { |
| return HandleMessageOnHandler(handlers_.begin()->first, method, params, |
| response); |
| } |
| |
| WriteServerErrorResponse( |
| response, |
| "Service protocol could not handle or find a handler for the " |
| "requested method."); |
| return false; |
| } |
| |
| static std::string CreateFlutterViewID(intptr_t handler) { |
| std::stringstream stream; |
| stream << kViewIdPrefx << "0x" << std::hex << handler; |
| return stream.str(); |
| } |
| |
| static std::string CreateIsolateID(int64_t isolate) { |
| std::stringstream stream; |
| stream << "isolates/" << isolate; |
| return stream.str(); |
| } |
| |
| void ServiceProtocol::Handler::Description::Write( |
| Handler* handler, |
| rapidjson::Value& view, |
| rapidjson::MemoryPoolAllocator<>& allocator) const { |
| view.SetObject(); |
| view.AddMember("type", "FlutterView", allocator); |
| view.AddMember("id", CreateFlutterViewID(reinterpret_cast<intptr_t>(handler)), |
| allocator); |
| if (isolate_port != 0) { |
| rapidjson::Value isolate(rapidjson::Type::kObjectType); |
| { |
| isolate.AddMember("type", "@Isolate", allocator); |
| isolate.AddMember("fixedId", true, allocator); |
| isolate.AddMember("id", CreateIsolateID(isolate_port), allocator); |
| isolate.AddMember("name", isolate_name, allocator); |
| isolate.AddMember("number", isolate_port, allocator); |
| } |
| view.AddMember("isolate", isolate, allocator); |
| } |
| } |
| |
| bool ServiceProtocol::HandleListViewsMethod( |
| rapidjson::Document* response) const { |
| fml::SharedLock lock(*handlers_mutex_); |
| std::vector<std::pair<intptr_t, Handler::Description>> descriptions; |
| for (const auto& handler : handlers_) { |
| descriptions.emplace_back(reinterpret_cast<intptr_t>(handler.first), |
| handler.second.Load()); |
| } |
| |
| auto& allocator = response->GetAllocator(); |
| |
| // Construct the response objects. |
| response->SetObject(); |
| response->AddMember("type", "FlutterViewList", allocator); |
| |
| rapidjson::Value viewsList(rapidjson::Type::kArrayType); |
| for (const auto& description : descriptions) { |
| rapidjson::Value view(rapidjson::Type::kObjectType); |
| description.second.Write(reinterpret_cast<Handler*>(description.first), |
| view, allocator); |
| viewsList.PushBack(view, allocator); |
| } |
| |
| response->AddMember("views", viewsList, allocator); |
| |
| return true; |
| } |
| |
| } // namespace flutter |