feat(smart-app): implement complete mobile app MVP

- App.tsx: full navigation (Auth stack + Main tabs with 5 screens)
- Auth: LoginScreen, RegisterScreen, ForgotPasswordScreen
- HomeScreen: dashboard with IoT metrics, weather widget, alerts, quick actions, sensors
- MapScreen: interactive map with layer toggles (6 layers)
- MarketplaceScreen: categories (6), products (5), search
- ChatScreen: AI chat with quick prompts (4), bot responses
- ProfileScreen: user info, stats, menu (9 items), logout
- AlertsScreen: alert list with severity, acknowledge
- SensorsScreen: sensor list with type filters (6 types), search
- ZonesScreen: zone cards with stats
- SettingsScreen: language picker (FR/EN/ES/DE), privacy, about
- Stores: iotStore (sensors, zones, alerts), notificationStore, uiStore + i18n
- Hooks: useSensors, useAlerts, useNotifications, useLocation
- Components: Card, Button, LoadingSpinner, ErrorBoundary, Header
- Services: iotService, notificationService (with axios API client)
- Utils: formatters (temp, AQI, noise, dates), validators (email, password, IBAN)
- Theme: colors.ts with full design system (Blue Ocean palette)
- Ditto: fixed MongoDB connection, new JWT secrets, official gateway image
This commit is contained in:
Eric FELIXINE
2026-06-01 18:00:35 -04:00
parent 08ca495bde
commit e30ae8ed09
35578 changed files with 3703534 additions and 43 deletions

View File

