Introduced concept of active child

Summary: A node can have an active child, if present we assume all others are inactive and we don't traverse them. This means the activities not on top and view pager views not active will not be scanned. Additionally on the desktop we are automatically collapsing these views. The net result is a lot less work done on the main thread

Reviewed By: lblasa

Differential Revision: D39310126

fbshipit-source-id: ebd0c69d46f2d42fe42e678c8327fcdc73d08385
This commit is contained in:
Luke De Feo
2022-09-12 03:48:43 -07:00
committed by Facebook GitHub Bot
parent a9fe381076
commit c76c993ce4
18 changed files with 104 additions and 42 deletions

View File

@@ -40,19 +40,37 @@ class LayoutTraversal(
descriptor.getChildren(node, children)
val childrenIds = mutableListOf<String>()
val activeChild = descriptor.getActiveChild(node)
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))
// if there is an active child then dont traverse it
if (activeChild == null) {
stack.add(child)
}
}
var activeChildId: String? = null
if (activeChild != null) {
stack.add(activeChild)
activeChildId =
descriptorRegister.descriptorForClassUnsafe(activeChild.javaClass).getId(activeChild)
}
val attributes = mutableMapOf<String, InspectableObject>()
descriptor.getData(node, attributes)
result.add(Node(descriptor.getId(node), descriptor.getName(node), attributes, childrenIds))
result.add(
Node(
descriptor.getId(node),
descriptor.getName(node),
attributes,
childrenIds,
activeChildId))
} catch (exception: Exception) {
Log.e(LogTag, "Error while processing node ${node.javaClass.name} ${node} ", exception)
}

View File

@@ -33,13 +33,7 @@ class NativeScanScheduler(val context: Context) : Scheduler.Task<ScanResult> {
Log.d(
"LAYOUT_SCHEDULER",
Thread.currentThread().name +
Looper.myLooper() +
", produced: " +
{
nodes.count()
} +
" nodes")
"${Thread.currentThread().name}${Looper.myLooper()} produced: ${nodes.count()} nodes")
return ScanResult(txId++, start, scanEnd, nodes)
}

View File

@@ -34,6 +34,12 @@ abstract class AbstractChainedDescriptor<T> : Descriptor<T>(), ChainedDescriptor
open fun onInit() {}
final override fun getActiveChild(node: T): Any? {
// ask each descriptor in the chain for an active child, if none available look up the chain
// until no more super descriptors
return onGetActiveChild(node) ?: mSuper?.getActiveChild(node)
}
/**
* A globally unique ID used to identify a node in a hierarchy. If your node does not have a
* globally unique ID it is fine to rely on [System.identityHashCode].
@@ -52,6 +58,8 @@ abstract class AbstractChainedDescriptor<T> : Descriptor<T>(), ChainedDescriptor
return onGetName(node)
}
abstract fun onGetActiveChild(node: T): Any?
abstract fun onGetName(node: T): String
/** The children this node exposes in the inspector. */

View File

