From a5da6923eb6947ef82f5590c8debeee94e5d13c6 Mon Sep 17 00:00:00 2001 From: Luke De Feo Date: Wed, 7 Sep 2022 04:37:17 -0700 Subject: [PATCH] Flatten layout during traversal Summary: Move from a nested structure to a flatten one for data exchange, this will allow us to only send sections of the UI in the future Reviewed By: lblasa Differential Revision: D38982138 fbshipit-source-id: d578a07a6d2d7e117fbd741bd6e33062223ce10d --- .../uidebugger/UIDebuggerFlipperPlugin.kt | 21 ++++-- .../flipper/plugins/uidebugger/common/Node.kt | 16 ---- .../uidebugger/core/ApplicationInspector.kt | 8 +- .../plugins/uidebugger/core/Context.kt | 2 +- .../uidebugger/core/LayoutTraversal.kt | 74 +++++++++---------- ...criptor.kt => ApplicationRefDescriptor.kt} | 2 +- .../descriptors/DescriptorRegister.kt | 14 +++- .../plugins/uidebugger/{ => model}/Events.kt | 11 +-- .../{common => model}/Inspectable.kt | 0 .../flipper/plugins/uidebugger/model/Node.kt | 18 +++++ .../public/ui-debugger/components/main.tsx | 60 +++++++++------ desktop/plugins/public/ui-debugger/index.tsx | 16 ++-- 12 files changed, 128 insertions(+), 114 deletions(-) delete mode 100644 android/src/main/java/com/facebook/flipper/plugins/uidebugger/common/Node.kt rename android/src/main/java/com/facebook/flipper/plugins/uidebugger/descriptors/{ApplicationDescriptor.kt => ApplicationRefDescriptor.kt} (95%) rename android/src/main/java/com/facebook/flipper/plugins/uidebugger/{ => model}/Events.kt (59%) rename android/src/main/java/com/facebook/flipper/plugins/uidebugger/{common => model}/Inspectable.kt (100%) create mode 100644 android/src/main/java/com/facebook/flipper/plugins/uidebugger/model/Node.kt 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 bec805f8f..7eb103e32 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 @@ -8,12 +8,14 @@ package com.facebook.flipper.plugins.uidebugger import android.app.Application +import android.util.Log import com.facebook.flipper.core.FlipperConnection import com.facebook.flipper.core.FlipperPlugin -import com.facebook.flipper.plugins.uidebugger.common.Node import com.facebook.flipper.plugins.uidebugger.core.ApplicationInspector import com.facebook.flipper.plugins.uidebugger.core.ApplicationRef import com.facebook.flipper.plugins.uidebugger.core.Context +import com.facebook.flipper.plugins.uidebugger.model.InitEvent +import com.facebook.flipper.plugins.uidebugger.model.NativeScanEvent import kotlinx.serialization.json.Json val LogTag = "FlipperUIDebugger" @@ -32,17 +34,22 @@ class UIDebuggerFlipperPlugin(val application: Application) : FlipperPlugin { this.connection = connection // temp solution, get from descriptor val inspector = ApplicationInspector(context) - val root: Node = inspector.inspect()!! - val initEvent = InitEvent(System.identityHashCode(application).toString()) + val rootDescriptor = + inspector.descriptorRegister.descriptorForClassUnsafe(context.applicationRef.javaClass) connection.send( InitEvent.name, Json.encodeToString( - InitEvent.serializer(), InitEvent(System.identityHashCode(application).toString()))) + InitEvent.serializer(), InitEvent(rootDescriptor.getId(context.applicationRef)))) - connection.send( - NativeScanEvent.name, - Json.encodeToString(NativeScanEvent.serializer(), NativeScanEvent(root))) + try { + val nodes = inspector.traversal.traverse() + connection.send( + NativeScanEvent.name, + Json.encodeToString(NativeScanEvent.serializer(), NativeScanEvent(nodes))) + } catch (e: java.lang.Exception) { + Log.e(LogTag, e.message.toString(), e) + } } @Throws(Exception::class) diff --git a/android/src/main/java/com/facebook/flipper/plugins/uidebugger/common/Node.kt b/android/src/main/java/com/facebook/flipper/plugins/uidebugger/common/Node.kt deleted file mode 100644 index 77a71a300..000000000 --- a/android/src/main/java/com/facebook/flipper/plugins/uidebugger/common/Node.kt +++ /dev/null @@ -1,16 +0,0 @@ -/* - * 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.common - -@kotlinx.serialization.Serializable -class Node() { - var id: String? = null - var name: String? = null - var attributes: Map = mapOf() - var children: List? = null -} diff --git a/android/src/main/java/com/facebook/flipper/plugins/uidebugger/core/ApplicationInspector.kt b/android/src/main/java/com/facebook/flipper/plugins/uidebugger/core/ApplicationInspector.kt index cff6a2a7c..93e8e735c 100644 --- a/android/src/main/java/com/facebook/flipper/plugins/uidebugger/core/ApplicationInspector.kt +++ b/android/src/main/java/com/facebook/flipper/plugins/uidebugger/core/ApplicationInspector.kt @@ -9,16 +9,11 @@ package com.facebook.flipper.plugins.uidebugger.core import android.view.View import android.view.ViewTreeObserver -import com.facebook.flipper.plugins.uidebugger.common.Node import com.facebook.flipper.plugins.uidebugger.descriptors.DescriptorRegister class ApplicationInspector(val context: Context) { val descriptorRegister = DescriptorRegister.withDefaults() - val traversal = LayoutTraversal(descriptorRegister) - - fun inspect(): Node? { - return traversal.inspect(context.application) - } + val traversal = LayoutTraversal(descriptorRegister, context.applicationRef) fun attachListeners(view: View) { // An OnGlobalLayoutListener watches the entire hierarchy for layout changes @@ -49,7 +44,6 @@ class ApplicationInspector(val context: Context) { } override fun onRootViewRemoved(view: View) {} - override fun onRootViewsChanged(views: java.util.List) {} }) diff --git a/android/src/main/java/com/facebook/flipper/plugins/uidebugger/core/Context.kt b/android/src/main/java/com/facebook/flipper/plugins/uidebugger/core/Context.kt index 29d49d417..eac7f2d0b 100644 --- a/android/src/main/java/com/facebook/flipper/plugins/uidebugger/core/Context.kt +++ b/android/src/main/java/com/facebook/flipper/plugins/uidebugger/core/Context.kt @@ -7,4 +7,4 @@ package com.facebook.flipper.plugins.uidebugger.core -class Context(val application: ApplicationRef) {} +class Context(val applicationRef: ApplicationRef) {} diff --git a/android/src/main/java/com/facebook/flipper/plugins/uidebugger/core/LayoutTraversal.kt b/android/src/main/java/com/facebook/flipper/plugins/uidebugger/core/LayoutTraversal.kt index 5eddc7c34..31de1cd28 100644 --- a/android/src/main/java/com/facebook/flipper/plugins/uidebugger/core/LayoutTraversal.kt +++ b/android/src/main/java/com/facebook/flipper/plugins/uidebugger/core/LayoutTraversal.kt @@ -7,63 +7,57 @@ package com.facebook.flipper.plugins.uidebugger.core +import android.util.Log +import com.facebook.flipper.plugins.uidebugger.LogTag import com.facebook.flipper.plugins.uidebugger.common.InspectableObject -import com.facebook.flipper.plugins.uidebugger.common.Node import com.facebook.flipper.plugins.uidebugger.descriptors.Descriptor import com.facebook.flipper.plugins.uidebugger.descriptors.DescriptorRegister +import com.facebook.flipper.plugins.uidebugger.model.Node -class LayoutTraversal(private val descriptorRegister: DescriptorRegister) { - class IntermediateNode(val node: Node) { - var children: List? = null - } +class LayoutTraversal( + private val descriptorRegister: DescriptorRegister, + val root: ApplicationRef +) { internal inline fun Descriptor<*>.asAny(): Descriptor = this as Descriptor - private fun describe(obj: Any): IntermediateNode { - var intermediate = IntermediateNode(Node()) + /** Traverses the native android hierarchy */ + fun traverse(): List { - val descriptor = descriptorRegister.descriptorForClass(obj::class.java) - descriptor?.let { descriptor -> - val anyDescriptor = descriptor.asAny() + val result = mutableListOf() + val stack = mutableListOf() + stack.add(this.root) - intermediate.node.id = anyDescriptor.getId(obj) - intermediate.node.name = anyDescriptor.getName(obj) + while (stack.isNotEmpty()) { - val attributes = mutableMapOf() - anyDescriptor.getData(obj, attributes) - intermediate.node.attributes = attributes + val node = stack.removeLast() - val children = mutableListOf() - anyDescriptor.getChildren(obj, children) - intermediate.children = children - } + try { - return intermediate - } + val descriptor = descriptorRegister.descriptorForClassUnsafe(node::class.java).asAny() - private fun traverse(entry: Any): Node? { - val root = describe(entry) - root?.let { intermediate -> - val queue = mutableListOf() - queue.add(intermediate) + val children = mutableListOf() + descriptor.getChildren(node, children) - while (queue.isNotEmpty()) { - val intermediateNode = queue.removeFirst() - - val children = mutableListOf() - intermediateNode.children?.forEach { - val intermediateChild = describe(it) - children.add(intermediateChild.node) - queue.add(intermediateChild) + val childrenIds = mutableListOf() + for (child in children) { + // it might make sense one day to remove id from the descriptor since its always the + // hash code + val childDescriptor = + descriptorRegister.descriptorForClassUnsafe(child::class.java).asAny() + childrenIds.add(childDescriptor.getId(child)) + stack.add(child) } - intermediateNode.node.children = children + + val attributes = mutableMapOf() + descriptor.getData(node, attributes) + + result.add(Node(descriptor.getId(node), descriptor.getName(node), attributes, childrenIds)) + } catch (exception: Exception) { + Log.e(LogTag, "Error while processing node ${node.javaClass.name} ${node} ", exception) } } - return root?.node - } - - fun inspect(applicationRef: ApplicationRef): Node? { - return traverse(applicationRef) + return result } } diff --git a/android/src/main/java/com/facebook/flipper/plugins/uidebugger/descriptors/ApplicationDescriptor.kt b/android/src/main/java/com/facebook/flipper/plugins/uidebugger/descriptors/ApplicationRefDescriptor.kt similarity index 95% rename from android/src/main/java/com/facebook/flipper/plugins/uidebugger/descriptors/ApplicationDescriptor.kt rename to android/src/main/java/com/facebook/flipper/plugins/uidebugger/descriptors/ApplicationRefDescriptor.kt index bb3002df2..8fe43684f 100644 --- a/android/src/main/java/com/facebook/flipper/plugins/uidebugger/descriptors/ApplicationDescriptor.kt +++ b/android/src/main/java/com/facebook/flipper/plugins/uidebugger/descriptors/ApplicationRefDescriptor.kt @@ -12,7 +12,7 @@ import com.facebook.flipper.plugins.uidebugger.common.InspectableObject import com.facebook.flipper.plugins.uidebugger.core.ApplicationRef import com.facebook.flipper.plugins.uidebugger.core.RootViewResolver -class ApplicationDescriptor : AbstractChainedDescriptor() { +class ApplicationRefDescriptor : AbstractChainedDescriptor() { val rootResolver = RootViewResolver() override fun onInit() {} diff --git a/android/src/main/java/com/facebook/flipper/plugins/uidebugger/descriptors/DescriptorRegister.kt b/android/src/main/java/com/facebook/flipper/plugins/uidebugger/descriptors/DescriptorRegister.kt index 41034b543..4297138db 100644 --- a/android/src/main/java/com/facebook/flipper/plugins/uidebugger/descriptors/DescriptorRegister.kt +++ b/android/src/main/java/com/facebook/flipper/plugins/uidebugger/descriptors/DescriptorRegister.kt @@ -13,6 +13,7 @@ import android.view.ViewGroup import android.view.Window import android.widget.Button import android.widget.TextView +import com.facebook.flipper.plugins.uidebugger.common.UIDebuggerException import com.facebook.flipper.plugins.uidebugger.core.ApplicationRef class DescriptorRegister { @@ -23,7 +24,7 @@ class DescriptorRegister { fun withDefaults(): DescriptorRegister { val mapping = DescriptorRegister() mapping.register(Any::class.java, ObjectDescriptor()) - mapping.register(ApplicationRef::class.java, ApplicationDescriptor()) + mapping.register(ApplicationRef::class.java, ApplicationRefDescriptor()) mapping.register(Activity::class.java, ActivityDescriptor()) mapping.register(Window::class.java, WindowDescriptor()) mapping.register(ViewGroup::class.java, ViewGroupDescriptor()) @@ -57,11 +58,16 @@ class DescriptorRegister { register[clazz] = descriptor } - fun descriptorForClass(clazz: Class<*>): Descriptor<*>? { - var clazz = clazz + fun descriptorForClass(clazz: Class): Descriptor? { + var clazz: Class<*> = clazz while (!register.containsKey(clazz)) { clazz = clazz.superclass } - return register[clazz] + return register[clazz] as Descriptor + } + + fun descriptorForClassUnsafe(clazz: Class): Descriptor { + return descriptorForClass(clazz) + ?: throw UIDebuggerException("No descriptor found for ${clazz.name}") } } diff --git a/android/src/main/java/com/facebook/flipper/plugins/uidebugger/Events.kt b/android/src/main/java/com/facebook/flipper/plugins/uidebugger/model/Events.kt similarity index 59% rename from android/src/main/java/com/facebook/flipper/plugins/uidebugger/Events.kt rename to android/src/main/java/com/facebook/flipper/plugins/uidebugger/model/Events.kt index 306935cec..950d5d690 100644 --- a/android/src/main/java/com/facebook/flipper/plugins/uidebugger/Events.kt +++ b/android/src/main/java/com/facebook/flipper/plugins/uidebugger/model/Events.kt @@ -5,21 +5,18 @@ * LICENSE file in the root directory of this source tree. */ -package com.facebook.flipper.plugins.uidebugger - -import com.facebook.flipper.plugins.uidebugger.common.Node +package com.facebook.flipper.plugins.uidebugger.model @kotlinx.serialization.Serializable data class InitEvent(val rootId: String) { companion object { - val name = "init" + const val name = "init" } } -// TODO flatten the tree into normalised list @kotlinx.serialization.Serializable -data class NativeScanEvent(val root: Node) { +data class NativeScanEvent(val nodes: List) { companion object { - val name = "nativeScan" + const val name = "nativeScan" } } diff --git a/android/src/main/java/com/facebook/flipper/plugins/uidebugger/common/Inspectable.kt b/android/src/main/java/com/facebook/flipper/plugins/uidebugger/model/Inspectable.kt similarity index 100% rename from android/src/main/java/com/facebook/flipper/plugins/uidebugger/common/Inspectable.kt rename to android/src/main/java/com/facebook/flipper/plugins/uidebugger/model/Inspectable.kt 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 new file mode 100644 index 000000000..e0eaf1aa8 --- /dev/null +++ b/android/src/main/java/com/facebook/flipper/plugins/uidebugger/model/Node.kt @@ -0,0 +1,18 @@ +/* + * 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 + +import com.facebook.flipper.plugins.uidebugger.common.InspectableObject + +@kotlinx.serialization.Serializable +data class Node( + val id: String, + val name: String, + val attributes: Map, + val children: List +) diff --git a/desktop/plugins/public/ui-debugger/components/main.tsx b/desktop/plugins/public/ui-debugger/components/main.tsx index 755a21625..64e16a255 100644 --- a/desktop/plugins/public/ui-debugger/components/main.tsx +++ b/desktop/plugins/public/ui-debugger/components/main.tsx @@ -14,43 +14,57 @@ import {Tree} from 'antd'; import type {DataNode} from 'antd/es/tree'; import {DownOutlined} from '@ant-design/icons'; -function treeToAntTree(uiNode: UINode): DataNode { - return { - key: uiNode.id, - title: uiNode.name, - children: uiNode.children ? uiNode.children.map(treeToAntTree) : [], - }; -} +// function treeToAntTree(uiNode: UINode): DataNode { +// return { +// key: uiNode.id, +// title: uiNode.name, +// children: uiNode.children ? uiNode.children.map(treeToAntTree) : [], +// }; +// } -function treeToMap(uiNode: UINode): Map { - const result = new Map(); +// function treeToMap(uiNode: UINode): Map { +// const result = new Map(); +// +// function treeToMapRec(node: UINode): void { +// result.set(node.id, node); +// for (const child of node.children) { +// treeToMapRec(child); +// } +// } +// +// treeToMapRec(uiNode); +// +// return result; +// } - function treeToMapRec(node: UINode): void { - result.set(node.id, node); - for (const child of node.children) { - treeToMapRec(child); - } +function nodesToAntTree(root: Id, nodes: Map): DataNode { + function uiNodeToAntNode(id: Id): DataNode { + const node = nodes.get(id); + return { + key: id, + title: node?.name, + children: node?.children.map((id) => uiNodeToAntNode(id)), + }; } - treeToMapRec(uiNode); - - return result; + return uiNodeToAntNode(root); } export function Component() { const instance = usePlugin(plugin); const rootId = useValue(instance.rootId); - const tree = useValue(instance.tree); + const nodes = useValue(instance.nodes); - if (tree) { - const nodeMap = treeToMap(tree); - const antTree = treeToAntTree(tree); + if (rootId) { + const antTree = nodesToAntTree(rootId, nodes); + console.log(antTree); + console.log(rootId); return ( { - console.log(nodeMap.get(selected[0] as string)); + console.log(nodes.get(selected[0] as string)); }} defaultExpandAll switcherIcon={} @@ -59,5 +73,5 @@ export function Component() { ); } - return
{rootId}
; + return
Nothing yet
; } diff --git a/desktop/plugins/public/ui-debugger/index.tsx b/desktop/plugins/public/ui-debugger/index.tsx index e26fa2b74..378dd5dd4 100644 --- a/desktop/plugins/public/ui-debugger/index.tsx +++ b/desktop/plugins/public/ui-debugger/index.tsx @@ -43,26 +43,26 @@ export type UINode = { id: Id; name: string; attributes: Record; - children: UINode[]; + children: Id[]; }; type Events = { init: {rootId: string}; - - nativeScan: {root: UINode}; + nativeScan: {nodes: UINode[]}; }; export function plugin(client: PluginClient) { - const rootId = createState(undefined); + const rootId = createState(undefined); - const tree = createState(undefined); + const nodesAtom = createState>(new Map()); client.onMessage('init', (root) => rootId.set(root.rootId)); - client.onMessage('nativeScan', ({root}) => { - tree.set(root as UINode); + client.onMessage('nativeScan', ({nodes}) => { + nodesAtom.set(new Map(nodes.map((node) => [node.id, node]))); + console.log(nodesAtom.get()); }); - return {rootId, tree}; + return {rootId, nodes: nodesAtom}; } export {Component} from './components/main';