Make litho/sections a separate plugin

Summary:
Breaking Litho/Sections off. Doesn't really make sense to have
two deps for this especially since they're pretty tightly coupled.

This makes our core already quite slim.

Reviewed By: jknoxville

Differential Revision: D17420118

fbshipit-source-id: 9a03911f4af6410745b9aefd0e6a75bdf106660f
This commit is contained in:
Pascal Hartig
2019-09-19 02:58:43 -07:00
committed by Facebook Github Bot
parent c4a9b603e2
commit 2baadf9867
17 changed files with 46 additions and 5 deletions

View File

@@ -1,150 +0,0 @@
/*
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the LICENSE
* file in the root directory of this source tree.
*/
package com.facebook.flipper.plugins.litho;
import static com.facebook.flipper.plugins.inspector.InspectorValue.Type.Color;
import android.graphics.drawable.ColorDrawable;
import android.graphics.drawable.Drawable;
import com.facebook.flipper.core.FlipperObject;
import com.facebook.flipper.plugins.inspector.InspectorValue;
import com.facebook.flipper.plugins.inspector.Named;
import com.facebook.litho.StateContainer;
import com.facebook.litho.annotations.Prop;
import com.facebook.litho.annotations.State;
import com.facebook.litho.drawable.ComparableColorDrawable;
import java.lang.reflect.Field;
import java.util.AbstractMap;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import javax.annotation.Nullable;
public class DataUtils {
static List<com.facebook.flipper.plugins.inspector.Named<com.facebook.flipper.core.FlipperObject>>
getPropData(Object node) throws Exception {
final FlipperObject.Builder props = new FlipperObject.Builder();
List<Named<FlipperObject>> data = new ArrayList<>();
boolean hasProps = false;
for (Field f : node.getClass().getDeclaredFields()) {
f.setAccessible(true);
final Prop annotation = f.getAnnotation(Prop.class);
if (annotation != null) {
if (f.get(node) != null
&& PropWithInspectorSection.class.isAssignableFrom(f.get(node).getClass())) {
final AbstractMap.SimpleEntry<String, String> datum =
((PropWithInspectorSection) f.get(node)).getFlipperLayoutInspectorSection();
if (datum != null) {
data.add(new Named<>(datum.getKey(), new FlipperObject(datum.getValue())));
}
}
switch (annotation.resType()) {
case COLOR:
props.put(f.getName(), f.get(node) == null ? "null" : fromColor((Integer) f.get(node)));
break;
case DRAWABLE:
props.put(
f.getName(), f.get(node) == null ? "null" : fromDrawable((Drawable) f.get(node)));
break;
default:
if (f.get(node) != null
&& PropWithDescription.class.isAssignableFrom(f.get(node).getClass())) {
final Object description =
((PropWithDescription) f.get(node)).getFlipperLayoutInspectorPropDescription();
// Treat the description as immutable for now, because it's a "translation" of the
// actual prop,
// mutating them is not going to change the original prop.
if (description instanceof Map<?, ?>) {
final Map<?, ?> descriptionMap = (Map<?, ?>) description;
for (Map.Entry<?, ?> entry : descriptionMap.entrySet()) {
props.put(entry.getKey().toString(), InspectorValue.immutable(entry.getValue()));
}
} else {
props.put(f.getName(), InspectorValue.immutable(description));
}
} else {
if (isTypeMutable(f.getType())) {
props.put(f.getName(), InspectorValue.mutable(f.get(node)));
} else {
props.put(f.getName(), InspectorValue.immutable(f.get(node)));
}
}
break;
}
hasProps = true;
}
}
if (hasProps) {
data.add(new Named<>("Props", props.build()));
}
return data;
}
@Nullable
static FlipperObject getStateData(Object node, StateContainer stateContainer) throws Exception {
if (stateContainer == null) {
return null;
}
final FlipperObject.Builder state = new FlipperObject.Builder();
boolean hasState = false;
for (Field f : stateContainer.getClass().getDeclaredFields()) {
f.setAccessible(true);
final State annotation = f.getAnnotation(State.class);
if (annotation != null) {
if (DataUtils.isTypeMutable(f.getType())) {
state.put(f.getName(), InspectorValue.mutable(f.get(stateContainer)));
} else {
state.put(f.getName(), InspectorValue.immutable(f.get(stateContainer)));
}
hasState = true;
}
}
return hasState ? state.build() : null;
}
static boolean isTypeMutable(Class<?> type) {
if (type == int.class || type == Integer.class) {
return true;
} else if (type == long.class || type == Long.class) {
return true;
} else if (type == float.class || type == Float.class) {
return true;
} else if (type == double.class || type == Double.class) {
return true;
} else if (type == boolean.class || type == Boolean.class) {
return true;
} else if (type.isAssignableFrom(String.class)) {
return true;
}
return false;
}
static com.facebook.flipper.plugins.inspector.InspectorValue fromDrawable(Drawable d) {
int color = 0;
if (d instanceof ColorDrawable) {
color = ((ColorDrawable) d).getColor();
} else if (d instanceof ComparableColorDrawable) {
color = ((ComparableColorDrawable) d).getColor();
}
return com.facebook.flipper.plugins.inspector.InspectorValue.mutable(Color, color);
}
static com.facebook.flipper.plugins.inspector.InspectorValue fromColor(int color) {
return com.facebook.flipper.plugins.inspector.InspectorValue.mutable(Color, color);
}
}

View File