@@ -0,0 +1,201 @@
/*
* 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 "IntersectionObserver.h"
#include <react/debug/react_native_assert.h>
#include <react/renderer/core/LayoutMetrics.h>
#include <react/renderer/core/LayoutableShadowNode.h>
#include <react/renderer/core/ShadowNodeFamily.h>
#include <utility>
namespace facebook::react {
IntersectionObserver::IntersectionObserver(
IntersectionObserverObserverId intersectionObserverId,
ShadowNode::Shared targetShadowNode,
std::vector<Float> thresholds)
: intersectionObserverId_(intersectionObserverId),
targetShadowNode_(std::move(targetShadowNode)),
thresholds_(std::move(thresholds)) {}
static Rect getRootBoundingRect(
const LayoutableShadowNode& layoutableRootShadowNode) {
auto layoutMetrics = layoutableRootShadowNode.getLayoutMetrics();
if (layoutMetrics == EmptyLayoutMetrics ||
layoutMetrics.displayType == DisplayType::None) {
return Rect{};
}
// Apply the transform to translate the root view to its location in the
// viewport.
return layoutMetrics.frame * layoutableRootShadowNode.getTransform();
}
static Rect getTargetBoundingRect(
const ShadowNodeFamily::AncestorList& targetAncestors) {
auto layoutMetrics = LayoutableShadowNode::computeRelativeLayoutMetrics(
targetAncestors,
{/* .includeTransform = */ true,
/* .includeViewportOffset = */ true});
return layoutMetrics == EmptyLayoutMetrics ? Rect{} : layoutMetrics.frame;
}
static Rect getClippedTargetBoundingRect(
const ShadowNodeFamily::AncestorList& targetAncestors) {
auto layoutMetrics = LayoutableShadowNode::computeRelativeLayoutMetrics(
targetAncestors,
{/* .includeTransform = */ true,
/* .includeViewportOffset = */ true,
/* .applyParentClipping = */ true});
return layoutMetrics == EmptyLayoutMetrics ? Rect{} : layoutMetrics.frame;
}
// Partially equivalent to
// https://w3c.github.io/IntersectionObserver/#compute-the-intersection
static Rect computeIntersection(
const Rect& rootBoundingRect,
const Rect& targetBoundingRect,
const ShadowNodeFamily::AncestorList& targetAncestors) {
auto absoluteIntersectionRect =
Rect::intersect(rootBoundingRect, targetBoundingRect);
Float absoluteIntersectionRectArea = absoluteIntersectionRect.size.width *
absoluteIntersectionRect.size.height;
Float targetBoundingRectArea =
targetBoundingRect.size.width * targetBoundingRect.size.height;
// Finish early if there is not intersection between the root and the target
// before we do any clipping.
if (absoluteIntersectionRectArea == 0 || targetBoundingRectArea == 0) {
return {};
}
// Coordinates of the target after clipping the parts hidden by a parent
// (e.g.: in scroll views, or in views with a parent with overflow: hidden)
auto clippedTargetBoundingRect =
getClippedTargetBoundingRect(targetAncestors);
return Rect::intersect(rootBoundingRect, clippedTargetBoundingRect);
}
// Partially equivalent to
// https://w3c.github.io/IntersectionObserver/#update-intersection-observations-algo
std::optional<IntersectionObserverEntry>
IntersectionObserver::updateIntersectionObservation(
const RootShadowNode& rootShadowNode,
double mountTime) {
const auto layoutableRootShadowNode =
dynamic_cast<const LayoutableShadowNode*>(&rootShadowNode);
react_native_assert(
layoutableRootShadowNode != nullptr &&
"RootShadowNode instances must always inherit from LayoutableShadowNode.");
auto targetAncestors =
targetShadowNode_->getFamily().getAncestors(rootShadowNode);
// Absolute coordinates of the root
auto rootBoundingRect = getRootBoundingRect(*layoutableRootShadowNode);
// Absolute coordinates of the target
auto targetBoundingRect = getTargetBoundingRect(targetAncestors);
auto intersectionRect = computeIntersection(
rootBoundingRect, targetBoundingRect, targetAncestors);
Float targetBoundingRectArea =
targetBoundingRect.size.width * targetBoundingRect.size.height;
auto intersectionRectArea =
intersectionRect.size.width * intersectionRect.size.height;
Float intersectionRatio =
targetBoundingRectArea == 0 // prevent division by zero
? 0
: intersectionRectArea / targetBoundingRectArea;
if (intersectionRatio == 0) {
return setNotIntersectingState(
rootBoundingRect, targetBoundingRect, mountTime);
}
auto highestThresholdCrossed = getHighestThresholdCrossed(intersectionRatio);
if (highestThresholdCrossed == -1) {
return setNotIntersectingState(
rootBoundingRect, targetBoundingRect, mountTime);
}
return setIntersectingState(
rootBoundingRect,
targetBoundingRect,
intersectionRect,
highestThresholdCrossed,
mountTime);
}
Float IntersectionObserver::getHighestThresholdCrossed(
Float intersectionRatio) {
Float highestThreshold = -1;
for (auto threshold : thresholds_) {
if (intersectionRatio >= threshold) {
highestThreshold = threshold;
}
}
return highestThreshold;
}
std::optional<IntersectionObserverEntry>
IntersectionObserver::setIntersectingState(
const Rect& rootBoundingRect,
const Rect& targetBoundingRect,
const Rect& intersectionRect,
Float threshold,
double mountTime) {
auto newState = IntersectionObserverState::Intersecting(threshold);
if (state_ != newState) {
state_ = newState;
IntersectionObserverEntry entry{
intersectionObserverId_,
targetShadowNode_,
targetBoundingRect,
rootBoundingRect,
intersectionRect,
true,
mountTime,
};
return std::optional<IntersectionObserverEntry>{std::move(entry)};
}
return std::nullopt;
}
std::optional<IntersectionObserverEntry>
IntersectionObserver::setNotIntersectingState(
const Rect& rootBoundingRect,
const Rect& targetBoundingRect,
double mountTime) {
if (state_ != IntersectionObserverState::NotIntersecting()) {
state_ = IntersectionObserverState::NotIntersecting();
IntersectionObserverEntry entry{
intersectionObserverId_,
targetShadowNode_,
targetBoundingRect,
rootBoundingRect,
std::nullopt,
false,
mountTime,
};
return std::optional<IntersectionObserverEntry>(std::move(entry));
}
return std::nullopt;
}
} // namespace facebook::react

View File

