Skip to content

Commit

Permalink
Merge 954f587 into sapling-pr-archive-passy
Browse files Browse the repository at this point in the history
  • Loading branch information
passy committed Jul 10, 2024
2 parents 892ff99 + 954f587 commit 0739364
Show file tree
Hide file tree
Showing 146 changed files with 2,614 additions and 1,106 deletions.
2 changes: 1 addition & 1 deletion .vscode/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
}
],
"[typescriptreact]": {
"editor.defaultFormatter": "dbaeumer.vscode-eslint"
"editor.defaultFormatter": "esbenp.prettier-vscode"
},
"[typescript]": {
"editor.defaultFormatter": "dbaeumer.vscode-eslint"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import com.facebook.flipper.plugins.jetpackcompose.model.ComposeInnerViewNode
import com.facebook.flipper.plugins.jetpackcompose.model.ComposeNode
import com.facebook.flipper.plugins.uidebugger.core.UIDContext
import com.facebook.flipper.plugins.uidebugger.descriptors.DescriptorRegister
import com.facebook.flipper.plugins.uidebugger.model.ActionIcon
import com.facebook.soloader.SoLoader

const val JetpackComposeTag = "Compose"
Expand All @@ -38,13 +39,32 @@ object UIDebuggerComposeSupport {
}

fun enable(context: UIDContext) {
addCustomActions(context)
addDescriptors(context.descriptorRegister)
}

private fun addDescriptors(register: DescriptorRegister) {
register.register(AbstractComposeView::class.java, AbstractComposeViewDescriptor)
register.register(ComposeNode::class.java, ComposeNodeDescriptor)
register.register(ComposeInnerViewNode::class.java, ComposeInnerViewDescriptor)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
register.register(AbstractComposeView::class.java, AbstractComposeViewDescriptor)
register.register(ComposeNode::class.java, ComposeNodeDescriptor)
register.register(ComposeInnerViewNode::class.java, ComposeInnerViewDescriptor)
}
}

private fun addCustomActions(context: UIDContext) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
context.addCustomActionGroup("Compose options", ActionIcon.Local("icons/compose-logo.png")) {
booleanAction(
"Hide System Nodes", AbstractComposeViewDescriptor.layoutInspector.hideSystemNodes) {
newValue ->
AbstractComposeViewDescriptor.layoutInspector.hideSystemNodes = newValue
newValue
}
unitAction("Reset Recomposition Counts", ActionIcon.Antd("CloseSquareOutlined")) {
AbstractComposeViewDescriptor.resetRecompositionCounts()
}
}
}
}

private fun enableDebugInspectorInfo() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,13 +23,19 @@ import facebook.internal.androidx.compose.ui.inspection.inspector.LayoutInspecto
import java.io.IOException

object AbstractComposeViewDescriptor : ChainedDescriptor<AbstractComposeView>() {
private val recompositionHandler by lazy {
RecompositionHandler(DefaultArtTooling("Flipper")).apply {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
attachJvmtiAgent()
startTrackingRecompositions(this)

@RequiresApi(Build.VERSION_CODES.Q) internal val layoutInspector = LayoutInspectorTree()

private val recompositionHandler =
RecompositionHandler(DefaultArtTooling("Flipper")).apply {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
attachJvmtiAgent()
startTrackingRecompositions(this)
}
}
}

fun resetRecompositionCounts() {
recompositionHandler.changeCollectionMode(startCollecting = true, keepCounts = false)
}

override fun onGetName(node: AbstractComposeView): String = node.javaClass.simpleName
Expand All @@ -51,15 +57,21 @@ object AbstractComposeViewDescriptor : ChainedDescriptor<AbstractComposeView>()
}

override fun onGetChildren(node: AbstractComposeView): List<Any> {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) {
return listOf(
WarningMessage(
"Flipper Compose Plugin works only on devices with Android Q (API 29) and above.",
getBounds(node)))
}

