/* * Copyright (c) Meta Platforms, Inc. and affiliates. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. */ #include "InspectorInterfaces.h" #include #include #include #include #include namespace facebook::react::jsinspector_modern { // pure destructors in C++ are odd. You would think they don't want an // implementation, but in fact the linker requires one. Define them to be // empty so that people don't count on them for any particular behaviour. IDestructible::~IDestructible() {} ILocalConnection::~ILocalConnection() {} IRemoteConnection::~IRemoteConnection() {} IInspector::~IInspector() {} IPageStatusListener::~IPageStatusListener() {} const folly::dynamic targetCapabilitiesToDynamic( const InspectorTargetCapabilities& capabilities) { return folly::dynamic::object( "nativePageReloads", capabilities.nativePageReloads)( "nativeSourceCodeFetching", capabilities.nativeSourceCodeFetching)( "prefersFuseboxFrontend", capabilities.prefersFuseboxFrontend); } namespace { class InspectorImpl : public IInspector { public: int addPage( const std::string& description, const std::string& vm, ConnectFunc connectFunc, InspectorTargetCapabilities capabilities) override; void removePage(int pageId) override; std::vector getPages() const override; std::unique_ptr connect( int pageId, std::unique_ptr remote) override; void registerPageStatusListener( std::weak_ptr listener) override; private: class Page { public: Page( int id, const std::string& title, const std::string& vm, ConnectFunc connectFunc, InspectorTargetCapabilities capabilities); operator InspectorPageDescription() const; ConnectFunc getConnectFunc() const; private: int id_; std::string description_; std::string vm_; ConnectFunc connectFunc_; InspectorTargetCapabilities capabilities_; }; mutable std::mutex mutex_; int nextPageId_{1}; std::map pages_; std::list> listeners_; }; InspectorImpl::Page::Page( int id, const std::string& description, const std::string& vm, ConnectFunc connectFunc, InspectorTargetCapabilities capabilities) : id_(id), description_(description), vm_(vm), connectFunc_(std::move(connectFunc)), capabilities_(std::move(capabilities)) {} InspectorImpl::Page::operator InspectorPageDescription() const { return InspectorPageDescription{ .id = id_, .description = description_, .vm = vm_, .capabilities = capabilities_, }; } InspectorImpl::ConnectFunc InspectorImpl::Page::getConnectFunc() const { return connectFunc_; } int InspectorImpl::addPage( const std::string& description, const std::string& vm, ConnectFunc connectFunc, InspectorTargetCapabilities capabilities) { std::scoped_lock lock(mutex_); // Note: getPages guarantees insertion/addition order. As an implementation // detail, incrementing page IDs takes advantage of std::map's key ordering. int pageId = nextPageId_++; assert(pages_.count(pageId) == 0 && "Unexpected duplicate page ID"); pages_.emplace( pageId, Page{pageId, description, vm, std::move(connectFunc), capabilities}); return pageId; } void InspectorImpl::removePage(int pageId) { std::scoped_lock lock(mutex_); if (pages_.erase(pageId) != 0) { for (auto listenerWeak : listeners_) { if (auto listener = listenerWeak.lock()) { listener->onPageRemoved(pageId); } } } } std::vector InspectorImpl::getPages() const { std::scoped_lock lock(mutex_); std::vector inspectorPages; // pages_ is a std::map keyed on an incremental id, so this is insertion // ordered. for (auto& it : pages_) { inspectorPages.push_back(InspectorPageDescription(it.second)); } return inspectorPages; } std::unique_ptr InspectorImpl::connect( int pageId, std::unique_ptr remote) { IInspector::ConnectFunc connectFunc; { std::scoped_lock lock(mutex_); auto it = pages_.find(pageId); if (it != pages_.end()) { connectFunc = it->second.getConnectFunc(); } } return connectFunc ? connectFunc(std::move(remote)) : nullptr; } void InspectorImpl::registerPageStatusListener( std::weak_ptr listener) { std::scoped_lock lock(mutex_); // Remove expired listeners for (auto it = listeners_.begin(); it != listeners_.end();) { if (it->expired()) { it = listeners_.erase(it); } else { ++it; } } listeners_.push_back(listener); } } // namespace IInspector& getInspectorInstance() { static InspectorImpl instance; return instance; } std::unique_ptr makeTestInspectorInstance() { return std::make_unique(); } } // namespace facebook::react::jsinspector_modern