Summary:
Modals were doing nothing (and sometimes crashing) when they were
being closed. The reason for this was due to the fact that the parent being
removed was not necessarily the view's parent. Consequently, trying to inform
said parent that its child was removed failed, because said parent wasn't a
view, and therefore had no record in mViewsToTags.
Reviewed By: sriramramani
Differential Revision: D3928850
Summary:
This fixes a crash for the case when we try to drop a view that has already been dropped.
**The Problem**
We got reports of a crash (t12912526) that occurs when the resolveViewManager method can't resolve a ViewManager for a View being dropped.
Investigating this, one thing in common between all the stack traces for this is that dropView is called from line 210 of FlatNativeViewHierarchyManager. This part of the code is specifically the part we added to remove strong references to any clipped children (from views that have subview clipping enabled).
So this is a problem specifically with Nodes and clipSubviews, which brings up some questions:
**when can this happen?**
The only situation this can possibly happen is when we drop a child (which is clipped) followed by dropping its parent in the same cycle. Consider a tree where each view only has one child, such as: A - B - C - D. This crash would happen if D is clipped, and we removed it, followed by removing any of its parents in the same frame.
**if the removes happen in different frames, does this bug occur?**
No - the reason is that before we execute the DropView operations, we run through StateBuilder, which traverses the shadow tree and marks updates, thus removing the view going away (such that the delete in the next frame doesn't try to re-delete it).
So why doesn't this happen when we're dropping in the same frame? The reason is that manageChildren (where this all starts) asks to remove some views. We handle this by removing said Nodes and their children from the shadow tree. Consequently, when StateBuilder iterates over the shadow tree, it can't do the right thing because said nodes no longer exist.
As a more concrete example, consider A - B - C - D again, and consider that both D and B are removed. StateBuilder only sees A, and realizes that it now has 0 children (whereas before it has 1), so it removes B from its children. However, this process isn't recursive, so C never gets cleaned up.
**why doesn't this happen with Nodes without clipping containers?**
The answer to this is that NativeViewHierarchyManager's dropView method checks the existance of each child before deeply dropping that child and its subtree. So in this case, we drop D and all its children, and when we come to drop B, we try to drop C (which exists) and then its children (D, which doesn't exist because we already dropped it, so we ignore it).
**why doesn't this happen with non-Nodes?**
The reason is that non-Nodes handles removes differently - every remove is enqueued in a call to NativeViewHierarchy's manageChildren, which explicitly asks the parent to remove said child. Consequently, we never try to remove a child that is already removed.
**Fix**
The initial fix was to check whether or not the view exists, but this updated patch just does the right thing at drop time - i.e. whenever a view is dropped, we notify the parent of this fact so that it can clear the reference from clipped views.
**One last Note**
There are two reasons for switching `super.dropView` to `dropView` - first, the comment is only partially correct - calling `super.dropView` will avoid looking at clipped children (as an aside, that could cause a leak in the case of nested clipping subviews), but will look at clipped grandchildren, because of the super class's iteration across the set of children.
Reviewed By: astreet
Differential Revision: D3815485
Summary: @public Make UIOperation public so that custom implementations can expose instances of it.
Reviewed By: ahmedre
Differential Revision: D3618197
Summary:
This patch fixes measureInWindow for Nodes backed by Views.
Whereas the intention was to call the super implementation when we have a
Node backed by a View, we instead called the super implementation of
measure, which doesn't measure relative to window.
Differential Revision: D3607890
Summary: Since Nodes' manageChildren doesn't enqueue the child updates immediately, commands were being directed to non-updated views. Previously we applied updates for the shadow node before dispatching the command, but we can instead wait to fire commands until after we update the view hierarchy.
Reviewed By: ahmedre
Differential Revision: D3568541
Summary: We do want to only apply updates when a view previously wasn't mounted and didn't have a backing view created. Previously we were applying updates to the view regardless of the mount state, which resulted in positioning bugs. Rather than revert, I cleaned up the code Ahmed fixed, since didUpdate || ensureBackingViewIsCreated() was both a bug and obscure, as the two should have a swapped order.
Reviewed By: sriramramani
Differential Revision: D3538734
Summary:
Previously, to fix the issue of commands happening before the Views
were made and attached to the hierarchy, a check was added to see if a node
had not been mounted to a View, to update its hierarchy. In reality, we need
to do this irrespective, since a node could be mounted to a View, but its
children may not yet be attached, for example. Note that if there is nothing
to be done, this won't do extra work (i.e. applyUpdates recursively goes
through the tree from the node on which we did the operation to apply updates,
but if there are no updates, we stop traversing that praticular subtree).
Reviewed By: sriramramani
Differential Revision: D3511462
Summary:
The results from measureInWindow were always wrong the first time it
was called. This was due to the fact that the view in question was not
actually a view yet, so the results were incorrect. This patch uses the
existing measure functionality (which can measure virtual nodes) to measure
the view, while modifying it to properly get the results relative to the
window instead of relative to the root view.
Reviewed By: sriramramani
Differential Revision: D3501544
Summary:
Depends on D3120798
Depends on D3120631
Enables D3120631 for nodes. This implementation seems to work but let me know if I'm doing something really stupid.
Reviewed By: ahmedre
Differential Revision: D3120814
Summary:
Modals were broken in Nodes, because the custom measurement logic for
all the children of the ReactModalShadowNode was not being applied (because we
wrapped it in a NativeViewWrapper). This change adds a custom flat node type
for modals.
Differential Revision: D3499557
Summary:
In manageChildren, we were assuming that the indices that
were passed in to be removed were sorted, however, they weren't.
This patch sorts the children to be removed. Note that it doesn't
explicitly sort move, since these are sorted by the MoveProxy class.
Reviewed By: astreet
Differential Revision: D3474639
Summary:
During the patch for fixing the order of UI operations, we apply
updates to any node receiving a ViewManager command in order to ensure that
nodes that were not yet mounted to a View and not yet attached to their parent
would be properly able to receive the event. However, if a node is already a
view, calling the update could cause unwanted things to happen (for example,
the View's bounds changing improperly), because we're only traversing that
node of the tree and down (instead of the entire tree). This fixes the issue
by only applying updates to the node if the view mount state has changed.
Reviewed By: sriramramani
Differential Revision: D3448356
Summary:
The dispatchViewManager command should, according to the spec, only
be executed after children are added. On Nodes, however, due to the fact that
the Views in question may not have been created until the call to the command
occurred, the dispatchViewManagerCommand may occur too early. Consequently,
ensure that we apply any state updates to the Node represented by that
reactTag before we enqueue the view manager command (this will ensure that
views are properly added to the parent, etc before sending the command).
Reviewed By: astreet
Differential Revision: D3428855
Summary:
Nodes crashed when setJSResponder was called on a virtual (non-View)
node, because a View could not be found using that react tag. The solution is
two fold - first, to figure out the View parent and pass that to
setJSResponder in addition to that of the virtual tag. Secondly, we weren't
mounting views that had animation properties (transform, for example) to
Views, which caused related code to fail.
Reviewed By: sriramramani
Differential Revision: D3301310
Summary:
Initially, we used to mount nodes to Views anytime a node was
clicked. This was not useful, since we could still not handle touch when
a touch event was already dispatched. Later, a fix was pushed that
supported handling touch events for non-View NodeRegions. Part of the
intention was to remove this code, but it was forgotten.
Reviewed By: sriramramani
Differential Revision: D3160532
Summary:
UIImplementation has a few methods that in the end touch native Views, such as dispatchViewManagerCommand, addAnimation, sendAccessibilityEvent etc. There are 2 cases where it is possible to have those methods called on shadow nodes that don't have backing Views created:
- backing view is scheduled to be created but not commited yet (StateBuilder accumulates createView commands into a queue and flushes it in the very end)
- shadow node doesn't mount to a View so there is no backing View
Touching View in UI thread in these 2 cases will either lead to silent error (e.g. failure callback will execute), or a native crash.
This diff is overriding all UIImplementation methods that touch Views in UI thread and makes sure that backing View is created before we do so.
Reviewed By: ahmedre
Differential Revision: D3046392
Summary: @public Split dispatchViewUpdates into two methods, which enables subclasses to commit pending ui operations, even when no root node is present.
Differential Revision: D3011191
Summary:
We keep a list of FlatShadowNodes that mount to Views that we want to delete, and only flush it at the end of an update cycle. This results in a situation where a root view is being removed before children are removed, which results in a crash in NativeViewHierarchyManager because a View that we are trying to remove no longer exists. There are a few approaches to fix the issue:
a) make a check if a View exists before removing it. While works, it removes a bug protection when we erroneously trying to remove a View that no longer exists (such as this). I'd prefer to keep the check in place
b) flush the views-to-drop queue. This works, but does some extra work in UI thread (namely, removing Views that would be removed anyway)
c) trim the views-to-drop queue to remove any Views that will be removed anyway. This does a tiny bit of extra work in BG thread, but less work in UI thread.
This diff implements option c).
Reviewed By: ahmedre
Differential Revision: D2990105
Summary:
Code in FlatUIImplementation.manageChildren() incorrectly assumed that moveFrom is sorted and moveTo is not, which is actually reverse: moveFrom is not sorted, and moveTo is. This means that we need to sort moveFrom before we can traverse it, and that we no longer need to sort moveTo (we did it by moving nodes to mNodesToMove first and then sorting it).
The sorting algorithm used is borrowed from Android implementation of insertion sort used in DualPivotQuicksort.doSort() when number of elements < INSERTION_SORT_THRESHOLD(32) which is 99.999% the case in UIImplementation.manageChildren() (most of the time this array is either empty or only contains 1 element).
Another (very rare) bug this is fixing is that the code only worked for FlatShadowNodes, but not all shadow nodes are FlatShadowNodes (there are rare exceptions, such as ARTShape, ARTGroup etc). New code works with all types of shadow nodes.
Reviewed By: ahmedre
Differential Revision: D2975787
Summary: We are currently traversing entire tree every time there is any update to any of the nodes, which is hugely inefficient. Instead, we can early out for nodes that are known to contain no updates. This saves a lof of CPU. This optimization can be turned off, and an Exception will be thrown if there was an unexpected update.
Reviewed By: ahmedre
Differential Revision: D2975706
Summary: Right now invalidate always tell the root node that the tree is dirty, and next update will traverse the entire tree in search of changes. While this works correctly, it's not the most efficient implementation. It is more efficient to store dirty flag in every node, and skip entire subtrees if this node and all descendants are already up to date. This diff is a first step towards that optimization.
Reviewed By: ahmedre
Differential Revision: D2955197
Summary:
There are 2 issues in removeChild() implementation.
a) There is a chance that a node that we are removing will be mounting to a View, but the create view request has not be created so there is no backing View for it yet. In that case, FlatNativeViewHierarchyManager.dropView() will throw an Exception failing to find the View. The fix is to only dropView() if one was (requested to be) created.
b) If the shadow node that we are removing doesn't mount to a View, but one or more of its children do, those Views will leak because noone is removing them. The fix is to recursively call removeChild() until we find a node that mounts to a View.
Reviewed By: ahmedre
Differential Revision: D2974938
Summary:
There are 2 reasons why someone would call StateBuilder.ensureBackingViewIsCreated():
1) to make sure a View is created, because we are going to use it NOW
2) make sure react styles are applied to View, which doesn't really need the View to be created immediately
This diff is splitting the method into 2, without changing behavior. Difference between the methods' signatures is coming from the fact that 1) never takes styles and 2) possibly takes styles.
This is a pure refactoring diff and should have no change in functionality or behavior.
Reviewed By: ahmedre
Differential Revision: D2916697
Summary:
Many of StateBuilder methods take a lot of arguments, which makes it hard to read. This patch is doing a bit of cleanup by removing tag from the method signatures, because tag can be easily obtained from the node itself.
This is a pure refactoring diff and should have no functional changes.
Reviewed By: ahmedre
Differential Revision: D2915815
Summary:
The current AndroidView stipulates that the backing shadow node can't
be a FlatShadowNode. In some cases, however, we want to apply some of the same
logic (ex not adding NodeRegions, etc) to other ViewManagers that have a
FlatShadowNode backing (and that don't necessarily create a FlatViewGroup).
This commit renames AndroidView to NativeViewWrapper, and re-introduces
AndroidView as an interface, so that logic for padding, NodeRegions, etc can
be shared.
Differential Revision: D2942387
Summary:
@public Relax the constraint on ReactTextInputManager.
The TextInput Advanced screen looked different with and without
nodes, namely child Text items were not being rendered on the Nodes version.
This patch fixes that.
Differential Revision: D2930800
Summary: FlatUIImplementation.handleCreateView was missing an Override annotation. This diff fixes it.
Reviewed By: sriramramani
Differential Revision: D2919596
Summary: Currently, we wrap all unknown shadow nodes with AndroidView. This works great, except when the shadow node is virtual, i.e. it *doesn't* mount to a View. In this case, we just need to keep it in the hierarchy as is. Fixes ARTSurfaceView not working correctly in groups.
Reviewed By: ahmedre
Differential Revision: D2933325
Summary: Alternative implementation of DrawImage using DraweeHierarchy instead of ImagePipeline directly. Yields same results, but potentially more stable. We'll run tests to measure performance of both.
Reviewed By: ahmedre
Differential Revision: D2746197
Summary: UIImplementation.measure() can only measure shadow nodes that map to native Views. As a result of this, every time we tried to measure a shadow node that doesn't map to a View, we trigger callback with no data (to indicate an error). To fix this issue, walk up until we find a node that maps to a View, then measure that View and adjust for the bounds of a virtual child.
Reviewed By: ahmedre
Differential Revision: D2800960
Summary:
React allows nesting <Image> inside <Text>, in which case it turns into an RCTTextInlineImage element. RNFeed is using it in a few places and thus we need to support it, too.
This diff implements it with InlineImageSpan (WithPipeline, and WithDrawee separately).
Reviewed By: ahmedre
Differential Revision: D2792569
Summary: When setJSResponder() is called on a shadow node that doesn't mount to a View, React runtime will crash in NativeViewHierarchyManager because it will fail to find a corresponding View. To fix the issue, make sure we forceMountToView() before we call enqueueSetJSResponder().
Reviewed By: ahmedre
Differential Revision: D2779523
Summary: Previously, manageChildren() would throw an Exception if moveFrom != null or moveTo != null. This is obviously not correct, because no matter how rare these events are, they actually happen in practice. This diff re-implements manageChildren() to support all the cases. It does so by first removing all the elements that need to be removed. During removal, those elements that need to be moved will be temporarily put into `mNodesToMove` array. Then the next step is to add all elements (including new elements to add, and existing elements to move). There is an fast path when only one of another is present. If both types are present, they need to be sorted, first, to maintain correct orded. A similar optimization is applied to `removeChildren()`: there is a fast path when moveFrom == null and a another fast path when removeFrom == null. When both are != null, a merge sort is used (it is assumed that both moveFrom and removeFrom are sorted).
Reviewed By: ahmedre
Differential Revision: D2768689
Summary: There is an OnLayoutEvent that needs to be dispatched when a ReactShadowNode gets re-laid out. Some applications rely on it, so we should support it. This diff adds this functionality.
Reviewed By: ahmedre
Differential Revision: D2768625
Summary:
There is currently a bug where we never release any Views that no longer display, still storing hard references in NativeViewHierarchyManager. This diff is fixing this bug, and here is how:
a) there is already logic in place to drop FlatShadowNodes (UIImplementation.removeShadowNode).
b) there is already logic in place to drop Views (NativeViewHierarchyManager.dropView(int reactTag) - used to private but needs to be protected so I can call it)
c) (the missing part) when we are about to drop a FlatShadowNode, check if it mount to a View (i.e. there is a View associated with that node), put it into a ArrayList. When we finished updates to a nodes hierarchy (which happens in non-UI thread), collect ids from those nodes and enqueue a UIOperation that will drop all the Views. We can either forward nodes as FlatShadowNode[], or only ids as int[]. Both should be fine, but as a rule of thumb we don't touch shadow node hierarchy from UI thread (as we don't touch Views from non-UI thread) so passing int[] is what this code is doing.
Reviewed By: ahmedre
Differential Revision: D2757310
Summary: This diff adds an `AndroidView` as a proxy for custom Views in FlatUIImplementation. Any ReactShadowNode that FlatUIImplementation doesn't recognize (because they don't extend from FlatShadowNode) will be wrapped with AndroidView to ensure that it measures and displays correctly. While not perfect, this is the easiest way to support custom Views (EditTexts, DrawerLayouts, ScrollViews etc).
Reviewed By: ahmedre
Differential Revision: D2751716
Summary: @public There are some properties, such as alpha or scale that we ONLY handle on a View level. This means that whenever we encounter a FlatShadowNode with this property, it should be mapped to a View. This diff is doing exactly this.
Reviewed By: ahmedre
Differential Revision: D2694495
Summary: @public There are some properties that we want to handle on a View level, as opposed to a FlatShadowNode level. For example, scale or alpha, that can be done very efficiently in hardware. Once we pop FlatShadowNode to a separate View, we need to apply these properties. This is where `BaseViewManager` comes in handy.
Reviewed By: sriramramani
Differential Revision: D2694290
Summary: @public This patch adds basic support for RCTImageView (only 'src', 'tintColor' and 'resizeMode' properties are supported for now), and a concept of AttachDetachListener that is required to support it to FlatUIImplementations.
Reviewed By: sriramramani
Differential Revision: D2564389
Summary: @public Initial version of FlatUIImplementation lacks any primitives support (such as RCTText, RCTImageView or RCTView). This diff add the first part, RCTText (alongside with RCTVirtualText and RCTRawText).
Reviewed By: sriramramani
Differential Revision: D2693348