val children = mutableListOf<Any>()
val count = node.childCount - 1
for (i in 0..count) {
val child: View = node.getChildAt(i)
children.add(child)

if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
val layoutInspector = LayoutInspectorTree()
layoutInspector.hideSystemNodes = true
layoutInspector.resetAccumulativeState()
val composeNodes =
try {
transform(child, layoutInspector.convert(child), layoutInspector)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,12 @@

package com.facebook.flipper.plugins.jetpackcompose.descriptors

import android.os.Build
import androidx.annotation.RequiresApi
import com.facebook.flipper.plugins.jetpackcompose.JetpackComposeTag
import com.facebook.flipper.plugins.jetpackcompose.descriptors.ComposeNodeDescriptor.toInspectableValue
import com.facebook.flipper.plugins.jetpackcompose.model.ComposeNode
import com.facebook.flipper.plugins.uidebugger.descriptors.AttributesInfo
import com.facebook.flipper.plugins.uidebugger.descriptors.BaseTags
import com.facebook.flipper.plugins.uidebugger.descriptors.Id
import com.facebook.flipper.plugins.uidebugger.descriptors.MetadataRegister
Expand All @@ -21,11 +24,12 @@ import com.facebook.flipper.plugins.uidebugger.model.Inspectable
import com.facebook.flipper.plugins.uidebugger.model.InspectableObject
import com.facebook.flipper.plugins.uidebugger.model.InspectableValue
import com.facebook.flipper.plugins.uidebugger.model.MetadataId
import com.facebook.flipper.plugins.uidebugger.util.Immediate
import com.facebook.flipper.plugins.uidebugger.util.Deferred
import com.facebook.flipper.plugins.uidebugger.util.MaybeDeferred
import facebook.internal.androidx.compose.ui.inspection.inspector.NodeParameter
import facebook.internal.androidx.compose.ui.inspection.inspector.ParameterType

@RequiresApi(Build.VERSION_CODES.Q)
object ComposeNodeDescriptor : NodeDescriptor<ComposeNode> {

private const val NAMESPACE = "ComposeNode"
Expand Down Expand Up @@ -75,66 +79,76 @@ object ComposeNodeDescriptor : NodeDescriptor<ComposeNode> {
return node.children
}

override fun getAttributes(node: ComposeNode): MaybeDeferred<Map<MetadataId, InspectableObject>> {

val builder = mutableMapOf<MetadataId, InspectableObject>()
val props = mutableMapOf<Int, Inspectable>()

props[IdAttributeId] = InspectableValue.Number(node.inspectorNode.id)
props[ViewIdAttributeId] = InspectableValue.Number(node.inspectorNode.viewId)
props[KeyAttributeId] = InspectableValue.Number(node.inspectorNode.key)
props[NameAttributeId] = InspectableValue.Text(node.inspectorNode.name)
props[FilenameAttributeId] = InspectableValue.Text(node.inspectorNode.fileName)
props[PackageHashAttributeId] = InspectableValue.Number(node.inspectorNode.packageHash)
props[LineNumberAttributeId] = InspectableValue.Number(node.inspectorNode.lineNumber)
props[OffsetAttributeId] = InspectableValue.Number(node.inspectorNode.offset)
props[LengthAttributeId] = InspectableValue.Number(node.inspectorNode.length)

props[BoxAttributeId] =
InspectableValue.Bounds(
Bounds(
node.inspectorNode.left,
node.inspectorNode.top,
node.inspectorNode.width,
node.inspectorNode.height))

node.inspectorNode.bounds?.let { bounds ->
val quadBounds = mutableMapOf<Int, Inspectable>()
quadBounds[Bounds0AttributeId] = InspectableValue.Coordinate(Coordinate(bounds.x0, bounds.y0))
quadBounds[Bounds1AttributeId] = InspectableValue.Coordinate(Coordinate(bounds.x1, bounds.y1))
quadBounds[Bounds2AttributeId] = InspectableValue.Coordinate(Coordinate(bounds.x2, bounds.y2))
quadBounds[Bounds3AttributeId] = InspectableValue.Coordinate(Coordinate(bounds.x3, bounds.y3))
props[BoundsAttributeId] = InspectableObject(quadBounds.toMap())
}
override fun getAttributes(
node: ComposeNode,
shouldGetAdditionalData: Boolean
): MaybeDeferred<AttributesInfo> {
return Deferred {
val builder = mutableMapOf<MetadataId, InspectableObject>()
val props = mutableMapOf<Int, Inspectable>()

props[IdAttributeId] = InspectableValue.Number(node.inspectorNode.id)
props[ViewIdAttributeId] = InspectableValue.Number(node.inspectorNode.viewId)
props[KeyAttributeId] = InspectableValue.Number(node.inspectorNode.key)
props[NameAttributeId] = InspectableValue.Text(node.inspectorNode.name)
props[FilenameAttributeId] = InspectableValue.Text(node.inspectorNode.fileName)
props[PackageHashAttributeId] = InspectableValue.Number(node.inspectorNode.packageHash)
props[LineNumberAttributeId] = InspectableValue.Number(node.inspectorNode.lineNumber)
props[OffsetAttributeId] = InspectableValue.Number(node.inspectorNode.offset)
props[LengthAttributeId] = InspectableValue.Number(node.inspectorNode.length)

props[BoxAttributeId] =
InspectableValue.Bounds(
Bounds(
node.inspectorNode.left,
node.inspectorNode.top,
node.inspectorNode.width,
node.inspectorNode.height))

node.inspectorNode.bounds?.let { bounds ->
val quadBounds = mutableMapOf<Int, Inspectable>()
quadBounds[Bounds0AttributeId] =
InspectableValue.Coordinate(Coordinate(bounds.x0, bounds.y0))
quadBounds[Bounds1AttributeId] =
InspectableValue.Coordinate(Coordinate(bounds.x1, bounds.y1))
quadBounds[Bounds2AttributeId] =
InspectableValue.Coordinate(Coordinate(bounds.x2, bounds.y2))
quadBounds[Bounds3AttributeId] =
InspectableValue.Coordinate(Coordinate(bounds.x3, bounds.y3))
props[BoundsAttributeId] = InspectableObject(quadBounds.toMap())
}

val params = mutableMapOf<Int, Inspectable>()
node.parameters.forEach { parameter ->
fillNodeParameters(parameter, params, node.inspectorNode.name)
}
builder[ParametersAttributeId] = InspectableObject(params.toMap())
val params = mutableMapOf<Int, Inspectable>()
node.getParameters(shouldGetAdditionalData).forEach { parameter ->
fillNodeParameters(parameter, params, node.inspectorNode.name)
}
builder[ParametersAttributeId] = InspectableObject(params.toMap())

val mergedSemantics = mutableMapOf<Int, Inspectable>()
node.mergedSemantics.forEach { parameter ->
fillNodeParameters(parameter, mergedSemantics, node.inspectorNode.name)
}
builder[MergedSemanticsAttributeId] = InspectableObject(mergedSemantics.toMap())
val mergedSemantics = mutableMapOf<Int, Inspectable>()
node.getMergedSemantics(shouldGetAdditionalData).forEach { parameter ->
fillNodeParameters(parameter, mergedSemantics, node.inspectorNode.name)
}
builder[MergedSemanticsAttributeId] = InspectableObject(mergedSemantics.toMap())

val unmergedSemantics = mutableMapOf<Int, Inspectable>()
node.unmergedSemantics.forEach { parameter ->
fillNodeParameters(parameter, unmergedSemantics, node.inspectorNode.name)
}
builder[UnmergedSemanticsAttributeId] = InspectableObject(unmergedSemantics.toMap())
val unmergedSemantics = mutableMapOf<Int, Inspectable>()
node.getUnmergedSemantics(shouldGetAdditionalData).forEach { parameter ->
fillNodeParameters(parameter, unmergedSemantics, node.inspectorNode.name)
}
builder[UnmergedSemanticsAttributeId] = InspectableObject(unmergedSemantics.toMap())

builder[SectionId] = InspectableObject(props.toMap())
builder[SectionId] = InspectableObject(props.toMap())

return Immediate(builder)
AttributesInfo(builder, node.hasAdditionalData)
}
}

override fun getInlineAttributes(node: ComposeNode): Map<String, String> {
val attributes = mutableMapOf<String, String>()
if (!node.inspectorNode.inlined) {
node.recompositionCount?.let { attributes["🔄"] = it.toString() }
node.skipCount?.let { attributes["⏭️"] = it.toString() }
node.recompositionCounts?.let { (counts, skips) ->
attributes["🔄"] = counts.toString()
attributes["⏭️"] = skips.toString()
}
} else {
attributes["inline"] = "true"
}
Expand Down Expand Up @@ -179,6 +193,7 @@ object ComposeNodeDescriptor : NodeDescriptor<ComposeNode> {
private fun NodeParameter.toInspectableValue(): InspectableValue {
return when (type) {
ParameterType.Iterable,
ParameterType.ComplexObject,
ParameterType.String -> InspectableValue.Text(value.toString())
ParameterType.Boolean -> InspectableValue.Boolean(value as Boolean)
ParameterType.Int32 -> InspectableValue.Number(value as Int)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,13 @@ import facebook.internal.androidx.compose.ui.inspection.inspector.InspectorNode
import facebook.internal.androidx.compose.ui.inspection.inspector.LayoutInspectorTree
import facebook.internal.androidx.compose.ui.inspection.inspector.NodeParameter
import facebook.internal.androidx.compose.ui.inspection.inspector.ParameterKind
import facebook.internal.androidx.compose.ui.inspection.inspector.ParameterType

// Same values as in AndroidX (ComposeLayoutInspector.kt)
private const val MAX_RECURSIONS = 2
private const val MAX_ITERABLE_SIZE = 5

@RequiresApi(Build.VERSION_CODES.Q)
class ComposeNode(
private val parentComposeView: View,
private val layoutInspectorTree: LayoutInspectorTree,
Expand All @@ -38,32 +40,59 @@ class ComposeNode(
inspectorNode.width,
inspectorNode.height)

val recompositionCount: Int?

val skipCount: Int?
val recompositionCounts: Pair<Int, Int>? by lazy {
recompositionHandler.getCounts(inspectorNode.key, inspectorNode.anchorId)?.let {
Pair(it.count, it.skips)
}
}

val children: List<Any> = collectChildren()

val parameters: List<NodeParameter>
val hasAdditionalData: Boolean
get() {
return hasAdditionalParameterData ||
hasAdditionalMergedSemanticsData ||
hasAdditionalUnmergedSemanticsData
}

val mergedSemantics: List<NodeParameter>
private var hasAdditionalParameterData: Boolean = false
private var hasAdditionalMergedSemanticsData: Boolean = false
private var hasAdditionalUnmergedSemanticsData: Boolean = false

val unmergedSemantics: List<NodeParameter>
fun getParameters(useReflection: Boolean): List<NodeParameter> {
return getNodeParameters(ParameterKind.Normal, useReflection)
}

fun getMergedSemantics(useReflection: Boolean): List<NodeParameter> {
return getNodeParameters(ParameterKind.MergedSemantics, useReflection)
}

init {
val count = recompositionHandler.getCounts(inspectorNode.key, inspectorNode.anchorId)
recompositionCount = count?.count
skipCount = count?.skips
parameters = getNodeParameters(ParameterKind.Normal)
mergedSemantics = getNodeParameters(ParameterKind.MergedSemantics)
unmergedSemantics = getNodeParameters(ParameterKind.UnmergedSemantics)
fun getUnmergedSemantics(useReflection: Boolean): List<NodeParameter> {
return getNodeParameters(ParameterKind.UnmergedSemantics, useReflection)
}

private fun getNodeParameters(kind: ParameterKind): List<NodeParameter> {
private fun getNodeParameters(kind: ParameterKind, useReflection: Boolean): List<NodeParameter> {
layoutInspectorTree.resetAccumulativeState()
return try {
layoutInspectorTree.convertParameters(
inspectorNode.id, inspectorNode, kind, MAX_RECURSIONS, MAX_ITERABLE_SIZE)
val params =
layoutInspectorTree.convertParameters(
inspectorNode.id,
inspectorNode,
kind,
MAX_RECURSIONS,
MAX_ITERABLE_SIZE,
useReflection)
if (!useReflection) {
// We only need to check for additional data if we are not using reflection since
// params parsed with useReflection == true wont have complex objects
val hasAdditionalData = hasAdditionalData(params)
when (kind) {
ParameterKind.Normal -> hasAdditionalParameterData = hasAdditionalData
ParameterKind.MergedSemantics -> hasAdditionalMergedSemanticsData = hasAdditionalData
ParameterKind.UnmergedSemantics -> hasAdditionalUnmergedSemanticsData = hasAdditionalData
}
}
params
} catch (t: Throwable) {
Log.e(TAG, "Failed to get parameters.", t)
emptyList()
Expand Down Expand Up @@ -109,6 +138,19 @@ class ComposeNode(
return null
}

private fun hasAdditionalData(params: List<NodeParameter>): Boolean {
val queue = ArrayDeque<NodeParameter>()
queue.addAll(params)
while (!queue.isEmpty()) {
val param = queue.removeFirst()
if (param.type == ParameterType.ComplexObject) {
return true
}
queue.addAll(param.elements)
}
return false
}

companion object {
private const val TAG = "ComposeNode"
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
load("@fbsource//tools/build_defs/android:fb_android_library.bzl", "fb_android_library")

fb_android_library(
name = "ui-inspection",
abi_generation_mode = "source_only",
create_suffixed_alias = True,
dataclass_generate = {
"mode": "EXPLICIT",
},
k2 = True,
oncall = "flipper",
deps = [
"//third-party/java/androidx/compose/ui/ui-android:ui-android-aar",
],
exported_deps = [
"//third-party/java/androidx/inspection/inspection:inspection",
"//xplat/sonar/android/plugins/jetpack-compose/src/main/java/facebook/internal/androidx/compose/ui/inspection/inspector:inspector",
],
)
Loading

0 comments on commit 0739364

Please sign in to comment.