@@ -0,0 +1,80 @@
/*
* 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.
*/
#pragma once
#include <react/renderer/components/root/RootShadowNode.h>
#include <react/renderer/core/ShadowNode.h>
#include <react/renderer/graphics/Float.h>
#include <react/renderer/graphics/Rect.h>
#include <memory>
#include "IntersectionObserverState.h"
namespace facebook::react {
using IntersectionObserverObserverId = int32_t;
struct IntersectionObserverEntry {
IntersectionObserverObserverId intersectionObserverId;
ShadowNode::Shared shadowNode;
Rect targetRect;
Rect rootRect;
std::optional<Rect> intersectionRect;
bool isIntersectingAboveThresholds;
// TODO(T156529385) Define `DOMHighResTimeStamp` as an alias for `double` and
// use it here.
double time;
};
class IntersectionObserver {
public:
IntersectionObserver(
IntersectionObserverObserverId intersectionObserverId,
ShadowNode::Shared targetShadowNode,
std::vector<Float> thresholds);
// Partially equivalent to
// https://w3c.github.io/IntersectionObserver/#update-intersection-observations-algo
std::optional<IntersectionObserverEntry> updateIntersectionObservation(
const RootShadowNode& rootShadowNode,
double mountTime);
IntersectionObserverObserverId getIntersectionObserverId() const {
return intersectionObserverId_;
}
const ShadowNode& getTargetShadowNode() const {
return *targetShadowNode_;
}
std::vector<Float> getThresholds() const {
return thresholds_;
}
private:
Float getHighestThresholdCrossed(Float intersectionRatio);
std::optional<IntersectionObserverEntry> setIntersectingState(
const Rect& rootBoundingRect,
const Rect& targetBoundingRect,
const Rect& intersectionRect,
Float threshold,
double mountTime);
std::optional<IntersectionObserverEntry> setNotIntersectingState(
const Rect& rootBoundingRect,
const Rect& targetBoundingRect,
double mountTime);
IntersectionObserverObserverId intersectionObserverId_;
ShadowNode::Shared targetShadowNode_;
std::vector<Float> thresholds_;
mutable IntersectionObserverState state_ =
IntersectionObserverState::Initial();
};
} // namespace facebook::react

View File