@@ -1,521 +0,0 @@
/*
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the LICENSE
* file in the root directory of this source tree.
*/
package com.facebook.flipper.plugins.litho;
import static com.facebook.flipper.plugins.inspector.InspectorValue.Type.Enum;
import static com.facebook.flipper.plugins.inspector.InspectorValue.Type.Number;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.view.View;
import androidx.core.util.Pair;
import com.facebook.flipper.core.FlipperDynamic;
import com.facebook.flipper.core.FlipperObject;
import com.facebook.flipper.plugins.inspector.HighlightedOverlay;
import com.facebook.flipper.plugins.inspector.InspectorValue;
import com.facebook.flipper.plugins.inspector.Named;
import com.facebook.flipper.plugins.inspector.NodeDescriptor;
import com.facebook.flipper.plugins.inspector.Touch;
import com.facebook.flipper.plugins.inspector.descriptors.ObjectDescriptor;
import com.facebook.litho.Component;
import com.facebook.litho.DebugComponent;
import com.facebook.litho.DebugLayoutNode;
import com.facebook.litho.LithoView;
import com.facebook.litho.StateContainer;
import com.facebook.yoga.YogaAlign;
import com.facebook.yoga.YogaDirection;
import com.facebook.yoga.YogaEdge;
import com.facebook.yoga.YogaFlexDirection;
import com.facebook.yoga.YogaJustify;
import com.facebook.yoga.YogaPositionType;
import com.facebook.yoga.YogaValue;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.annotation.Nullable;
public class DebugComponentDescriptor extends NodeDescriptor<DebugComponent> {
private Map<String, List<Pair<String[], FlipperDynamic>>> mOverrides = new HashMap<>();
private DebugComponent.Overrider mOverrider =
new DebugComponent.Overrider() {
@Override
public void applyComponentOverrides(String key, Component component) {
final List<Pair<String[], FlipperDynamic>> overrides = mOverrides.get(key);
if (overrides == null) {
return;
}
for (Pair<String[], FlipperDynamic> override : overrides) {
if (override.first[0].equals("Props")) {
applyReflectiveOverride(component, override.first[1], override.second);
}
}
}
@Override
public void applyStateOverrides(String key, StateContainer stateContainer) {
final List<Pair<String[], FlipperDynamic>> overrides = mOverrides.get(key);
if (overrides == null) {
return;
}
for (Pair<String[], FlipperDynamic> override : overrides) {
if (override.first[0].equals("State")) {
applyReflectiveOverride(stateContainer, override.first[1], override.second);
}
}
}
@Override
public void applyLayoutOverrides(String key, DebugLayoutNode node) {
final List<Pair<String[], FlipperDynamic>> overrides = mOverrides.get(key);
if (overrides == null) {
return;
}
for (Pair<String[], FlipperDynamic> override : overrides) {
if (override.first[0].equals("Layout")) {
try {
applyLayoutOverride(
node,
Arrays.copyOfRange(override.first, 1, override.first.length),
override.second);
} catch (Exception ignored) {
}
}
}
}
};
@Override
public void init(DebugComponent node) {
// We rely on the LithoView being invalidated when a component hierarchy changes.
}
@Override
public String getId(DebugComponent node) {
return node.getGlobalKey();
}
@Override
public String getName(DebugComponent node) throws Exception {
NodeDescriptor componentDescriptor = descriptorForClass(node.getComponent().getClass());
if (componentDescriptor.getClass() != ObjectDescriptor.class) {
return componentDescriptor.getName(node.getComponent());
}
return node.getComponent().getSimpleName();
}
@Override
public int getChildCount(DebugComponent node) {
if (node.getMountedView() != null || node.getMountedDrawable() != null) {
return 1;
} else {
return node.getChildComponents().size();
}
}
@Override
public Object getChildAt(DebugComponent node, int index) {
final View mountedView = node.getMountedView();
final Drawable mountedDrawable = node.getMountedDrawable();
if (mountedView != null) {
return mountedView;
} else if (mountedDrawable != null) {
return mountedDrawable;
} else {
return node.getChildComponents().get(index);
}
}
@Override
public List<Named<FlipperObject>> getData(DebugComponent node) throws Exception {
NodeDescriptor componentDescriptor = descriptorForClass(node.getComponent().getClass());
if (componentDescriptor.getClass() != ObjectDescriptor.class) {
return componentDescriptor.getData(node.getComponent());
}
final List<Named<FlipperObject>> data = new ArrayList<>();
final FlipperObject layoutData = getLayoutData(node);
if (layoutData != null) {
data.add(new Named<>("Layout", layoutData));
}
final List<Named<FlipperObject>> propData = getPropData(node);
if (propData != null) {
data.addAll(propData);
}
final FlipperObject stateData = getStateData(node);
if (stateData != null) {
data.add(new Named<>("State", stateData));
}
return data;
}
@Nullable
private static FlipperObject getLayoutData(DebugComponent node) {
final DebugLayoutNode layout = node.getLayoutNode();
if (layout == null) {
return null;
}
final FlipperObject.Builder data = new FlipperObject.Builder();
data.put("background", DataUtils.fromDrawable(layout.getBackground()));
data.put("foreground", DataUtils.fromDrawable(layout.getForeground()));
data.put("direction", InspectorValue.mutable(Enum, layout.getLayoutDirection().toString()));
data.put("flex-direction", InspectorValue.mutable(Enum, layout.getFlexDirection().toString()));
data.put(
"justify-content", InspectorValue.mutable(Enum, layout.getJustifyContent().toString()));
data.put("align-items", InspectorValue.mutable(Enum, layout.getAlignItems().toString()));
data.put("align-self", InspectorValue.mutable(Enum, layout.getAlignSelf().toString()));
data.put("align-content", InspectorValue.mutable(Enum, layout.getAlignContent().toString()));
data.put("position-type", InspectorValue.mutable(Enum, layout.getPositionType().toString()));
data.put("flex-grow", fromFloat(layout.getFlexGrow()));
data.put("flex-shrink", fromFloat(layout.getFlexShrink()));
data.put("flex-basis", fromYogaValue(layout.getFlexBasis()));
data.put("width", fromYogaValue(layout.getWidth()));
data.put("min-width", fromYogaValue(layout.getMinWidth()));
data.put("max-width", fromYogaValue(layout.getMaxWidth()));
data.put("height", fromYogaValue(layout.getHeight()));
data.put("min-height", fromYogaValue(layout.getMinHeight()));
data.put("max-height", fromYogaValue(layout.getMaxHeight()));
data.put("aspect-ratio", fromFloat(layout.getAspectRatio()));
data.put(
"margin",
new FlipperObject.Builder()
.put("left", fromYogaValue(layout.getMargin(YogaEdge.LEFT)))
.put("top", fromYogaValue(layout.getMargin(YogaEdge.TOP)))
.put("right", fromYogaValue(layout.getMargin(YogaEdge.RIGHT)))
.put("bottom", fromYogaValue(layout.getMargin(YogaEdge.BOTTOM)))
.put("start", fromYogaValue(layout.getMargin(YogaEdge.START)))
.put("end", fromYogaValue(layout.getMargin(YogaEdge.END)))
.put("horizontal", fromYogaValue(layout.getMargin(YogaEdge.HORIZONTAL)))
.put("vertical", fromYogaValue(layout.getMargin(YogaEdge.VERTICAL)))
.put("all", fromYogaValue(layout.getMargin(YogaEdge.ALL))));
data.put(
"padding",
new FlipperObject.Builder()
.put("left", fromYogaValue(layout.getPadding(YogaEdge.LEFT)))
.put("top", fromYogaValue(layout.getPadding(YogaEdge.TOP)))
.put("right", fromYogaValue(layout.getPadding(YogaEdge.RIGHT)))
.put("bottom", fromYogaValue(layout.getPadding(YogaEdge.BOTTOM)))
.put("start", fromYogaValue(layout.getPadding(YogaEdge.START)))
.put("end", fromYogaValue(layout.getPadding(YogaEdge.END)))
.put("horizontal", fromYogaValue(layout.getPadding(YogaEdge.HORIZONTAL)))
.put("vertical", fromYogaValue(layout.getPadding(YogaEdge.VERTICAL)))
.put("all", fromYogaValue(layout.getPadding(YogaEdge.ALL))));
data.put(
"border",
new FlipperObject.Builder()
.put("left", fromFloat(layout.getBorderWidth(YogaEdge.LEFT)))
.put("top", fromFloat(layout.getBorderWidth(YogaEdge.TOP)))
.put("right", fromFloat(layout.getBorderWidth(YogaEdge.RIGHT)))
.put("bottom", fromFloat(layout.getBorderWidth(YogaEdge.BOTTOM)))
.put("start", fromFloat(layout.getBorderWidth(YogaEdge.START)))
.put("end", fromFloat(layout.getBorderWidth(YogaEdge.END)))
.put("horizontal", fromFloat(layout.getBorderWidth(YogaEdge.HORIZONTAL)))
.put("vertical", fromFloat(layout.getBorderWidth(YogaEdge.VERTICAL)))
.put("all", fromFloat(layout.getBorderWidth(YogaEdge.ALL))));
data.put(
"position",
new FlipperObject.Builder()
.put("left", fromYogaValue(layout.getPosition(YogaEdge.LEFT)))
.put("top", fromYogaValue(layout.getPosition(YogaEdge.TOP)))
.put("right", fromYogaValue(layout.getPosition(YogaEdge.RIGHT)))
.put("bottom", fromYogaValue(layout.getPosition(YogaEdge.BOTTOM)))
.put("start", fromYogaValue(layout.getPosition(YogaEdge.START)))
.put("end", fromYogaValue(layout.getPosition(YogaEdge.END)))
.put("horizontal", fromYogaValue(layout.getPosition(YogaEdge.HORIZONTAL)))
.put("vertical", fromYogaValue(layout.getPosition(YogaEdge.VERTICAL)))
.put("all", fromYogaValue(layout.getPosition(YogaEdge.ALL))));
return data.build();
}
@Nullable
private static List<Named<FlipperObject>> getPropData(DebugComponent node) throws Exception {
if (node.canResolve()) {
return null;
}
final Component component = node.getComponent();
return DataUtils.getPropData(component);
}
@Nullable
private static FlipperObject getStateData(DebugComponent node) throws Exception {
return DataUtils.getStateData(node, node.getStateContainer());
}
@Override
public void setValue(DebugComponent node, String[] path, FlipperDynamic value) {
List<Pair<String[], FlipperDynamic>> overrides = mOverrides.get(node.getGlobalKey());
if (overrides == null) {
overrides = new ArrayList<>();
mOverrides.put(node.getGlobalKey(), overrides);
}
overrides.add(new Pair<>(path, value));
node.setOverrider(mOverrider);
node.rerender();
}
@Override
public List<Named<String>> getAttributes(DebugComponent node) {
final List<Named<String>> attributes = new ArrayList<>();
final String key = node.getKey();
final String testKey = node.getTestKey();
if (key != null && key.trim().length() > 0) {
attributes.add(new Named<>("key", key));
}
if (testKey != null && testKey.trim().length() > 0) {
attributes.add(new Named<>("testKey", testKey));
}
return attributes;
}
@Override
public FlipperObject getExtraInfo(DebugComponent node) {
FlipperObject.Builder extraInfo = new FlipperObject.Builder();
final NodeDescriptor descriptor = descriptorForClass(View.class);
final View hostView = node.getComponentHost();
final View lithoView = node.getLithoView();
if (hostView != null) {
try {
extraInfo.put("linkedNode", descriptor.getId(hostView));
} catch (Exception ignored) {
// doesn't have linked node descriptor
}
} else if (lithoView != null) {
try {
extraInfo.put("linkedNode", descriptor.getId(lithoView)).put("expandWithParent", true);
} catch (Exception ignored) {
// doesn't add linked node descriptor
}
}
return extraInfo.build();
}
@Override
public void setHighlighted(DebugComponent node, boolean selected, boolean isAlignmentMode) {
final LithoView lithoView = node.getLithoView();
if (lithoView == null) {
return;
}
if (!selected) {
HighlightedOverlay.removeHighlight(lithoView);
return;
}
final DebugLayoutNode layout = node.getLayoutNode();
final boolean hasNode = layout != null;
final Rect margin;
if (!node.isRoot()) {
margin =
new Rect(
hasNode ? (int) layout.getResultMargin(YogaEdge.START) : 0,
hasNode ? (int) layout.getResultMargin(YogaEdge.TOP) : 0,
hasNode ? (int) layout.getResultMargin(YogaEdge.END) : 0,
hasNode ? (int) layout.getResultMargin(YogaEdge.BOTTOM) : 0);
} else {
// Margin not applied if you're at the root
margin = new Rect();
}
final Rect padding =
new Rect(
hasNode ? (int) layout.getResultPadding(YogaEdge.START) : 0,
hasNode ? (int) layout.getResultPadding(YogaEdge.TOP) : 0,
hasNode ? (int) layout.getResultPadding(YogaEdge.END) : 0,
hasNode ? (int) layout.getResultPadding(YogaEdge.BOTTOM) : 0);
final Rect contentBounds = node.getBoundsInLithoView();
HighlightedOverlay.setHighlighted(lithoView, margin, padding, contentBounds, isAlignmentMode);
}
@Override
public void hitTest(DebugComponent node, Touch touch) {
for (int i = getChildCount(node) - 1; i >= 0; i--) {
final Object child = getChildAt(node, i);
if (child instanceof DebugComponent) {
final DebugComponent componentChild = (DebugComponent) child;
final Rect bounds = componentChild.getBounds();
if (touch.containedIn(bounds.left, bounds.top, bounds.right, bounds.bottom)) {
touch.continueWithOffset(i, bounds.left, bounds.top);
return;
}
} else if (child instanceof View || child instanceof Drawable) {
// Components can only mount one view or drawable and its bounds are the same as the
// hosting component.
touch.continueWithOffset(i, 0, 0);
return;
}
}
touch.finish();
}
@Override
public String getDecoration(DebugComponent node) throws Exception {
if (node.getComponent() != null) {
NodeDescriptor componentDescriptor = descriptorForClass(node.getComponent().getClass());
if (componentDescriptor.getClass() != ObjectDescriptor.class) {
return componentDescriptor.getDecoration(node.getComponent());
}
}
return "litho";
}
@Override
public boolean matches(String query, DebugComponent node) throws Exception {
NodeDescriptor descriptor = descriptorForClass(Object.class);
return descriptor.matches(query, node) || getId(node).equals(query);
}
private static void applyLayoutOverride(
DebugLayoutNode node, String[] path, FlipperDynamic value) {
switch (path[0]) {
case "background":
node.setBackgroundColor(value.asInt());
break;
case "foreground":
node.setForegroundColor(value.asInt());
break;
case "direction":
node.setLayoutDirection(YogaDirection.valueOf(value.asString().toUpperCase()));
break;
case "flex-direction":
node.setFlexDirection(YogaFlexDirection.valueOf(value.asString().toUpperCase()));
break;
case "justify-content":
node.setJustifyContent(YogaJustify.valueOf(value.asString().toUpperCase()));
break;
case "align-items":
node.setAlignItems(YogaAlign.valueOf(value.asString().toUpperCase()));
break;
case "align-self":
node.setAlignSelf(YogaAlign.valueOf(value.asString().toUpperCase()));
break;
case "align-content":
node.setAlignContent(YogaAlign.valueOf(value.asString().toUpperCase()));
break;
case "position-type":
node.setPositionType(YogaPositionType.valueOf(value.asString().toUpperCase()));
break;
case "flex-grow":
node.setFlexGrow(value.asFloat());
break;
case "flex-shrink":
node.setFlexShrink(value.asFloat());
break;
case "flex-basis":
node.setFlexBasis(YogaValue.parse(value.asString()));
break;
case "width":
node.setWidth(YogaValue.parse(value.asString()));
break;
case "min-width":
node.setMinWidth(YogaValue.parse(value.asString()));
break;
case "max-width":
node.setMaxWidth(YogaValue.parse(value.asString()));
break;
case "height":
node.setHeight(YogaValue.parse(value.asString()));
break;
case "min-height":
node.setMinHeight(YogaValue.parse(value.asString()));
break;
case "max-height":
node.setMaxHeight(YogaValue.parse(value.asString()));
break;
case "aspect-ratio":
node.setAspectRatio(value.asFloat());
break;
case "margin":
node.setMargin(edgeFromString(path[1]), YogaValue.parse(value.asString()));
break;
case "padding":
node.setPadding(edgeFromString(path[1]), YogaValue.parse(value.asString()));
break;
case "border":
node.setBorderWidth(edgeFromString(path[1]), value.asFloat());
break;
case "position":
node.setPosition(edgeFromString(path[1]), YogaValue.parse(value.asString()));
break;
}
}
private static YogaEdge edgeFromString(String s) {
return YogaEdge.valueOf(s.toUpperCase());
}
private static void applyReflectiveOverride(Object o, String key, FlipperDynamic dynamic) {
try {
final Field field = o.getClass().getDeclaredField(key);
field.setAccessible(true);
final Class type = field.getType();
Object value = null;
if (type == int.class || type == Integer.class) {
value = dynamic.asInt();
} else if (type == long.class || type == Long.class) {
value = dynamic.asLong();
} else if (type == float.class || type == Float.class) {
value = dynamic.asFloat();
} else if (type == double.class || type == Double.class) {
value = dynamic.asDouble();
} else if (type == boolean.class || type == Boolean.class) {
value = dynamic.asBoolean();
} else if (type.isAssignableFrom(String.class)) {
value = dynamic.asString();
}
if (value != null) {
field.set(o, value);
}
} catch (Exception ignored) {
}
}
private static InspectorValue fromFloat(float f) {
if (Float.isNaN(f)) {
return InspectorValue.mutable(Enum, "undefined");
}
return InspectorValue.mutable(Number, f);
}
static InspectorValue fromYogaValue(YogaValue v) {
// TODO add support for Type.Dimension or similar
return InspectorValue.mutable(Enum, v.toString());
}
}

