diff --git a/change/react-native-windows-2020-06-12-10-45-34-PR-BackportCallJSQueue.json b/change/react-native-windows-2020-06-12-10-45-34-PR-BackportCallJSQueue.json new file mode 100644 index 00000000000..52917989b71 --- /dev/null +++ b/change/react-native-windows-2020-06-12-10-45-34-PR-BackportCallJSQueue.json @@ -0,0 +1,8 @@ +{ + "type": "prerelease", + "comment": "[0.62] Cherry pick PR #5071 for CallJsFunction queuing", + "packageName": "react-native-windows", + "email": "vmorozov@microsoft.com", + "dependentChangeType": "patch", + "date": "2020-06-12T17:45:34.556Z" +} diff --git a/vnext/Microsoft.ReactNative/ReactHost/React.h b/vnext/Microsoft.ReactNative/ReactHost/React.h index 11a44973a68..3ef3ed7b430 100644 --- a/vnext/Microsoft.ReactNative/ReactHost/React.h +++ b/vnext/Microsoft.ReactNative/ReactHost/React.h @@ -48,6 +48,7 @@ enum class ReactInstanceState { WaitingForDebugger, Loaded, HasError, + Unloaded, }; /**An Office wrapper that extends FB's React Instance and makes it a 1:1 relationship with the bundle, diff --git a/vnext/Microsoft.ReactNative/ReactHost/ReactInstanceWin.cpp b/vnext/Microsoft.ReactNative/ReactHost/ReactInstanceWin.cpp index 49e4ccf380e..e6b11467f0b 100644 --- a/vnext/Microsoft.ReactNative/ReactHost/ReactInstanceWin.cpp +++ b/vnext/Microsoft.ReactNative/ReactHost/ReactInstanceWin.cpp @@ -76,19 +76,13 @@ winrt::Microsoft::ReactNative::IReactPropertyBag ReactContext::Properties() noex void ReactContext::CallJSFunction(std::string &&module, std::string &&method, folly::dynamic &¶ms) noexcept { if (auto instance = m_reactInstance.GetStrongPtr()) { - if (instance->State() == ReactInstanceState::Loaded) { - if (auto fbInstance = instance->GetInnerInstance()) { - fbInstance->callJSFunction(std::move(module), std::move(method), std::move(params)); - } - } + instance->CallJsFunction(std::move(module), std::move(method), std::move(params)); } } void ReactContext::DispatchEvent(int64_t viewTag, std::string &&eventName, folly::dynamic &&eventData) noexcept { if (auto instance = m_reactInstance.GetStrongPtr()) { - if (instance->State() == ReactInstanceState::Loaded) { - instance->DispatchEvent(viewTag, std::move(eventName), std::move(eventData)); - } + instance->DispatchEvent(viewTag, std::move(eventName), std::move(eventData)); } } @@ -371,6 +365,7 @@ void ReactInstanceWin::LoadJSBundles() noexcept { instanceWrapper->loadBundleSync(Mso::Copy(options.Identity)); } catch (...) { strongThis->m_state = ReactInstanceState::HasError; + strongThis->AbandonJSCallQueue(); strongThis->OnReactInstanceLoaded(Mso::ExceptionErrorProvider().MakeErrorCode(std::current_exception())); return; } @@ -390,8 +385,10 @@ void ReactInstanceWin::OnReactInstanceLoaded(const Mso::ErrorCode &errorCode) no strongThis->m_isLoaded = true; if (!errorCode) { strongThis->m_state = ReactInstanceState::Loaded; + strongThis->DrainJSCallQueue(); } else { strongThis->m_state = ReactInstanceState::HasError; + strongThis->AbandonJSCallQueue(); } if (auto onLoaded = strongThis->m_options.OnInstanceLoaded.Get()) { @@ -414,6 +411,8 @@ Mso::Future ReactInstanceWin::Destroy() noexcept { } m_isDestroyed = true; + m_state = ReactInstanceState::Unloaded; + AbandonJSCallQueue(); if (!m_isLoaded) { OnReactInstanceLoaded(Mso::CancellationErrorProvider().MakeErrorCode(true)); @@ -583,6 +582,7 @@ std::function ReactInstanceWin::GetErrorCallback() noexcept { void ReactInstanceWin::OnErrorWithMessage(const std::string &errorMessage) noexcept { m_state = ReactInstanceState::HasError; + AbandonJSCallQueue(); if (m_redboxHandler && m_redboxHandler->isDevSupportEnabled()) { ErrorInfo errorInfo; @@ -636,12 +636,54 @@ void ReactInstanceWin::OnDebuggerAttach() noexcept { m_updateUI(); } +void ReactInstanceWin::DrainJSCallQueue() noexcept { + // Handle all items in the queue one by one. + for (;;) { + JSCallEntry entry; // To avoid callJSFunction under the lock + { + std::scoped_lock lock{m_mutex}; + if (m_state == ReactInstanceState::Loaded && !m_jsCallQueue.empty()) { + entry = std::move(m_jsCallQueue.front()); + m_jsCallQueue.pop_front(); + } else { + break; + } + } + + if (auto instance = m_instance.LoadWithLock()) { + instance->callJSFunction(std::move(entry.ModuleName), std::move(entry.MethodName), std::move(entry.Args)); + } + } +} + +void ReactInstanceWin::AbandonJSCallQueue() noexcept { + std::deque jsCallQueue; // To avoid destruction under the lock + { + std::scoped_lock lock{m_mutex}; + if (m_state == ReactInstanceState::HasError || m_state == ReactInstanceState::Unloaded) { + jsCallQueue = std::move(m_jsCallQueue); + } + } +} + void ReactInstanceWin::CallJsFunction( std::string &&moduleName, std::string &&method, folly::dynamic &¶ms) noexcept { - // callJSFunction can be called from any thread. The native bridge will post the call to the right queue internally. - if (m_state == ReactInstanceState::Loaded) { + bool shouldCall{false}; // To call callJSFunction outside of lock + { + std::scoped_lock lock{m_mutex}; + if (m_state == ReactInstanceState::Loaded && m_jsCallQueue.empty()) { + shouldCall = true; + } else if ( + m_state == ReactInstanceState::Loading || m_state == ReactInstanceState::WaitingForDebugger || + (m_state == ReactInstanceState::Loaded && !m_jsCallQueue.empty())) { + m_jsCallQueue.push_back(JSCallEntry{std::move(moduleName), std::move(method), std::move(params)}); + } + // otherwise ignore the call + } + + if (shouldCall) { if (auto instance = m_instance.LoadWithLock()) { instance->callJSFunction(std::move(moduleName), std::move(method), std::move(params)); } @@ -649,11 +691,8 @@ void ReactInstanceWin::CallJsFunction( } void ReactInstanceWin::DispatchEvent(int64_t viewTag, std::string &&eventName, folly::dynamic &&eventData) noexcept { - if (m_state == ReactInstanceState::Loaded) { - if (auto instance = m_instanceWrapper.LoadWithLock()) { - instance->DispatchEvent(viewTag, eventName, std::move(eventData)); - } - } + folly::dynamic params = folly::dynamic::array(viewTag, std::move(eventName), std::move(eventData)); + CallJsFunction("RCTEventEmitter", "receiveEvent", std::move(params)); } facebook::react::INativeUIManager *ReactInstanceWin::NativeUIManager() noexcept { diff --git a/vnext/Microsoft.ReactNative/ReactHost/ReactInstanceWin.h b/vnext/Microsoft.ReactNative/ReactHost/ReactInstanceWin.h index 5f2957f8389..37d8e4c89de 100644 --- a/vnext/Microsoft.ReactNative/ReactHost/ReactInstanceWin.h +++ b/vnext/Microsoft.ReactNative/ReactHost/ReactInstanceWin.h @@ -116,6 +116,15 @@ class ReactInstanceWin final : public Mso::ActiveObject m_appearanceListener; std::string m_bundleRootPath; Mso::DispatchQueue m_uiQueue; + std::deque m_jsCallQueue; }; } // namespace Mso::React