@@ -0,0 +1,229 @@
/*
* 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 "IntersectionObserverManager.h"
#include <cxxreact/JSExecutor.h>
#include <react/renderer/debug/SystraceSection.h>
#include <utility>
#include "IntersectionObserver.h"
namespace facebook::react {
IntersectionObserverManager::IntersectionObserverManager() = default;
void IntersectionObserverManager::observe(
IntersectionObserverObserverId intersectionObserverId,
const ShadowNode::Shared& shadowNode,
std::vector<Float> thresholds,
const UIManager& uiManager) {
SystraceSection s("IntersectionObserverManager::observe");
auto surfaceId = shadowNode->getSurfaceId();
// The actual observer lives in the array, so we need to create it there and
// then get a reference. Otherwise we only update its state in a copy.
IntersectionObserver* observer;
// Register observer
{
std::unique_lock lock(observersMutex_);
auto& observers = observersBySurfaceId_[surfaceId];
observers.emplace_back(IntersectionObserver{
intersectionObserverId, shadowNode, std::move(thresholds)});
observer = &observers.back();
}
// Notification of initial state.
// Ideally, we'd have well defined event loop step to notify observers
// (like on the Web) and we'd send the initial notification there, but as
// we don't have it we have to run this check once and manually dispatch.
auto& shadowTreeRegistry = uiManager.getShadowTreeRegistry();
MountingCoordinator::Shared mountingCoordinator = nullptr;
RootShadowNode::Shared rootShadowNode = nullptr;
shadowTreeRegistry.visit(surfaceId, [&](const ShadowTree& shadowTree) {
mountingCoordinator = shadowTree.getMountingCoordinator();
rootShadowNode = shadowTree.getCurrentRevision().rootShadowNode;
});
auto hasPendingTransactions = mountingCoordinator != nullptr &&
mountingCoordinator->hasPendingTransactions();
if (!hasPendingTransactions) {
auto entry = observer->updateIntersectionObservation(
*rootShadowNode, JSExecutor::performanceNow());
if (entry) {
{
std::unique_lock lock(pendingEntriesMutex_);
pendingEntries_.push_back(std::move(entry).value());
}
notifyObserversIfNecessary();
}
}
}
void IntersectionObserverManager::unobserve(
IntersectionObserverObserverId intersectionObserverId,
const ShadowNode& shadowNode) {
SystraceSection s("IntersectionObserverManager::unobserve");
{
std::unique_lock lock(observersMutex_);
auto surfaceId = shadowNode.getSurfaceId();
auto observersIt = observersBySurfaceId_.find(surfaceId);
if (observersIt == observersBySurfaceId_.end()) {
return;
}
auto& observers = observersIt->second;
observers.erase(
std::remove_if(
observers.begin(),
observers.end(),
[intersectionObserverId, &shadowNode](const auto& observer) {
return observer.getIntersectionObserverId() ==
intersectionObserverId &&
ShadowNode::sameFamily(
observer.getTargetShadowNode(), shadowNode);
}),
observers.end());
if (observers.empty()) {
observersBySurfaceId_.erase(surfaceId);
}
}
{
std::unique_lock lock(pendingEntriesMutex_);
pendingEntries_.erase(
std::remove_if(
pendingEntries_.begin(),
pendingEntries_.end(),
[intersectionObserverId, &shadowNode](const auto& entry) {
return entry.intersectionObserverId == intersectionObserverId &&
ShadowNode::sameFamily(*entry.shadowNode, shadowNode);
}),
pendingEntries_.end());
}
}
void IntersectionObserverManager::connect(
UIManager& uiManager,
std::function<void()> notifyIntersectionObserversCallback) {
SystraceSection s("IntersectionObserverManager::connect");
notifyIntersectionObserversCallback_ =
std::move(notifyIntersectionObserversCallback);
// Fail-safe in case the caller doesn't guarantee consistency.
if (mountHookRegistered_) {
return;
}
uiManager.registerMountHook(*this);
mountHookRegistered_ = true;
}
void IntersectionObserverManager::disconnect(UIManager& uiManager) {
SystraceSection s("IntersectionObserverManager::disconnect");
// Fail-safe in case the caller doesn't guarantee consistency.
if (!mountHookRegistered_) {
return;
}
uiManager.unregisterMountHook(*this);
mountHookRegistered_ = false;
notifyIntersectionObserversCallback_ = nullptr;
}
std::vector<IntersectionObserverEntry>
IntersectionObserverManager::takeRecords() {
std::unique_lock lock(pendingEntriesMutex_);
notifiedIntersectionObservers_ = false;
std::vector<IntersectionObserverEntry> entries;
pendingEntries_.swap(entries);
return entries;
}
void IntersectionObserverManager::shadowTreeDidMount(
const RootShadowNode::Shared& rootShadowNode,
double mountTime) noexcept {
updateIntersectionObservations(*rootShadowNode, mountTime);
}
void IntersectionObserverManager::updateIntersectionObservations(
const RootShadowNode& rootShadowNode,
double mountTime) {
SystraceSection s(
"IntersectionObserverManager::updateIntersectionObservations");
std::vector<IntersectionObserverEntry> entries;
// Run intersection observations
{
std::shared_lock lock(observersMutex_);
auto surfaceId = rootShadowNode.getSurfaceId();
auto observersIt = observersBySurfaceId_.find(surfaceId);
if (observersIt == observersBySurfaceId_.end()) {
return;
}
auto& observers = observersIt->second;
for (auto& observer : observers) {
auto entry =
observer.updateIntersectionObservation(rootShadowNode, mountTime);
if (entry) {
entries.push_back(std::move(entry).value());
}
}
}
{
std::unique_lock lock(pendingEntriesMutex_);
pendingEntries_.insert(
pendingEntries_.end(), entries.begin(), entries.end());
}
notifyObserversIfNecessary();
}
/**
* This method allows us to avoid scheduling multiple calls to notify observers
* in the JS thread. We schedule one and skip subsequent ones (we just append
* the entries to the pending list and wait for the scheduled task to consume
* all of them).
*/
void IntersectionObserverManager::notifyObserversIfNecessary() {
bool dispatchNotification = false;
{
std::unique_lock lock(pendingEntriesMutex_);
if (!pendingEntries_.empty() && !notifiedIntersectionObservers_) {
notifiedIntersectionObservers_ = true;
dispatchNotification = true;
}
}
if (dispatchNotification) {
notifyObservers();
}
}
void IntersectionObserverManager::notifyObservers() {
SystraceSection s("IntersectionObserverManager::notifyObservers");
notifyIntersectionObserversCallback_();
}
} // namespace facebook::react

View File