View File

@@ -1,219 +0,0 @@
/*
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the LICENSE
* file in the root directory of this source tree.
*/
package com.facebook.flipper.plugins.litho;
import android.graphics.Rect;
import android.view.View;
import android.view.ViewGroup;
import androidx.core.view.MarginLayoutParamsCompat;
import androidx.core.view.ViewCompat;
import com.facebook.flipper.core.ErrorReportingRunnable;
import com.facebook.flipper.core.FlipperDynamic;
import com.facebook.flipper.core.FlipperObject;
import com.facebook.flipper.plugins.inspector.HighlightedOverlay;
import com.facebook.flipper.plugins.inspector.Named;
import com.facebook.flipper.plugins.inspector.NodeDescriptor;
import com.facebook.flipper.plugins.inspector.Touch;
import com.facebook.litho.sections.Section;
import com.facebook.litho.sections.debug.DebugSection;
import java.util.ArrayList;
import java.util.List;
import javax.annotation.Nullable;
public class DebugSectionDescriptor extends NodeDescriptor<DebugSection> {
@Override
public void invalidate(final DebugSection debugSection) {
super.invalidate(debugSection);
new ErrorReportingRunnable(mConnection) {
@Override
protected void runOrThrow() throws Exception {
for (int i = 0; i < getChildCount(debugSection); i++) {
Object child = getChildAt(debugSection, i);
if (child instanceof DebugSection) {
invalidate((DebugSection) child);
}
}
}
}.run();
}
@Override
public void init(DebugSection node) throws Exception {}
@Override
public String getId(DebugSection node) throws Exception {
return node.getGlobalKey();
}
@Override
public String getName(DebugSection node) throws Exception {
return node.getName();
}
@Override
public int getChildCount(DebugSection node) throws Exception {
return node.getSectionChildren().size();
}
@Override
public Object getChildAt(DebugSection node, int index) throws Exception {
return node.getSectionChildren().get(index);
}
@Override
public List<Named<FlipperObject>> getData(DebugSection node) throws Exception {
// TODO T39526148 add changeset info
final List<Named<FlipperObject>> data = new ArrayList<>();
final List<Named<FlipperObject>> propData = getPropData(node);
if (propData != null) {
data.addAll(propData);
}
final FlipperObject stateData = getStateData(node);
if (stateData != null) {
data.add(new Named<>("State", stateData));
}
return data;
}
private static @Nullable List<Named<FlipperObject>> getPropData(DebugSection node)
throws Exception {
final Section section = node.getSection();
return DataUtils.getPropData(section);
}
private static @Nullable FlipperObject getStateData(DebugSection node) throws Exception {
return DataUtils.getStateData(node, node.getStateContainer());
}
@Override
public void setValue(DebugSection node, String[] path, FlipperDynamic value) throws Exception {
// TODO T39526148
}
@Override
public List<Named<String>> getAttributes(DebugSection node) throws Exception {
// TODO T39526148
final List<Named<String>> attrs = new ArrayList<>();
return attrs;
}
@Override
public void setHighlighted(DebugSection node, boolean selected, boolean isAlignmentMode)
throws Exception {
final int childCount = getChildCount(node);
if (node.isDiffSectionSpec()) {
for (int i = 0; i < childCount; i++) {
final View view = (View) getChildAt(node, i);
highlightChildView(view, selected, isAlignmentMode);
}
} else {
for (int i = 0; i < childCount; i++) {
final Object child = getChildAt(node, i);
final NodeDescriptor descriptor = descriptorForClass(child.getClass());
descriptor.setHighlighted(child, selected, isAlignmentMode);
}
}
}
// This is similar to the implementation in ViewDescriptor but doesn't
// target the parent view.
private void highlightChildView(View node, boolean selected, boolean isAlignmentMode) {
if (!selected) {
HighlightedOverlay.removeHighlight(node);
return;
}
final Rect padding =
new Rect(
ViewCompat.getPaddingStart(node),
node.getPaddingTop(),
ViewCompat.getPaddingEnd(node),
node.getPaddingBottom());
final Rect margin;
final ViewGroup.LayoutParams params = node.getLayoutParams();
if (params instanceof ViewGroup.MarginLayoutParams) {
final ViewGroup.MarginLayoutParams marginParams = (ViewGroup.MarginLayoutParams) params;
margin =
new Rect(
MarginLayoutParamsCompat.getMarginStart(marginParams),
marginParams.topMargin,
MarginLayoutParamsCompat.getMarginEnd(marginParams),
marginParams.bottomMargin);
} else {
margin = new Rect();
}
final int left = node.getLeft();
final int top = node.getTop();
final Rect contentBounds = new Rect(left, top, left + node.getWidth(), top + node.getHeight());
contentBounds.offset(-left, -top);
HighlightedOverlay.setHighlighted(node, margin, padding, contentBounds, false);
}
@Override
public void hitTest(DebugSection node, Touch touch) throws Exception {
final int childCount = getChildCount(node);
// For a DiffSectionSpec, check if child view to see if the touch is in its bounds.
// For a GroupSectionSpec, check the bounds of the entire section.
if (node.isDiffSectionSpec()) {
for (int i = 0; i < childCount; i++) {
View child = (View) getChildAt(node, i);
int left = child.getLeft() + (int) child.getTranslationX();
int top = (child.getTop() + (int) child.getTranslationY());
int right = (child.getRight() + (int) child.getTranslationX());
int bottom = (child.getBottom() + (int) child.getTranslationY());
final boolean hit = touch.containedIn(left, top, right, bottom);
if (hit) {
touch.continueWithOffset(i, left, top);
return;
}
}
touch.finish();
} else {
for (int i = 0; i < childCount; i++) {
DebugSection child = (DebugSection) getChildAt(node, i);
Rect bounds = child.getBounds();
final boolean hit = touch.containedIn(bounds.left, bounds.top, bounds.right, bounds.bottom);
if (hit) {
touch.continueWithOffset(i, 0, 0);
return;
}
}
touch.finish();
}
}
@Override
public String getDecoration(DebugSection node) throws Exception {
// TODO T39526148
return null;
}
@Override
public boolean matches(String query, DebugSection node) throws Exception {
final NodeDescriptor descriptor = descriptorForClass(Object.class);
return descriptor.matches(query, node);
}
@Override
public int getAXChildCount(DebugSection node) {
return 0;
}
}