@@ -13,6 +13,9 @@ import com.facebook.flipper.plugins.uidebugger.stetho.FragmentCompat
class ActivityDescriptor : AbstractChainedDescriptor<Activity>() {
override fun onInit() {}
override fun onGetActiveChild(node: Activity): Any? {
return null
}
override fun onGetId(activity: Activity): String {
return Integer.toString(System.identityHashCode(activity))

View File

@@ -16,6 +16,9 @@ class ApplicationRefDescriptor : AbstractChainedDescriptor<ApplicationRef>() {
val rootResolver = RootViewResolver()
override fun onInit() {}
override fun onGetActiveChild(node: ApplicationRef): Any? {
return if (node.activitiesStack.size > 0) node.activitiesStack.last() else null
}
override fun onGetId(applicationRef: ApplicationRef): String {
return applicationRef.application.packageName
@@ -38,7 +41,6 @@ class ApplicationRefDescriptor : AbstractChainedDescriptor<ApplicationRef>() {
if (activity.window.decorView == root.view) {
children.add(activity)
added = true
break
}
}

View File

@@ -26,4 +26,7 @@ class ButtonDescriptor : AbstractChainedDescriptor<Button>() {
) {}
override fun onGetChildren(button: Button, children: MutableList<Any>) {}
override fun onGetActiveChild(node: Button): Any? {
return null
}
}

View File

@@ -26,31 +26,4 @@ abstract class Descriptor<T> : NodeDescriptor<T> {
}
return null
}
// override fun inspect(obj: Any): FlipperObject {
// val descriptor = descriptorForObject(obj)
// descriptor?.let { descriptor ->
// return (descriptor as Descriptor<Any>).get(obj)
// }
//
// return FlipperObject.Builder().build()
// }
//
// override fun get(node: T): FlipperObject {
// val builder = FlipperObject.Builder()
//
// val propsBuilder = FlipperObject.Builder()
// getData(node, propsBuilder)
//
// val childrenBuilder = FlipperArray.Builder()
// getChildren(node, childrenBuilder)
//
// builder
// .put("key", getId(node))
// .put("title", getName(node))
// .put("data", propsBuilder)
// .put("children", childrenBuilder)
//
// return builder.build()
// }
}

View File

@@ -13,6 +13,7 @@ import android.view.ViewGroup
import android.view.Window
import android.widget.Button
import android.widget.TextView
import androidx.viewpager.widget.ViewPager
import com.facebook.flipper.plugins.uidebugger.common.UIDebuggerException
import com.facebook.flipper.plugins.uidebugger.core.ApplicationRef
@@ -31,6 +32,7 @@ class DescriptorRegister {
mapping.register(View::class.java, ViewDescriptor())
mapping.register(TextView::class.java, TextViewDescriptor())
mapping.register(Button::class.java, ButtonDescriptor())
mapping.register(ViewPager::class.java, ViewPagerDescriptor())
for (clazz in mapping.register.keys) {
val descriptor: Descriptor<*>? = mapping.register[clazz]

View File

@@ -28,6 +28,12 @@ interface NodeDescriptor<T> {
/** The children this node exposes in the inspector. */
fun getChildren(node: T, children: MutableList<Any>)
/**
* If you have overlapping children this indicates which child is active / on top, we will only
* listen to / traverse this child. If return null we assume all children are 'active'
*/
fun getActiveChild(node: T): Any?
/**
* 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.

View File

@@ -11,6 +11,9 @@ import com.facebook.flipper.plugins.uidebugger.common.InspectableObject
class ObjectDescriptor : Descriptor<Any>() {
override fun init() {}
override fun getActiveChild(node: Any): Any? {
return null
}
override fun getId(obj: Any): String {
return Integer.toString(System.identityHashCode(obj))

View File

@@ -26,4 +26,8 @@ class TextViewDescriptor : AbstractChainedDescriptor<TextView>() {
textView: TextView,
attributeSections: MutableMap<String, InspectableObject>
) {}
override fun onGetActiveChild(node: TextView): Any? {
return null
}
}

View File

@@ -256,4 +256,8 @@ class ViewDescriptor : AbstractChainedDescriptor<View>() {
} catch (ignored: Exception) {}
}
}
override fun onGetActiveChild(node: View): Any? {
return null
}
}

View File

@@ -85,4 +85,8 @@ class ViewGroupDescriptor : AbstractChainedDescriptor<ViewGroup>() {
}
}
}
override fun onGetActiveChild(node: ViewGroup): Any? {
return null
}
}

View File

@@ -0,0 +1,19 @@
/*
* 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 androidx.viewpager.widget.ViewPager
class ViewPagerDescriptor : AbstractChainedDescriptor<ViewPager>() {
override fun onGetId(node: ViewPager): String = System.identityHashCode(node).toString()
override fun onGetName(node: ViewPager): String = node.javaClass.simpleName
override fun onGetActiveChild(node: ViewPager): Any? = node.getChildAt(node.currentItem)
}

View File

@@ -28,4 +28,8 @@ class WindowDescriptor : AbstractChainedDescriptor<Window>() {
window: Window,
attributeSections: MutableMap<String, InspectableObject>
) {}
override fun onGetActiveChild(node: Window): Any? {
return null
}
}

View File

@@ -14,5 +14,6 @@ data class Node(
val id: String,
val name: String,
val attributes: Map<String, InspectableObject>,
val children: List<String>
val children: List<String>,
val activeChild: String?,
)

View File

@@ -24,9 +24,20 @@ import {DownOutlined} from '@ant-design/icons';
import {useHotkeys} from 'react-hotkeys-hook';
import {Id, UINode} from '../types';
function nodesToAntTree(root: Id, nodes: Map<Id, UINode>): DataNode {
function nodesToAntTree(root: Id, nodes: Map<Id, UINode>): [DataNode, Id[]] {
const inactive: Id[] = [];
function uiNodeToAntNode(id: Id): DataNode {
const node = nodes.get(id);
if (node?.activeChild) {
for (const child of node.children) {
if (child !== node?.activeChild) {
inactive.push(child);
}
}
}
return {
key: id,
title: node?.name,
@@ -34,7 +45,7 @@ function nodesToAntTree(root: Id, nodes: Map<Id, UINode>): DataNode {
};
}
return uiNodeToAntNode(root);
return [uiNodeToAntNode(root), inactive];
}
function formatDiff(start: number, end: number): string {
@@ -119,7 +130,7 @@ export function Component() {
);
if (rootId) {
const antTree = nodesToAntTree(rootId, nodes);
const [antTree, inactive] = nodesToAntTree(rootId, nodes);
return (
<>
<Layout.ScrollContainer>
@@ -130,7 +141,9 @@ export function Component() {
setSelectedNode(selected[0] as string);
}}
defaultExpandAll
expandedKeys={[...nodes.keys()]}
expandedKeys={[...nodes.keys()].filter(
(key) => !inactive.includes(key),
)}
switcherIcon={<DownOutlined />}
treeData={[antTree]}
/>

View File

@@ -42,4 +42,5 @@ export type UINode = {
name: string;
attributes: Record<string, Inspectable>;
children: Id[];
activeChild?: Id;
};