diff --git a/ReactCommon/fabric/components/view/accessibility/AccessibilityProps.cpp b/ReactCommon/fabric/components/view/accessibility/AccessibilityProps.cpp index 84d18dfc8..1bdd986e8 100644 --- a/ReactCommon/fabric/components/view/accessibility/AccessibilityProps.cpp +++ b/ReactCommon/fabric/components/view/accessibility/AccessibilityProps.cpp @@ -10,6 +10,7 @@ #include #include #include +#include namespace facebook { namespace react { @@ -17,10 +18,8 @@ namespace react { AccessibilityProps::AccessibilityProps( const AccessibilityProps &sourceProps, const RawProps &rawProps) - : accessible(convertRawProp( - rawProps, - "accessible", - sourceProps.accessible)), + : accessible( + convertRawProp(rawProps, "accessible", sourceProps.accessible)), accessibilityTraits(convertRawProp( rawProps, "accessibilityTraits", @@ -48,7 +47,20 @@ AccessibilityProps::AccessibilityProps( accessibilityIgnoresInvertColors(convertRawProp( rawProps, "accessibilityIgnoresInvertColors", - sourceProps.accessibilityIgnoresInvertColors)) {} + sourceProps.accessibilityIgnoresInvertColors)), + testId(convertRawProp(rawProps, "testId", sourceProps.testId)) {} + +#pragma mark - DebugStringConvertible + +#if RN_DEBUG_STRING_CONVERTIBLE +SharedDebugStringConvertibleList AccessibilityProps::getDebugProps() const { + const auto &defaultProps = AccessibilityProps(); + LOG(INFO) << "Call AccessibilityProps::getDebugProps with testId " << testId; + return SharedDebugStringConvertibleList{ + debugStringConvertibleItem("testId", testId, defaultProps.testId), + }; +} +#endif // RN_DEBUG_STRING_CONVERTIBLE } // namespace react } // namespace facebook diff --git a/ReactCommon/fabric/components/view/accessibility/AccessibilityProps.h b/ReactCommon/fabric/components/view/accessibility/AccessibilityProps.h index 66fe55389..953425fc1 100644 --- a/ReactCommon/fabric/components/view/accessibility/AccessibilityProps.h +++ b/ReactCommon/fabric/components/view/accessibility/AccessibilityProps.h @@ -37,6 +37,12 @@ class AccessibilityProps : public virtual DebugStringConvertible { const bool accessibilityElementsHidden{false}; const bool accessibilityIgnoresInvertColors{false}; const std::string testId{""}; + +#pragma mark - DebugStringConvertible + +#if RN_DEBUG_STRING_CONVERTIBLE + SharedDebugStringConvertibleList getDebugProps() const override; +#endif }; } // namespace react diff --git a/ReactCommon/fabric/uimanager/BUCK b/ReactCommon/fabric/uimanager/BUCK index 939589962..682cd1a31 100644 --- a/ReactCommon/fabric/uimanager/BUCK +++ b/ReactCommon/fabric/uimanager/BUCK @@ -39,7 +39,7 @@ rn_xplat_cxx_library( fbobjc_compiler_flags = APPLE_COMPILER_FLAGS, fbobjc_preprocessor_flags = get_debug_preprocessor_flags() + get_apple_inspector_flags(), fbobjc_tests = [ - ":tests", + ":uimanagertests", ], force_static = True, macosx_tests_override = [], @@ -67,7 +67,7 @@ rn_xplat_cxx_library( ) fb_xplat_cxx_test( - name = "tests", + name = "uimanagertests", srcs = glob(["tests/**/*.cpp"]), headers = glob(["tests/**/*.h"]), compiler_flags = [ @@ -82,5 +82,11 @@ fb_xplat_cxx_test( "xplat//folly:molly", "xplat//third-party/gmock:gtest", ":uimanager", + react_native_xplat_target("fabric/components/activityindicator:activityindicator"), + react_native_xplat_target("fabric/components/image:image"), + react_native_xplat_target("fabric/components/root:root"), + react_native_xplat_target("fabric/components/scrollview:scrollview"), + react_native_xplat_target("fabric/components/text:text"), + react_native_xplat_target("fabric/components/view:view"), ], ) diff --git a/ReactCommon/fabric/uimanager/ComponentDescriptorRegistry.h b/ReactCommon/fabric/uimanager/ComponentDescriptorRegistry.h index 4a4100e79..6629ad156 100644 --- a/ReactCommon/fabric/uimanager/ComponentDescriptorRegistry.h +++ b/ReactCommon/fabric/uimanager/ComponentDescriptorRegistry.h @@ -30,7 +30,11 @@ class ComponentDescriptorRegistry { const SharedComponentDescriptor operator[]( const ComponentName &componentName) const; SharedShadowNode createNode( - Tag tag, const std::string &viewName, Tag rootTag, const folly::dynamic &props, const SharedEventTarget &eventTarget) const; + Tag tag, + const std::string &viewName, + Tag rootTag, + const folly::dynamic &props, + const SharedEventTarget &eventTarget) const; private: std::unordered_map diff --git a/ReactCommon/fabric/uimanager/ReactBytecodeInterpreter.cpp b/ReactCommon/fabric/uimanager/ReactBytecodeInterpreter.cpp new file mode 100644 index 000000000..227e405bb --- /dev/null +++ b/ReactCommon/fabric/uimanager/ReactBytecodeInterpreter.cpp @@ -0,0 +1,137 @@ +/** + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +#include "ReactBytecodeInterpreter.h" + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace facebook { +namespace react { + +struct RBCContext { + const Tag rootTag; + const std::vector &nodes; + const std::vector ®isters; + const ComponentDescriptorRegistry &componentDescriptorRegistry; + const NativeModuleRegistry &nativeModuleRegistry; +}; + +// TODO: use RBCContext instead of all the separate arguments. +SharedShadowNode ReactBytecodeInterpreter::runCommand( + const folly::dynamic &command, + Tag rootTag, + std::vector &nodes, + std::vector ®isters, + const ComponentDescriptorRegistry &componentDescriptorRegistry, + const NativeModuleRegistry &nativeModuleRegistry) { + const std::string &opcode = command[0].asString(); + const int tagOffset = 420000; + if (opcode == "createNode") { + int tag = command[1].asInt(); + const auto &type = command[2].asString(); + const auto parentTag = command[3].asInt(); + const auto &props = command[4]; + nodes[tag] = componentDescriptorRegistry.createNode( + tag + tagOffset, type, rootTag, props, nullptr); + if (parentTag > -1) { // parentTag == -1 indicates root node + auto parentShadowNode = nodes[parentTag]; + const SharedComponentDescriptor &componentDescriptor = + componentDescriptorRegistry[parentShadowNode]; + componentDescriptor->appendChild(parentShadowNode, nodes[tag]); + } + } else if (opcode == "childSetNode") { + LOG(INFO) + << "(stop) ReactBytecodeInterpreter inject serialized 'server rendered' view tree"; + return nodes[command[1].asInt()]; + } else if (opcode == "loadNativeBool") { + int registerNumber = command[1].asInt(); + const folly::dynamic &value = nativeModuleRegistry.call( + command[2].asString(), command[3].asString(), command[4]); + registers[registerNumber] = value.asBool(); + } else if (opcode == "conditional") { + int registerNumber = command[1].asInt(); + auto conditionDynamic = registers[registerNumber]; + if (conditionDynamic.isNull()) { + // TODO: provide original command or command line? + auto err = std::runtime_error( + "register " + command[1].asString() + + " wasn't loaded before access"); + throw err; + } else if (conditionDynamic.type() != folly::dynamic::BOOL) { + // TODO: provide original command or command line? + auto err = std::runtime_error( + "register " + command[1].asString() + " had type '" + + conditionDynamic.typeName() + + "' but needs to be 'boolean' for conditionals"); + throw err; + } + const auto &nextCommands = + conditionDynamic.asBool() ? command[2] : command[3]; + for (const auto &nextCommand : nextCommands) { + runCommand( + nextCommand, + rootTag, + nodes, + registers, + componentDescriptorRegistry, + nativeModuleRegistry); + } + } else { + throw std::runtime_error("Unsupported opcode: " + command[0].asString()); + } + return nullptr; +} + +SharedShadowNode ReactBytecodeInterpreter::buildShadowTree( + const std::string &jsonStr, + Tag rootTag, + const folly::dynamic ¶ms, + const ComponentDescriptorRegistry &componentDescriptorRegistry, + const NativeModuleRegistry &nativeModuleRegistry) { + LOG(INFO) + << "(strt) ReactBytecodeInterpreter inject hardcoded 'server rendered' view tree"; + std::string content = jsonStr; + for (const auto ¶m : params.items()) { + const auto &key = param.first.asString(); + size_t start_pos = content.find(key); + if (start_pos != std::string::npos) { + content.replace(start_pos, key.length(), param.second.asString()); + } + } + auto parsed = folly::parseJson(content); + auto commands = parsed["commands"]; + std::vector nodes(commands.size() * 2); + std::vector registers(32); + for (const auto &command : commands) { + auto ret = runCommand( + command, + rootTag, + nodes, + registers, + componentDescriptorRegistry, + nativeModuleRegistry); + if (ret != nullptr) { + return ret; + } + } + throw std::runtime_error( + "Missing childSetNode command in template content:\n" + content); + return SharedShadowNode{}; +} + +} // namespace react +} // namespace facebook diff --git a/ReactCommon/fabric/uimanager/ReactBytecodeInterpreter.h b/ReactCommon/fabric/uimanager/ReactBytecodeInterpreter.h new file mode 100644 index 000000000..396ce8e75 --- /dev/null +++ b/ReactCommon/fabric/uimanager/ReactBytecodeInterpreter.h @@ -0,0 +1,63 @@ +/** + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +#pragma once + +#include + +#include + +#include +#include +#include + +namespace facebook { +namespace react { + +// Temporary NativeModuleRegistry definition +using NativeModuleCallFn = + std::function; + +class NativeModuleRegistry { + public: + void registerModule( + const std::string &moduleName, + NativeModuleCallFn callFn) { + modules_.emplace(moduleName, callFn); + } + + folly::dynamic call( + const std::string &moduleName, + const std::string &methodName, + const folly::dynamic &args) const { + return modules_.at(moduleName)(methodName, args); + } + + private: + std::unordered_map modules_; +}; + +class ReactBytecodeInterpreter { + public: + static SharedShadowNode buildShadowTree( + const std::string &jsonStr, + int rootTag, + const folly::dynamic ¶ms, + const ComponentDescriptorRegistry &componentDescriptorRegistry, + const NativeModuleRegistry &nativeModuleRegistry); + + private: + static SharedShadowNode runCommand( + const folly::dynamic &command, + Tag rootTag, + std::vector &nodes, + std::vector ®isters, + const ComponentDescriptorRegistry &componentDescriptorRegistry, + const NativeModuleRegistry &nativeModuleRegistry); +}; +} // namespace react +} // namespace facebook diff --git a/ReactCommon/fabric/uimanager/Scheduler.cpp b/ReactCommon/fabric/uimanager/Scheduler.cpp index 330145b51..b04ab1627 100644 --- a/ReactCommon/fabric/uimanager/Scheduler.cpp +++ b/ReactCommon/fabric/uimanager/Scheduler.cpp @@ -11,7 +11,7 @@ #include #include #include -#include +#include #include "ComponentDescriptorFactory.h" #include "Differentiator.h" @@ -73,21 +73,27 @@ void Scheduler::startSurface( auto shadowTree = std::make_unique(surfaceId, layoutConstraints, layoutContext); shadowTree->setDelegate(this); - shadowTreeRegistry_.emplace(surfaceId, std::move(shadowTree)); -#ifndef ANDROID + LOG(INFO) << "initialProps in Scheduler::startSurface - type: " << initialProps.type() << initialProps.typeName() << initialProps; // TODO: Is this an ok place to do this? - auto serializedCommands = initialProps.find("serializedCommands"); - if (serializedCommands != initialProps.items().end()) { - auto tree = TemplateRenderer::buildShadowTree(serializedCommands->second.asString(), surfaceId, folly::dynamic::object(), *componentDescriptorRegistry_); - - uiManagerDidFinishTransactionWithoutLock(surfaceId, std::make_shared(SharedShadowNodeList {tree})); - // TODO: hydrate rather than replace - uiManager_->startSurface(surfaceId, moduleName, initialProps); - } else { - uiManager_->startSurface(surfaceId, moduleName, initialProps); + if (initialProps.type() == folly::dynamic::OBJECT) { + auto serializedCommands = initialProps.find("serializedCommands"); + if (serializedCommands != initialProps.items().end()) { + NativeModuleRegistry nMR; + auto tree = ReactBytecodeInterpreter::buildShadowTree(serializedCommands->second.asString(), surfaceId, folly::dynamic::object(), *componentDescriptorRegistry_, nMR); + shadowTree->complete(std::make_shared(SharedShadowNodeList {tree})); + shadowTreeRegistry_.emplace(surfaceId, std::move(shadowTree)); + // TODO: hydrate rather than replace + #ifndef ANDROID + uiManager_->startSurface(surfaceId, moduleName, initialProps); + #endif + return; + } } + shadowTreeRegistry_.emplace(surfaceId, std::move(shadowTree)); +#ifndef ANDROID + uiManager_->startSurface(surfaceId, moduleName, initialProps); #endif } @@ -130,16 +136,6 @@ void Scheduler::constraintSurfaceLayout( }); } -void Scheduler::uiManagerDidFinishTransactionWithoutLock(Tag rootTag, const SharedShadowNodeUnsharedList &rootChildNodes) { - const auto iterator = shadowTreeRegistry_.find(rootTag); - if (iterator == shadowTreeRegistry_.end()) { - // This might happen during surface unmounting/deallocation process - // due to the asynchronous nature of JS calls. - return; - } - iterator->second->complete(rootChildNodes); -} - #pragma mark - Delegate void Scheduler::setDelegate(SchedulerDelegate *delegate) { @@ -167,7 +163,13 @@ void Scheduler::uiManagerDidFinishTransaction( Tag rootTag, const SharedShadowNodeUnsharedList &rootChildNodes) { std::lock_guard lock(mutex_); - uiManagerDidFinishTransactionWithoutLock(rootTag, rootChildNodes); + const auto iterator = shadowTreeRegistry_.find(rootTag); + if (iterator == shadowTreeRegistry_.end()) { + // This might happen during surface unmounting/deallocation process + // due to the asynchronous nature of JS calls. + return; + } + iterator->second->complete(rootChildNodes); } void Scheduler::uiManagerDidCreateShadowNode( diff --git a/ReactCommon/fabric/uimanager/TemplateRenderer.cpp b/ReactCommon/fabric/uimanager/TemplateRenderer.cpp deleted file mode 100644 index 4e41accd8..000000000 --- a/ReactCommon/fabric/uimanager/TemplateRenderer.cpp +++ /dev/null @@ -1,57 +0,0 @@ -/** - * Copyright (c) Facebook, Inc. and its affiliates. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - */ - -#include "TemplateRenderer.h" - -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include - -namespace facebook { - namespace react { - SharedShadowNode TemplateRenderer::buildShadowTree(const std::string &jsonStr, int rootTag, const folly::dynamic ¶ms, const ComponentDescriptorRegistry &componentDescriptorRegistry) { - LOG(INFO) << "(strt) TemplateRenderer inject hardcoded 'server rendered' view tree"; - std::string content = jsonStr; - for (const auto& param : params.items()) { - const auto& key = param.first.asString(); - size_t start_pos = content.find(key); - if(start_pos != std::string::npos) { - content.replace(start_pos, key.length(), param.second.asString()); - } - } - auto json = folly::parseJson(content); - std::vector nodes; - nodes.resize(json.size() * 2); - int tagOffset = 4560; // MAYBE TODO: use number of existing tags so they don't collide rather than random value - for (const auto& command : json) { - if (command[0] == "createNode") { - int tag = command[1].asInt(); - const auto& type = command[2].asString(); - const auto& props = command[3]; - nodes[tag] = componentDescriptorRegistry.createNode(tag + tagOffset, type, rootTag, props, nullptr); - } else if (command[0] == "appendChild") { - auto parentShadowNode = nodes[command[1].asInt()]; - const SharedComponentDescriptor &componentDescriptor = componentDescriptorRegistry[parentShadowNode]; - componentDescriptor->appendChild(parentShadowNode, nodes[command[2].asInt()]); - } else if (command[0] == "childSetNode") { - LOG(INFO) << "(stop) TemplateView inject serialized 'server rendered' view tree"; - return nodes[command[1].asInt()]; - } - } - throw std::runtime_error("Missing childSetNode command in template content:\n" + content); - return SharedShadowNode {}; - } - } -} diff --git a/ReactCommon/fabric/uimanager/TemplateRenderer.h b/ReactCommon/fabric/uimanager/TemplateRenderer.h deleted file mode 100644 index 36978538e..000000000 --- a/ReactCommon/fabric/uimanager/TemplateRenderer.h +++ /dev/null @@ -1,25 +0,0 @@ -/** - * Copyright (c) Facebook, Inc. and its affiliates. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - */ - -#pragma once - -#include - -#include - -#include -#include -#include - -namespace facebook { - namespace react { - class TemplateRenderer { - public: - static SharedShadowNode buildShadowTree(const std::string &jsonStr, int rootTag, const folly::dynamic ¶ms, const ComponentDescriptorRegistry &componentDescriptorRegistry); - }; - } // react -} // facebook diff --git a/ReactCommon/fabric/uimanager/tests/ReactBytecodeInterpreterTest.cpp b/ReactCommon/fabric/uimanager/tests/ReactBytecodeInterpreterTest.cpp new file mode 100644 index 000000000..c96f4f496 --- /dev/null +++ b/ReactCommon/fabric/uimanager/tests/ReactBytecodeInterpreterTest.cpp @@ -0,0 +1,144 @@ +/** + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +#include + +#include +#include +#include + +using namespace facebook::react; + +#include +#include +#include +#include +#include +#include +#include +#include + +namespace facebook { +namespace react { + +SharedComponentDescriptorRegistry ComponentDescriptorFactory::buildRegistry( + const SharedEventDispatcher &eventDispatcher, + const SharedContextContainer &contextContainer) { + auto registry = std::make_shared(); + registry->registerComponentDescriptor( + std::make_shared(eventDispatcher)); + registry->registerComponentDescriptor( + std::make_shared( + eventDispatcher, contextContainer)); + registry->registerComponentDescriptor( + std::make_shared(eventDispatcher)); + registry->registerComponentDescriptor( + std::make_shared( + eventDispatcher, contextContainer)); + registry->registerComponentDescriptor( + std::make_shared(eventDispatcher)); + registry->registerComponentDescriptor( + std::make_shared(eventDispatcher)); + registry->registerComponentDescriptor( + std::make_shared( + eventDispatcher)); + return registry; +} + +bool mockSimpleTestValue_; + +NativeModuleRegistry buildNativeModuleRegistry(); + +NativeModuleRegistry buildNativeModuleRegistry() { + NativeModuleRegistry nMR; + nMR.registerModule( + "MobileConfig", + [&](const std::string &methodName, const folly::dynamic &args) { + return mockSimpleTestValue_; + }); + return nMR; +} + +} // namespace react +} // namespace facebook + +TEST(ReactBytecodeInterpreterTest, testSimpleBytecode) { + auto surfaceId = 11; + auto componentDescriptorRegistry = + ComponentDescriptorFactory::buildRegistry(nullptr, nullptr); + auto nativeModuleRegistry = buildNativeModuleRegistry(); + + auto bytecode = R"delim({"version":0.1,"commands":[ + ["createNode",2,"RCTView",-1,{"opacity": 0.5, "testId": "root"}], + ["createNode",4,"RCTView",2,{"testId": "child"}], + ["childSetNode",2] + ]})delim"; + + mockSimpleTestValue_ = true; + + auto root1 = ReactBytecodeInterpreter::buildShadowTree( + bytecode, + surfaceId, + folly::dynamic::object(), + *componentDescriptorRegistry, + nativeModuleRegistry); + LOG(INFO) << std::endl << root1->getDebugDescription(); + auto props1 = std::dynamic_pointer_cast(root1->getProps()); + ASSERT_NEAR(props1->opacity, 0.5, 0.001); + ASSERT_STREQ(props1->testId.c_str(), "root"); + auto children1 = root1->getChildren(); + ASSERT_EQ(children1.size(), 1); + auto child_props1 = + std::dynamic_pointer_cast(children1.at(0)->getProps()); + ASSERT_STREQ(child_props1->testId.c_str(), "child"); +} + +TEST(ReactBytecodeInterpreterTest, testConditionalBytecode) { + auto surfaceId = 11; + auto componentDescriptorRegistry = + ComponentDescriptorFactory::buildRegistry(nullptr, nullptr); + auto nativeModuleRegistry = buildNativeModuleRegistry(); + + auto bytecode = R"delim({"version":0.1,"commands":[ + ["createNode",2,"RCTView",-1,{"testId": "root"}], + ["loadNativeBool",1,"MobileConfig","getBool",["qe:simple_test"]], + ["conditional",1, + [["createNode",4,"RCTView",2,{"testId": "cond_true"}]], + [["createNode",4,"RCTView",2,{"testId": "cond_false"}]] + ], + ["childSetNode",2] + ]})delim"; + + mockSimpleTestValue_ = true; + + auto root1 = ReactBytecodeInterpreter::buildShadowTree( + bytecode, + surfaceId, + folly::dynamic::object(), + *componentDescriptorRegistry, + nativeModuleRegistry); + LOG(INFO) << std::endl << root1->getDebugDescription(); + auto props1 = std::dynamic_pointer_cast(root1->getProps()); + ASSERT_STREQ(props1->testId.c_str(), "root"); + auto children1 = root1->getChildren(); + ASSERT_EQ(children1.size(), 1); + auto child_props1 = + std::dynamic_pointer_cast(children1.at(0)->getProps()); + ASSERT_STREQ(child_props1->testId.c_str(), "cond_true"); + + mockSimpleTestValue_ = false; + + auto root2 = ReactBytecodeInterpreter::buildShadowTree( + bytecode, + surfaceId, + folly::dynamic::object(), + *componentDescriptorRegistry, + nativeModuleRegistry); + auto child_props2 = std::dynamic_pointer_cast( + root2->getChildren().at(0)->getProps()); + ASSERT_STREQ(child_props2->testId.c_str(), "cond_false"); +}