View File

@@ -1,88 +0,0 @@
/*
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the LICENSE
* file in the root directory of this source tree.
*/
package com.facebook.flipper.plugins.litho;
import android.view.View;
import android.view.ViewGroup;
import com.facebook.flipper.core.FlipperConnection;
import com.facebook.flipper.core.FlipperObject;
import com.facebook.flipper.core.FlipperReceiver;
import com.facebook.flipper.core.FlipperResponder;
import com.facebook.flipper.plugins.common.MainThreadFlipperReceiver;
import com.facebook.flipper.plugins.inspector.ApplicationWrapper;
import com.facebook.flipper.plugins.inspector.InspectorFlipperPlugin;
import com.facebook.flipper.plugins.inspector.ObjectTracker;
import com.facebook.litho.LithoView;
import java.util.Stack;
public final class GenerateLithoAccessibilityRenderExtensionCommand
implements InspectorFlipperPlugin.ExtensionCommand {
@Override
public String command() {
return "forceLithoAXRender";
}
@Override
public FlipperReceiver receiver(final ObjectTracker tracker, final FlipperConnection connection) {
return new MainThreadFlipperReceiver() {
@Override
public void onReceiveOnMainThread(
final FlipperObject params, final FlipperResponder responder) throws Exception {
final String applicationId = params.getString("applicationId");
// check that the application is valid
if (applicationId == null) {
return;
}
final Object obj = tracker.get(applicationId);
if (obj != null && !(obj instanceof ApplicationWrapper)) {
return;
}
final ApplicationWrapper applicationWrapper = ((ApplicationWrapper) obj);
final boolean forceLithoAXRender = params.getBoolean("forceLithoAXRender");
final boolean prevForceLithoAXRender = Boolean.getBoolean("is_accessibility_enabled");
// nothing has changed, so return
if (forceLithoAXRender == prevForceLithoAXRender) {
return;
}
// change property and rerender
System.setProperty("is_accessibility_enabled", forceLithoAXRender + "");
forceRerenderAllLithoViews(forceLithoAXRender, applicationWrapper);
}
};
}
private void forceRerenderAllLithoViews(
boolean forceLithoAXRender, ApplicationWrapper applicationWrapper) {
// iterate through tree and rerender all litho views
Stack<ViewGroup> lithoViewSearchStack = new Stack<>();
for (View root : applicationWrapper.getViewRoots()) {
if (root instanceof ViewGroup) {
lithoViewSearchStack.push((ViewGroup) root);
}
}
while (!lithoViewSearchStack.isEmpty()) {
ViewGroup v = lithoViewSearchStack.pop();
if (v instanceof LithoView) {
((LithoView) v).rerenderForAccessibility(forceLithoAXRender);
} else {
for (int i = 0; i < v.getChildCount(); i++) {
View child = v.getChildAt(i);
if (child instanceof ViewGroup) {
lithoViewSearchStack.push((ViewGroup) child);
}
}
}
}
}
}