@@ -0,0 +1,74 @@
/*
* 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.
*/
#pragma once
#include <react/renderer/core/ShadowNode.h>
#include <react/renderer/graphics/Float.h>
#include <react/renderer/uimanager/UIManager.h>
#include <react/renderer/uimanager/UIManagerMountHook.h>
#include <vector>
#include "IntersectionObserver.h"
namespace facebook::react {
class IntersectionObserverManager final : public UIManagerMountHook {
public:
IntersectionObserverManager();
void observe(
IntersectionObserverObserverId intersectionObserverId,
const ShadowNode::Shared& shadowNode,
std::vector<Float> thresholds,
const UIManager& uiManager);
void unobserve(
IntersectionObserverObserverId intersectionObserverId,
const ShadowNode& shadowNode);
void connect(
UIManager& uiManager,
std::function<void()> notifyIntersectionObserversCallback);
void disconnect(UIManager& uiManager);
std::vector<IntersectionObserverEntry> takeRecords();
#pragma mark - UIManagerMountHook
void shadowTreeDidMount(
const RootShadowNode::Shared& rootShadowNode,
double mountTime) noexcept override;
private:
mutable std::unordered_map<SurfaceId, std::vector<IntersectionObserver>>
observersBySurfaceId_;
mutable std::shared_mutex observersMutex_;
mutable std::function<void()> notifyIntersectionObserversCallback_;
mutable std::vector<IntersectionObserverEntry> pendingEntries_;
mutable std::mutex pendingEntriesMutex_;
mutable bool notifiedIntersectionObservers_{};
mutable bool mountHookRegistered_{};
void notifyObserversIfNecessary();
void notifyObservers();
// Equivalent to
// https://w3c.github.io/IntersectionObserver/#update-intersection-observations-algo
void updateIntersectionObservations(
const RootShadowNode& rootShadowNode,
double mountTime);
const IntersectionObserver& getRegisteredIntersectionObserver(
SurfaceId surfaceId,
IntersectionObserverObserverId observerId) const;
};
} // namespace facebook::react

View File

@@ -0,0 +1,60 @@
/*
* 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 "IntersectionObserverState.h"
namespace facebook::react {
IntersectionObserverState IntersectionObserverState::Initial() {
static const IntersectionObserverState state =
IntersectionObserverState(IntersectionObserverStateType::Initial);
return state;
}
IntersectionObserverState IntersectionObserverState::NotIntersecting() {
static const IntersectionObserverState state =
IntersectionObserverState(IntersectionObserverStateType::NotIntersecting);
return state;
}
IntersectionObserverState IntersectionObserverState::Intersecting(
Float threshold) {
return IntersectionObserverState(
IntersectionObserverStateType::Intersecting, threshold);
}
IntersectionObserverState::IntersectionObserverState(
IntersectionObserverStateType state)
: state_(state) {}
IntersectionObserverState::IntersectionObserverState(
IntersectionObserverStateType state,
Float threshold)
: state_(state), threshold_(threshold) {}
bool IntersectionObserverState::isIntersecting() const {
return state_ == IntersectionObserverStateType::Intersecting;
}
bool IntersectionObserverState::operator==(
const IntersectionObserverState& other) const {
if (state_ != other.state_) {
return false;
}
if (state_ != IntersectionObserverStateType::Intersecting) {
return true;
}
return threshold_ == other.threshold_;
}
bool IntersectionObserverState::operator!=(
const IntersectionObserverState& other) const {
return !(*this == other);
}
} // namespace facebook::react

View File

@@ -0,0 +1,46 @@
/*
* 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.
*/
#pragma once
#include <react/renderer/graphics/Float.h>
namespace {
enum class IntersectionObserverStateType {
Initial,
NotIntersecting,
Intersecting,
};
}
namespace facebook::react {
class IntersectionObserverState {
public:
static IntersectionObserverState Initial();
static IntersectionObserverState NotIntersecting();
static IntersectionObserverState Intersecting(Float threshold);
bool isIntersecting() const;
bool operator==(const IntersectionObserverState& other) const;
bool operator!=(const IntersectionObserverState& other) const;
private:
explicit IntersectionObserverState(IntersectionObserverStateType state);
IntersectionObserverState(
IntersectionObserverStateType state,
Float threshold);
IntersectionObserverStateType state_;
// This value is only relevant if the state is
// IntersectionObserverStateType::Intersecting.
Float threshold_{};
};
} // namespace facebook::react

View File

