Summary:
RN has an optimization in which a ScrollView (or similar ViewGroups)
can ask to remove clipped subviews from the View hierarchy. This patch
implements this optimization for Nodes, but instead of adding and removing the
Views, it attaches and detaches Views instead.
Note that this patch does not handle overflow: visible. This is addressed in a
stacked patch on top of this patch (to simplify the review process).
Reviewed By: astreet
Differential Revision: D3235050
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:
Canvas.save by default saves both the matrix (for translations,
scaling, etc) and the clip (clipRect) - in most of our cases, we really only
care to save and restore the clip, not the matrix.
Reviewed By: sriramramani
Differential Revision: D3235698
Summary:
With nodes, it's possible for a touchable region to not be
explicitly mounted to a View. To work around this (and allow the region to
be the handler of the touch event), FlatViewGroup intercepts touch events
when the touch lies within any of the virtual NodeRegions.
This can sometimes be wrong - the canonical example is when touch starts
outside of a particular FlatViewGroup (so someone else, for example a
sibling) intercepts the touch event, and then the person moves over a
different FlatViewGroup, causing it to intercept the touch event when it
shouldn't. To fix this, we only allow intercepting touch events due to
NodeRegions on the down event.
Reviewed By: astreet
Differential Revision: D3160152
Summary:
By default, Nodes causes views to not be clipped, unless overflow is
explicitly set to hidden. Consequently, Nodes sets all the clipping bounds to
negative infinity, and does some extra work (saving the canvas layer,
clipping, etc) before drawing. This optimization skips the extra work when
it's not needed.
Reviewed By: sriramramani
Differential Revision: D3161268
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: This allows users of the API to have greater control over handling RTL. One example is Components which needs this greater control to correctly handle RTL.
Differential Revision: D3120721
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: In some rare cases, RCTText.measure can receive negative width which will crash with an assertion in android.text.Layout because it expects a positive value. This is a temporary fix to treat negative values as unconstrained width until the original bug is fixed.
Reviewed By: sriramramani
Differential Revision: D3038767
Summary:
Since we now have the correct fix in place, this patch is to revert
the hack we put in place to not be launch blocking.
Reviewed By: sriramramani
Differential Revision: D3019883
Summary: In FlatViewGroup, we flatten some react nodes into parent while mounting others into child Views. This is causing touch events being dispatched to wrong targets because child Views are \"stealing\" touch events from flattened Views. To fix the issue, implement ReactCompoundViewGroup to provide information about both virtual and non-virtual nodes.
Reviewed By: ahmedre
Differential Revision: D3018054
Summary: Before this patch, we only collected virtual nodes in NodeRegions, because NodeRegions are only needed to implement ReactCompoundView.reactTargetForTouch() which is only interested in virtual nodes. In the next patch, FlatViewGroup will implement ReactCompoundViewGroup interface which requires knowledge of both virtual and non-virtual children. As a step towards that, we need to include non-virtual nodes in NodeRegions. This patch is implementing that. By itself, it should have not cause any changes in application behavior: we add non-virtual nodes to NodeRegions and mark them as non-virtual, then skip all non-virtual nodes in reactTagForTouch().
Reviewed By: ahmedre
Differential Revision: D3018047
Summary: Before the patch, order of NodeRegions was inconsistent. Given parent A and 3 children B, C and D, we collect DrawCommands like this: A, B, C, D but NodeRegions were collected as B, C, D, A which neither matches draw order, nor a reverse of it. This patch changes it so that NodeRegions are collected in drawing order (A, B, C, D) and we iterate backwards to find correct touch target (in case they overlap).
Reviewed By: ahmedre
Differential Revision: D3018034
Summary:
This is a hack to fix the Groups dialog bug so that we can get some
real data in production from using Nodes. This should be replaced with a
better solution in the near future.
Differential Revision: D3016859
Summary: CSSNode.addChildAt() calls dirty() to invalidate the node and propagate dirty flag up to the root. However, ReactShadowNode overrides dirty() for virtual nodes so it does nothing. This results in bugs where an added text doesn't trigger a measure pass because RCTText is never dirtied. To fix the bug, override addChildAt() in RCTVirtualText and explicitly call notifyChanged(true) to make sure hosting RCTText is dirtied and re-measured/re-laid out.
Reviewed By: ahmedre
Differential Revision: D3016827
Summary:
This code reverts D3004541, since it fixes the symptom instead
of the root cause. Root cause fix is in D3011191.
Differential Revision: D3011291
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:
getLayoutWidth is the same as right - left, and since we have right
and left, we can save a method call.
Reviewed By: sriramramani
Differential Revision: D3010697
Summary:
In D2980358, alignment was implemented for nodes. This unfortunately
introduced a bug, which is that when we have a BoringLayout that is set to
ALIGN_CENTER, it can't be seen. This is a result of the fact that the width
passed in to BoringLayout is Integer.MAX_VALUE. Since measure has already
been called at this point, we can just pass the layout's width as the width
of the BoringLayout.
Reviewed By: sriramramani
Differential Revision: D3004971
Summary:
Groups had a crash when running with React with Nodes when returning
from a search screen. This was due to the fact that the node representing a
ShimmerFrameLayout was being dropped, and then later we were trying to detach
it. Since the view was already dropped, we shouldn't try to detach it since
it's already dropped and removed from the view hierarchy.
Reviewed By: sriramramani
Differential Revision: D3004541
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:
Fix screenshot tests for React with nodes. It was broken due to
calling clipRect with bounds of [-∞, ∞], which, due to a bug in Canvas that
appeared in screenshot tests, caused the view not to draw. Since this is a
no-op anyway, this patch just doesn't call clipRect when we have infinite
bounds.
Differential Revision: D2975494
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:
Examples that use defaultValue as part of their declaration (such as
those examples under UIExplorerApp's TextInput) were being ignored. This patch
supports having text set directly on RCTTextInput and handles it properly.
Note that this patch depends on D2962643 and D2964847, without which the
TextInput example will look wrong.
Differential Revision: D2968002
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:
OnLayoutEvent was incorrectly dispatching x/y coordinates relative to native View instead of relative to parent. This was causing issues in many cases, such as when Node was mounting to a View, in which case x/y would always be 0. This diff is fixing it.
This diff also caches last OnLayoutEvent to avoid dispatching the event on every layout even when layout didn't chance.
Reviewed By: ahmedre
Differential Revision: D2955087
Summary:
Setting the color for a TextInput with nodes was broken. The text color was
not being applied due to an optimization that prevented setting spans if
begin and end were the same (which is the case for an empty TextInput).
This patch depends on D2962643.
Differential Revision: D2962394
Summary:
The spannable flags for RCTVirtualText were always being set to
INCLUSIVE_EXCLUSIVE - this is different than the behavior that is found in
ReactTextShadowNode, which sets EXCLUSIVE_INCLUSIVE as the default flag
unless the text is at the beginning.
This is needed to fix various problems with TextInput, including the handling
of empty spans (see also D2962394 which depends on this patch), and making the
behavior consistent when styled children of a TextInput are changed.
Differential Revision: D2962643
Summary:
Implement measure for RCTTextInput. This is (almost) the same as
the one implemented for ReactTextInputShadowNode.
Differential Revision: D2964847
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:
There is a rare case in React Native when a hierarchy is created and then *immediately* discarded all in one transaction. This is causing a View leak, because a View is created and then never attached to a hierarchy because it shadow node got detached. Since we never attached the View to the hierarchy, we cannot do a detach either and the View is kept in NativeViewHierarchyManager forever, causing a View leak. To fix the issue, don't create a backing View whenever a shadow node mounts to a View. Instead, put the create request into a queue and ONLY execute it if a shadow node is still attached to a hierarchy. This is fixing the View leak because will not create a View unless it's going to be attached somewhere.
The same logic applies to View style update (we don't want to update a View that is going to be detached from a hierarchy).
Reviewed By: ahmedre
Differential Revision: D2916826
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:
When layout happens, left/top/right/bottom coordinates (and thus clip-*, too) are sometimes not at a pixel boundary for 2 reasons:
a) width/height in Flexbox are floats, and can contain any value, not just a whole pixel. E.g. style={{width: 3.1415926}} is perfectly valid
b) floating point arithmetics sometimes leads to values barely outside of pixel boundaries (width 18.0f can become 18.000001f when a sibling/child size changes).
a) is \"breaking\" screenshot tests, which slightly but differ from reference implementation
b) causes extra View updates/redraws for no reason
This patch is rounding DrawCommand bounds to a whole pixel to avoid these 2 issues.
Reviewed By: ahmedre
Differential Revision: D2934401
Summary:
Public
We wanted to do this before, but couldn't because touch events had timestamps
set by the system (and matched System.currentTimeMillis), but now we set those
timestamps! The idea behind this change is that System.currentTimeMillis is
unreliable, but nanoTime isn't, and it also guarantees that we will never have two events with the same
timestamp.
We're still seeing crashes with touch events not ending correctly in JS, this
might be the cause of that.
Reviewed By: foghina
Differential Revision: D2953917
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: Both DrawView and AbstractDrawCommand have clipping logic, this diff is moving out logic into a common base class. This also reverts the screenshot tests fix, which was causing issues with overflow: visible.
Reviewed By: ahmedre
Differential Revision: D2933780
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: Not every CSSNode in a hierarchy is a FlatShadowNode, some virtual nodes can be ReactShadowNodes for compatibility with ART nodes. This diff fixes StateBuilder unconditionally casting a node to FlatShadowNode, which is causing a crash in Groups.
Reviewed By: nickholub
Differential Revision: D2941031