View File

@@ -1,27 +0,0 @@
/*
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the LICENSE
* file in the root directory of this source tree.
*/
package com.facebook.flipper.plugins.litho;
import com.facebook.flipper.plugins.inspector.DescriptorMapping;
import com.facebook.litho.DebugComponent;
import com.facebook.litho.LithoView;
import com.facebook.litho.sections.debug.DebugSection;
import com.facebook.litho.widget.LithoRecylerView;
public final class LithoFlipperDescriptors {
public static void add(DescriptorMapping descriptorMapping) {
descriptorMapping.register(LithoView.class, new LithoViewDescriptor());
descriptorMapping.register(DebugComponent.class, new DebugComponentDescriptor());
}
public static void addWithSections(DescriptorMapping descriptorMapping) {
add(descriptorMapping);
descriptorMapping.register(LithoRecylerView.class, new LithoRecyclerViewDescriptor());
descriptorMapping.register(DebugSection.class, new DebugSectionDescriptor());
}
}

View File

@@ -1,154 +0,0 @@
/*
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the LICENSE
* file in the root directory of this source tree.
*/
package com.facebook.flipper.plugins.litho;
import android.view.View;
import android.view.ViewGroup;
import com.facebook.flipper.core.FlipperDynamic;
import com.facebook.flipper.core.FlipperObject;
import com.facebook.flipper.plugins.inspector.Named;
import com.facebook.flipper.plugins.inspector.NodeDescriptor;
import com.facebook.flipper.plugins.inspector.Touch;
import com.facebook.litho.sections.debug.DebugSection;
import com.facebook.litho.widget.LithoRecylerView;
import java.util.ArrayList;
import java.util.List;
public class LithoRecyclerViewDescriptor extends NodeDescriptor<LithoRecylerView> {
@Override
public void invalidate(final LithoRecylerView node) {
super.invalidate(node);
new com.facebook.flipper.core.ErrorReportingRunnable(mConnection) {
@Override
protected void runOrThrow() throws Exception {
final Object child;
child = getChildAt(node, 0);
if (child instanceof DebugSection) {
DebugSection childSection = (DebugSection) child;
final NodeDescriptor descriptor = descriptorForClass(DebugSection.class);
descriptor.invalidate(childSection);
}
}
}.run();
}
@Override
public void init(final LithoRecylerView node) throws Exception {
final NodeDescriptor descriptor = descriptorForClass(ViewGroup.class);
descriptor.init(node);
}
@Override
public String getId(LithoRecylerView node) throws Exception {
final NodeDescriptor descriptor = descriptorForClass(ViewGroup.class);
return descriptor.getId(node);
}
@Override
public String getName(LithoRecylerView node) throws Exception {
final NodeDescriptor descriptor = descriptorForClass(ViewGroup.class);
return descriptor.getName(node);
}
@Override
public int getChildCount(LithoRecylerView node) throws Exception {
// TODO T39526148 this might not always be true when using the RecyclerBinder manually.
return 1;
}
@Override
public Object getChildAt(LithoRecylerView node, int index) throws Exception {
// TODO T39526148 account for the case above
final NodeDescriptor descriptor = descriptorForClass(ViewGroup.class);
int count = descriptor.getChildCount(node);
final List<View> childrenViews = new ArrayList<>();
for (int i = 0; i < count; i++) {
childrenViews.add((View) descriptor.getChildAt(node, i));
}
return DebugSection.getRootInstance(childrenViews);
}
@Override
public List<Named<FlipperObject>> getData(LithoRecylerView node) throws Exception {
final List<Named<FlipperObject>> props = new ArrayList<>();
final NodeDescriptor descriptor = descriptorForClass(ViewGroup.class);
props.addAll(descriptor.getData(node));
return props;
}
@Override
public void setValue(LithoRecylerView node, String[] path, FlipperDynamic value)
throws Exception {
final NodeDescriptor descriptor = descriptorForClass(ViewGroup.class);
descriptor.setValue(node, path, value);
}
@Override
public List<Named<String>> getAttributes(LithoRecylerView node) throws Exception {
final NodeDescriptor descriptor = descriptorForClass(ViewGroup.class);
return descriptor.getAttributes(node);
}
@Override
public FlipperObject getExtraInfo(LithoRecylerView node) {
final NodeDescriptor descriptor = descriptorForClass(ViewGroup.class);
return descriptor.getExtraInfo(node);
}
@Override
public void hitTest(LithoRecylerView node, Touch touch) throws Exception {
touch.continueWithOffset(0, 0, 0);
}
@Override
public void axHitTest(LithoRecylerView node, Touch touch) throws Exception {
final NodeDescriptor descriptor = descriptorForClass(ViewGroup.class);
descriptor.axHitTest(node, touch);
}
@Override
public String getAXName(LithoRecylerView node) throws Exception {
final NodeDescriptor descriptor = descriptorForClass(ViewGroup.class);
return descriptor.getAXName(node);
}
@Override
public List<Named<String>> getAXAttributes(LithoRecylerView node) throws Exception {
final NodeDescriptor descriptor = descriptorForClass(ViewGroup.class);
return descriptor.getAXAttributes(node);
}
@Override
public void setHighlighted(LithoRecylerView node, boolean selected, boolean isAlignmentMode)
throws Exception {
final NodeDescriptor descriptor = descriptorForClass(ViewGroup.class);
descriptor.setHighlighted(node, selected, isAlignmentMode);
}
@Override
public String getDecoration(LithoRecylerView node) throws Exception {
final NodeDescriptor descriptor = descriptorForClass(ViewGroup.class);
return descriptor.getDecoration(node);
}
@Override
public String getAXDecoration(LithoRecylerView node) throws Exception {
final NodeDescriptor descriptor = descriptorForClass(ViewGroup.class);
return descriptor.getAXDecoration(node);
}
@Override
public boolean matches(String query, LithoRecylerView node) throws Exception {
NodeDescriptor descriptor = descriptorForClass(Object.class);
return descriptor.matches(query, node);
}
}

View File