@@ -0,0 +1,200 @@
/*
* 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 "MutationObserver.h"
#include <react/renderer/core/ShadowNodeTraits.h>
#include <react/renderer/uimanager/primitives.h>
namespace facebook::react {
MutationObserver::MutationObserver(MutationObserverId mutationObserverId)
: mutationObserverId_(mutationObserverId) {}
void MutationObserver::observe(
ShadowNode::Shared targetShadowNode,
bool observeSubtree) {
if (observeSubtree) {
deeplyObservedShadowNodes_.push_back(targetShadowNode);
} else {
shallowlyObservedShadowNodes_.push_back(targetShadowNode);
}
}
void MutationObserver::unobserve(const ShadowNode& targetShadowNode) {
// We don't know if it's being observed deeply or not, so we need to check
// both possibilities.
deeplyObservedShadowNodes_.erase(
std::remove_if(
deeplyObservedShadowNodes_.begin(),
deeplyObservedShadowNodes_.end(),
[&targetShadowNode](auto shadowNode) {
return ShadowNode::sameFamily(*shadowNode, targetShadowNode);
}),
deeplyObservedShadowNodes_.end());
shallowlyObservedShadowNodes_.erase(
std::remove_if(
shallowlyObservedShadowNodes_.begin(),
shallowlyObservedShadowNodes_.end(),
[&targetShadowNode](const auto shadowNode) {
return ShadowNode::sameFamily(*shadowNode, targetShadowNode);
}),
shallowlyObservedShadowNodes_.end());
}
bool MutationObserver::isObserving() const {
return !deeplyObservedShadowNodes_.empty() ||
!shallowlyObservedShadowNodes_.empty();
}
static ShadowNode::Shared getShadowNodeInTree(
const ShadowNode& shadowNode,
const ShadowNode& newTree) {
auto ancestors = shadowNode.getFamily().getAncestors(newTree);
if (ancestors.empty()) {
return nullptr;
}
auto pair = ancestors.rbegin();
return pair->first.get().getChildren().at(pair->second);
}
static ShadowNode::Shared findNodeOfSameFamily(
const ShadowNode::ListOfShared& list,
const ShadowNode& node) {
for (auto& current : list) {
if (ShadowNode::sameFamily(node, *current)) {
return current;
}
}
return nullptr;
}
void MutationObserver::recordMutations(
const RootShadowNode& oldRootShadowNode,
const RootShadowNode& newRootShadowNode,
std::vector<MutationRecord>& recordedMutations) const {
// This tracks the nodes that have already been processed by this observer,
// so we avoid unnecessary work and duplicated entries.
SetOfShadowNodePointers processedNodes;
// We go over the deeply observed nodes first to avoid skipping nodes that
// have only been checked shallowly.
for (auto targetShadowNode : deeplyObservedShadowNodes_) {
recordMutationsInTarget(
targetShadowNode,
oldRootShadowNode,
newRootShadowNode,
true,
recordedMutations,
processedNodes);
}
for (auto targetShadowNode : shallowlyObservedShadowNodes_) {
recordMutationsInTarget(
targetShadowNode,
oldRootShadowNode,
newRootShadowNode,
false,
recordedMutations,
processedNodes);
}
}
void MutationObserver::recordMutationsInTarget(
ShadowNode::Shared targetShadowNode,
const RootShadowNode& oldRootShadowNode,
const RootShadowNode& newRootShadowNode,
bool observeSubtree,
std::vector<MutationRecord>& recordedMutations,
SetOfShadowNodePointers& processedNodes) const {
// If the node isnt't present in the old tree, it's either:
// - A new node. In that case, the mutation happened in its parent, not in the
// node itself.
// - A non-existent node. In that case, there are no new mutations.
auto oldTargetShadowNode =
getShadowNodeInTree(*targetShadowNode, oldRootShadowNode);
if (!oldTargetShadowNode) {
return;
}
// If the node isn't present in the new tree (and we didn't return in the
// previous check), it means the whole node was removed. In that case we don't
// record any mutations in the node itself (maybe in its parent if there are
// other observers set up).
auto newTargetShadowNode =
getShadowNodeInTree(*targetShadowNode, newRootShadowNode);
if (!newTargetShadowNode) {
return;
}
recordMutationsInSubtrees(
std::move(targetShadowNode),
*oldTargetShadowNode,
*newTargetShadowNode,
observeSubtree,
recordedMutations,
processedNodes);
}
void MutationObserver::recordMutationsInSubtrees(
ShadowNode::Shared targetShadowNode,
const ShadowNode& oldNode,
const ShadowNode& newNode,
bool observeSubtree,
std::vector<MutationRecord>& recordedMutations,
SetOfShadowNodePointers processedNodes) const {
bool isSameNode = &oldNode == &newNode;
// If the nodes are referentially equal, their children are also the same.
if (isSameNode || processedNodes.find(&newNode) != processedNodes.end()) {
return;
}
processedNodes.insert(&newNode);
auto oldChildren = oldNode.getChildren();
auto newChildren = newNode.getChildren();
std::vector<ShadowNode::Shared> addedNodes;
std::vector<ShadowNode::Shared> removedNodes;
// Check for removed nodes (and equal nodes for further inspection)
for (auto& oldChild : oldChildren) {
auto newChild = findNodeOfSameFamily(newChildren, *oldChild);
if (!newChild) {
removedNodes.push_back(oldChild);
} else if (observeSubtree) {
// Nodes are present in both tress. If `subtree` is set to true,
// we continue checking their children.
recordMutationsInSubtrees(
targetShadowNode,
*oldChild,
*newChild,
observeSubtree,
recordedMutations,
processedNodes);
}
}
// Check for added nodes
for (auto& newChild : newChildren) {
auto oldChild = findNodeOfSameFamily(oldChildren, *newChild);
if (!oldChild) {
addedNodes.push_back(newChild);
}
}
if (!addedNodes.empty() || !removedNodes.empty()) {
recordedMutations.emplace_back(MutationRecord{
mutationObserverId_,
targetShadowNode,
std::move(addedNodes),
std::move(removedNodes)});
}
}
} // namespace facebook::react

View File

@@ -0,0 +1,64 @@
/*
* 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.
*/
#pragma once
#include <react/renderer/components/root/RootShadowNode.h>
#include <react/renderer/core/ShadowNode.h>
#include <memory>
#include <utility>
namespace facebook::react {
using MutationObserverId = int32_t;
struct MutationRecord {
MutationObserverId mutationObserverId;
ShadowNode::Shared targetShadowNode;
std::vector<ShadowNode::Shared> addedShadowNodes;
std::vector<ShadowNode::Shared> removedShadowNodes;
};
class MutationObserver {
public:
MutationObserver(MutationObserverId intersectionObserverId);
void observe(ShadowNode::Shared targetShadowNode, bool observeSubtree);
void unobserve(const ShadowNode& targetShadowNode);
bool isObserving() const;
void recordMutations(
const RootShadowNode& oldRootShadowNode,
const RootShadowNode& newRootShadowNode,
std::vector<MutationRecord>& recordedMutations) const;
private:
MutationObserverId mutationObserverId_;
std::vector<ShadowNode::Shared> deeplyObservedShadowNodes_;
std::vector<ShadowNode::Shared> shallowlyObservedShadowNodes_;
using SetOfShadowNodePointers = std::unordered_set<const ShadowNode*>;
void recordMutationsInTarget(
ShadowNode::Shared targetShadowNode,
const RootShadowNode& oldRootShadowNode,
const RootShadowNode& newRootShadowNode,
bool observeSubtree,
std::vector<MutationRecord>& recordedMutations,
SetOfShadowNodePointers& processedNodes) const;
void recordMutationsInSubtrees(
ShadowNode::Shared targetShadowNode,
const ShadowNode& oldNode,
const ShadowNode& newNode,
bool observeSubtree,
std::vector<MutationRecord>& recordedMutations,
SetOfShadowNodePointers processedNodes) const;
};
} // namespace facebook::react

