Skip to content

Commit f5a3180

Browse files
committed
Enforced thread safety on UIImplementation methods that mutate the shadowNodeRegistry
1 parent 54af5b1 commit f5a3180

File tree

1 file changed

+141
-131
lines changed

1 file changed

+141
-131
lines changed

ReactAndroid/src/main/java/com/facebook/react/uimanager/UIImplementation.java

+141-131
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@
4343
* shadow node hierarchy that is then mapped to a native view hierarchy.
4444
*/
4545
public class UIImplementation {
46+
protected Object uiImplementationThreadLock = new Object();
4647

4748
protected final EventDispatcher mEventDispatcher;
4849
protected final ReactApplicationContext mReactContext;
@@ -197,23 +198,25 @@ public void updateRootView(
197198
*/
198199
public <T extends SizeMonitoringFrameLayout & MeasureSpecProvider> void registerRootView(
199200
T rootView, int tag, ThemedReactContext context) {
200-
final ReactShadowNode rootCSSNode = createRootShadowNode();
201-
rootCSSNode.setReactTag(tag);
202-
rootCSSNode.setThemedContext(context);
203-
204-
int widthMeasureSpec = rootView.getWidthMeasureSpec();
205-
int heightMeasureSpec = rootView.getHeightMeasureSpec();
206-
updateRootView(rootCSSNode, widthMeasureSpec, heightMeasureSpec);
207-
208-
context.runOnNativeModulesQueueThread(new Runnable() {
209-
@Override
210-
public void run() {
211-
mShadowNodeRegistry.addRootNode(rootCSSNode);
212-
}
213-
});
201+
synchronized (uiImplementationThreadLock) {
202+
final ReactShadowNode rootCSSNode = createRootShadowNode();
203+
rootCSSNode.setReactTag(tag);
204+
rootCSSNode.setThemedContext(context);
205+
206+
int widthMeasureSpec = rootView.getWidthMeasureSpec();
207+
int heightMeasureSpec = rootView.getHeightMeasureSpec();
208+
updateRootView(rootCSSNode, widthMeasureSpec, heightMeasureSpec);
209+
210+
context.runOnNativeModulesQueueThread(new Runnable() {
211+
@Override
212+
public void run() {
213+
mShadowNodeRegistry.addRootNode(rootCSSNode);
214+
}
215+
});
214216

215-
// register it within NativeViewHierarchyManager
216-
mOperationsQueue.addRootView(tag, rootView, context);
217+
// register it within NativeViewHierarchyManager
218+
mOperationsQueue.addRootView(tag, rootView, context);
219+
}
217220
}
218221

219222
/**
@@ -228,7 +231,9 @@ public void removeRootView(int rootViewTag) {
228231
* Unregisters a root node with a given tag from the shadow node registry
229232
*/
230233
public void removeRootShadowNode(int rootViewTag) {
231-
mShadowNodeRegistry.removeRootNode(rootViewTag);
234+
synchronized (uiImplementationThreadLock) {
235+
mShadowNodeRegistry.removeRootNode(rootViewTag);
236+
}
232237
}
233238

234239
/**
@@ -279,23 +284,25 @@ public Map<String, Long> getProfiledBatchPerfCounters() {
279284
* Invoked by React to create a new node with a given tag, class name and properties.
280285
*/
281286
public void createView(int tag, String className, int rootViewTag, ReadableMap props) {
282-
ReactShadowNode cssNode = createShadowNode(className);
283-
ReactShadowNode rootNode = mShadowNodeRegistry.getNode(rootViewTag);
284-
Assertions.assertNotNull(rootNode, "Root node with tag " + rootViewTag + " doesn't exist");
285-
cssNode.setReactTag(tag);
286-
cssNode.setViewClassName(className);
287-
cssNode.setRootTag(rootNode.getReactTag());
288-
cssNode.setThemedContext(rootNode.getThemedContext());
289-
290-
mShadowNodeRegistry.addNode(cssNode);
287+
synchronized (uiImplementationThreadLock) {
288+
ReactShadowNode cssNode = createShadowNode(className);
289+
ReactShadowNode rootNode = mShadowNodeRegistry.getNode(rootViewTag);
290+
Assertions.assertNotNull(rootNode, "Root node with tag " + rootViewTag + " doesn't exist");
291+
cssNode.setReactTag(tag);
292+
cssNode.setViewClassName(className);
293+
cssNode.setRootTag(rootNode.getReactTag());
294+
cssNode.setThemedContext(rootNode.getThemedContext());
295+
296+
mShadowNodeRegistry.addNode(cssNode);
297+
298+
ReactStylesDiffMap styles = null;
299+
if (props != null) {
300+
styles = new ReactStylesDiffMap(props);
301+
cssNode.updateProperties(styles);
302+
}
291303

292-
ReactStylesDiffMap styles = null;
293-
if (props != null) {
294-
styles = new ReactStylesDiffMap(props);
295-
cssNode.updateProperties(styles);
304+
handleCreateView(cssNode, rootViewTag, styles);
296305
}
297-
298-
handleCreateView(cssNode, rootViewTag, styles);
299306
}
300307

301308
protected void handleCreateView(
@@ -363,106 +370,108 @@ public void manageChildren(
363370
@Nullable ReadableArray addChildTags,
364371
@Nullable ReadableArray addAtIndices,
365372
@Nullable ReadableArray removeFrom) {
366-
ReactShadowNode cssNodeToManage = mShadowNodeRegistry.getNode(viewTag);
373+
synchronized (uiImplementationThreadLock) {
374+
ReactShadowNode cssNodeToManage = mShadowNodeRegistry.getNode(viewTag);
367375

368-
int numToMove = moveFrom == null ? 0 : moveFrom.size();
369-
int numToAdd = addChildTags == null ? 0 : addChildTags.size();
370-
int numToRemove = removeFrom == null ? 0 : removeFrom.size();
376+
int numToMove = moveFrom == null ? 0 : moveFrom.size();
377+
int numToAdd = addChildTags == null ? 0 : addChildTags.size();
378+
int numToRemove = removeFrom == null ? 0 : removeFrom.size();
371379

372-
if (numToMove != 0 && (moveTo == null || numToMove != moveTo.size())) {
373-
throw new IllegalViewOperationException("Size of moveFrom != size of moveTo!");
374-
}
380+
if (numToMove != 0 && (moveTo == null || numToMove != moveTo.size())) {
381+
throw new IllegalViewOperationException("Size of moveFrom != size of moveTo!");
382+
}
375383

376-
if (numToAdd != 0 && (addAtIndices == null || numToAdd != addAtIndices.size())) {
377-
throw new IllegalViewOperationException("Size of addChildTags != size of addAtIndices!");
378-
}
384+
if (numToAdd != 0 && (addAtIndices == null || numToAdd != addAtIndices.size())) {
385+
throw new IllegalViewOperationException("Size of addChildTags != size of addAtIndices!");
386+
}
379387

380-
// We treat moves as an add and a delete
381-
ViewAtIndex[] viewsToAdd = new ViewAtIndex[numToMove + numToAdd];
382-
int[] indicesToRemove = new int[numToMove + numToRemove];
383-
int[] tagsToRemove = new int[indicesToRemove.length];
384-
int[] tagsToDelete = new int[numToRemove];
385-
386-
if (numToMove > 0) {
387-
Assertions.assertNotNull(moveFrom);
388-
Assertions.assertNotNull(moveTo);
389-
for (int i = 0; i < numToMove; i++) {
390-
int moveFromIndex = moveFrom.getInt(i);
391-
int tagToMove = cssNodeToManage.getChildAt(moveFromIndex).getReactTag();
392-
viewsToAdd[i] = new ViewAtIndex(
393-
tagToMove,
394-
moveTo.getInt(i));
395-
indicesToRemove[i] = moveFromIndex;
396-
tagsToRemove[i] = tagToMove;
388+
// We treat moves as an add and a delete
389+
ViewAtIndex[] viewsToAdd = new ViewAtIndex[numToMove + numToAdd];
390+
int[] indicesToRemove = new int[numToMove + numToRemove];
391+
int[] tagsToRemove = new int[indicesToRemove.length];
392+
int[] tagsToDelete = new int[numToRemove];
393+
394+
if (numToMove > 0) {
395+
Assertions.assertNotNull(moveFrom);
396+
Assertions.assertNotNull(moveTo);
397+
for (int i = 0; i < numToMove; i++) {
398+
int moveFromIndex = moveFrom.getInt(i);
399+
int tagToMove = cssNodeToManage.getChildAt(moveFromIndex).getReactTag();
400+
viewsToAdd[i] = new ViewAtIndex(
401+
tagToMove,
402+
moveTo.getInt(i));
403+
indicesToRemove[i] = moveFromIndex;
404+
tagsToRemove[i] = tagToMove;
405+
}
397406
}
398-
}
399407

400-
if (numToAdd > 0) {
401-
Assertions.assertNotNull(addChildTags);
402-
Assertions.assertNotNull(addAtIndices);
403-
for (int i = 0; i < numToAdd; i++) {
404-
int viewTagToAdd = addChildTags.getInt(i);
405-
int indexToAddAt = addAtIndices.getInt(i);
406-
viewsToAdd[numToMove + i] = new ViewAtIndex(viewTagToAdd, indexToAddAt);
408+
if (numToAdd > 0) {
409+
Assertions.assertNotNull(addChildTags);
410+
Assertions.assertNotNull(addAtIndices);
411+
for (int i = 0; i < numToAdd; i++) {
412+
int viewTagToAdd = addChildTags.getInt(i);
413+
int indexToAddAt = addAtIndices.getInt(i);
414+
viewsToAdd[numToMove + i] = new ViewAtIndex(viewTagToAdd, indexToAddAt);
415+
}
407416
}
408-
}
409417

410-
if (numToRemove > 0) {
411-
Assertions.assertNotNull(removeFrom);
412-
for (int i = 0; i < numToRemove; i++) {
413-
int indexToRemove = removeFrom.getInt(i);
414-
int tagToRemove = cssNodeToManage.getChildAt(indexToRemove).getReactTag();
415-
indicesToRemove[numToMove + i] = indexToRemove;
416-
tagsToRemove[numToMove + i] = tagToRemove;
417-
tagsToDelete[i] = tagToRemove;
418+
if (numToRemove > 0) {
419+
Assertions.assertNotNull(removeFrom);
420+
for (int i = 0; i < numToRemove; i++) {
421+
int indexToRemove = removeFrom.getInt(i);
422+
int tagToRemove = cssNodeToManage.getChildAt(indexToRemove).getReactTag();
423+
indicesToRemove[numToMove + i] = indexToRemove;
424+
tagsToRemove[numToMove + i] = tagToRemove;
425+
tagsToDelete[i] = tagToRemove;
426+
}
418427
}
419-
}
420428

421-
// NB: moveFrom and removeFrom are both relative to the starting state of the View's children.
422-
// moveTo and addAt are both relative to the final state of the View's children.
423-
//
424-
// 1) Sort the views to add and indices to remove by index
425-
// 2) Iterate the indices being removed from high to low and remove them. Going high to low
426-
// makes sure we remove the correct index when there are multiple to remove.
427-
// 3) Iterate the views being added by index low to high and add them. Like the view removal,
428-
// iteration direction is important to preserve the correct index.
429-
430-
Arrays.sort(viewsToAdd, ViewAtIndex.COMPARATOR);
431-
Arrays.sort(indicesToRemove);
432-
433-
// Apply changes to CSSNodeDEPRECATED hierarchy
434-
int lastIndexRemoved = -1;
435-
for (int i = indicesToRemove.length - 1; i >= 0; i--) {
436-
int indexToRemove = indicesToRemove[i];
437-
if (indexToRemove == lastIndexRemoved) {
438-
throw new IllegalViewOperationException("Repeated indices in Removal list for view tag: "
439-
+ viewTag);
429+
// NB: moveFrom and removeFrom are both relative to the starting state of the View's children.
430+
// moveTo and addAt are both relative to the final state of the View's children.
431+
//
432+
// 1) Sort the views to add and indices to remove by index
433+
// 2) Iterate the indices being removed from high to low and remove them. Going high to low
434+
// makes sure we remove the correct index when there are multiple to remove.
435+
// 3) Iterate the views being added by index low to high and add them. Like the view removal,
436+
// iteration direction is important to preserve the correct index.
437+
438+
Arrays.sort(viewsToAdd, ViewAtIndex.COMPARATOR);
439+
Arrays.sort(indicesToRemove);
440+
441+
// Apply changes to CSSNodeDEPRECATED hierarchy
442+
int lastIndexRemoved = -1;
443+
for (int i = indicesToRemove.length - 1; i >= 0; i--) {
444+
int indexToRemove = indicesToRemove[i];
445+
if (indexToRemove == lastIndexRemoved) {
446+
throw new IllegalViewOperationException("Repeated indices in Removal list for view tag: "
447+
+ viewTag);
448+
}
449+
cssNodeToManage.removeChildAt(indicesToRemove[i]);
450+
lastIndexRemoved = indicesToRemove[i];
440451
}
441-
cssNodeToManage.removeChildAt(indicesToRemove[i]);
442-
lastIndexRemoved = indicesToRemove[i];
443-
}
444452

445-
for (int i = 0; i < viewsToAdd.length; i++) {
446-
ViewAtIndex viewAtIndex = viewsToAdd[i];
447-
ReactShadowNode cssNodeToAdd = mShadowNodeRegistry.getNode(viewAtIndex.mTag);
448-
if (cssNodeToAdd == null) {
449-
throw new IllegalViewOperationException("Trying to add unknown view tag: "
450-
+ viewAtIndex.mTag);
453+
for (int i = 0; i < viewsToAdd.length; i++) {
454+
ViewAtIndex viewAtIndex = viewsToAdd[i];
455+
ReactShadowNode cssNodeToAdd = mShadowNodeRegistry.getNode(viewAtIndex.mTag);
456+
if (cssNodeToAdd == null) {
457+
throw new IllegalViewOperationException("Trying to add unknown view tag: "
458+
+ viewAtIndex.mTag);
459+
}
460+
cssNodeToManage.addChildAt(cssNodeToAdd, viewAtIndex.mIndex);
451461
}
452-
cssNodeToManage.addChildAt(cssNodeToAdd, viewAtIndex.mIndex);
453-
}
454462

455-
if (!cssNodeToManage.isVirtual() && !cssNodeToManage.isVirtualAnchor()) {
456-
mNativeViewHierarchyOptimizer.handleManageChildren(
457-
cssNodeToManage,
458-
indicesToRemove,
459-
tagsToRemove,
460-
viewsToAdd,
461-
tagsToDelete);
462-
}
463+
if (!cssNodeToManage.isVirtual() && !cssNodeToManage.isVirtualAnchor()) {
464+
mNativeViewHierarchyOptimizer.handleManageChildren(
465+
cssNodeToManage,
466+
indicesToRemove,
467+
tagsToRemove,
468+
viewsToAdd,
469+
tagsToDelete);
470+
}
463471

464-
for (int i = 0; i < tagsToDelete.length; i++) {
465-
removeShadowNode(mShadowNodeRegistry.getNode(tagsToDelete[i]));
472+
for (int i = 0; i < tagsToDelete.length; i++) {
473+
removeShadowNode(mShadowNodeRegistry.getNode(tagsToDelete[i]));
474+
}
466475
}
467476
}
468477

@@ -476,22 +485,23 @@ public void manageChildren(
476485
public void setChildren(
477486
int viewTag,
478487
ReadableArray childrenTags) {
479-
480-
ReactShadowNode cssNodeToManage = mShadowNodeRegistry.getNode(viewTag);
481-
482-
for (int i = 0; i < childrenTags.size(); i++) {
483-
ReactShadowNode cssNodeToAdd = mShadowNodeRegistry.getNode(childrenTags.getInt(i));
484-
if (cssNodeToAdd == null) {
485-
throw new IllegalViewOperationException("Trying to add unknown view tag: "
486-
+ childrenTags.getInt(i));
488+
synchronized (uiImplementationThreadLock) {
489+
ReactShadowNode cssNodeToManage = mShadowNodeRegistry.getNode(viewTag);
490+
491+
for (int i = 0; i < childrenTags.size(); i++) {
492+
ReactShadowNode cssNodeToAdd = mShadowNodeRegistry.getNode(childrenTags.getInt(i));
493+
if (cssNodeToAdd == null) {
494+
throw new IllegalViewOperationException("Trying to add unknown view tag: "
495+
+ childrenTags.getInt(i));
496+
}
497+
cssNodeToManage.addChildAt(cssNodeToAdd, i);
487498
}
488-
cssNodeToManage.addChildAt(cssNodeToAdd, i);
489-
}
490499

491-
if (!cssNodeToManage.isVirtual() && !cssNodeToManage.isVirtualAnchor()) {
492-
mNativeViewHierarchyOptimizer.handleSetChildren(
493-
cssNodeToManage,
494-
childrenTags);
500+
if (!cssNodeToManage.isVirtual() && !cssNodeToManage.isVirtualAnchor()) {
501+
mNativeViewHierarchyOptimizer.handleSetChildren(
502+
cssNodeToManage,
503+
childrenTags);
504+
}
495505
}
496506
}
497507

0 commit comments

Comments
 (0)