@@ -1,166 +0,0 @@
/*
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the LICENSE
* file in the root directory of this source tree.
*/
package com.facebook.flipper.plugins.litho;
import android.graphics.Rect;
import android.view.ViewGroup;
import com.facebook.flipper.core.FlipperDynamic;
import com.facebook.flipper.core.FlipperObject;
import com.facebook.flipper.plugins.inspector.Named;
import com.facebook.flipper.plugins.inspector.NodeDescriptor;
import com.facebook.flipper.plugins.inspector.Touch;
import com.facebook.litho.DebugComponent;
import com.facebook.litho.LithoView;
import java.util.ArrayList;
import java.util.List;
import javax.annotation.Nullable;
public class LithoViewDescriptor extends NodeDescriptor<LithoView> {
@Override
public void init(LithoView node) throws Exception {
node.setOnDirtyMountListener(
new LithoView.OnDirtyMountListener() {
@Override
public void onDirtyMount(LithoView view) {
invalidate(view);
invalidateAX(view);
}
});
}
@Override
public String getId(LithoView node) throws Exception {
final NodeDescriptor descriptor = descriptorForClass(ViewGroup.class);
return descriptor.getId(node);
}
@Override
public String getName(LithoView node) throws Exception {
final NodeDescriptor descriptor = descriptorForClass(ViewGroup.class);
return descriptor.getName(node);
}
@Override
public String getAXName(LithoView node) throws Exception {
final NodeDescriptor descriptor = descriptorForClass(ViewGroup.class);
return descriptor.getAXName(node);
}
@Override
public int getChildCount(LithoView node) {
return DebugComponent.getRootInstance(node) == null ? 0 : 1;
}
@Override
public int getAXChildCount(LithoView node) {
return node.getChildCount();
}
@Override
public Object getChildAt(LithoView node, int index) {
return DebugComponent.getRootInstance(node);
}
@Override
public @Nullable Object getAXChildAt(LithoView node, int index) {
return node.getChildAt(index);
}
@Override
public List<Named<FlipperObject>> getData(LithoView node) throws Exception {
final List<Named<FlipperObject>> props = new ArrayList<>();
final NodeDescriptor descriptor = descriptorForClass(ViewGroup.class);
final Rect mountedBounds = node.getPreviousMountBounds();
props.add(
0,
new Named<>(
"LithoView",
new FlipperObject.Builder()
.put(
"mountbounds",
new FlipperObject.Builder()
.put("left", mountedBounds.left)
.put("top", mountedBounds.top)
.put("right", mountedBounds.right)
.put("bottom", mountedBounds.bottom))
.build()));
props.addAll(descriptor.getData(node));
return props;
}
@Override
public List<Named<FlipperObject>> getAXData(LithoView node) throws Exception {
final List<Named<FlipperObject>> props = new ArrayList<>();
final NodeDescriptor descriptor = descriptorForClass(ViewGroup.class);
props.addAll(descriptor.getAXData(node));
return props;
}
@Override
public void setValue(LithoView node, String[] path, FlipperDynamic value) throws Exception {
final NodeDescriptor descriptor = descriptorForClass(ViewGroup.class);
descriptor.setValue(node, path, value);
}
@Override
public List<Named<String>> getAttributes(LithoView node) throws Exception {
final NodeDescriptor descriptor = descriptorForClass(ViewGroup.class);
return descriptor.getAttributes(node);
}
@Override
public List<Named<String>> getAXAttributes(LithoView node) throws Exception {
final NodeDescriptor descriptor = descriptorForClass(ViewGroup.class);
return descriptor.getAXAttributes(node);
}
@Override
public FlipperObject getExtraInfo(LithoView node) {
final NodeDescriptor descriptor = descriptorForClass(ViewGroup.class);
return descriptor.getExtraInfo(node);
}
@Override
public void setHighlighted(LithoView node, boolean selected, boolean isAlignmentMode)
throws Exception {
final NodeDescriptor descriptor = descriptorForClass(ViewGroup.class);
descriptor.setHighlighted(node, selected, isAlignmentMode);
}
@Override
public void hitTest(LithoView node, Touch touch) {
touch.continueWithOffset(0, 0, 0);
}
@Override
public void axHitTest(LithoView node, Touch touch) throws Exception {
final NodeDescriptor descriptor = descriptorForClass(ViewGroup.class);
descriptor.axHitTest(node, touch);
}
@Override
public String getDecoration(LithoView node) throws Exception {
final NodeDescriptor descriptor = descriptorForClass(ViewGroup.class);
return descriptor.getDecoration(node);
}
@Override
public String getAXDecoration(LithoView node) throws Exception {
final NodeDescriptor descriptor = descriptorForClass(ViewGroup.class);
return descriptor.getAXDecoration(node);
}
@Override
public boolean matches(String query, LithoView node) throws Exception {
NodeDescriptor descriptor = descriptorForClass(Object.class);
return descriptor.matches(query, node);
}
}

View File

@@ -1,12 +0,0 @@
/*
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the LICENSE
* file in the root directory of this source tree.
*/
package com.facebook.flipper.plugins.litho;
public interface PropWithDescription {
Object getFlipperLayoutInspectorPropDescription();
}

View File

@@ -1,15 +0,0 @@
/*
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the LICENSE
* file in the root directory of this source tree.
*/
package com.facebook.flipper.plugins.litho;
import java.util.AbstractMap;
import javax.annotation.CheckForNull;
public interface PropWithInspectorSection {
@CheckForNull
AbstractMap.SimpleEntry<String, String> getFlipperLayoutInspectorSection();
}

View File