View File

@@ -0,0 +1,142 @@
/*
* 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 "MutationObserverManager.h"
#include <react/renderer/debug/SystraceSection.h>
#include <utility>
#include "MutationObserver.h"
namespace facebook::react {
MutationObserverManager::MutationObserverManager() = default;
void MutationObserverManager::observe(
MutationObserverId mutationObserverId,
ShadowNode::Shared shadowNode,
bool observeSubtree,
const UIManager& uiManager) {
SystraceSection s("MutationObserverManager::observe");
auto surfaceId = shadowNode->getSurfaceId();
auto& observers = observersBySurfaceId_[surfaceId];
auto observerIt = observers.find(mutationObserverId);
if (observerIt == observers.end()) {
auto observer = MutationObserver{mutationObserverId};
observer.observe(shadowNode, observeSubtree);
observers.insert({mutationObserverId, std::move(observer)});
} else {
auto observer = observerIt->second;
observer.observe(shadowNode, observeSubtree);
}
}
void MutationObserverManager::unobserve(
MutationObserverId mutationObserverId,
const ShadowNode& shadowNode) {
SystraceSection s("MutationObserverManager::unobserve");
auto surfaceId = shadowNode.getSurfaceId();
auto observersIt = observersBySurfaceId_.find(surfaceId);
if (observersIt == observersBySurfaceId_.end()) {
return;
}
auto& observers = observersIt->second;
auto observerIt = observers.find(mutationObserverId);
if (observerIt == observers.end()) {
return;
}
auto& observer = observerIt->second;
observer.unobserve(shadowNode);
if (!observer.isObserving()) {
observers.erase(mutationObserverId);
}
if (observers.empty()) {
observersBySurfaceId_.erase(surfaceId);
}
}
void MutationObserverManager::connect(
UIManager& uiManager,
std::function<void(std::vector<MutationRecord>&)> onMutations) {
SystraceSection s("MutationObserverManager::connect");
// Fail-safe in case the caller doesn't guarantee consistency.
if (commitHookRegistered_) {
return;
}
onMutations_ = onMutations;
uiManager.registerCommitHook(*this);
commitHookRegistered_ = true;
}
void MutationObserverManager::disconnect(UIManager& uiManager) {
SystraceSection s("MutationObserverManager::disconnect");
// Fail-safe in case the caller doesn't guarantee consistency.
if (!commitHookRegistered_) {
return;
}
uiManager.unregisterCommitHook(*this);
onMutations_ = nullptr;
commitHookRegistered_ = false;
}
void MutationObserverManager::commitHookWasRegistered(
const UIManager& uiManager) noexcept {}
void MutationObserverManager::commitHookWasUnregistered(
const UIManager& uiManager) noexcept {}
RootShadowNode::Unshared MutationObserverManager::shadowTreeWillCommit(
const ShadowTree& shadowTree,
const RootShadowNode::Shared& oldRootShadowNode,
const RootShadowNode::Unshared& newRootShadowNode) noexcept {
runMutationObservations(shadowTree, *oldRootShadowNode, *newRootShadowNode);
return newRootShadowNode;
}
void MutationObserverManager::runMutationObservations(
const ShadowTree& shadowTree,
const RootShadowNode& oldRootShadowNode,
const RootShadowNode& newRootShadowNode) {
SystraceSection s("MutationObserverManager::runMutationObservations");
auto surfaceId = shadowTree.getSurfaceId();
auto observersIt = observersBySurfaceId_.find(surfaceId);
if (observersIt == observersBySurfaceId_.end()) {
return;
}
std::vector<MutationRecord> mutationRecords;
auto& observers = observersIt->second;
for (const auto& [mutationObserverId, observer] : observers) {
observer.recordMutations(
oldRootShadowNode, newRootShadowNode, mutationRecords);
}
if (!mutationRecords.empty()) {
onMutations_(mutationRecords);
}
return;
}
} // namespace facebook::react

View File

@@ -0,0 +1,63 @@
/*
* 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.
*/
#pragma once
#include <react/renderer/core/ShadowNode.h>
#include <react/renderer/uimanager/UIManager.h>
#include <react/renderer/uimanager/UIManagerCommitHook.h>
#include <vector>
#include "MutationObserver.h"
namespace facebook::react {
class MutationObserverManager final : public UIManagerCommitHook {
public:
MutationObserverManager();
void observe(
MutationObserverId mutationObserverId,
ShadowNode::Shared shadowNode,
bool observeSubtree,
const UIManager& uiManager);
void unobserve(
MutationObserverId mutationObserverId,
const ShadowNode& shadowNode);
void connect(
UIManager& uiManager,
std::function<void(std::vector<MutationRecord>&)> onMutations);
void disconnect(UIManager& uiManager);
#pragma mark - UIManagerCommitHook
void commitHookWasRegistered(const UIManager& uiManager) noexcept override;
void commitHookWasUnregistered(const UIManager& uiManager) noexcept override;
RootShadowNode::Unshared shadowTreeWillCommit(
const ShadowTree& shadowTree,
const RootShadowNode::Shared& oldRootShadowNode,
const RootShadowNode::Unshared& newRootShadowNode) noexcept override;
private:
std::unordered_map<
SurfaceId,
std::unordered_map<MutationObserverId, MutationObserver>>
observersBySurfaceId_;
std::function<void(std::vector<MutationRecord>&)> onMutations_;
bool commitHookRegistered_{};
void runMutationObservations(
const ShadowTree& shadowTree,
const RootShadowNode& oldRootShadowNode,
const RootShadowNode& newRootShadowNode);
};
} // namespace facebook::react