/** * Replaces the View specified by oldTag with the View specified by newTag within oldTag's parent. */ public void replaceExistingNonRootView(int oldTag, int newTag) { if (mShadowNodeRegistry.isRootNode(oldTag) || mShadowNodeRegistry.isRootNode(newTag)) { throw new IllegalViewOperationException("Trying to add or replace a root tag!"); } ReactShadowNode oldNode = mShadowNodeRegistry.getNode(oldTag); if (oldNode == null) { throw new IllegalViewOperationException("Trying to replace unknown view tag: " + oldTag); } ReactShadowNode parent = oldNode.getParent(); if (parent == null) { throw new IllegalViewOperationException("Node is not attached to a parent: " + oldTag); } int oldIndex = parent.indexOf(oldNode); if (oldIndex < 0) { throw new IllegalStateException("Didn't find child tag in parent"); } WritableArray tagsToAdd = Arguments.createArray(); tagsToAdd.pushInt(newTag); WritableArray addAtIndices = Arguments.createArray(); addAtIndices.pushInt(oldIndex); WritableArray indicesToRemove = Arguments.createArray(); indicesToRemove.pushInt(oldIndex); manageChildren(parent.getReactTag(), null, null, tagsToAdd, addAtIndices, indicesToRemove); }
/** * Handles a manageChildren call. This may translate into multiple manageChildren calls for * multiple other views. * * <p>NB: the assumption for calling this method is that all corresponding ReactShadowNodes have * been updated **but tagsToDelete have NOT been deleted yet**. This is because we need to use the * metadata from those nodes to figure out the correct commands to dispatch. This is unlike all * other calls on this class where we assume all operations on the shadow hierarchy have already * completed by the time a corresponding method here is called. */ public void handleManageChildren( ReactShadowNode nodeToManage, int[] indicesToRemove, int[] tagsToRemove, ViewAtIndex[] viewsToAdd, int[] tagsToDelete) { if (!ENABLED) { mUIViewOperationQueue.enqueueManageChildren( nodeToManage.getReactTag(), indicesToRemove, viewsToAdd, tagsToDelete); return; } // We operate on tagsToRemove instead of indicesToRemove because by the time this method is // called, these views have already been removed from the shadow hierarchy and the indices are // no longer useful to operate on for (int i = 0; i < tagsToRemove.length; i++) { int tagToRemove = tagsToRemove[i]; boolean delete = false; for (int j = 0; j < tagsToDelete.length; j++) { if (tagsToDelete[j] == tagToRemove) { delete = true; break; } } ReactShadowNode nodeToRemove = mShadowNodeRegistry.getNode(tagToRemove); removeNodeFromParent(nodeToRemove, delete); } for (int i = 0; i < viewsToAdd.length; i++) { ViewAtIndex toAdd = viewsToAdd[i]; ReactShadowNode nodeToAdd = mShadowNodeRegistry.getNode(toAdd.mTag); addNodeToNode(nodeToManage, nodeToAdd, toAdd.mIndex); } }
private void removeShadowNode(ReactShadowNode nodeToRemove) { mNativeViewHierarchyOptimizer.handleRemoveNode(nodeToRemove); mShadowNodeRegistry.removeNode(nodeToRemove.getReactTag()); for (int i = nodeToRemove.getChildCount() - 1; i >= 0; i--) { removeShadowNode(nodeToRemove.getChildAt(i)); } nodeToRemove.removeAllChildren(); }
public void setJSResponder(int reactTag, boolean blockNativeResponder) { assertViewExists(reactTag, "setJSResponder"); ReactShadowNode node = mShadowNodeRegistry.getNode(reactTag); while (node.isVirtual() || node.isLayoutOnly()) { node = node.getParent(); } mOperationsQueue.enqueueSetJSResponder(node.getReactTag(), reactTag, blockNativeResponder); }
private void assertViewExists(int reactTag, String operationNameForExceptionMessage) { if (mShadowNodeRegistry.getNode(reactTag) == null) { throw new IllegalViewOperationException( "Unable to execute operation " + operationNameForExceptionMessage + " on view with " + "tag: " + reactTag + ", since the view does not exists"); } }
/** Invoked at the end of the transaction to commit any updates to the node hierarchy. */ public void dispatchViewUpdates(EventDispatcher eventDispatcher, int batchId) { for (int i = 0; i < mShadowNodeRegistry.getRootNodeCount(); i++) { int tag = mShadowNodeRegistry.getRootTag(i); ReactShadowNode cssRoot = mShadowNodeRegistry.getNode(tag); notifyOnBeforeLayoutRecursive(cssRoot); SystraceMessage.beginSection(Systrace.TRACE_TAG_REACT_JAVA_BRIDGE, "cssRoot.calculateLayout") .arg("rootTag", tag) .flush(); try { cssRoot.calculateLayout(mLayoutContext); } finally { Systrace.endSection(Systrace.TRACE_TAG_REACT_JAVA_BRIDGE); } applyUpdatesRecursive(cssRoot, 0f, 0f, eventDispatcher); } mNativeViewHierarchyOptimizer.onBatchComplete(); mOperationsQueue.dispatchViewUpdates(batchId); }
/** 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) { ViewManager viewManager = mViewManagers.get(className); ReactShadowNode cssNode = viewManager.createShadowNodeInstance(); ReactShadowNode rootNode = mShadowNodeRegistry.getNode(rootViewTag); cssNode.setReactTag(tag); cssNode.setViewClassName(className); cssNode.setRootNode(rootNode); cssNode.setThemedContext(rootNode.getThemedContext()); mShadowNodeRegistry.addNode(cssNode); CatalystStylesDiffMap styles = null; if (props != null) { styles = new CatalystStylesDiffMap(props); cssNode.updateProperties(styles); } if (!cssNode.isVirtual()) { mNativeViewHierarchyOptimizer.handleCreateView(cssNode, rootViewTag, styles); } }
private void measureLayoutRelativeToParent(int tag, int[] outputBuffer) { ReactShadowNode node = mShadowNodeRegistry.getNode(tag); if (node == null) { throw new IllegalViewOperationException("No native view for tag " + tag + " exists!"); } ReactShadowNode parent = node.getParent(); if (parent == null) { throw new IllegalViewOperationException("View with tag " + tag + " doesn't have a parent!"); } measureLayoutRelativeToVerifiedAncestor(node, parent, outputBuffer); }
private void measureLayout(int tag, int ancestorTag, int[] outputBuffer) { ReactShadowNode node = mShadowNodeRegistry.getNode(tag); ReactShadowNode ancestor = mShadowNodeRegistry.getNode(ancestorTag); if (node == null || ancestor == null) { throw new IllegalViewOperationException( "Tag " + (node == null ? tag : ancestorTag) + " does not exist"); } if (node != ancestor) { ReactShadowNode currentParent = node.getParent(); while (currentParent != ancestor) { if (currentParent == null) { throw new IllegalViewOperationException( "Tag " + ancestorTag + " is not an ancestor of tag " + tag); } currentParent = currentParent.getParent(); } } measureLayoutRelativeToVerifiedAncestor(node, ancestor, outputBuffer); }
/** Invoked when native view that corresponds to a root node has its size changed. */ public void updateRootNodeSize( int rootViewTag, int newWidth, int newHeight, EventDispatcher eventDispatcher) { ReactShadowNode rootCSSNode = mShadowNodeRegistry.getNode(rootViewTag); rootCSSNode.setStyleWidth(newWidth); rootCSSNode.setStyleHeight(newHeight); // If we're in the middle of a batch, the change will automatically be dispatched at the end of // the batch. As all batches are executed as a single runnable on the event queue this should // always be empty, but that calling architecture is an implementation detail. if (mOperationsQueue.isEmpty()) { dispatchViewUpdates(eventDispatcher, -1); // -1 = no associated batch id } }
/** * Method which takes a container tag and then releases all subviews for that container upon * receipt. TODO: The method name is incorrect and will be renamed, #6033872 * * @param containerTag the tag of the container for which the subviews must be removed */ public void removeSubviewsFromContainerWithID(int containerTag) { ReactShadowNode containerNode = mShadowNodeRegistry.getNode(containerTag); if (containerNode == null) { throw new IllegalViewOperationException( "Trying to remove subviews of an unknown view tag: " + containerTag); } WritableArray indicesToRemove = Arguments.createArray(); for (int childIndex = 0; childIndex < containerNode.getChildCount(); childIndex++) { indicesToRemove.pushInt(childIndex); } manageChildren(containerTag, null, null, null, null, indicesToRemove); }
/** * Registers a root node with a given tag, size and ThemedReactContext and adds it to a node * registry. */ public void registerRootView( SizeMonitoringFrameLayout rootView, int tag, int width, int height, ThemedReactContext context) { final ReactShadowNode rootCSSNode = new ReactShadowNode(); rootCSSNode.setReactTag(tag); rootCSSNode.setThemedContext(context); rootCSSNode.setStyleWidth(width); rootCSSNode.setStyleHeight(height); rootCSSNode.setViewClassName("Root"); mShadowNodeRegistry.addRootNode(rootCSSNode); // register it within NativeViewHierarchyManager mOperationsQueue.addRootView(tag, rootView, context); }
/** Invoked by React to create a new node with a given tag has its properties changed. */ public void updateView(int tag, String className, ReadableMap props) { ViewManager viewManager = mViewManagers.get(className); if (viewManager == null) { throw new IllegalViewOperationException("Got unknown view type: " + className); } ReactShadowNode cssNode = mShadowNodeRegistry.getNode(tag); if (cssNode == null) { throw new IllegalViewOperationException("Trying to update non-existent view with tag " + tag); } if (props != null) { CatalystStylesDiffMap styles = new CatalystStylesDiffMap(props); cssNode.updateProperties(styles); if (!cssNode.isVirtual()) { mNativeViewHierarchyOptimizer.handleUpdateView(cssNode, className, styles); } } }
private void applyUpdatesRecursive( ReactShadowNode cssNode, float absoluteX, float absoluteY, EventDispatcher eventDispatcher) { if (!cssNode.hasUpdates()) { return; } if (!cssNode.isVirtualAnchor()) { for (int i = 0; i < cssNode.getChildCount(); i++) { applyUpdatesRecursive( cssNode.getChildAt(i), absoluteX + cssNode.getLayoutX(), absoluteY + cssNode.getLayoutY(), eventDispatcher); } } int tag = cssNode.getReactTag(); if (!mShadowNodeRegistry.isRootNode(tag)) { cssNode.dispatchUpdates( absoluteX, absoluteY, mOperationsQueue, mNativeViewHierarchyOptimizer, eventDispatcher); } cssNode.markUpdateSeen(); }
/** Unregisters a root node with a given tag. */ public void removeRootView(int rootViewTag) { mShadowNodeRegistry.removeRootNode(rootViewTag); mOperationsQueue.enqueueRemoveRootView(rootViewTag); }
/** * Invoked when there is a mutation in a node tree. * * @param tag react tag of the node we want to manage * @param indicesToRemove ordered (asc) list of indicies at which view should be removed * @param viewsToAdd ordered (asc based on mIndex property) list of tag-index pairs that represent * a view which should be added at the specified index * @param tagsToDelete list of tags corresponding to views that should be removed */ public void manageChildren( int viewTag, @Nullable ReadableArray moveFrom, @Nullable ReadableArray moveTo, @Nullable ReadableArray addChildTags, @Nullable ReadableArray addAtIndices, @Nullable ReadableArray removeFrom) { ReactShadowNode cssNodeToManage = mShadowNodeRegistry.getNode(viewTag); int numToMove = moveFrom == null ? 0 : moveFrom.size(); int numToAdd = addChildTags == null ? 0 : addChildTags.size(); int numToRemove = removeFrom == null ? 0 : removeFrom.size(); if (numToMove != 0 && (moveTo == null || numToMove != moveTo.size())) { 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) { Assertions.assertNotNull(addChildTags); 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) { Assertions.assertNotNull(removeFrom); for (int i = 0; i < numToRemove; i++) { int indexToRemove = removeFrom.getInt(i); int tagToRemove = cssNodeToManage.getChildAt(indexToRemove).getReactTag(); indicesToRemove[numToMove + i] = indexToRemove; tagsToRemove[numToMove + i] = tagToRemove; tagsToDelete[i] = tagToRemove; } } // NB: moveFrom and removeFrom are both relative to the starting state of the View's children. // moveTo and addAt are both relative to the final state of the View's children. // // 1) Sort the views to add and indices to remove by index // 2) Iterate the indices being removed from high to low and remove them. Going high to low // makes sure we remove the correct index when there are multiple to remove. // 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. Arrays.sort(viewsToAdd, ViewAtIndex.COMPARATOR); Arrays.sort(indicesToRemove); // Apply changes to CSSNode 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])); } }