@@ -1,522 +0,0 @@
/*
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the LICENSE
* file in the root directory of this source tree.
*/
package com.facebook.flipper.plugins.sections;
import android.annotation.SuppressLint;
import com.facebook.flipper.core.FlipperArray;
import com.facebook.flipper.core.FlipperObject;
import com.facebook.litho.sections.Change;
import com.facebook.litho.sections.ChangesInfo;
import com.facebook.litho.sections.ChangesetDebugConfiguration;
import com.facebook.litho.sections.ChangesetDebugConfiguration.ChangesetDebugInfo;
import com.facebook.litho.sections.ChangesetDebugConfiguration.ChangesetDebugListener;
import com.facebook.litho.sections.Section;
import com.facebook.litho.sections.SectionsLogEventUtils;
import com.facebook.litho.sections.SectionsLogEventUtils.ApplyNewChangeSet;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.atomic.AtomicInteger;
public class ChangesetDebug implements ChangesetDebugListener {
private static ChangesetListener sSectionsFlipperPlugin;
private static ChangesetDebug sInstance;
private static AtomicInteger sChangesetIdGenerator = new AtomicInteger();
public interface ChangesetListener {
void onChangesetApplied(
String eventName,
String eventSource,
String updateStateMethodName,
boolean isAsync,
String surfaceId,
String id,
FlipperArray tree,
FlipperObject changesetData);
}
public static void setListener(ChangesetListener listener) {
if (sInstance == null) {
sInstance = new ChangesetDebug(listener);
ChangesetDebugConfiguration.setListener(sInstance);
}
}
private ChangesetDebug(ChangesetListener listener) {
sSectionsFlipperPlugin = listener;
}
public void onChangesetApplied(
Section rootSection,
Section oldSection,
ChangesInfo changesInfo,
String surfaceId,
@ApplyNewChangeSet int source,
String attribution) {}
public void onChangesetApplied(
Section rootSection,
ChangesInfo changesInfo,
String surfaceId,
ChangesetDebugInfo changesetDebugInfo) {
final FlipperArray.Builder tree = new FlipperArray.Builder();
final FlipperObject.Builder changesetData = new FlipperObject.Builder();
final Section oldRootSection = changesetDebugInfo.getOldSection();
final int eventSource = changesetDebugInfo.getSource();
final String attribution = changesetDebugInfo.getAttribution();
final String stateUpdateAttribution = changesetDebugInfo.getUpdateStateAttribution();
final String eventSourceName =
SectionsLogEventUtils.applyNewChangeSetSourceToString(eventSource);
extractSidePanelChangesetData(changesInfo, changesetData, rootSection.getGlobalKey());
createSectionTree(rootSection, tree, oldRootSection, stateUpdateAttribution);
List<DataModelChangeInfo> prevData = getDataFromPreviousTree(oldRootSection);
applyChangesInfoOnPreviousData(prevData, changesInfo, tree);
String eventSourceSection;
if (stateUpdateAttribution == null) {
eventSourceSection = rootSection == null ? "" : rootSection.getSimpleName();
} else {
final Section updatedStateSection =
findSectionInPreviousTree(oldRootSection, stateUpdateAttribution);
eventSourceSection = updatedStateSection == null ? "" : updatedStateSection.getSimpleName();
}
final String stateUpdateMethodName = stateUpdateAttribution == null ? null : attribution;
sSectionsFlipperPlugin.onChangesetApplied(
eventSourceName,
eventSourceSection,
stateUpdateMethodName,
isEventAsync(eventSource),
surfaceId,
sChangesetIdGenerator.incrementAndGet() + "-" + surfaceId,
tree.build(),
changesetData.build());
}
private static boolean isEventAsync(@ApplyNewChangeSet int source) {
switch (source) {
case ApplyNewChangeSet.SET_ROOT_ASYNC:
case ApplyNewChangeSet.UPDATE_STATE_ASYNC:
return true;
default:
return false;
}
}
/** Extract the changesets for this section tree. */
private static void extractSidePanelChangesetData(
ChangesInfo changesInfo, FlipperObject.Builder changesetData, String globalKey) {
final FlipperObject.Builder sectionChangesetInfo = new FlipperObject.Builder();
final List<Change> changeList = changesInfo.getAllChanges();
final FlipperObject.Builder changesets = new FlipperObject.Builder();
for (int i = 0; i < changeList.size(); i++) {
final Change change = changeList.get(i);
final FlipperObject.Builder changeData = new FlipperObject.Builder();
changeData.put("type", Change.changeTypeToString(change.getType()));
changeData.put("index", change.getIndex());
if (change.getToIndex() >= 0) {
changeData.put("toIndex", change.getToIndex());
}
changeData.put("count", change.getCount());
changeData.put("render_infos", ChangesetDebugConfiguration.getRenderInfoNames(change));
changeData.put("prev_data", getPrevDataFromChange(change));
changeData.put("next_data", getNextDataFromChange(change));
changesets.put(i + "", changeData.build());
}
sectionChangesetInfo.put("changesets", changesets.build());
changesetData.put(globalKey, sectionChangesetInfo.build());
}
private static List getPrevDataFromChange(Change change) {
if (change.getPrevData() != null) {
return getDataNamesFromChange(change.getPrevData());
}
List data = new ArrayList<>();
if (change.getRenderInfo() != null) {
data.add(change.getRenderInfo().getDebugInfo("SCS_DATA_INFO_PREV"));
} else if (change.getRenderInfos() != null) {
for (int i = 0; i < change.getRenderInfos().size(); i++) {
data.add(change.getRenderInfos().get(i).getDebugInfo("SCS_DATA_INFO_PREV"));
}
}
return data;
}
private static List getNextDataFromChange(Change change) {
if (change.getNextData() != null) {
return getDataNamesFromChange(change.getNextData());
}
List data = new ArrayList<>();
if (change.getRenderInfo() != null) {
data.add(change.getRenderInfo().getDebugInfo("SCS_DATA_INFO_NEXT"));
} else if (change.getRenderInfos() != null) {
for (int i = 0; i < change.getRenderInfos().size(); i++) {
data.add(change.getRenderInfos().get(i).getDebugInfo("SCS_DATA_INFO_NEXT"));
}
}
return data;
}
private static List<String> getDataNamesFromChange(List<?> data) {
final List<String> names = new ArrayList<>();
if (data == null) {
return names;
}
for (int i = 0; i < data.size(); i++) {
names.add(data.get(i).toString());
}
return names;
}
/** Finds the section with the same global key in the previous tree, if it existed. */
private static Section findSectionInPreviousTree(Section previousRoot, String globalKey) {
if (previousRoot == null) {
return null;
}
if (previousRoot.getGlobalKey().equals(globalKey)) {
return previousRoot;
}
if (previousRoot.getChildren() == null) {
return null;
}
int count = previousRoot.getChildren().size();
for (int i = 0; i < count; i++) {
Section child = previousRoot.getChildren().get(i);
final Section previousSection = findSectionInPreviousTree(child, globalKey);
if (previousSection != null) {
return previousSection;
}
}
return null;
}
/** Represents a data model node in a DiffSection. */
private static class DataModelChangeInfo {
static final int UNCHANGED = -100;
Object model;
int operation = UNCHANGED;
String sectionKey;
}
/**
* Skips all nodes which have been removed and finds the item on which the Change operation with
* the given index is applied on.
*/
private static int getPositionWithChangesApplied(
List<DataModelChangeInfo> dataInfos, int operationIndex) {
int count = -1;
int i = 0;
int size = dataInfos.size();
for (i = 0; i < size; i++) {
if (dataInfos.get(i).operation != Change.DELETE
&& dataInfos.get(i).operation != Change.DELETE_RANGE) {
count++;
}
if (count == operationIndex) {
break;
}
}
return i;
}
/** For a given DiffSectionSpec section, returns the list of data on which it performs diffing. */
private static List getDataFromPreviousSection(Section previousSection) {
List data = new ArrayList();
if (previousSection == null) {
return data;
}
Class clasz = previousSection.getClass();
final String diffSectionType = clasz.getSimpleName();
try {
if (diffSectionType.equals("DataDiffSection")) {
Field field = clasz.getDeclaredField("data");
field.setAccessible(true);
data = (List) field.get(previousSection);
} else if (diffSectionType.equals("SingleComponentSection")) {
Field field = clasz.getDeclaredField("component");
field.setAccessible(true);
data.add(field.get(previousSection));
}
} catch (@SuppressLint("NewApi") NoSuchFieldException | IllegalAccessException e) {
e.printStackTrace();
}
return data;
}
private static void addRemovedSectionNodes(
Section previousRoot,
Section currentRoot,
final FlipperArray.Builder tree,
String stateUpdateAttribution) {
final Map<String, Section> prevSections = serializeChildren(previousRoot);
final Map<String, Section> currSections = serializeChildren(currentRoot);
final boolean checkStateUpdateTrigger = stateUpdateAttribution != null;
for (String prevSectionKey : prevSections.keySet()) {
if (!currSections.containsKey(prevSectionKey)) {
final FlipperObject.Builder nodeBuilder = new FlipperObject.Builder();
final Section section = prevSections.get(prevSectionKey);
nodeBuilder.put("identifier", prevSectionKey);
nodeBuilder.put("name", section.getSimpleName());
nodeBuilder.put(
"parent", section.getParent() == null ? null : section.getParent().getGlobalKey());
nodeBuilder.put("removed", true);
nodeBuilder.put("isSection", true);
if (checkStateUpdateTrigger && stateUpdateAttribution.equals(prevSectionKey)) {
nodeBuilder.put("didTriggerStateUpdate", true);
}
tree.put(nodeBuilder.build());
}
}
}
private static Map<String, Section> serializeChildren(Section root) {
final Map<String, Section> children = new HashMap<>();
serializeChildrenRecursive(root, children);
return children;
}
private static void serializeChildrenRecursive(Section root, Map<String, Section> children) {
if (root == null) {
return;
}
children.put(root.getGlobalKey(), root);
if (root.getChildren() == null) {
return;
}
for (Section child : root.getChildren()) {
serializeChildrenRecursive(child, children);
}
}
private static List<DataModelChangeInfo> getDataFromPreviousTree(Section previousRoot) {
final List<DataModelChangeInfo> dataModelChangeInfos = new ArrayList<>();
getDataFromPreviousTreeRecursive(previousRoot, dataModelChangeInfos);
return dataModelChangeInfos;
}
private static void getDataFromPreviousTreeRecursive(
Section previousRoot, List<DataModelChangeInfo> data) {
if (previousRoot == null) {
return;
}
if (previousRoot.isDiffSectionSpec()) {
List models = getDataFromPreviousSection(previousRoot);
for (int i = 0; i < models.size(); i++) {
DataModelChangeInfo dataModelChangeInfo = new DataModelChangeInfo();
dataModelChangeInfo.model = models.get(i);
dataModelChangeInfo.sectionKey = previousRoot.getGlobalKey();
data.add(dataModelChangeInfo);
}
} else if (previousRoot.getChildren() != null) {
for (Section child : previousRoot.getChildren()) {
getDataFromPreviousTreeRecursive(child, data);
}
}
}
private static void applyChangesInfoOnPreviousData(
List<DataModelChangeInfo> dataInfos, ChangesInfo changesInfo, FlipperArray.Builder tree) {
final List<Change> changes = changesInfo.getAllChanges();
for (int i = 0; i < changes.size(); i++) {
final Change change = changes.get(i);
int index = change.getIndex();
switch (change.getType()) {
case Change.INSERT:
{
DataModelChangeInfo dataInfo = new DataModelChangeInfo();
if (change.getNextData() != null) {
dataInfo.model = change.getNextData().get(0);
} else {
dataInfo.model = getNextDataFromChange(change);
}
dataInfo.sectionKey =
(String) change.getRenderInfo().getDebugInfo("section_global_key");
dataInfo.operation = Change.INSERT;
int addToPosition = getPositionWithChangesApplied(dataInfos, index);
dataInfos.add(addToPosition, dataInfo);
break;
}
case Change.INSERT_RANGE:
{
int addToPosition = getPositionWithChangesApplied(dataInfos, index);
for (int item = 0; item < change.getCount(); item++) {
DataModelChangeInfo dataInfo = new DataModelChangeInfo();
if (change.getNextData() != null) {
dataInfo.model = change.getNextData().get(item);
} else {
dataInfo.model = getNextDataFromChange(change);
}
dataInfo.operation = Change.INSERT_RANGE;
dataInfo.sectionKey =
(String) change.getRenderInfos().get(item).getDebugInfo("section_global_key");
dataInfos.add(addToPosition + item, dataInfo);
}
break;
}
case Change.DELETE:
{
int addToPosition = getPositionWithChangesApplied(dataInfos, index);
DataModelChangeInfo dataInfo = dataInfos.get(addToPosition);
dataInfo.operation = Change.DELETE;
break;
}
case Change.DELETE_RANGE:
{
int addToPosition = getPositionWithChangesApplied(dataInfos, index);
for (int del = addToPosition; del < addToPosition + change.getCount(); del++) {
dataInfos.get(del).operation = Change.DELETE_RANGE;
}
break;
}
case Change.UPDATE:
{
int getPosition = getPositionWithChangesApplied(dataInfos, index);
DataModelChangeInfo dataInfo = dataInfos.get(getPosition);
dataInfo.operation = Change.UPDATE;
dataInfo.model = getNextDataFromChange(change);
break;
}
case Change.UPDATE_RANGE:
{
for (int updateIndex = index; updateIndex < index + change.getCount(); updateIndex++) {
int getPosition = getPositionWithChangesApplied(dataInfos, updateIndex);
DataModelChangeInfo dataInfo = dataInfos.get(getPosition);
dataInfo.operation = Change.UPDATE_RANGE;
dataInfo.model = getNextDataFromChange(change);
}
break;
}
default:
break;
}
}
for (int i = 0; i < dataInfos.size(); i++) {
DataModelChangeInfo dataInfo = dataInfos.get(i);
final FlipperObject.Builder dataObject = new FlipperObject.Builder();
String name = dataInfo.model == null ? "N/A" : dataInfo.model.toString();
int operation = dataInfo.operation;
dataObject.put("identifier", name);
dataObject.put("name", name);
dataObject.put("parent", dataInfo.sectionKey);
dataObject.put("unchanged", operation == DataModelChangeInfo.UNCHANGED);
dataObject.put("inserted", operation == Change.INSERT || operation == Change.INSERT_RANGE);
dataObject.put("removed", operation == Change.DELETE || operation == Change.DELETE_RANGE);
dataObject.put("updated", operation == Change.UPDATE || operation == Change.UPDATE_RANGE);
dataObject.put("isDataModel", true);
tree.put(dataObject.build());
}
}
private static void createSectionTree(
Section rootSection,
FlipperArray.Builder tree,
Section oldRootSection,
String stateUpdateAttribution) {
createSectionTreeRecursive(rootSection, "", tree, oldRootSection, 0, stateUpdateAttribution);
addRemovedSectionNodes(oldRootSection, rootSection, tree, stateUpdateAttribution);
}
private static void createSectionTreeRecursive(
Section rootSection,
String parentKey,
FlipperArray.Builder tree,
Section oldRootSection,
int startIndex,
String stateUpdateAttribution) {
if (rootSection == null) {
return;
}
int endIndex = startIndex + ChangesetDebugConfiguration.getSectionCount(rootSection) - 1;
final String name = "[" + startIndex + ", " + endIndex + "] " + rootSection.getSimpleName();
final String globalKey = rootSection.getGlobalKey();
final FlipperObject.Builder nodeBuilder = new FlipperObject.Builder();
final Section oldSection = findSectionInPreviousTree(oldRootSection, globalKey);
final boolean isDirty = ChangesetDebugConfiguration.isSectionDirty(oldSection, rootSection);
final boolean triggeredStateUpdate =
stateUpdateAttribution != null && stateUpdateAttribution.equals(globalKey);
nodeBuilder.put("identifier", globalKey);
nodeBuilder.put("name", name);
nodeBuilder.put("parent", parentKey);
nodeBuilder.put("isDirty", isDirty);
nodeBuilder.put("isReused", !isDirty);
nodeBuilder.put("didTriggerStateUpdate", triggeredStateUpdate);
nodeBuilder.put("isSection", true);
tree.put(nodeBuilder.build());
if (rootSection.getChildren() == null) {
return;
}
for (int i = 0; i < rootSection.getChildren().size(); i++) {
if (i > 0) {
startIndex +=
ChangesetDebugConfiguration.getSectionCount(rootSection.getChildren().get(i - 1));
}
createSectionTreeRecursive(
rootSection.getChildren().get(i),
globalKey,
tree,
oldRootSection,
startIndex,
stateUpdateAttribution);
}
}
}

