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) descriptor.getChildren(node, children)
val childrenIds = mutableListOf<String>() val childrenIds = mutableListOf<String>()
val activeChild = descriptor.getActiveChild(node)
for (child in children) { for (child in children) {
// it might make sense one day to remove id from the descriptor since its always the // it might make sense one day to remove id from the descriptor since its always the
// hash code // hash code
val childDescriptor = val childDescriptor =
descriptorRegister.descriptorForClassUnsafe(child::class.java).asAny() descriptorRegister.descriptorForClassUnsafe(child::class.java).asAny()
childrenIds.add(childDescriptor.getId(child)) childrenIds.add(childDescriptor.getId(child))
stack.add(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>() val attributes = mutableMapOf<String, InspectableObject>()
descriptor.getData(node, attributes) 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) { } catch (exception: Exception) {
Log.e(LogTag, "Error while processing node ${node.javaClass.name} ${node} ", 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( Log.d(
"LAYOUT_SCHEDULER", "LAYOUT_SCHEDULER",
Thread.currentThread().name + "${Thread.currentThread().name}${Looper.myLooper()} produced: ${nodes.count()} nodes")
Looper.myLooper() +
", produced: " +
{
nodes.count()
} +
" nodes")
return ScanResult(txId++, start, scanEnd, nodes) return ScanResult(txId++, start, scanEnd, nodes)
} }

View File

@@ -34,6 +34,12 @@ abstract class AbstractChainedDescriptor<T> : Descriptor<T>(), ChainedDescriptor
open fun onInit() {} 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 * 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]. * 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) return onGetName(node)
} }
abstract fun onGetActiveChild(node: T): Any?
abstract fun onGetName(node: T): String abstract fun onGetName(node: T): String
/** The children this node exposes in the inspector. */ /** 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>() { class ActivityDescriptor : AbstractChainedDescriptor<Activity>() {
override fun onInit() {} override fun onInit() {}
override fun onGetActiveChild(node: Activity): Any? {
return null
}
override fun onGetId(activity: Activity): String { override fun onGetId(activity: Activity): String {
return Integer.toString(System.identityHashCode(activity)) return Integer.toString(System.identityHashCode(activity))

View File

@@ -16,6 +16,9 @@ class ApplicationRefDescriptor : AbstractChainedDescriptor<ApplicationRef>() {
val rootResolver = RootViewResolver() val rootResolver = RootViewResolver()
override fun onInit() {} 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 { override fun onGetId(applicationRef: ApplicationRef): String {
return applicationRef.application.packageName return applicationRef.application.packageName
@@ -38,7 +41,6 @@ class ApplicationRefDescriptor : AbstractChainedDescriptor<ApplicationRef>() {
if (activity.window.decorView == root.view) { if (activity.window.decorView == root.view) {
children.add(activity) children.add(activity)
added = true added = true
break break
} }
} }

View File

@@ -26,4 +26,7 @@ class ButtonDescriptor : AbstractChainedDescriptor<Button>() {
) {} ) {}
override fun onGetChildren(button: Button, children: MutableList<Any>) {} 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 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.view.Window
import android.widget.Button import android.widget.Button
import android.widget.TextView import android.widget.TextView
import androidx.viewpager.widget.ViewPager
import com.facebook.flipper.plugins.uidebugger.common.UIDebuggerException import com.facebook.flipper.plugins.uidebugger.common.UIDebuggerException
import com.facebook.flipper.plugins.uidebugger.core.ApplicationRef import com.facebook.flipper.plugins.uidebugger.core.ApplicationRef
@@ -31,6 +32,7 @@ class DescriptorRegister {
mapping.register(View::class.java, ViewDescriptor()) mapping.register(View::class.java, ViewDescriptor())
mapping.register(TextView::class.java, TextViewDescriptor()) mapping.register(TextView::class.java, TextViewDescriptor())
mapping.register(Button::class.java, ButtonDescriptor()) mapping.register(Button::class.java, ButtonDescriptor())
mapping.register(ViewPager::class.java, ViewPagerDescriptor())
for (clazz in mapping.register.keys) { for (clazz in mapping.register.keys) {
val descriptor: Descriptor<*>? = mapping.register[clazz] val descriptor: Descriptor<*>? = mapping.register[clazz]

View File

@@ -28,6 +28,12 @@ interface NodeDescriptor<T> {
/** The children this node exposes in the inspector. */ /** The children this node exposes in the inspector. */
fun getChildren(node: T, children: MutableList<Any>) 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 * 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. * 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>() { class ObjectDescriptor : Descriptor<Any>() {
override fun init() {} override fun init() {}
override fun getActiveChild(node: Any): Any? {
return null
}
override fun getId(obj: Any): String { override fun getId(obj: Any): String {
return Integer.toString(System.identityHashCode(obj)) return Integer.toString(System.identityHashCode(obj))

View File

@@ -26,4 +26,8 @@ class TextViewDescriptor : AbstractChainedDescriptor<TextView>() {
textView: TextView, textView: TextView,
attributeSections: MutableMap<String, InspectableObject> 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) {} } 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, window: Window,
attributeSections: MutableMap<String, InspectableObject> 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 id: String,
val name: String, val name: String,
val attributes: Map<String, InspectableObject>, 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 {useHotkeys} from 'react-hotkeys-hook';
import {Id, UINode} from '../types'; 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 { function uiNodeToAntNode(id: Id): DataNode {
const node = nodes.get(id); const node = nodes.get(id);
if (node?.activeChild) {
for (const child of node.children) {
if (child !== node?.activeChild) {
inactive.push(child);
}
}
}
return { return {
key: id, key: id,
title: node?.name, 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 { function formatDiff(start: number, end: number): string {
@@ -119,7 +130,7 @@ export function Component() {
); );
if (rootId) { if (rootId) {
const antTree = nodesToAntTree(rootId, nodes); const [antTree, inactive] = nodesToAntTree(rootId, nodes);
return ( return (
<> <>
<Layout.ScrollContainer> <Layout.ScrollContainer>
@@ -130,7 +141,9 @@ export function Component() {
setSelectedNode(selected[0] as string); setSelectedNode(selected[0] as string);
}} }}
defaultExpandAll defaultExpandAll
expandedKeys={[...nodes.keys()]} expandedKeys={[...nodes.keys()].filter(
(key) => !inactive.includes(key),
)}
switcherIcon={<DownOutlined />} switcherIcon={<DownOutlined />}
treeData={[antTree]} treeData={[antTree]}
/> />

View File

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