Enforced thread safety on UIImplementation methods that mutate the shadowNodeRegistry

This commit is contained in:
Mike Grabowski 2019-04-17 21:32:29 +02:00
parent 54af5b151e
commit f5a31801a0

View File

@ -43,6 +43,7 @@ import javax.annotation.Nullable;
* shadow node hierarchy that is then mapped to a native view hierarchy. * shadow node hierarchy that is then mapped to a native view hierarchy.
*/ */
public class UIImplementation { public class UIImplementation {
protected Object uiImplementationThreadLock = new Object();
protected final EventDispatcher mEventDispatcher; protected final EventDispatcher mEventDispatcher;
protected final ReactApplicationContext mReactContext; protected final ReactApplicationContext mReactContext;
@ -197,23 +198,25 @@ public class UIImplementation {
*/ */
public <T extends SizeMonitoringFrameLayout & MeasureSpecProvider> void registerRootView( public <T extends SizeMonitoringFrameLayout & MeasureSpecProvider> void registerRootView(
T rootView, int tag, ThemedReactContext context) { T rootView, int tag, ThemedReactContext context) {
final ReactShadowNode rootCSSNode = createRootShadowNode(); synchronized (uiImplementationThreadLock) {
rootCSSNode.setReactTag(tag); final ReactShadowNode rootCSSNode = createRootShadowNode();
rootCSSNode.setThemedContext(context); rootCSSNode.setReactTag(tag);
rootCSSNode.setThemedContext(context);
int widthMeasureSpec = rootView.getWidthMeasureSpec(); int widthMeasureSpec = rootView.getWidthMeasureSpec();
int heightMeasureSpec = rootView.getHeightMeasureSpec(); int heightMeasureSpec = rootView.getHeightMeasureSpec();
updateRootView(rootCSSNode, widthMeasureSpec, heightMeasureSpec); updateRootView(rootCSSNode, widthMeasureSpec, heightMeasureSpec);
context.runOnNativeModulesQueueThread(new Runnable() { context.runOnNativeModulesQueueThread(new Runnable() {
@Override @Override
public void run() { public void run() {
mShadowNodeRegistry.addRootNode(rootCSSNode); mShadowNodeRegistry.addRootNode(rootCSSNode);
} }
}); });
// register it within NativeViewHierarchyManager // register it within NativeViewHierarchyManager
mOperationsQueue.addRootView(tag, rootView, context); mOperationsQueue.addRootView(tag, rootView, context);
}
} }
/** /**
@ -228,7 +231,9 @@ public class UIImplementation {
* Unregisters a root node with a given tag from the shadow node registry * Unregisters a root node with a given tag from the shadow node registry
*/ */
public void removeRootShadowNode(int rootViewTag) { public void removeRootShadowNode(int rootViewTag) {
mShadowNodeRegistry.removeRootNode(rootViewTag); synchronized (uiImplementationThreadLock) {
mShadowNodeRegistry.removeRootNode(rootViewTag);
}
} }
/** /**
@ -279,23 +284,25 @@ public class UIImplementation {
* Invoked by React to create a new node with a given tag, class name and properties. * Invoked by React to create a new node with a given tag, class name and properties.
*/ */
public void createView(int tag, String className, int rootViewTag, ReadableMap props) { public void createView(int tag, String className, int rootViewTag, ReadableMap props) {
ReactShadowNode cssNode = createShadowNode(className); synchronized (uiImplementationThreadLock) {
ReactShadowNode rootNode = mShadowNodeRegistry.getNode(rootViewTag); ReactShadowNode cssNode = createShadowNode(className);
Assertions.assertNotNull(rootNode, "Root node with tag " + rootViewTag + " doesn't exist"); ReactShadowNode rootNode = mShadowNodeRegistry.getNode(rootViewTag);
cssNode.setReactTag(tag); Assertions.assertNotNull(rootNode, "Root node with tag " + rootViewTag + " doesn't exist");
cssNode.setViewClassName(className); cssNode.setReactTag(tag);
cssNode.setRootTag(rootNode.getReactTag()); cssNode.setViewClassName(className);
cssNode.setThemedContext(rootNode.getThemedContext()); cssNode.setRootTag(rootNode.getReactTag());
cssNode.setThemedContext(rootNode.getThemedContext());
mShadowNodeRegistry.addNode(cssNode); mShadowNodeRegistry.addNode(cssNode);
ReactStylesDiffMap styles = null; ReactStylesDiffMap styles = null;
if (props != null) { if (props != null) {
styles = new ReactStylesDiffMap(props); styles = new ReactStylesDiffMap(props);
cssNode.updateProperties(styles); cssNode.updateProperties(styles);
}
handleCreateView(cssNode, rootViewTag, styles);
} }
handleCreateView(cssNode, rootViewTag, styles);
} }
protected void handleCreateView( protected void handleCreateView(
@ -363,106 +370,108 @@ public class UIImplementation {
@Nullable ReadableArray addChildTags, @Nullable ReadableArray addChildTags,
@Nullable ReadableArray addAtIndices, @Nullable ReadableArray addAtIndices,
@Nullable ReadableArray removeFrom) { @Nullable ReadableArray removeFrom) {
ReactShadowNode cssNodeToManage = mShadowNodeRegistry.getNode(viewTag); synchronized (uiImplementationThreadLock) {
ReactShadowNode cssNodeToManage = mShadowNodeRegistry.getNode(viewTag);
int numToMove = moveFrom == null ? 0 : moveFrom.size(); int numToMove = moveFrom == null ? 0 : moveFrom.size();
int numToAdd = addChildTags == null ? 0 : addChildTags.size(); int numToAdd = addChildTags == null ? 0 : addChildTags.size();
int numToRemove = removeFrom == null ? 0 : removeFrom.size(); int numToRemove = removeFrom == null ? 0 : removeFrom.size();
if (numToMove != 0 && (moveTo == null || numToMove != moveTo.size())) { if (numToMove != 0 && (moveTo == null || numToMove != moveTo.size())) {
throw new IllegalViewOperationException("Size of moveFrom != size of moveTo!"); throw new IllegalViewOperationException("Size of moveFrom != size of moveTo!");
}
if (numToAdd != 0 && (addAtIndices == null || numToAdd != addAtIndices.size())) {
throw new IllegalViewOperationException("Size of addChildTags != size of addAtIndices!");
}
// We treat moves as an add and a delete
ViewAtIndex[] viewsToAdd = new ViewAtIndex[numToMove + numToAdd];
int[] indicesToRemove = new int[numToMove + numToRemove];
int[] tagsToRemove = new int[indicesToRemove.length];
int[] tagsToDelete = new int[numToRemove];
if (numToMove > 0) {
Assertions.assertNotNull(moveFrom);
Assertions.assertNotNull(moveTo);
for (int i = 0; i < numToMove; i++) {
int moveFromIndex = moveFrom.getInt(i);
int tagToMove = cssNodeToManage.getChildAt(moveFromIndex).getReactTag();
viewsToAdd[i] = new ViewAtIndex(
tagToMove,
moveTo.getInt(i));
indicesToRemove[i] = moveFromIndex;
tagsToRemove[i] = tagToMove;
} }
}
if (numToAdd > 0) { if (numToAdd != 0 && (addAtIndices == null || numToAdd != addAtIndices.size())) {
Assertions.assertNotNull(addChildTags); throw new IllegalViewOperationException("Size of addChildTags != size of addAtIndices!");
Assertions.assertNotNull(addAtIndices);
for (int i = 0; i < numToAdd; i++) {
int viewTagToAdd = addChildTags.getInt(i);
int indexToAddAt = addAtIndices.getInt(i);
viewsToAdd[numToMove + i] = new ViewAtIndex(viewTagToAdd, indexToAddAt);
} }
}
if (numToRemove > 0) { // We treat moves as an add and a delete
Assertions.assertNotNull(removeFrom); ViewAtIndex[] viewsToAdd = new ViewAtIndex[numToMove + numToAdd];
for (int i = 0; i < numToRemove; i++) { int[] indicesToRemove = new int[numToMove + numToRemove];
int indexToRemove = removeFrom.getInt(i); int[] tagsToRemove = new int[indicesToRemove.length];
int tagToRemove = cssNodeToManage.getChildAt(indexToRemove).getReactTag(); int[] tagsToDelete = new int[numToRemove];
indicesToRemove[numToMove + i] = indexToRemove;
tagsToRemove[numToMove + i] = tagToRemove; if (numToMove > 0) {
tagsToDelete[i] = tagToRemove; Assertions.assertNotNull(moveFrom);
Assertions.assertNotNull(moveTo);
for (int i = 0; i < numToMove; i++) {
int moveFromIndex = moveFrom.getInt(i);
int tagToMove = cssNodeToManage.getChildAt(moveFromIndex).getReactTag();
viewsToAdd[i] = new ViewAtIndex(
tagToMove,
moveTo.getInt(i));
indicesToRemove[i] = moveFromIndex;
tagsToRemove[i] = tagToMove;
}
} }
}
// NB: moveFrom and removeFrom are both relative to the starting state of the View's children. if (numToAdd > 0) {
// moveTo and addAt are both relative to the final state of the View's children. Assertions.assertNotNull(addChildTags);
// Assertions.assertNotNull(addAtIndices);
// 1) Sort the views to add and indices to remove by index for (int i = 0; i < numToAdd; i++) {
// 2) Iterate the indices being removed from high to low and remove them. Going high to low int viewTagToAdd = addChildTags.getInt(i);
// makes sure we remove the correct index when there are multiple to remove. int indexToAddAt = addAtIndices.getInt(i);
// 3) Iterate the views being added by index low to high and add them. Like the view removal, viewsToAdd[numToMove + i] = new ViewAtIndex(viewTagToAdd, indexToAddAt);
// iteration direction is important to preserve the correct index. }
Arrays.sort(viewsToAdd, ViewAtIndex.COMPARATOR);
Arrays.sort(indicesToRemove);
// Apply changes to CSSNodeDEPRECATED hierarchy
int lastIndexRemoved = -1;
for (int i = indicesToRemove.length - 1; i >= 0; i--) {
int indexToRemove = indicesToRemove[i];
if (indexToRemove == lastIndexRemoved) {
throw new IllegalViewOperationException("Repeated indices in Removal list for view tag: "
+ viewTag);
} }
cssNodeToManage.removeChildAt(indicesToRemove[i]);
lastIndexRemoved = indicesToRemove[i];
}
for (int i = 0; i < viewsToAdd.length; i++) { if (numToRemove > 0) {
ViewAtIndex viewAtIndex = viewsToAdd[i]; Assertions.assertNotNull(removeFrom);
ReactShadowNode cssNodeToAdd = mShadowNodeRegistry.getNode(viewAtIndex.mTag); for (int i = 0; i < numToRemove; i++) {
if (cssNodeToAdd == null) { int indexToRemove = removeFrom.getInt(i);
throw new IllegalViewOperationException("Trying to add unknown view tag: " int tagToRemove = cssNodeToManage.getChildAt(indexToRemove).getReactTag();
+ viewAtIndex.mTag); indicesToRemove[numToMove + i] = indexToRemove;
tagsToRemove[numToMove + i] = tagToRemove;
tagsToDelete[i] = tagToRemove;
}
} }
cssNodeToManage.addChildAt(cssNodeToAdd, viewAtIndex.mIndex);
}
if (!cssNodeToManage.isVirtual() && !cssNodeToManage.isVirtualAnchor()) { // NB: moveFrom and removeFrom are both relative to the starting state of the View's children.
mNativeViewHierarchyOptimizer.handleManageChildren( // moveTo and addAt are both relative to the final state of the View's children.
cssNodeToManage, //
indicesToRemove, // 1) Sort the views to add and indices to remove by index
tagsToRemove, // 2) Iterate the indices being removed from high to low and remove them. Going high to low
viewsToAdd, // makes sure we remove the correct index when there are multiple to remove.
tagsToDelete); // 3) Iterate the views being added by index low to high and add them. Like the view removal,
} // iteration direction is important to preserve the correct index.
for (int i = 0; i < tagsToDelete.length; i++) { Arrays.sort(viewsToAdd, ViewAtIndex.COMPARATOR);
removeShadowNode(mShadowNodeRegistry.getNode(tagsToDelete[i])); Arrays.sort(indicesToRemove);
// Apply changes to CSSNodeDEPRECATED hierarchy
int lastIndexRemoved = -1;
for (int i = indicesToRemove.length - 1; i >= 0; i--) {
int indexToRemove = indicesToRemove[i];
if (indexToRemove == lastIndexRemoved) {
throw new IllegalViewOperationException("Repeated indices in Removal list for view tag: "
+ viewTag);
}
cssNodeToManage.removeChildAt(indicesToRemove[i]);
lastIndexRemoved = indicesToRemove[i];
}
for (int i = 0; i < viewsToAdd.length; i++) {
ViewAtIndex viewAtIndex = viewsToAdd[i];
ReactShadowNode cssNodeToAdd = mShadowNodeRegistry.getNode(viewAtIndex.mTag);
if (cssNodeToAdd == null) {
throw new IllegalViewOperationException("Trying to add unknown view tag: "
+ viewAtIndex.mTag);
}
cssNodeToManage.addChildAt(cssNodeToAdd, viewAtIndex.mIndex);
}
if (!cssNodeToManage.isVirtual() && !cssNodeToManage.isVirtualAnchor()) {
mNativeViewHierarchyOptimizer.handleManageChildren(
cssNodeToManage,
indicesToRemove,
tagsToRemove,
viewsToAdd,
tagsToDelete);
}
for (int i = 0; i < tagsToDelete.length; i++) {
removeShadowNode(mShadowNodeRegistry.getNode(tagsToDelete[i]));
}
} }
} }
@ -476,22 +485,23 @@ public class UIImplementation {
public void setChildren( public void setChildren(
int viewTag, int viewTag,
ReadableArray childrenTags) { ReadableArray childrenTags) {
synchronized (uiImplementationThreadLock) {
ReactShadowNode cssNodeToManage = mShadowNodeRegistry.getNode(viewTag);
ReactShadowNode cssNodeToManage = mShadowNodeRegistry.getNode(viewTag); for (int i = 0; i < childrenTags.size(); i++) {
ReactShadowNode cssNodeToAdd = mShadowNodeRegistry.getNode(childrenTags.getInt(i));
for (int i = 0; i < childrenTags.size(); i++) { if (cssNodeToAdd == null) {
ReactShadowNode cssNodeToAdd = mShadowNodeRegistry.getNode(childrenTags.getInt(i)); throw new IllegalViewOperationException("Trying to add unknown view tag: "
if (cssNodeToAdd == null) { + childrenTags.getInt(i));
throw new IllegalViewOperationException("Trying to add unknown view tag: " }
+ childrenTags.getInt(i)); cssNodeToManage.addChildAt(cssNodeToAdd, i);
} }
cssNodeToManage.addChildAt(cssNodeToAdd, i);
}
if (!cssNodeToManage.isVirtual() && !cssNodeToManage.isVirtualAnchor()) { if (!cssNodeToManage.isVirtual() && !cssNodeToManage.isVirtualAnchor()) {
mNativeViewHierarchyOptimizer.handleSetChildren( mNativeViewHierarchyOptimizer.handleSetChildren(
cssNodeToManage, cssNodeToManage,
childrenTags); childrenTags);
}
} }
} }