View File

@@ -1,124 +0,0 @@
/*
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the LICENSE
* file in the root directory of this source tree.
*/
package com.facebook.flipper.plugins.sections;
import com.facebook.flipper.core.FlipperArray;
import com.facebook.flipper.core.FlipperConnection;
import com.facebook.flipper.core.FlipperObject;
import com.facebook.flipper.core.FlipperPlugin;
import com.facebook.flipper.plugins.sections.ChangesetDebug.ChangesetListener;
public class SectionsFlipperPlugin implements FlipperPlugin, ChangesetListener {
private final boolean mEnableDebugging;
private FlipperConnection mConnection;
public SectionsFlipperPlugin(boolean enableDebugging) {
mEnableDebugging = enableDebugging;
}
@Override
public String getId() {
return "Sections";
}
@Override
public void onConnect(FlipperConnection connection) throws Exception {
if (!mEnableDebugging) {
return;
}
mConnection = connection;
ChangesetDebug.setListener(this);
}
@Override
public void onDisconnect() throws Exception {}
@Override
public boolean runInBackground() {
return false;
}
/**
* @param eventName Name of event
* @param isAsync Whether the event was sync or async
* @param surfaceId SectionTree tag
* @param id Changeset generation unique id
* @param tree Representation of the SectionTree hierarchy
* @param changesetData Changeset information
*/
@Override
public void onChangesetApplied(
String eventName,
String eventSource,
String updateStateMethodName,
boolean isAsync,
String surfaceId,
String id,
FlipperArray tree,
FlipperObject changesetData) {
if (mConnection == null) {
return;
}
final String reason = eventName + " " + eventSource;
final FlipperObject.Builder eventPayloadBuilder = new FlipperObject.Builder();
eventPayloadBuilder.put("Event", eventName);
eventPayloadBuilder.put("Async", isAsync);
eventPayloadBuilder.put("Section", eventSource);
if (updateStateMethodName != null) {
eventPayloadBuilder.put("Update state method", updateStateMethodName);
}
mConnection.send(
"addEvent",
new FlipperObject.Builder()
.put("id", id)
.put("update_mode", isAsync ? 0 : 1)
.put("reason", reason)
.put("surface_key", surfaceId)
.put("tree_generation_timestamp", 10000) // TODO
.put("stack_trace", new FlipperArray.Builder().build())
.put("payload", eventPayloadBuilder.build())
.build());
mConnection.send(
"updateTreeGenerationHierarchyGeneration",
new FlipperObject.Builder()
.put("id", id)
.put("hierarchy_generation_timestamp", 10000) // TODO
.put("hierarchy_generation_duration", 0) // TODO
.put("tree", tree)
.put("reason", eventName)
.build());
// Not sure both CHANGESET_GENERATED and CHANGESET_APPLIED need to sent here, need
// to investigate a bit more.
mConnection.send(
"updateTreeGenerationChangesetGeneration",
new FlipperObject.Builder()
.put("type", "CHANGESET_GENERATED")
.put("identifier", id)
.put("tree_generation_id", "" + id)
.put("timestamp", 10000) // TODO
.put("duration", 0) // TODO
.put("changeset", changesetData)
.build());
mConnection.send(
"updateTreeGenerationChangesetApplication",
new FlipperObject.Builder()
.put("type", "CHANGESET_APPLIED")
.put("identifier", id)
.put("tree_generation_id", id)
.put("timestamp", 10000) // TODO
.put("duration", 0) // TODO
.put("changeset", changesetData)
.build());
}
}