diff --git a/android/plugins/litho/src/main/java/com/facebook/flipper/plugins/uidebugger/litho/descriptors/DebugComponentDescriptor.kt b/android/plugins/litho/src/main/java/com/facebook/flipper/plugins/uidebugger/litho/descriptors/DebugComponentDescriptor.kt index 72ec56981..dc4bff5e5 100644 --- a/android/plugins/litho/src/main/java/com/facebook/flipper/plugins/uidebugger/litho/descriptors/DebugComponentDescriptor.kt +++ b/android/plugins/litho/src/main/java/com/facebook/flipper/plugins/uidebugger/litho/descriptors/DebugComponentDescriptor.kt @@ -8,16 +8,15 @@ package com.facebook.flipper.plugins.uidebugger.litho.descriptors import android.graphics.Bitmap -import com.facebook.flipper.plugins.uidebugger.descriptors.BaseTags -import com.facebook.flipper.plugins.uidebugger.descriptors.DescriptorRegister -import com.facebook.flipper.plugins.uidebugger.descriptors.NodeDescriptor -import com.facebook.flipper.plugins.uidebugger.descriptors.OffsetChild +import com.facebook.flipper.plugins.uidebugger.descriptors.* import com.facebook.flipper.plugins.uidebugger.litho.LithoTag import com.facebook.flipper.plugins.uidebugger.model.Bounds import com.facebook.flipper.plugins.uidebugger.model.InspectableObject +import com.facebook.flipper.plugins.uidebugger.model.MetadataId import com.facebook.litho.DebugComponent class DebugComponentDescriptor(val register: DescriptorRegister) : NodeDescriptor { + private val NAMESPACE = "DebugComponent" override fun getName(node: DebugComponent): String { return node.component.simpleName @@ -54,7 +53,15 @@ class DebugComponentDescriptor(val register: DescriptorRegister) : NodeDescripto override fun getActiveChild(node: DebugComponent): Any? = null - override fun getData(node: DebugComponent) = mapOf() + private val LayoutId = + MetadataRegister.register(MetadataRegister.TYPE_LAYOUT, NAMESPACE, "Litho Layout") + private val UserPropsId = + MetadataRegister.register(MetadataRegister.TYPE_ATTRIBUTE, NAMESPACE, "User Props") + override fun getData(node: DebugComponent): Map { + val attributeSections = mutableMapOf() + + return attributeSections + } override fun getBounds(node: DebugComponent): Bounds = Bounds.fromRect(node.boundsInParentDebugComponent) diff --git a/android/plugins/litho/src/main/java/com/facebook/flipper/plugins/uidebugger/litho/descriptors/LithoViewDescriptor.kt b/android/plugins/litho/src/main/java/com/facebook/flipper/plugins/uidebugger/litho/descriptors/LithoViewDescriptor.kt index d77d4b6a4..264e62c6f 100644 --- a/android/plugins/litho/src/main/java/com/facebook/flipper/plugins/uidebugger/litho/descriptors/LithoViewDescriptor.kt +++ b/android/plugins/litho/src/main/java/com/facebook/flipper/plugins/uidebugger/litho/descriptors/LithoViewDescriptor.kt @@ -8,15 +8,19 @@ package com.facebook.flipper.plugins.uidebugger.litho.descriptors import com.facebook.flipper.plugins.uidebugger.descriptors.ChainedDescriptor -import com.facebook.flipper.plugins.uidebugger.descriptors.SectionName -import com.facebook.flipper.plugins.uidebugger.model.Inspectable +import com.facebook.flipper.plugins.uidebugger.descriptors.MetadataRegister 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.litho.DebugComponent import com.facebook.litho.LithoView object LithoViewDescriptor : ChainedDescriptor() { + private const val NAMESPACE = "LithoView" + private val SectionId = + MetadataRegister.register(MetadataRegister.TYPE_ATTRIBUTE, NAMESPACE, NAMESPACE) + override fun onGetName(node: LithoView): String = node.javaClass.simpleName override fun onGetChildren(node: LithoView): List { @@ -28,14 +32,18 @@ object LithoViewDescriptor : ChainedDescriptor() { return result } + private val IsIncrementalMountEnabledAttributeId = + MetadataRegister.register( + MetadataRegister.TYPE_ATTRIBUTE, NAMESPACE, "isIncrementalMountEnabled") + override fun onGetData( node: LithoView, - attributeSections: MutableMap + attributeSections: MutableMap ) { - attributeSections["Litho"] = + attributeSections[SectionId] = InspectableObject( - mapOf( - "isIncrementalMountEnabled" to - InspectableValue.Boolean(node.isIncrementalMountEnabled, false))) + mapOf( + IsIncrementalMountEnabledAttributeId to + InspectableValue.Boolean(node.isIncrementalMountEnabled))) } } diff --git a/android/plugins/litho/src/main/java/com/facebook/flipper/plugins/uidebugger/litho/descriptors/MountedDawable.kt b/android/plugins/litho/src/main/java/com/facebook/flipper/plugins/uidebugger/litho/descriptors/MountedDawable.kt index 37b81a412..a1b10b7ff 100644 --- a/android/plugins/litho/src/main/java/com/facebook/flipper/plugins/uidebugger/litho/descriptors/MountedDawable.kt +++ b/android/plugins/litho/src/main/java/com/facebook/flipper/plugins/uidebugger/litho/descriptors/MountedDawable.kt @@ -11,6 +11,7 @@ import android.graphics.Bitmap import com.facebook.flipper.plugins.uidebugger.descriptors.* import com.facebook.flipper.plugins.uidebugger.model.Bounds import com.facebook.flipper.plugins.uidebugger.model.InspectableObject +import com.facebook.flipper.plugins.uidebugger.model.MetadataId /** a drawable or view that is mounted, along with the correct descriptor */ class MountedObject(val obj: Any, val descriptor: NodeDescriptor) @@ -37,7 +38,7 @@ object MountedObjectDescriptor : NodeDescriptor { override fun getActiveChild(node: MountedObject): Any? = node.descriptor.getActiveChild(node.obj) - override fun getData(node: MountedObject): Map = + override fun getData(node: MountedObject): Map = node.descriptor.getData(node.obj) override fun getTags(node: MountedObject): Set = node.descriptor.getTags(node.obj) diff --git a/android/plugins/litho/src/main/java/com/facebook/flipper/plugins/uidebugger/litho/descriptors/TextDrawableDescriptor.kt b/android/plugins/litho/src/main/java/com/facebook/flipper/plugins/uidebugger/litho/descriptors/TextDrawableDescriptor.kt index 883de6f17..91262cc50 100644 --- a/android/plugins/litho/src/main/java/com/facebook/flipper/plugins/uidebugger/litho/descriptors/TextDrawableDescriptor.kt +++ b/android/plugins/litho/src/main/java/com/facebook/flipper/plugins/uidebugger/litho/descriptors/TextDrawableDescriptor.kt @@ -8,23 +8,30 @@ package com.facebook.flipper.plugins.uidebugger.litho.descriptors import com.facebook.flipper.plugins.uidebugger.descriptors.ChainedDescriptor -import com.facebook.flipper.plugins.uidebugger.descriptors.SectionName +import com.facebook.flipper.plugins.uidebugger.descriptors.MetadataRegister 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.litho.widget.TextDrawable object TextDrawableDescriptor : ChainedDescriptor() { + + private const val NAMESPACE = "TextDrawable" + private val SectionId = + MetadataRegister.register(MetadataRegister.TYPE_ATTRIBUTE, NAMESPACE, NAMESPACE) + override fun onGetName(node: TextDrawable): String = node.javaClass.simpleName + private val TextAttributeId = + MetadataRegister.register(MetadataRegister.TYPE_ATTRIBUTE, NAMESPACE, "text") override fun onGetData( node: TextDrawable, - attributeSections: MutableMap + attributeSections: MutableMap ) { - val props = - mapOf("text" to InspectableValue.Text(node.text.toString(), false)) + mapOf(TextAttributeId to InspectableValue.Text(node.text.toString())) - attributeSections["TextDrawable"] = InspectableObject(props) + attributeSections[SectionId] = InspectableObject(props) } } diff --git a/android/src/main/java/com/facebook/flipper/plugins/uidebugger/UIDebuggerFlipperPlugin.kt b/android/src/main/java/com/facebook/flipper/plugins/uidebugger/UIDebuggerFlipperPlugin.kt index 2af61ed50..fc76db82d 100644 --- a/android/src/main/java/com/facebook/flipper/plugins/uidebugger/UIDebuggerFlipperPlugin.kt +++ b/android/src/main/java/com/facebook/flipper/plugins/uidebugger/UIDebuggerFlipperPlugin.kt @@ -13,8 +13,10 @@ import com.facebook.flipper.core.FlipperConnection import com.facebook.flipper.core.FlipperPlugin import com.facebook.flipper.plugins.uidebugger.core.* import com.facebook.flipper.plugins.uidebugger.descriptors.DescriptorRegister +import com.facebook.flipper.plugins.uidebugger.descriptors.MetadataRegister import com.facebook.flipper.plugins.uidebugger.descriptors.nodeId import com.facebook.flipper.plugins.uidebugger.model.InitEvent +import com.facebook.flipper.plugins.uidebugger.model.MetadataUpdateEvent import com.facebook.flipper.plugins.uidebugger.observers.TreeObserverFactory import kotlinx.serialization.json.Json @@ -51,6 +53,12 @@ class UIDebuggerFlipperPlugin( InitEvent.name, Json.encodeToString(InitEvent.serializer(), InitEvent(context.applicationRef.nodeId()))) + connection.send( + MetadataUpdateEvent.name, + Json.encodeToString( + MetadataUpdateEvent.serializer(), + MetadataUpdateEvent(MetadataRegister.staticMetadata()))) + context.treeObserverManager.start() } @@ -59,6 +67,8 @@ class UIDebuggerFlipperPlugin( this.context.connectionRef.connection = null Log.i(LogTag, "Disconnected") + MetadataRegister.clear() + context.treeObserverManager.stop() context.bitmapPool.recycleAll() } diff --git a/android/src/main/java/com/facebook/flipper/plugins/uidebugger/common/EnumMapping.kt b/android/src/main/java/com/facebook/flipper/plugins/uidebugger/common/EnumMapping.kt index da081f686..bb191b175 100644 --- a/android/src/main/java/com/facebook/flipper/plugins/uidebugger/common/EnumMapping.kt +++ b/android/src/main/java/com/facebook/flipper/plugins/uidebugger/common/EnumMapping.kt @@ -33,8 +33,8 @@ open class EnumMapping(private val mapping: Map) { "Could not convert string $key to enum value, possible values ${mapping.entries} ") } - fun toInspectable(value: T, mutable: Boolean): InspectableValue.Enum { - return InspectableValue.Enum(Enumeration(mapping.keys, getStringRepresentation(value)), mutable) + fun toInspectable(value: T): InspectableValue.Enum { + return InspectableValue.Enum(Enumeration(mapping.keys, getStringRepresentation(value))) } companion object { const val NoMapping = "__UNKNOWN_ENUM_VALUE__" diff --git a/android/src/main/java/com/facebook/flipper/plugins/uidebugger/core/NativeScanScheduler.kt b/android/src/main/java/com/facebook/flipper/plugins/uidebugger/core/NativeScanScheduler.kt index b97773610..9276d93a6 100644 --- a/android/src/main/java/com/facebook/flipper/plugins/uidebugger/core/NativeScanScheduler.kt +++ b/android/src/main/java/com/facebook/flipper/plugins/uidebugger/core/NativeScanScheduler.kt @@ -10,7 +10,9 @@ package com.facebook.flipper.plugins.uidebugger.core import android.os.Looper import android.util.Log import com.facebook.flipper.plugins.uidebugger.LogTag +import com.facebook.flipper.plugins.uidebugger.descriptors.MetadataRegister import com.facebook.flipper.plugins.uidebugger.descriptors.nodeId +import com.facebook.flipper.plugins.uidebugger.model.MetadataUpdateEvent import com.facebook.flipper.plugins.uidebugger.model.Node import com.facebook.flipper.plugins.uidebugger.model.PerfStatsEvent import com.facebook.flipper.plugins.uidebugger.model.SubtreeUpdateEvent @@ -50,7 +52,16 @@ class NativeScanScheduler(val context: Context) : Scheduler.Task { return ScanResult(txId++, start, scanEnd, nodes) } - override fun process(input: ScanResult) { + private fun sendMetadata() { + val metadata = MetadataRegister.dynamicMetadata() + if (metadata.size > 0) { + context.connectionRef.connection?.send( + MetadataUpdateEvent.name, + Json.encodeToString(MetadataUpdateEvent.serializer(), MetadataUpdateEvent(metadata))) + } + } + + private fun sendSubtreeUpdate(input: ScanResult) { val serialized = Json.encodeToString( SubtreeUpdateEvent.serializer(), @@ -79,4 +90,9 @@ class NativeScanScheduler(val context: Context) : Scheduler.Task { socketEnd, input.nodes.size))) } + + override fun process(input: ScanResult) { + sendMetadata() + sendSubtreeUpdate(input) + } } diff --git a/android/src/main/java/com/facebook/flipper/plugins/uidebugger/descriptors/ChainedDescriptor.kt b/android/src/main/java/com/facebook/flipper/plugins/uidebugger/descriptors/ChainedDescriptor.kt index fd675b4c6..89626f800 100644 --- a/android/src/main/java/com/facebook/flipper/plugins/uidebugger/descriptors/ChainedDescriptor.kt +++ b/android/src/main/java/com/facebook/flipper/plugins/uidebugger/descriptors/ChainedDescriptor.kt @@ -10,6 +10,7 @@ package com.facebook.flipper.plugins.uidebugger.descriptors import android.graphics.Bitmap import com.facebook.flipper.plugins.uidebugger.model.Bounds import com.facebook.flipper.plugins.uidebugger.model.InspectableObject +import com.facebook.flipper.plugins.uidebugger.model.MetadataId /** * A chained descriptor is a special type of descriptor that models the inheritance hierarchy in @@ -72,8 +73,8 @@ abstract class ChainedDescriptor : NodeDescriptor { open fun onGetChildren(node: T): List? = null - final override fun getData(node: T): Map { - val builder = mutableMapOf() + final override fun getData(node: T): Map { + val builder = mutableMapOf() onGetData(node, builder) var curDescriptor: ChainedDescriptor? = mSuper @@ -90,7 +91,7 @@ abstract class ChainedDescriptor : NodeDescriptor { * Get the data to show for this node in the sidebar of the inspector. Each key will be a have its * own section */ - open fun onGetData(node: T, attributeSections: MutableMap) {} + open fun onGetData(node: T, attributeSections: MutableMap) {} /** Get a snapshot of the node. */ final override fun getSnapshot(node: T, bitmap: Bitmap?): Bitmap? { diff --git a/android/src/main/java/com/facebook/flipper/plugins/uidebugger/descriptors/ColorDrawableDescriptor.kt b/android/src/main/java/com/facebook/flipper/plugins/uidebugger/descriptors/ColorDrawableDescriptor.kt index 4a3c95b24..a485e1ff3 100644 --- a/android/src/main/java/com/facebook/flipper/plugins/uidebugger/descriptors/ColorDrawableDescriptor.kt +++ b/android/src/main/java/com/facebook/flipper/plugins/uidebugger/descriptors/ColorDrawableDescriptor.kt @@ -8,22 +8,25 @@ package com.facebook.flipper.plugins.uidebugger.descriptors import android.graphics.drawable.ColorDrawable -import com.facebook.flipper.plugins.uidebugger.model.Color -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.* object ColorDrawableDescriptor : ChainedDescriptor() { + private const val NAMESPACE = "ColorDrawable" + private var SectionId = + MetadataRegister.register(MetadataRegister.TYPE_ATTRIBUTE, NAMESPACE, NAMESPACE) + private var ColorAttributeId = + MetadataRegister.register(MetadataRegister.TYPE_ATTRIBUTE, NAMESPACE, "color") + override fun onGetName(node: ColorDrawable): String = node.javaClass.simpleName override fun onGetData( node: ColorDrawable, - attributeSections: MutableMap + attributeSections: MutableMap ) { - val props = mutableMapOf() - props["color"] = InspectableValue.Color(Color.fromColor(node.color), mutable = true) + val props = mutableMapOf() + props[ColorAttributeId] = InspectableValue.Color(Color.fromColor(node.color)) - attributeSections["ColorDrawable"] = InspectableObject(props.toMap()) + attributeSections[SectionId] = InspectableObject(props.toMap()) } } diff --git a/android/src/main/java/com/facebook/flipper/plugins/uidebugger/descriptors/DrawableDescriptor.kt b/android/src/main/java/com/facebook/flipper/plugins/uidebugger/descriptors/DrawableDescriptor.kt index 62668c2a2..a8f21fd9c 100644 --- a/android/src/main/java/com/facebook/flipper/plugins/uidebugger/descriptors/DrawableDescriptor.kt +++ b/android/src/main/java/com/facebook/flipper/plugins/uidebugger/descriptors/DrawableDescriptor.kt @@ -12,6 +12,15 @@ import android.os.Build import com.facebook.flipper.plugins.uidebugger.model.* object DrawableDescriptor : ChainedDescriptor() { + + private const val NAMESPACE = "Drawable" + private var SectionId = + MetadataRegister.register(MetadataRegister.TYPE_ATTRIBUTE, NAMESPACE, NAMESPACE) + private var AlphaAttributeId = + MetadataRegister.register(MetadataRegister.TYPE_ATTRIBUTE, NAMESPACE, "alpha") + private var BoundsAttributeId = + MetadataRegister.register(MetadataRegister.TYPE_ATTRIBUTE, NAMESPACE, "bounds") + override fun onGetName(node: Drawable): String = node.javaClass.simpleName override fun onGetBounds(node: Drawable): Bounds = @@ -19,16 +28,16 @@ object DrawableDescriptor : ChainedDescriptor() { override fun onGetData( node: Drawable, - attributeSections: MutableMap + attributeSections: MutableMap ) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { - val props = mutableMapOf() - props["alpha"] = InspectableValue.Number(node.alpha, true) + val props = mutableMapOf() + props[AlphaAttributeId] = InspectableValue.Number(node.alpha) val bounds = node.bounds - props["bounds"] = InspectableValue.Bounds(Bounds.fromRect(bounds)) + props[BoundsAttributeId] = InspectableValue.Bounds(Bounds.fromRect(bounds)) - attributeSections["Drawable"] = InspectableObject(props.toMap()) + attributeSections[SectionId] = InspectableObject(props.toMap()) } } diff --git a/android/src/main/java/com/facebook/flipper/plugins/uidebugger/descriptors/FragmentFrameworkDescriptor.kt b/android/src/main/java/com/facebook/flipper/plugins/uidebugger/descriptors/FragmentFrameworkDescriptor.kt index 6df1d55fa..7a9135f5b 100644 --- a/android/src/main/java/com/facebook/flipper/plugins/uidebugger/descriptors/FragmentFrameworkDescriptor.kt +++ b/android/src/main/java/com/facebook/flipper/plugins/uidebugger/descriptors/FragmentFrameworkDescriptor.kt @@ -11,9 +11,14 @@ import android.os.Bundle 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 object FragmentFrameworkDescriptor : ChainedDescriptor() { + private const val NAMESPACE = "Fragment" + private var SectionId = + MetadataRegister.register(MetadataRegister.TYPE_ATTRIBUTE, NAMESPACE, NAMESPACE) + override fun onGetName(node: android.app.Fragment): String { return node.javaClass.simpleName } @@ -23,19 +28,24 @@ object FragmentFrameworkDescriptor : ChainedDescriptor() { override fun onGetData( node: android.app.Fragment, - attributeSections: MutableMap + attributeSections: MutableMap ) { val args: Bundle = node.arguments - val props = mutableMapOf() + val props = mutableMapOf() for (key in args.keySet()) { + val metadata = MetadataRegister.get(NAMESPACE, key) + val identifier = + metadata?.id + ?: MetadataRegister.registerDynamic(MetadataRegister.TYPE_ATTRIBUTE, NAMESPACE, key) + when (val value = args[key]) { - is Number -> props[key] = InspectableValue.Number(value) - is Boolean -> props[key] = InspectableValue.Boolean(value) - is String -> props[key] = InspectableValue.Text(value) + is Number -> props[identifier] = InspectableValue.Number(value) + is Boolean -> props[identifier] = InspectableValue.Boolean(value) + is String -> props[identifier] = InspectableValue.Text(value) } } - attributeSections["Fragment"] = InspectableObject(props.toMap()) + attributeSections[SectionId] = InspectableObject(props.toMap()) } } diff --git a/android/src/main/java/com/facebook/flipper/plugins/uidebugger/descriptors/FragmentSupportDescriptor.kt b/android/src/main/java/com/facebook/flipper/plugins/uidebugger/descriptors/FragmentSupportDescriptor.kt index 24449379c..d866e4eff 100644 --- a/android/src/main/java/com/facebook/flipper/plugins/uidebugger/descriptors/FragmentSupportDescriptor.kt +++ b/android/src/main/java/com/facebook/flipper/plugins/uidebugger/descriptors/FragmentSupportDescriptor.kt @@ -10,9 +10,14 @@ package com.facebook.flipper.plugins.uidebugger.descriptors 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 object FragmentSupportDescriptor : ChainedDescriptor() { + private const val NAMESPACE = "Fragment" + private var SectionId = + MetadataRegister.register(MetadataRegister.TYPE_ATTRIBUTE, NAMESPACE, NAMESPACE) + override fun onGetName(node: androidx.fragment.app.Fragment): String { return node.javaClass.simpleName } @@ -22,19 +27,23 @@ object FragmentSupportDescriptor : ChainedDescriptor + attributeSections: MutableMap ) { val args = node.arguments args?.let { bundle -> - val props = mutableMapOf() + val props = mutableMapOf() for (key in bundle.keySet()) { + val metadata = MetadataRegister.get(NAMESPACE, key) + val identifier = + metadata?.id + ?: MetadataRegister.registerDynamic(MetadataRegister.TYPE_ATTRIBUTE, NAMESPACE, key) when (val value = bundle[key]) { - is Number -> props[key] = InspectableValue.Number(value) - is Boolean -> props[key] = InspectableValue.Boolean(value) - is String -> props[key] = InspectableValue.Text(value) + is Number -> props[identifier] = InspectableValue.Number(value) + is Boolean -> props[identifier] = InspectableValue.Boolean(value) + is String -> props[identifier] = InspectableValue.Text(value) } } - attributeSections["Fragment"] = InspectableObject(props.toMap()) + attributeSections[SectionId] = InspectableObject(props.toMap()) } } } diff --git a/android/src/main/java/com/facebook/flipper/plugins/uidebugger/descriptors/ImageViewDescriptor.kt b/android/src/main/java/com/facebook/flipper/plugins/uidebugger/descriptors/ImageViewDescriptor.kt index 3f07d5338..0d968df7a 100644 --- a/android/src/main/java/com/facebook/flipper/plugins/uidebugger/descriptors/ImageViewDescriptor.kt +++ b/android/src/main/java/com/facebook/flipper/plugins/uidebugger/descriptors/ImageViewDescriptor.kt @@ -12,18 +12,27 @@ import android.widget.ImageView.ScaleType import com.facebook.flipper.plugins.uidebugger.common.EnumMapping import com.facebook.flipper.plugins.uidebugger.model.Inspectable import com.facebook.flipper.plugins.uidebugger.model.InspectableObject +import com.facebook.flipper.plugins.uidebugger.model.MetadataId object ImageViewDescriptor : ChainedDescriptor() { + + private const val NAMESPACE = "ImageView" + + private var SectionId = + MetadataRegister.register(MetadataRegister.TYPE_ATTRIBUTE, NAMESPACE, NAMESPACE) + private var ScaleTypeAttributeId = + MetadataRegister.register(MetadataRegister.TYPE_ATTRIBUTE, NAMESPACE, "scaleType") + override fun onGetName(node: ImageView): String = node.javaClass.simpleName override fun onGetData( node: ImageView, - attributeSections: MutableMap + attributeSections: MutableMap ) { - val props = mutableMapOf() - props["scaleType"] = scaleTypeMapping.toInspectable(node.scaleType, true) + val props = mutableMapOf() + props[ScaleTypeAttributeId] = scaleTypeMapping.toInspectable(node.scaleType) - attributeSections["ImageView"] = InspectableObject(props) + attributeSections[SectionId] = InspectableObject(props) } private val scaleTypeMapping: EnumMapping = diff --git a/android/src/main/java/com/facebook/flipper/plugins/uidebugger/descriptors/MetadataRegister.kt b/android/src/main/java/com/facebook/flipper/plugins/uidebugger/descriptors/MetadataRegister.kt new file mode 100644 index 000000000..fd80ad99c --- /dev/null +++ b/android/src/main/java/com/facebook/flipper/plugins/uidebugger/descriptors/MetadataRegister.kt @@ -0,0 +1,96 @@ +/* + * Copyright (c) Meta Platforms, Inc. and 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.uidebugger.descriptors + +import com.facebook.flipper.plugins.uidebugger.model.Metadata +import com.facebook.flipper.plugins.uidebugger.model.MetadataId + +/** + * Registry of attribute metadata. There's two types of attributes: + * - Static attributes. Those that are known at build time. + * - Dynamic attributes. Those that are known at runtime. + */ +object MetadataRegister { + + const val TYPE_IDENTITY = "identity" + const val TYPE_ATTRIBUTE = "attribute" + const val TYPE_LAYOUT = "layout" + const val TYPE_DOCUMENTATION = "documentation" + + private var generator: MetadataId = 0 + private val staticMetadata: MutableMap = mutableMapOf() + private val dynamicMetadata: MutableMap = mutableMapOf() + private val queried: MutableSet = mutableSetOf() + + fun key(namespace: String, name: String): String = "${namespace}_$name" + + private fun register( + metadata: MutableMap, + type: String, + namespace: String, + name: String, + mutable: Boolean + ): MetadataId { + val key = key(namespace, name) + metadata[key]?.let { m -> + return m.id + } + + val identifier = ++generator + metadata[key] = Metadata(identifier, type, namespace, name, mutable) + return identifier + } + + fun register( + type: String, + namespace: String, + name: String, + mutable: Boolean = false + ): MetadataId { + return register(staticMetadata, type, namespace, name, mutable) + } + + fun registerDynamic( + type: String, + namespace: String, + name: String, + mutable: Boolean = false + ): MetadataId { + return register(dynamicMetadata, type, namespace, name, mutable) + } + + fun get(namespace: String, name: String): Metadata? { + val key = key(namespace, name) + staticMetadata[key]?.let { + return it + } + return dynamicMetadata[key] + } + + fun staticMetadata(): Map { + val metadata: MutableMap = mutableMapOf() + staticMetadata.forEach { entry -> metadata[entry.value.id] = entry.value } + return metadata + } + + fun dynamicMetadata(): Map { + val metadata: MutableMap = mutableMapOf() + dynamicMetadata.forEach { entry -> + if (!queried.contains(entry.value.id)) { + metadata[entry.value.id] = entry.value + queried.add(entry.value.id) + } + } + + return metadata + } + + fun clear() { + queried.clear() + } +} diff --git a/android/src/main/java/com/facebook/flipper/plugins/uidebugger/descriptors/NodeDescriptor.kt b/android/src/main/java/com/facebook/flipper/plugins/uidebugger/descriptors/NodeDescriptor.kt index ca48eedb7..35247fbb3 100644 --- a/android/src/main/java/com/facebook/flipper/plugins/uidebugger/descriptors/NodeDescriptor.kt +++ b/android/src/main/java/com/facebook/flipper/plugins/uidebugger/descriptors/NodeDescriptor.kt @@ -10,6 +10,7 @@ package com.facebook.flipper.plugins.uidebugger.descriptors import android.graphics.Bitmap import com.facebook.flipper.plugins.uidebugger.model.Bounds import com.facebook.flipper.plugins.uidebugger.model.InspectableObject +import com.facebook.flipper.plugins.uidebugger.model.MetadataId /* Descriptors are an extension point used during traversal to extract data out of arbitrary @@ -19,8 +20,6 @@ import com.facebook.flipper.plugins.uidebugger.model.InspectableObject Descriptors should be stateless and each descriptor should be a singleton */ -typealias SectionName = String - object BaseTags { const val Declarative = "Declarative" @@ -63,7 +62,7 @@ interface NodeDescriptor { * Get the data to show for this node in the sidebar of the inspector. The object will be shown in * order and with a header matching the given name. */ - fun getData(node: T): Map + fun getData(node: T): Map /** * Set of tags to describe this node in an abstract way for the UI Unfortunately this can't be an diff --git a/android/src/main/java/com/facebook/flipper/plugins/uidebugger/descriptors/ObjectDescriptor.kt b/android/src/main/java/com/facebook/flipper/plugins/uidebugger/descriptors/ObjectDescriptor.kt index 4e733deae..9a03ce557 100644 --- a/android/src/main/java/com/facebook/flipper/plugins/uidebugger/descriptors/ObjectDescriptor.kt +++ b/android/src/main/java/com/facebook/flipper/plugins/uidebugger/descriptors/ObjectDescriptor.kt @@ -10,6 +10,7 @@ package com.facebook.flipper.plugins.uidebugger.descriptors import android.graphics.Bitmap import com.facebook.flipper.plugins.uidebugger.model.Bounds import com.facebook.flipper.plugins.uidebugger.model.InspectableObject +import com.facebook.flipper.plugins.uidebugger.model.MetadataId object ObjectDescriptor : NodeDescriptor { @@ -21,7 +22,7 @@ object ObjectDescriptor : NodeDescriptor { override fun getChildren(node: Any) = listOf() - override fun getData(node: Any) = mutableMapOf() + override fun getData(node: Any) = mutableMapOf() override fun getBounds(node: Any): Bounds? = null diff --git a/android/src/main/java/com/facebook/flipper/plugins/uidebugger/descriptors/OffsetChildDescriptor.kt b/android/src/main/java/com/facebook/flipper/plugins/uidebugger/descriptors/OffsetChildDescriptor.kt index d59d002ed..7d074f52d 100644 --- a/android/src/main/java/com/facebook/flipper/plugins/uidebugger/descriptors/OffsetChildDescriptor.kt +++ b/android/src/main/java/com/facebook/flipper/plugins/uidebugger/descriptors/OffsetChildDescriptor.kt @@ -10,6 +10,7 @@ package com.facebook.flipper.plugins.uidebugger.descriptors import android.graphics.Bitmap import com.facebook.flipper.plugins.uidebugger.model.Bounds import com.facebook.flipper.plugins.uidebugger.model.InspectableObject +import com.facebook.flipper.plugins.uidebugger.model.MetadataId /** a drawable or view that is mounted, along with the correct descriptor */ class OffsetChild(val child: Any, val descriptor: NodeDescriptor, val x: Int, val y: Int) { @@ -34,7 +35,7 @@ object OffsetChildDescriptor : NodeDescriptor { override fun getActiveChild(node: OffsetChild): Any? = node.descriptor.getActiveChild(node.child) - override fun getData(node: OffsetChild): Map = + override fun getData(node: OffsetChild): Map = node.descriptor.getData(node.child) override fun getTags(node: OffsetChild): Set = node.descriptor.getTags(node.child) diff --git a/android/src/main/java/com/facebook/flipper/plugins/uidebugger/descriptors/TextViewDescriptor.kt b/android/src/main/java/com/facebook/flipper/plugins/uidebugger/descriptors/TextViewDescriptor.kt index c81e4c494..c0d2ff273 100644 --- a/android/src/main/java/com/facebook/flipper/plugins/uidebugger/descriptors/TextViewDescriptor.kt +++ b/android/src/main/java/com/facebook/flipper/plugins/uidebugger/descriptors/TextViewDescriptor.kt @@ -13,45 +13,73 @@ import com.facebook.flipper.plugins.uidebugger.model.Color 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 object TextViewDescriptor : ChainedDescriptor() { + private const val NAMESPACE = "TextView" + + private var SectionId = + MetadataRegister.register(MetadataRegister.TYPE_ATTRIBUTE, NAMESPACE, NAMESPACE) + private val TextAttributeId = + MetadataRegister.register(MetadataRegister.TYPE_ATTRIBUTE, NAMESPACE, "text") + private val TextSizeAttributeId = + MetadataRegister.register(MetadataRegister.TYPE_ATTRIBUTE, NAMESPACE, "textSize") + private val TextColorAttributeId = + MetadataRegister.register(MetadataRegister.TYPE_ATTRIBUTE, NAMESPACE, "textColor") + private val IsBoldAttributeId = + MetadataRegister.register(MetadataRegister.TYPE_ATTRIBUTE, NAMESPACE, "isBold") + private val IsItalicAttributeId = + MetadataRegister.register(MetadataRegister.TYPE_ATTRIBUTE, NAMESPACE, "isItalic") + private val WeightAttributeId = + MetadataRegister.register(MetadataRegister.TYPE_ATTRIBUTE, NAMESPACE, "weight") + private val TypefaceAttributeId = + MetadataRegister.register(MetadataRegister.TYPE_ATTRIBUTE, NAMESPACE, "typeface") + private val MinLinesAttributeId = + MetadataRegister.register(MetadataRegister.TYPE_ATTRIBUTE, NAMESPACE, "minLines") + private val MaxLinesAttributeId = + MetadataRegister.register(MetadataRegister.TYPE_ATTRIBUTE, NAMESPACE, "maxLines") + private val MinWidthAttributeId = + MetadataRegister.register(MetadataRegister.TYPE_ATTRIBUTE, NAMESPACE, "minWidth") + private val MaxWidthAttributeId = + MetadataRegister.register(MetadataRegister.TYPE_ATTRIBUTE, NAMESPACE, "maxWidth") + override fun onGetName(node: TextView): String = node.javaClass.simpleName override fun onGetData( node: TextView, - attributeSections: MutableMap + attributeSections: MutableMap ) { val props = - mutableMapOf( - "text" to InspectableValue.Text(node.text.toString(), false), - "textSize" to InspectableValue.Number(node.textSize, false), - "textColor" to - InspectableValue.Color(Color.fromColor(node.textColors.defaultColor), false)) + mutableMapOf( + TextAttributeId to InspectableValue.Text(node.text.toString()), + TextSizeAttributeId to InspectableValue.Number(node.textSize), + TextColorAttributeId to + InspectableValue.Color(Color.fromColor(node.textColors.defaultColor))) val typeface = node.typeface if (typeface != null) { val typeFaceProp = - mutableMapOf( - "isBold" to InspectableValue.Boolean(typeface.isBold, false), - "isItalic" to InspectableValue.Boolean(typeface.isItalic, false), + mutableMapOf( + IsBoldAttributeId to InspectableValue.Boolean(typeface.isBold), + IsItalicAttributeId to InspectableValue.Boolean(typeface.isItalic), ) if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) { - typeFaceProp["weight"] = InspectableValue.Number(typeface.weight, false) + typeFaceProp[WeightAttributeId] = InspectableValue.Number(typeface.weight) } - props["typeface"] = InspectableObject(typeFaceProp) + props[TypefaceAttributeId] = InspectableObject(typeFaceProp) } if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) { - props["minLines"] = InspectableValue.Number(node.minLines, false) - props["maxLines"] = InspectableValue.Number(node.maxLines, false) - props["minWidth"] = InspectableValue.Number(node.minWidth, false) - props["maxWidth"] = InspectableValue.Number(node.maxWidth, false) + props[MinLinesAttributeId] = InspectableValue.Number(node.minLines) + props[MaxLinesAttributeId] = InspectableValue.Number(node.maxLines) + props[MinWidthAttributeId] = InspectableValue.Number(node.minWidth) + props[MaxWidthAttributeId] = InspectableValue.Number(node.maxWidth) } - attributeSections["TextView"] = InspectableObject(props) + attributeSections[SectionId] = InspectableObject(props) } } diff --git a/android/src/main/java/com/facebook/flipper/plugins/uidebugger/descriptors/ViewDescriptor.kt b/android/src/main/java/com/facebook/flipper/plugins/uidebugger/descriptors/ViewDescriptor.kt index 31d3edc47..db44c039f 100644 --- a/android/src/main/java/com/facebook/flipper/plugins/uidebugger/descriptors/ViewDescriptor.kt +++ b/android/src/main/java/com/facebook/flipper/plugins/uidebugger/descriptors/ViewDescriptor.kt @@ -23,15 +23,86 @@ import android.widget.LinearLayout import androidx.core.widget.NestedScrollView import androidx.viewpager.widget.ViewPager import com.facebook.flipper.plugins.uidebugger.common.* -import com.facebook.flipper.plugins.uidebugger.common.BitmapPool -import com.facebook.flipper.plugins.uidebugger.common.EnumMapping import com.facebook.flipper.plugins.uidebugger.model.* -import com.facebook.flipper.plugins.uidebugger.model.Bounds import com.facebook.flipper.plugins.uidebugger.util.ResourcesUtil import java.lang.reflect.Field object ViewDescriptor : ChainedDescriptor() { + private const val NAMESPACE = "View" + + private var SectionId = + MetadataRegister.register(MetadataRegister.TYPE_ATTRIBUTE, NAMESPACE, NAMESPACE) + private val PositionAttributeId = + MetadataRegister.register(MetadataRegister.TYPE_LAYOUT, NAMESPACE, "position") + private val GlobalPositionAttributeId = + MetadataRegister.register(MetadataRegister.TYPE_LAYOUT, NAMESPACE, "globalPosition") + private val SizeAttributeId = + MetadataRegister.register(MetadataRegister.TYPE_LAYOUT, NAMESPACE, "size") + private val BoundsAttributeId = + MetadataRegister.register(MetadataRegister.TYPE_LAYOUT, NAMESPACE, "bounds") + private val PaddingAttributeId = + MetadataRegister.register(MetadataRegister.TYPE_LAYOUT, NAMESPACE, "padding") + private val LocalVisibleRectAttributeId = + MetadataRegister.register(MetadataRegister.TYPE_LAYOUT, NAMESPACE, "localVisibleRect") + private val RotationAttributeId = + MetadataRegister.register(MetadataRegister.TYPE_LAYOUT, NAMESPACE, "rotation") + private val ScaleAttributeId = + MetadataRegister.register(MetadataRegister.TYPE_LAYOUT, NAMESPACE, "scale") + private val PivotAttributeId = + MetadataRegister.register(MetadataRegister.TYPE_LAYOUT, NAMESPACE, "pivot") + private val LayoutParamsAttributeId = + MetadataRegister.register(MetadataRegister.TYPE_LAYOUT, NAMESPACE, "layoutParams") + private val LayoutDirectionAttributeId = + MetadataRegister.register(MetadataRegister.TYPE_LAYOUT, NAMESPACE, "layoutDirection") + private val TranslationAttributeId = + MetadataRegister.register(MetadataRegister.TYPE_LAYOUT, NAMESPACE, "translation") + private val ElevationAttributeId = + MetadataRegister.register(MetadataRegister.TYPE_LAYOUT, NAMESPACE, "elevation") + private val VisibilityAttributeId = + MetadataRegister.register(MetadataRegister.TYPE_ATTRIBUTE, NAMESPACE, "visibility") + + private val BackgroundAttributeId = + MetadataRegister.register(MetadataRegister.TYPE_ATTRIBUTE, NAMESPACE, "background") + private val ForegroundAttributeId = + MetadataRegister.register(MetadataRegister.TYPE_ATTRIBUTE, NAMESPACE, "foreground") + + private val AlphaAttributeId = + MetadataRegister.register(MetadataRegister.TYPE_ATTRIBUTE, NAMESPACE, "alpha") + private val StateAttributeId = + MetadataRegister.register(MetadataRegister.TYPE_ATTRIBUTE, NAMESPACE, "state") + + private val StateEnabledAttributeId = + MetadataRegister.register(MetadataRegister.TYPE_ATTRIBUTE, NAMESPACE, "enabled") + private val StateActivatedAttributeId = + MetadataRegister.register(MetadataRegister.TYPE_ATTRIBUTE, NAMESPACE, "activated") + private val StateFocusedAttributeId = + MetadataRegister.register(MetadataRegister.TYPE_ATTRIBUTE, NAMESPACE, "focused") + private val StateSelectedAttributeId = + MetadataRegister.register(MetadataRegister.TYPE_ATTRIBUTE, NAMESPACE, "selected") + + private val TextDirectionAttributeId = + MetadataRegister.register(MetadataRegister.TYPE_ATTRIBUTE, NAMESPACE, "textDirection") + private val TextAlignmentAttributeId = + MetadataRegister.register(MetadataRegister.TYPE_ATTRIBUTE, NAMESPACE, "textAlignment") + + private val TagAttributeId = + MetadataRegister.register(MetadataRegister.TYPE_ATTRIBUTE, NAMESPACE, "tag") + private val KeyedTagsAttributeId = + MetadataRegister.register(MetadataRegister.TYPE_ATTRIBUTE, NAMESPACE, "keyedTags") + + private val WidthAttributeId = + MetadataRegister.register(MetadataRegister.TYPE_LAYOUT, NAMESPACE, "width") + private val HeightAttributeId = + MetadataRegister.register(MetadataRegister.TYPE_LAYOUT, NAMESPACE, "height") + + private val MarginAttributeId = + MetadataRegister.register(MetadataRegister.TYPE_LAYOUT, NAMESPACE, "margin") + private val WeightAttributeId = + MetadataRegister.register(MetadataRegister.TYPE_LAYOUT, NAMESPACE, "weight") + private val GravityAttributeId = + MetadataRegister.register(MetadataRegister.TYPE_LAYOUT, NAMESPACE, "gravity") + override fun onGetName(node: View): String = node.javaClass.simpleName override fun onGetBounds(node: View): Bounds { @@ -64,12 +135,9 @@ object ViewDescriptor : ChainedDescriptor() { override fun onGetTags(node: View): Set = BaseTags.NativeAndroid - override fun onGetData( - node: View, - attributeSections: MutableMap - ) { + override fun onGetData(node: View, attributeSections: MutableMap) { - val props = mutableMapOf() + val props = mutableMapOf() val positionOnScreen = IntArray(2) node.getLocationOnScreen(positionOnScreen) @@ -78,80 +146,83 @@ object ViewDescriptor : ChainedDescriptor() { node.getLocalVisibleRect(localVisible) if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP_MR1) { - props["position"] = InspectableValue.Coordinate3D(Coordinate3D(node.x, node.y, node.z)) + props[PositionAttributeId] = + InspectableValue.Coordinate3D(Coordinate3D(node.x, node.y, node.z)) } else { - props["position"] = InspectableValue.Coordinate(Coordinate(node.x, node.y)) + props[PositionAttributeId] = InspectableValue.Coordinate(Coordinate(node.x, node.y)) } - props["globalPosition"] = + props[GlobalPositionAttributeId] = InspectableValue.Coordinate(Coordinate(positionOnScreen[0], positionOnScreen[1])) - props["size"] = InspectableValue.Size(Size(node.width, node.height), mutable = true) + props[SizeAttributeId] = InspectableValue.Size(Size(node.width, node.height)) - props["bounds"] = InspectableValue.Bounds(Bounds(node.left, node.top, node.right, node.bottom)) - props["padding"] = + props[BoundsAttributeId] = + InspectableValue.Bounds(Bounds(node.left, node.top, node.right, node.bottom)) + props[PaddingAttributeId] = InspectableValue.SpaceBox( SpaceBox(node.paddingTop, node.paddingRight, node.paddingBottom, node.paddingLeft)) - props["localVisibleRect"] = + props[LocalVisibleRectAttributeId] = InspectableObject( mapOf( - "position" to + PositionAttributeId to InspectableValue.Coordinate(Coordinate(localVisible.left, localVisible.top)), - "size" to InspectableValue.Size(Size(localVisible.width(), localVisible.height()))), + SizeAttributeId to + InspectableValue.Size(Size(localVisible.width(), localVisible.height()))), ) - props["rotation"] = + props[RotationAttributeId] = InspectableValue.Coordinate3D(Coordinate3D(node.rotationX, node.rotationY, node.rotation)) - props["scale"] = InspectableValue.Coordinate(Coordinate(node.scaleX, node.scaleY)) - props["pivot"] = InspectableValue.Coordinate(Coordinate(node.pivotX, node.pivotY)) + props[ScaleAttributeId] = InspectableValue.Coordinate(Coordinate(node.scaleX, node.scaleY)) + props[PivotAttributeId] = InspectableValue.Coordinate(Coordinate(node.pivotX, node.pivotY)) - props["layoutParams"] = getLayoutParams(node) + props[LayoutParamsAttributeId] = getLayoutParams(node) if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) { - props["layoutDirection"] = LayoutDirectionMapping.toInspectable(node.layoutDirection, false) + props[LayoutDirectionAttributeId] = LayoutDirectionMapping.toInspectable(node.layoutDirection) } if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { - props["translation"] = + props[TranslationAttributeId] = InspectableValue.Coordinate3D( Coordinate3D(node.translationX, node.translationY, node.translationZ)) } else { - props["translation"] = + props[TranslationAttributeId] = InspectableValue.Coordinate(Coordinate(node.translationX, node.translationY)) } if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { - props["elevation"] = InspectableValue.Number(node.elevation) + props[ElevationAttributeId] = InspectableValue.Number(node.elevation) } - props["visibility"] = VisibilityMapping.toInspectable(node.visibility, mutable = false) + props[VisibilityAttributeId] = VisibilityMapping.toInspectable(node.visibility) - fromDrawable(node.background)?.let { background -> props["background"] = background } + fromDrawable(node.background)?.let { background -> props[BackgroundAttributeId] = background } if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { - fromDrawable(node.foreground)?.let { foreground -> props["foreground"] = foreground } + fromDrawable(node.foreground)?.let { foreground -> props[ForegroundAttributeId] = foreground } } - props["alpha"] = InspectableValue.Number(node.alpha, mutable = true) - props["state"] = + props[AlphaAttributeId] = InspectableValue.Number(node.alpha) + props[StateAttributeId] = InspectableObject( mapOf( - "enabled" to InspectableValue.Boolean(node.isEnabled, mutable = false), - "activated" to InspectableValue.Boolean(node.isActivated, mutable = false), - "focused" to InspectableValue.Boolean(node.isFocused, mutable = false), - "selected" to InspectableValue.Boolean(node.isSelected, mutable = false))) + StateEnabledAttributeId to InspectableValue.Boolean(node.isEnabled), + StateActivatedAttributeId to InspectableValue.Boolean(node.isActivated), + StateFocusedAttributeId to InspectableValue.Boolean(node.isFocused), + StateSelectedAttributeId to InspectableValue.Boolean(node.isSelected))) if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) { - props["textDirection"] = TextDirectionMapping.toInspectable(node.textDirection, false) - props["textAlignment"] = TextAlignmentMapping.toInspectable(node.textAlignment, false) + props[TextDirectionAttributeId] = TextDirectionMapping.toInspectable(node.textDirection) + props[TextAlignmentAttributeId] = TextAlignmentMapping.toInspectable(node.textAlignment) } node.tag ?.let { InspectableValue.fromAny(it, mutable = false) } - ?.let { tag -> props.put("tag", tag) } + ?.let { tag -> props.put(TagAttributeId, tag) } - props["keyedTags"] = InspectableObject(getViewTags(node)) + props[KeyedTagsAttributeId] = InspectableObject(getViewTags(node)) - attributeSections["View"] = InspectableObject(props.toMap()) + attributeSections[SectionId] = InspectableObject(props.toMap()) } override fun onGetSnapshot(node: View, bitmap: Bitmap?): Bitmap? { @@ -180,19 +251,19 @@ object ViewDescriptor : ChainedDescriptor() { private fun fromDrawable(d: Drawable?): Inspectable? { return if (d is ColorDrawable) { - InspectableValue.Color(Color.fromColor(d.color), mutable = false) + InspectableValue.Color(Color.fromColor(d.color)) } else null } private fun getLayoutParams(node: View): InspectableObject { val layoutParams = node.layoutParams - val params = mutableMapOf() - params["width"] = LayoutParamsMapping.toInspectable(layoutParams.width, mutable = true) - params["height"] = LayoutParamsMapping.toInspectable(layoutParams.height, mutable = true) + val params = mutableMapOf() + params[WidthAttributeId] = LayoutParamsMapping.toInspectable(layoutParams.width) + params[HeightAttributeId] = LayoutParamsMapping.toInspectable(layoutParams.height) if (layoutParams is ViewGroup.MarginLayoutParams) { - params["margin"] = + params[MarginAttributeId] = InspectableValue.SpaceBox( SpaceBox( layoutParams.topMargin, @@ -201,17 +272,17 @@ object ViewDescriptor : ChainedDescriptor() { layoutParams.leftMargin)) } if (layoutParams is FrameLayout.LayoutParams) { - params["gravity"] = GravityMapping.toInspectable(layoutParams.gravity, mutable = true) + params[GravityAttributeId] = GravityMapping.toInspectable(layoutParams.gravity) } if (layoutParams is LinearLayout.LayoutParams) { - params["weight"] = InspectableValue.Number(layoutParams.weight, mutable = true) - params["gravity"] = GravityMapping.toInspectable(layoutParams.gravity, mutable = true) + params[WeightAttributeId] = InspectableValue.Number(layoutParams.weight) + params[GravityAttributeId] = GravityMapping.toInspectable(layoutParams.gravity) } return InspectableObject(params) } - private fun getViewTags(node: View): MutableMap { - val tags = mutableMapOf() + private fun getViewTags(node: View): MutableMap { + val tags = mutableMapOf() KeyedTagsField?.let { field -> val keyedTags = field.get(node) as SparseArray<*>? @@ -224,7 +295,13 @@ object ViewDescriptor : ChainedDescriptor() { keyedTags .valueAt(i) ?.let { InspectableValue.fromAny(it, false) } - ?.let { tags.put(id, it) } + ?.let { + val metadata = MetadataRegister.get(NAMESPACE, id) + val identifier = + metadata?.id + ?: MetadataRegister.register(MetadataRegister.TYPE_ATTRIBUTE, NAMESPACE, id) + tags.put(identifier, it) + } i++ } } diff --git a/android/src/main/java/com/facebook/flipper/plugins/uidebugger/descriptors/ViewGroupDescriptor.kt b/android/src/main/java/com/facebook/flipper/plugins/uidebugger/descriptors/ViewGroupDescriptor.kt index 7eeb9ae47..97ee6db19 100644 --- a/android/src/main/java/com/facebook/flipper/plugins/uidebugger/descriptors/ViewGroupDescriptor.kt +++ b/android/src/main/java/com/facebook/flipper/plugins/uidebugger/descriptors/ViewGroupDescriptor.kt @@ -17,6 +17,9 @@ import com.facebook.flipper.plugins.uidebugger.model.* object ViewGroupDescriptor : ChainedDescriptor() { + private const val NAMESPACE = "ViewGroup" + private var SectionId = + MetadataRegister.register(MetadataRegister.TYPE_ATTRIBUTE, NAMESPACE, NAMESPACE) override fun onGetName(node: ViewGroup): String { return node.javaClass.simpleName } @@ -37,21 +40,28 @@ object ViewGroupDescriptor : ChainedDescriptor() { return children } + private val LayoutModeAttributeId = + MetadataRegister.register(MetadataRegister.TYPE_LAYOUT, NAMESPACE, "layoutMode") + private val ClipChildrenAttributeId = + MetadataRegister.register(MetadataRegister.TYPE_LAYOUT, NAMESPACE, "layoutMode") + private val ClipToPaddingAttributeId = + MetadataRegister.register(MetadataRegister.TYPE_LAYOUT, NAMESPACE, "clipToPadding") + override fun onGetData( node: ViewGroup, - attributeSections: MutableMap + attributeSections: MutableMap ) { - val viewGroupAttrs = mutableMapOf() + val props = mutableMapOf() if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) { - viewGroupAttrs["LayoutMode"] = LayoutModeMapping.toInspectable(node.layoutMode, true) - viewGroupAttrs["ClipChildren"] = InspectableValue.Boolean(node.clipChildren, true) + props[LayoutModeAttributeId] = LayoutModeMapping.toInspectable(node.layoutMode) + props[ClipChildrenAttributeId] = InspectableValue.Boolean(node.clipChildren) } if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { - viewGroupAttrs["ClipToPadding"] = InspectableValue.Boolean(node.clipToPadding, true) + props[ClipToPaddingAttributeId] = InspectableValue.Boolean(node.clipToPadding) } - attributeSections["ViewGroup"] = InspectableObject(viewGroupAttrs) + attributeSections[SectionId] = InspectableObject(props) } private val LayoutModeMapping: EnumMapping = diff --git a/android/src/main/java/com/facebook/flipper/plugins/uidebugger/descriptors/ViewPagerDescriptor.kt b/android/src/main/java/com/facebook/flipper/plugins/uidebugger/descriptors/ViewPagerDescriptor.kt index da548b7c6..2b6a8b618 100644 --- a/android/src/main/java/com/facebook/flipper/plugins/uidebugger/descriptors/ViewPagerDescriptor.kt +++ b/android/src/main/java/com/facebook/flipper/plugins/uidebugger/descriptors/ViewPagerDescriptor.kt @@ -11,9 +11,13 @@ import androidx.viewpager.widget.ViewPager import com.facebook.flipper.plugins.uidebugger.core.FragmentTracker import com.facebook.flipper.plugins.uidebugger.model.InspectableObject import com.facebook.flipper.plugins.uidebugger.model.InspectableValue +import com.facebook.flipper.plugins.uidebugger.model.MetadataId object ViewPagerDescriptor : ChainedDescriptor() { + private const val NAMESPACE = "ViewPager" + private var SectionId = + MetadataRegister.register(MetadataRegister.TYPE_ATTRIBUTE, NAMESPACE, NAMESPACE) override fun onGetName(node: ViewPager): String = node.javaClass.simpleName override fun onGetActiveChild(node: ViewPager): Any? { @@ -25,14 +29,16 @@ object ViewPagerDescriptor : ChainedDescriptor() { return child } + private val CurrentItemIndexAttributeId = + MetadataRegister.register(MetadataRegister.TYPE_ATTRIBUTE, NAMESPACE, "currentItemIndex") override fun onGetData( node: ViewPager, - attributeSections: MutableMap + attributeSections: MutableMap ) { val props = InspectableObject( - mapOf("currentItemIndex" to InspectableValue.Number(node.currentItem, false))) + mapOf(CurrentItemIndexAttributeId to InspectableValue.Number(node.currentItem))) - attributeSections["ViewPager"] = props + attributeSections[SectionId] = props } } diff --git a/android/src/main/java/com/facebook/flipper/plugins/uidebugger/descriptors/WindowDescriptor.kt b/android/src/main/java/com/facebook/flipper/plugins/uidebugger/descriptors/WindowDescriptor.kt index fae88b9fd..47f084a0c 100644 --- a/android/src/main/java/com/facebook/flipper/plugins/uidebugger/descriptors/WindowDescriptor.kt +++ b/android/src/main/java/com/facebook/flipper/plugins/uidebugger/descriptors/WindowDescriptor.kt @@ -14,10 +14,14 @@ import com.facebook.flipper.plugins.uidebugger.model.Color 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 java.lang.reflect.Field object WindowDescriptor : ChainedDescriptor() { + private const val NAMESPACE = "Window" + private var SectionId = + MetadataRegister.register(MetadataRegister.TYPE_ATTRIBUTE, NAMESPACE, NAMESPACE) private var internalRStyleableClass: Class<*>? = null private var internalRStyleableFields: Array? = null private var internalRStyleableWindowField: Field? = null @@ -32,7 +36,7 @@ object WindowDescriptor : ChainedDescriptor() { @SuppressLint("PrivateApi") override fun onGetData( node: Window, - attributeSections: MutableMap + attributeSections: MutableMap ) { try { if (internalRStyleableClass == null) { @@ -58,7 +62,7 @@ object WindowDescriptor : ChainedDescriptor() { } } - val props = mutableMapOf() + val props = mutableMapOf() val typedValue = TypedValue() for ((index, attr) in windowStyleable.withIndex()) { @@ -68,20 +72,28 @@ object WindowDescriptor : ChainedDescriptor() { // Strip 'Windows_' (length: 7) from the name. val name = fieldName.substring(7) + val metadata = MetadataRegister.get(NAMESPACE, name) + val identifier = + metadata?.id + ?: MetadataRegister.registerDynamic( + MetadataRegister.TYPE_ATTRIBUTE, NAMESPACE, name) + when (typedValue.type) { TypedValue.TYPE_STRING -> - props[name] = InspectableValue.Text(typedValue.string.toString()) + props[identifier] = InspectableValue.Text(typedValue.string.toString()) TypedValue.TYPE_INT_BOOLEAN -> - props[name] = InspectableValue.Boolean(typedValue.data != 0) + props[identifier] = InspectableValue.Boolean(typedValue.data != 0) TypedValue.TYPE_INT_HEX -> - props[name] = InspectableValue.Text("0x" + Integer.toHexString(typedValue.data)) + props[identifier] = + InspectableValue.Text("0x" + Integer.toHexString(typedValue.data)) TypedValue.TYPE_FLOAT -> - props[name] = + props[identifier] = InspectableValue.Number(java.lang.Float.intBitsToFloat(typedValue.data)) TypedValue.TYPE_REFERENCE -> { val resId = typedValue.data if (resId != 0) { - props[name] = InspectableValue.Text(node.context.resources.getResourceName(resId)) + props[identifier] = + InspectableValue.Text(node.context.resources.getResourceName(resId)) } } else -> { @@ -90,18 +102,18 @@ object WindowDescriptor : ChainedDescriptor() { try { val hexColor = "#" + Integer.toHexString(typedValue.data) val color = android.graphics.Color.parseColor(hexColor) - props[name] = InspectableValue.Color(Color.fromColor(color)) + props[identifier] = InspectableValue.Color(Color.fromColor(color)) } catch (e: Exception) {} } else if (typedValue.type >= TypedValue.TYPE_FIRST_INT && typedValue.type <= TypedValue.TYPE_LAST_INT) { - props[name] = InspectableValue.Number(typedValue.data) + props[identifier] = InspectableValue.Number(typedValue.data) } } } } } - attributeSections["Window"] = InspectableObject(props.toMap()) + attributeSections[SectionId] = InspectableObject(props.toMap()) } } } diff --git a/android/src/main/java/com/facebook/flipper/plugins/uidebugger/model/Events.kt b/android/src/main/java/com/facebook/flipper/plugins/uidebugger/model/Events.kt index 8b5691b0f..6907441ca 100644 --- a/android/src/main/java/com/facebook/flipper/plugins/uidebugger/model/Events.kt +++ b/android/src/main/java/com/facebook/flipper/plugins/uidebugger/model/Events.kt @@ -10,12 +10,21 @@ package com.facebook.flipper.plugins.uidebugger.model import com.facebook.flipper.plugins.uidebugger.descriptors.Id @kotlinx.serialization.Serializable -data class InitEvent(val rootId: Id) { +data class InitEvent( + val rootId: Id, +) { companion object { const val name = "init" } } +@kotlinx.serialization.Serializable +data class MetadataUpdateEvent(val attributeMetadata: Map = emptyMap()) { + companion object { + const val name = "metadataUpdate" + } +} + @kotlinx.serialization.Serializable data class SubtreeUpdateEvent( val txId: Long, diff --git a/android/src/main/java/com/facebook/flipper/plugins/uidebugger/model/Inspectable.kt b/android/src/main/java/com/facebook/flipper/plugins/uidebugger/model/Inspectable.kt index 503fbf867..42c7a82cc 100644 --- a/android/src/main/java/com/facebook/flipper/plugins/uidebugger/model/Inspectable.kt +++ b/android/src/main/java/com/facebook/flipper/plugins/uidebugger/model/Inspectable.kt @@ -16,93 +16,77 @@ import kotlinx.serialization.descriptors.SerialDescriptor import kotlinx.serialization.encoding.Decoder import kotlinx.serialization.encoding.Encoder -@kotlinx.serialization.Serializable -sealed class Inspectable { - abstract val mutable: kotlin.Boolean -} +@kotlinx.serialization.Serializable sealed class Inspectable {} // In this context, mutable means you can add/remove items, // for native android this should probably be false. @SerialName("array") @Serializable -data class InspectableArray(val items: List, override val mutable: Boolean = false) : - Inspectable() +data class InspectableArray(val id: Int, val items: List) : Inspectable() // In this context, mutable means you can add / remove keys, // for native android this should probably be false. @SerialName("object") @Serializable -data class InspectableObject( - val fields: Map, - override val mutable: Boolean = false -) : Inspectable() +data class InspectableObject(val fields: Map) : Inspectable() @kotlinx.serialization.Serializable sealed class InspectableValue : Inspectable() { @kotlinx.serialization.Serializable @SerialName("text") - class Text(val value: String, override val mutable: kotlin.Boolean = false) : InspectableValue() + class Text(val value: String) : InspectableValue() @kotlinx.serialization.Serializable @SerialName("boolean") - class Boolean(val value: kotlin.Boolean, override val mutable: kotlin.Boolean = false) : - InspectableValue() + class Boolean(val value: kotlin.Boolean) : InspectableValue() @SerialName("number") @kotlinx.serialization.Serializable data class Number( @Serializable(with = NumberSerializer::class) val value: kotlin.Number, - override val mutable: kotlin.Boolean = false ) : InspectableValue() @SerialName("color") @kotlinx.serialization.Serializable data class Color( val value: com.facebook.flipper.plugins.uidebugger.model.Color, - override val mutable: kotlin.Boolean = false ) : InspectableValue() @SerialName("coordinate") @kotlinx.serialization.Serializable data class Coordinate( val value: com.facebook.flipper.plugins.uidebugger.model.Coordinate, - override val mutable: kotlin.Boolean = false ) : InspectableValue() @SerialName("coordinate3d") @kotlinx.serialization.Serializable data class Coordinate3D( val value: com.facebook.flipper.plugins.uidebugger.model.Coordinate3D, - override val mutable: kotlin.Boolean = false ) : InspectableValue() @SerialName("size") @kotlinx.serialization.Serializable data class Size( val value: com.facebook.flipper.plugins.uidebugger.model.Size, - override val mutable: kotlin.Boolean = false ) : InspectableValue() @SerialName("bounds") @kotlinx.serialization.Serializable data class Bounds( val value: com.facebook.flipper.plugins.uidebugger.model.Bounds, - override val mutable: kotlin.Boolean = false ) : InspectableValue() @SerialName("space") @kotlinx.serialization.Serializable data class SpaceBox( val value: com.facebook.flipper.plugins.uidebugger.model.SpaceBox, - override val mutable: kotlin.Boolean = false ) : InspectableValue() @SerialName("enum") @kotlinx.serialization.Serializable data class Enum( val value: com.facebook.flipper.plugins.uidebugger.model.Enumeration, - override val mutable: kotlin.Boolean = false ) : InspectableValue() companion object { @@ -112,9 +96,9 @@ sealed class InspectableValue : Inspectable() { */ fun fromAny(any: Any, mutable: kotlin.Boolean = false): Inspectable? { return when (any) { - is kotlin.Number -> InspectableValue.Number(any, mutable) - is kotlin.Boolean -> InspectableValue.Boolean(any, mutable) - is kotlin.String -> InspectableValue.Text(any, mutable) + is kotlin.Number -> InspectableValue.Number(any) + is kotlin.Boolean -> InspectableValue.Boolean(any) + is kotlin.String -> InspectableValue.Text(any) else -> null } } diff --git a/android/src/main/java/com/facebook/flipper/plugins/uidebugger/model/Metadata.kt b/android/src/main/java/com/facebook/flipper/plugins/uidebugger/model/Metadata.kt new file mode 100644 index 000000000..58716453f --- /dev/null +++ b/android/src/main/java/com/facebook/flipper/plugins/uidebugger/model/Metadata.kt @@ -0,0 +1,25 @@ +/* + * Copyright (c) Meta Platforms, Inc. and 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.uidebugger.model + +typealias MetadataId = Int + +/** + * Represent metadata associated for an attribute. MetadataId is a unique identifier used by + * attributes to refer to its metadata. Type refers to attribute semantics. It can represent: + * identity, attributes, layout, documentation, or a custom type. + */ +@kotlinx.serialization.Serializable +data class Metadata( + val id: MetadataId, + val type: String, + val namespace: String, + val name: String, + val mutable: kotlin.Boolean, + val tags: List? = emptyList() +) {} diff --git a/android/src/main/java/com/facebook/flipper/plugins/uidebugger/model/Node.kt b/android/src/main/java/com/facebook/flipper/plugins/uidebugger/model/Node.kt index 5789e78e2..a067b4e2c 100644 --- a/android/src/main/java/com/facebook/flipper/plugins/uidebugger/model/Node.kt +++ b/android/src/main/java/com/facebook/flipper/plugins/uidebugger/model/Node.kt @@ -13,7 +13,7 @@ import com.facebook.flipper.plugins.uidebugger.descriptors.Id data class Node( val id: Id, val name: String, - val attributes: Map, + val attributes: Map, val bounds: Bounds?, val tags: Set, val children: List, diff --git a/android/src/main/java/com/facebook/flipper/plugins/uidebugger/observers/TreeObserverManager.kt b/android/src/main/java/com/facebook/flipper/plugins/uidebugger/observers/TreeObserverManager.kt index 90bb68814..188f1158e 100644 --- a/android/src/main/java/com/facebook/flipper/plugins/uidebugger/observers/TreeObserverManager.kt +++ b/android/src/main/java/com/facebook/flipper/plugins/uidebugger/observers/TreeObserverManager.kt @@ -16,8 +16,10 @@ import com.facebook.flipper.plugins.uidebugger.LogTag import com.facebook.flipper.plugins.uidebugger.common.BitmapPool import com.facebook.flipper.plugins.uidebugger.core.Context import com.facebook.flipper.plugins.uidebugger.descriptors.Id +import com.facebook.flipper.plugins.uidebugger.descriptors.MetadataRegister import com.facebook.flipper.plugins.uidebugger.model.Coordinate import com.facebook.flipper.plugins.uidebugger.model.CoordinateUpdateEvent +import com.facebook.flipper.plugins.uidebugger.model.MetadataUpdateEvent import com.facebook.flipper.plugins.uidebugger.model.Node import com.facebook.flipper.plugins.uidebugger.model.PerfStatsEvent import com.facebook.flipper.plugins.uidebugger.model.SubtreeUpdateEvent @@ -69,9 +71,7 @@ class TreeObserverManager(val context: Context) { workerScope.launch { while (isActive) { try { - - val update = updates.receive() - when (update) { + when (val update = updates.receive()) { is SubtreeUpdate -> sendSubtreeUpdate(update) is CoordinateUpdate -> { val event = @@ -88,11 +88,22 @@ class TreeObserverManager(val context: Context) { } } - fun sendSubtreeUpdate(treeUpdate: SubtreeUpdate) { + private fun sendMetadata() { + val metadata = MetadataRegister.dynamicMetadata() + if (metadata.size > 0) { + context.connectionRef.connection?.send( + MetadataUpdateEvent.name, + Json.encodeToString(MetadataUpdateEvent.serializer(), MetadataUpdateEvent(metadata))) + } + } + + private fun sendSubtreeUpdate(treeUpdate: SubtreeUpdate) { val onWorkerThread = System.currentTimeMillis() val txId = txId.getAndIncrement().toLong() - var serialized: String? + sendMetadata() + + val serialized: String? if (treeUpdate.snapshot == null) { serialized = Json.encodeToString( diff --git a/android/src/test/java/com/facebook/flipper/plugins/uidebugger/EnumMappingTest.kt b/android/src/test/java/com/facebook/flipper/plugins/uidebugger/EnumMappingTest.kt index d077225d7..770932633 100644 --- a/android/src/test/java/com/facebook/flipper/plugins/uidebugger/EnumMappingTest.kt +++ b/android/src/test/java/com/facebook/flipper/plugins/uidebugger/EnumMappingTest.kt @@ -39,9 +39,7 @@ class EnumMappingTest { @Test fun testTurnsIntoEnumInspectable() { assertThat( - visibility.toInspectable(View.GONE, true), - equalTo( - InspectableValue.Enum( - Enumeration(setOf("VISIBLE", "INVISIBLE", "GONE"), "GONE"), mutable = true))) + visibility.toInspectable(View.GONE), + equalTo(InspectableValue.Enum(Enumeration(setOf("VISIBLE", "INVISIBLE", "GONE"), "GONE")))) } } diff --git a/desktop/plugins/public/ui-debugger/components/main.tsx b/desktop/plugins/public/ui-debugger/components/main.tsx index 83dd4e633..dc781c970 100644 --- a/desktop/plugins/public/ui-debugger/components/main.tsx +++ b/desktop/plugins/public/ui-debugger/components/main.tsx @@ -11,7 +11,7 @@ import React, {useState} from 'react'; import {plugin} from '../index'; import {DetailSidebar, Layout, usePlugin, useValue} from 'flipper-plugin'; import {useHotkeys} from 'react-hotkeys-hook'; -import {Id, Snapshot, UINode} from '../types'; +import {Id, Metadata, MetadataId, Snapshot, UINode} from '../types'; import {PerfStats} from './PerfStats'; import {Tree} from './Tree'; import {Visualization2D} from './Visualization2D'; @@ -22,6 +22,7 @@ export function Component() { const instance = usePlugin(plugin); const rootId = useValue(instance.rootId); const nodes: Map = useValue(instance.nodes); + const metadata: Map = useValue(instance.metadata); const snapshots: Map = useValue(instance.snapshots); const [showPerfStats, setShowPerfStats] = useState(false); @@ -32,13 +33,16 @@ export function Component() { const {ctrlPressed} = useKeyboardModifiers(); - function renderSidebar(node: UINode | undefined) { + function renderSidebar( + node: UINode | undefined, + metadata: Map, + ) { if (!node) { return; } return ( - + ); } @@ -68,7 +72,7 @@ export function Component() { onSelectNode={setSelectedNode} modifierPressed={ctrlPressed} /> - {selectedNode && renderSidebar(nodes.get(selectedNode))} + {selectedNode && renderSidebar(nodes.get(selectedNode), metadata)} ); } diff --git a/desktop/plugins/public/ui-debugger/components/sidebar/Inspector.tsx b/desktop/plugins/public/ui-debugger/components/sidebar/Inspector.tsx index b7bdbbac5..135ab7ed3 100644 --- a/desktop/plugins/public/ui-debugger/components/sidebar/Inspector.tsx +++ b/desktop/plugins/public/ui-debugger/components/sidebar/Inspector.tsx @@ -11,16 +11,17 @@ import React from 'react'; // eslint-disable-next-line rulesdir/no-restricted-imports-clone import {Glyph} from 'flipper'; import {Layout, Tab, Tabs} from 'flipper-plugin'; -import {UINode} from '../../types'; +import {Metadata, MetadataId, UINode} from '../../types'; import {IdentityInspector} from './inspector/IdentityInspector'; import {AttributesInspector} from './inspector/AttributesInspector'; import {DocumentationInspector} from './inspector/DocumentationInspector'; type Props = { node: UINode; + metadata: Map; }; -export const Inspector: React.FC = ({node}) => { +export const Inspector: React.FC = ({node, metadata}) => { return ( @@ -38,7 +39,11 @@ export const Inspector: React.FC = ({node}) => { }> - + = ({node}) => { }> - + = ({ }; const ObjectAttributeInspector: React.FC<{ + metadata: Map; name: string; - value: Record; + fields: Record; level: number; -}> = ({name, value, level}) => { +}> = ({metadata, name, fields, level}) => { return (
{name} - {Object.keys(value).map(function (key, _) { + {Object.keys(fields).map(function (key, _) { + const metadataId: number = Number(key); + const inspectableValue = fields[metadataId]; + const attributeName = metadata.get(metadataId)?.name ?? ''; return ( - {create(key, value[key], level + 2)} + {create(metadata, attributeName, inspectableValue, level + 2)} ); })} @@ -95,145 +105,143 @@ const ObjectAttributeInspector: React.FC<{ ); }; -function create(key: string, inspectable: Inspectable, level: number = 2) { +function create( + metadata: Map, + name: string, + inspectable: Inspectable, + level: number = 2, +) { switch (inspectable.type) { case 'boolean': return ( - + ); case 'enum': return ( - + {inspectable.value.value} ); case 'text': return ( - + {inspectable.value} ); case 'number': return ( - + {inspectable.value} ); case 'color': return ( - + ); case 'size': return ( - + ); case 'bounds': return ( - + ); case 'coordinate': return ( - + ); case 'coordinate3d': return ( - + ); case 'space': return ( - + ); case 'object': return ( ); default: return ( - + {JSON.stringify(inspectable)} ); } } -/** - * Filter out those inspectables that affect sizing, positioning, and - * overall layout of elements. - */ -const layoutFilter = new Set([ - 'size', - 'padding', - 'margin', - 'bounds', - 'position', - 'globalPosition', - 'localVisibleRect', - 'rotation', - 'scale', - 'pivot', - 'layoutParams', - 'layoutDirection', - 'translation', - 'elevation', -]); function createSection( mode: InspectorMode, + metadata: Map, name: string, inspectable: InspectableObject, ) { - const fields = Object.keys(inspectable.fields).filter( - (key) => - (mode === 'attributes' && !layoutFilter.has(key)) || - (mode === 'layout' && layoutFilter.has(key)), - ); - if (!fields || fields.length === 0) { - return; + const children: any[] = []; + Object.keys(inspectable.fields).forEach((key, _index) => { + const metadataId: number = Number(key); + const attributeMetadata = metadata.get(metadataId); + if (attributeMetadata && attributeMetadata.type === mode) { + const attributeValue = inspectable.fields[metadataId]; + children.push(create(metadata, attributeMetadata.name, attributeValue)); + } + }); + + if (children.length > 0) { + return ( + + {...children} + + ); } - return ( - - {fields.map(function (key, _) { - return create(key, inspectable.fields[key]); - })} - - ); } -type InspectorMode = 'layout' | 'attributes'; +type InspectorMode = 'layout' | 'attribute'; type Props = { node: UINode; + metadata: Map; mode: InspectorMode; rawDisplayEnabled?: boolean; }; export const AttributesInspector: React.FC = ({ node, + metadata, mode, rawDisplayEnabled = false, }) => { return ( <> {Object.keys(node.attributes).map(function (key, _) { + const metadataId: number = Number(key); + /** + * The node top-level attributes refer to the displayable panels. + * The panel name is obtained by querying the metadata. + * The inspectable contains the actual attributes belonging to each panel. + */ return createSection( mode, - key, - node.attributes[key] as InspectableObject, + metadata, + metadata.get(metadataId)?.name ?? '', + node.attributes[metadataId] as InspectableObject, ); })} {rawDisplayEnabled ?? ( diff --git a/desktop/plugins/public/ui-debugger/index.tsx b/desktop/plugins/public/ui-debugger/index.tsx index 72f3356ea..bcdfbb11a 100644 --- a/desktop/plugins/public/ui-debugger/index.tsx +++ b/desktop/plugins/public/ui-debugger/index.tsx @@ -8,12 +8,36 @@ */ import {PluginClient, createState, createDataSource} from 'flipper-plugin'; -import {Events, Id, PerfStatsEvent, Snapshot, TreeState, UINode} from './types'; +import { + Events, + Id, + Metadata, + MetadataId, + PerfStatsEvent, + Snapshot, + TreeState, + UINode, +} from './types'; import './node_modules/react-complex-tree/lib/style.css'; export function plugin(client: PluginClient) { const rootId = createState(undefined); - client.onMessage('init', (root) => rootId.set(root.rootId)); + const metadata = createState>(new Map()); + + client.onMessage('init', (event) => { + rootId.set(event.rootId); + }); + + client.onMessage('metadataUpdate', (event) => { + if (!event.attributeMetadata) { + return; + } + metadata.update((draft) => { + for (const [_key, value] of Object.entries(event.attributeMetadata)) { + draft.set(value.id, value); + } + }); + }); const perfEvents = createDataSource([], { key: 'txId', @@ -23,13 +47,13 @@ export function plugin(client: PluginClient) { perfEvents.append(event); }); - const nodesAtom = createState>(new Map()); - const snapshotsAtom = createState>(new Map()); + const nodes = createState>(new Map()); + const snapshots = createState>(new Map()); const treeState = createState({expandedNodes: []}); client.onMessage('coordinateUpdate', (event) => { - nodesAtom.update((draft) => { + nodes.update((draft) => { const node = draft.get(event.nodeId); if (!node) { console.warn(`Coordinate update for non existing node `, event); @@ -42,13 +66,13 @@ export function plugin(client: PluginClient) { const seenNodes = new Set(); client.onMessage('subtreeUpdate', (event) => { - snapshotsAtom.update((draft) => { + snapshots.update((draft) => { draft.set(event.rootId, event.snapshot); }); - nodesAtom.update((draft) => { - for (const node of event.nodes) { + nodes.update((draft) => { + event.nodes.forEach((node) => { draft.set(node.id, node); - } + }); }); treeState.update((draft) => { @@ -74,8 +98,9 @@ export function plugin(client: PluginClient) { return { rootId, - snapshots: snapshotsAtom, - nodes: nodesAtom, + nodes, + metadata, + snapshots, perfEvents, treeState, }; diff --git a/desktop/plugins/public/ui-debugger/types.tsx b/desktop/plugins/public/ui-debugger/types.tsx index 48dbbd3f6..4da4ded6c 100644 --- a/desktop/plugins/public/ui-debugger/types.tsx +++ b/desktop/plugins/public/ui-debugger/types.tsx @@ -14,6 +14,7 @@ export type Events = { subtreeUpdate: SubtreeUpdateEvent; coordinateUpdate: CoordinateUpdateEvent; perfStats: PerfStatsEvent; + metadataUpdate: UpdateMetadataEvent; }; export type CoordinateUpdateEvent = { @@ -29,7 +30,9 @@ export type SubtreeUpdateEvent = { snapshot: Snapshot; }; -export type InitEvent = {rootId: Id}; +export type InitEvent = { + rootId: Id; +}; export type PerfStatsEvent = { txId: number; @@ -43,16 +46,29 @@ export type PerfStatsEvent = { nodesCount: number; }; +export type UpdateMetadataEvent = { + attributeMetadata: Record; +}; + export type UINode = { id: Id; name: string; - attributes: Record; + attributes: Record; children: Id[]; bounds: Bounds; tags: Tag[]; activeChild?: Id; }; +export type Metadata = { + id: MetadataId; + type: string; + namespace: string; + name: string; + mutable: boolean; + tags?: string[]; +}; + export type Bounds = { x: number; y: number; @@ -93,6 +109,7 @@ export type Color = { export type Snapshot = string; export type Id = number | TreeItemIndex; +export type MetadataId = number; export type TreeState = {expandedNodes: Id[]}; export type Tag = 'Native' | 'Declarative' | 'Android' | 'Litho '; @@ -113,64 +130,54 @@ export type Inspectable = export type InspectableText = { type: 'text'; value: string; - mutable: boolean; }; export type InspectableNumber = { type: 'number'; value: number; - mutable: boolean; }; export type InspectableBoolean = { type: 'boolean'; value: boolean; - mutable: boolean; }; export type InspectableEnum = { type: 'enum'; value: {value: string; values: string[]}; - mutable: boolean; }; export type InspectableColor = { type: 'color'; value: Color; - mutable: boolean; }; export type InspectableBounds = { type: 'bounds'; value: Bounds; - mutable: boolean; }; export type InspectableSize = { type: 'size'; value: Size; - mutable: boolean; }; export type InspectableCoordinate = { type: 'coordinate'; value: Coordinate; - mutable: boolean; }; export type InspectableCoordinate3D = { type: 'coordinate3d'; value: Coordinate3D; - mutable: boolean; }; export type InspectableSpaceBox = { type: 'space'; value: SpaceBox; - mutable: boolean; }; export type InspectableObject = { type: 'object'; - fields: Record; + fields: Record; };