Merge branch 'main' of github.com:facebook/flipper into universalBuild
This commit is contained in:
@@ -3,7 +3,7 @@
|
||||
# This source code is licensed under the MIT license found in the
|
||||
# LICENSE file in the root directory of this source tree.
|
||||
|
||||
flipperkit_version = '0.222.0'
|
||||
flipperkit_version = '0.233.0'
|
||||
Pod::Spec.new do |spec|
|
||||
spec.name = 'Flipper'
|
||||
spec.cocoapods_version = '>= 1.10'
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
# LICENSE file in the root directory of this source tree.
|
||||
|
||||
folly_compiler_flags = '-DDEBUG=1 -DFLIPPER_OSS=1 -DFB_SONARKIT_ENABLED=1 -DFOLLY_HAVE_BACKTRACE=1 -DFOLLY_HAVE_CLOCK_GETTIME=1 -DFOLLY_NO_CONFIG -DFOLLY_MOBILE=1 -DFOLLY_USE_LIBCPP=1 -DFOLLY_HAVE_LIBGFLAGS=0 -DFOLLY_HAVE_LIBJEMALLOC=0 -DFOLLY_HAVE_PREADV=0 -DFOLLY_HAVE_PWRITEV=0 -DFOLLY_HAVE_TFO=0 -DFOLLY_USE_SYMBOLIZER=0'
|
||||
flipperkit_version = '0.222.0'
|
||||
flipperkit_version = '0.233.0'
|
||||
Pod::Spec.new do |spec|
|
||||
spec.name = 'FlipperKit'
|
||||
spec.version = flipperkit_version
|
||||
|
||||
@@ -7,7 +7,6 @@
|
||||
|
||||
package com.facebook.flipper.plugins.jetpackcompose.descriptors
|
||||
|
||||
import android.graphics.Bitmap
|
||||
import android.view.ViewGroup
|
||||
import com.facebook.flipper.plugins.jetpackcompose.model.ComposeInnerViewNode
|
||||
import com.facebook.flipper.plugins.uidebugger.descriptors.Id
|
||||
@@ -49,13 +48,6 @@ object ComposeInnerViewDescriptor : NodeDescriptor<ComposeInnerViewNode> {
|
||||
return ViewDescriptor.getChildren(node.view)
|
||||
}
|
||||
|
||||
override fun getSnapshot(node: ComposeInnerViewNode, bitmap: Bitmap?): Bitmap? {
|
||||
if (node.view is ViewGroup) {
|
||||
return ViewGroupDescriptor.getSnapshot(node.view, bitmap)
|
||||
}
|
||||
return ViewDescriptor.getSnapshot(node.view, bitmap)
|
||||
}
|
||||
|
||||
override fun getActiveChild(node: ComposeInnerViewNode): Any? {
|
||||
if (node.view is ViewGroup) {
|
||||
return ViewGroupDescriptor.getActiveChild(node.view)
|
||||
|
||||
@@ -7,7 +7,6 @@
|
||||
|
||||
package com.facebook.flipper.plugins.jetpackcompose.descriptors
|
||||
|
||||
import android.graphics.Bitmap
|
||||
import com.facebook.flipper.plugins.jetpackcompose.model.ComposeNode
|
||||
import com.facebook.flipper.plugins.uidebugger.descriptors.BaseTags
|
||||
import com.facebook.flipper.plugins.uidebugger.descriptors.Id
|
||||
@@ -130,8 +129,6 @@ object ComposeNodeDescriptor : NodeDescriptor<ComposeNode> {
|
||||
|
||||
override fun getQualifiedName(node: ComposeNode): String = node.inspectorNode.name
|
||||
|
||||
override fun getSnapshot(node: ComposeNode, bitmap: Bitmap?): Bitmap? = null
|
||||
|
||||
override fun getActiveChild(node: ComposeNode): Any? = null
|
||||
|
||||
override fun getTags(node: ComposeNode): Set<String> = setOf(BaseTags.Android, "Compose")
|
||||
|
||||
@@ -7,7 +7,6 @@
|
||||
|
||||
package com.facebook.flipper.plugins.uidebugger.litho.descriptors
|
||||
|
||||
import android.graphics.Bitmap
|
||||
import com.facebook.flipper.plugins.uidebugger.descriptors.DescriptorRegister
|
||||
import com.facebook.flipper.plugins.uidebugger.descriptors.Id
|
||||
import com.facebook.flipper.plugins.uidebugger.descriptors.MetadataRegister
|
||||
@@ -102,10 +101,13 @@ class DebugComponentDescriptor(val register: DescriptorRegister) : NodeDescripto
|
||||
override fun getAttributes(
|
||||
node: DebugComponent
|
||||
): MaybeDeferred<Map<MetadataId, InspectableObject>> {
|
||||
|
||||
// this accesses the litho view so do this on the main thread
|
||||
val mountingData = getMountingData(node)
|
||||
|
||||
return Deferred {
|
||||
val attributeSections = mutableMapOf<MetadataId, InspectableObject>()
|
||||
|
||||
val mountingData = getMountingData(node)
|
||||
attributeSections[MountingDataId] = InspectableObject(mountingData)
|
||||
|
||||
val layoutProps = LayoutPropExtractor.getProps(node)
|
||||
@@ -139,8 +141,6 @@ class DebugComponentDescriptor(val register: DescriptorRegister) : NodeDescripto
|
||||
return tags
|
||||
}
|
||||
|
||||
override fun getSnapshot(node: DebugComponent, bitmap: Bitmap?): Bitmap? = null
|
||||
|
||||
override fun getInlineAttributes(node: DebugComponent): Map<String, String> {
|
||||
val attributes = mutableMapOf<String, String>()
|
||||
val key = node.key
|
||||
|
||||
@@ -31,7 +31,8 @@ object TextDrawableDescriptor : ChainedDescriptor<TextDrawable>() {
|
||||
attributeSections: MutableMap<MetadataId, InspectableObject>
|
||||
) {
|
||||
val props =
|
||||
mapOf<Int, Inspectable>(TextAttributeId to InspectableValue.Text(node.text.toString()))
|
||||
mapOf<Int, Inspectable>(
|
||||
TextAttributeId to InspectableValue.Text(node.text?.toString() ?: "null"))
|
||||
|
||||
attributeSections[SectionId] = InspectableObject(props)
|
||||
}
|
||||
|
||||
@@ -9,9 +9,9 @@ package com.facebook.flipper.plugins.uidebugger.litho.descriptors.props
|
||||
|
||||
import android.graphics.drawable.ColorDrawable
|
||||
import android.graphics.drawable.Drawable
|
||||
import com.facebook.flipper.plugins.uidebugger.common.enumToInspectableSet
|
||||
import com.facebook.flipper.plugins.uidebugger.descriptors.MetadataRegister
|
||||
import com.facebook.flipper.plugins.uidebugger.model.*
|
||||
import com.facebook.flipper.plugins.uidebugger.util.enumToInspectableSet
|
||||
import com.facebook.litho.DebugComponent
|
||||
import com.facebook.yoga.*
|
||||
|
||||
|
||||
@@ -75,13 +75,13 @@ dependencies {
|
||||
|
||||
// Compose
|
||||
implementation 'androidx.legacy:legacy-support-v4:1.0.0'
|
||||
implementation 'androidx.activity:activity-ktx:1.7.2'
|
||||
implementation 'androidx.compose.runtime:runtime:1.4.3'
|
||||
implementation 'androidx.activity:activity-compose:1.7.2'
|
||||
implementation 'androidx.compose.ui:ui:1.4.3'
|
||||
implementation 'androidx.compose.material3:material3:1.1.1'
|
||||
implementation 'androidx.compose.ui:ui-tooling:1.4.3'
|
||||
implementation 'androidx.compose.ui:ui-tooling-preview:1.4.3'
|
||||
implementation 'androidx.activity:activity-ktx:1.8.0'
|
||||
implementation 'androidx.compose.runtime:runtime:1.5.4'
|
||||
implementation 'androidx.activity:activity-compose:1.8.0'
|
||||
implementation 'androidx.compose.ui:ui:1.5.4'
|
||||
implementation 'androidx.compose.material3:material3:1.1.2'
|
||||
implementation 'androidx.compose.ui:ui-tooling:1.5.4'
|
||||
implementation 'androidx.compose.ui:ui-tooling-preview:1.5.4'
|
||||
|
||||
// Third-party
|
||||
implementation deps.soloader
|
||||
|
||||
@@ -26,7 +26,6 @@ import com.facebook.flipper.plugins.uidebugger.UIDebuggerFlipperPlugin;
|
||||
import com.facebook.flipper.plugins.uidebugger.core.UIDContext;
|
||||
import com.facebook.flipper.plugins.uidebugger.descriptors.DescriptorRegister;
|
||||
import com.facebook.flipper.plugins.uidebugger.litho.UIDebuggerLithoSupport;
|
||||
import com.facebook.flipper.plugins.uidebugger.observers.TreeObserverFactory;
|
||||
import com.facebook.litho.config.ComponentsConfiguration;
|
||||
import com.facebook.litho.editor.flipper.LithoFlipperDescriptors;
|
||||
import java.util.Arrays;
|
||||
@@ -63,7 +62,6 @@ public final class FlipperInitializer {
|
||||
client.addPlugin(NavigationFlipperPlugin.getInstance());
|
||||
|
||||
DescriptorRegister descriptorRegister = DescriptorRegister.Companion.withDefaults();
|
||||
TreeObserverFactory treeObserverFactory = TreeObserverFactory.Companion.withDefaults();
|
||||
UIDContext uidContext = UIDContext.Companion.create((Application) context);
|
||||
UIDebuggerLithoSupport.INSTANCE.enable(uidContext);
|
||||
UIDebuggerComposeSupport.INSTANCE.enable(uidContext);
|
||||
|
||||
@@ -34,7 +34,7 @@ public class AccessibilityRoleUtil {
|
||||
* <p>https://github.com/google/talkback/blob/master/compositor/src/main/res/raw/compositor.json
|
||||
*/
|
||||
public enum AccessibilityRole {
|
||||
NONE(null, ""),
|
||||
NONE("android.view.View", ""),
|
||||
BUTTON("android.widget.Button", "Button"),
|
||||
CHECK_BOX("android.widget.CompoundButton", "Check box"),
|
||||
DROP_DOWN_LIST("android.widget.Spinner", "Drop down list"),
|
||||
|
||||
@@ -48,7 +48,8 @@ class UIDebuggerFlipperPlugin(val context: UIDContext) : FlipperPlugin {
|
||||
MetadataUpdateEvent.serializer(),
|
||||
MetadataUpdateEvent(MetadataRegister.extractPendingMetadata())))
|
||||
|
||||
context.treeObserverManager.start()
|
||||
context.updateQueue.start()
|
||||
context.decorViewTracker.start()
|
||||
|
||||
context.connectionListeners.forEach { it.onConnect() }
|
||||
}
|
||||
@@ -59,7 +60,9 @@ class UIDebuggerFlipperPlugin(val context: UIDContext) : FlipperPlugin {
|
||||
|
||||
MetadataRegister.reset()
|
||||
|
||||
context.treeObserverManager.stop()
|
||||
context.decorViewTracker.stop()
|
||||
context.updateQueue.stop()
|
||||
|
||||
context.bitmapPool.recycleAll()
|
||||
context.connectionListeners.forEach { it.onDisconnect() }
|
||||
context.clearFrameworkEvents()
|
||||
|
||||
@@ -12,6 +12,7 @@ import android.app.Activity
|
||||
import android.app.Application
|
||||
import android.os.Build
|
||||
import android.os.Bundle
|
||||
import android.view.View
|
||||
import java.lang.ref.WeakReference
|
||||
import java.lang.reflect.Field
|
||||
import java.lang.reflect.Method
|
||||
@@ -103,6 +104,11 @@ object ActivityTracker : Application.ActivityLifecycleCallbacks {
|
||||
return stack
|
||||
}
|
||||
|
||||
val decorViewToActivityMap: Map<View, Activity>
|
||||
get() {
|
||||
return activitiesStack.toList().associateBy { it.window.decorView }
|
||||
}
|
||||
|
||||
/**
|
||||
* Activity tracker is used to track activities. However, it cannot track via life-cycle events
|
||||
* all those activities that were created prior to initialisation via the `start(application:
|
||||
|
||||
@@ -17,10 +17,11 @@ class ApplicationRef(val application: Application) {
|
||||
|
||||
// the root view resolver will contain all root views 100% It is needed for 2 cases:
|
||||
// 1. In some cases an activity will not be picked up by the activity tracker,
|
||||
// the root view resolver will at least find the decor view
|
||||
// the root view resolver will at least find the decor view, this is the case for various
|
||||
// kinds of custom overlays
|
||||
// 2. Dialog fragments
|
||||
val rootsResolver: RootViewResolver = RootViewResolver()
|
||||
|
||||
val windowManagerUtility = WindowManagerUtility()
|
||||
val activitiesStack: List<Activity>
|
||||
get() {
|
||||
return ActivityTracker.activitiesStack
|
||||
|
||||
@@ -5,18 +5,17 @@
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
package com.facebook.flipper.plugins.uidebugger.common
|
||||
package com.facebook.flipper.plugins.uidebugger.core
|
||||
|
||||
import android.graphics.Bitmap
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
/** BitmapPool is intended to be used on the main thread. In other words, it is not thread-safe. */
|
||||
class BitmapPool(private val config: Bitmap.Config = Bitmap.Config.RGB_565) {
|
||||
|
||||
interface ReusableBitmap {
|
||||
val bitmap: Bitmap?
|
||||
val bitmap: Bitmap
|
||||
|
||||
fun readyForReuse()
|
||||
}
|
||||
@@ -57,7 +56,7 @@ class BitmapPool(private val config: Bitmap.Config = Bitmap.Config.RGB_565) {
|
||||
override fun readyForReuse() {
|
||||
val key = generateKey(bitmap.width, bitmap.height)
|
||||
|
||||
mainScope.launch {
|
||||
synchronized(this@BitmapPool) {
|
||||
if (isRecycled) {
|
||||
bitmap.recycle()
|
||||
} else {
|
||||
@@ -0,0 +1,127 @@
|
||||
/*
|
||||
* 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.core
|
||||
|
||||
import android.app.Activity
|
||||
import android.util.Log
|
||||
import android.view.View
|
||||
import android.view.ViewTreeObserver
|
||||
import com.facebook.flipper.plugins.uidebugger.LogTag
|
||||
import com.facebook.flipper.plugins.uidebugger.descriptors.ApplicationRefDescriptor
|
||||
import com.facebook.flipper.plugins.uidebugger.descriptors.ViewDescriptor
|
||||
import com.facebook.flipper.plugins.uidebugger.util.StopWatch
|
||||
import com.facebook.flipper.plugins.uidebugger.util.Throttler
|
||||
import com.facebook.flipper.plugins.uidebugger.util.objectIdentity
|
||||
|
||||
/**
|
||||
* The UIDebugger does 3 things:
|
||||
* 1. Observe changes
|
||||
* 2. Traverse UI hierarchy, gathering tree
|
||||
* 3. Generate snapshot
|
||||
*
|
||||
* All 3 of these stages need to work on the same view else there will be major inconsistencies
|
||||
*
|
||||
* The first responsibility of this class is to track changes to root views, find the top most decor
|
||||
* view and add a pre draw observer to it. There should only ever be one active predraw listener at
|
||||
* once.
|
||||
*
|
||||
* This pre-draw observer triggers a full traversal of the UI, the traversal of the hierarchy might
|
||||
* skip some branches (active child) so its essential that both the active child decision and top
|
||||
* root decision match.
|
||||
*
|
||||
* The observer also triggers a snapshot, again its essential the same root view as we do for
|
||||
* traversal and observation
|
||||
*/
|
||||
class DecorViewTracker(private val context: UIDContext, private val snapshotter: Snapshotter) {
|
||||
|
||||
private var currentDecorView: View? = null
|
||||
private var preDrawListener: ViewTreeObserver.OnPreDrawListener? = null
|
||||
private val mStopWatch = StopWatch()
|
||||
|
||||
fun start() {
|
||||
|
||||
val applicationRef = context.applicationRef
|
||||
|
||||
val rootViewListener =
|
||||
object : RootViewResolver.Listener {
|
||||
override fun onRootViewAdded(rootView: View) {}
|
||||
|
||||
override fun onRootViewRemoved(rootView: View) {}
|
||||
|
||||
override fun onRootViewsChanged(rootViews: List<View>) {
|
||||
// remove predraw listen from current view as its going away or will be covered
|
||||
Log.i(LogTag, "Removing pre draw listener from ${currentDecorView?.objectIdentity()}")
|
||||
currentDecorView?.viewTreeObserver?.removeOnPreDrawListener(preDrawListener)
|
||||
|
||||
// setup new listener on top most view, that will be the active child in traversal
|
||||
|
||||
val decorViewToActivity: Map<View, Activity> = ActivityTracker.decorViewToActivityMap
|
||||
|
||||
val topView =
|
||||
rootViews.lastOrNull { view ->
|
||||
val activityOrView = decorViewToActivity[view] ?: view
|
||||
ApplicationRefDescriptor.isUsefulRoot(activityOrView)
|
||||
}
|
||||
|
||||
if (topView != null) {
|
||||
val throttler =
|
||||
Throttler(500) { currentDecorView?.let { traverseSnapshotAndSend(it) } }
|
||||
|
||||
preDrawListener =
|
||||
ViewTreeObserver.OnPreDrawListener {
|
||||
throttler.trigger()
|
||||
true
|
||||
}
|
||||
|
||||
topView.viewTreeObserver.addOnPreDrawListener(preDrawListener)
|
||||
currentDecorView = topView
|
||||
|
||||
Log.i(LogTag, "Added pre draw listener to ${topView.objectIdentity()}")
|
||||
|
||||
// schedule traversal immediately when we detect a new decor view
|
||||
throttler.trigger()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
context.applicationRef.rootsResolver.attachListener(rootViewListener)
|
||||
// On subscribe, trigger a traversal on whatever roots we have
|
||||
rootViewListener.onRootViewsChanged(applicationRef.rootsResolver.rootViews())
|
||||
|
||||
Log.i(
|
||||
LogTag,
|
||||
"Starting tracking root views, currently ${context.applicationRef.rootsResolver.rootViews().size} root views")
|
||||
}
|
||||
|
||||
fun stop() {
|
||||
context.applicationRef.rootsResolver.attachListener(null)
|
||||
currentDecorView?.viewTreeObserver?.removeOnPreDrawListener(preDrawListener)
|
||||
currentDecorView = null
|
||||
preDrawListener = null
|
||||
}
|
||||
|
||||
private suspend fun traverseSnapshotAndSend(decorView: View) {
|
||||
|
||||
val startTimestamp = System.currentTimeMillis()
|
||||
|
||||
val (nodes, traversalTime) =
|
||||
StopWatch.time { context.layoutTraversal.traverse(context.applicationRef) }
|
||||
|
||||
val (reusableBitmap, snapshotMs) = StopWatch.timeSuspend { snapshotter.takeSnapshot(decorView) }
|
||||
|
||||
context.updateQueue.enqueueUpdate(
|
||||
Update(
|
||||
ViewDescriptor.getId(decorView),
|
||||
nodes,
|
||||
startTimestamp,
|
||||
traversalTime,
|
||||
snapshotMs,
|
||||
System.currentTimeMillis(),
|
||||
reusableBitmap))
|
||||
}
|
||||
}
|
||||
@@ -5,11 +5,10 @@
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
package com.facebook.flipper.plugins.uidebugger.traversal
|
||||
package com.facebook.flipper.plugins.uidebugger.core
|
||||
|
||||
import android.util.Log
|
||||
import com.facebook.flipper.plugins.uidebugger.LogTag
|
||||
import com.facebook.flipper.plugins.uidebugger.core.UIDContext
|
||||
import com.facebook.flipper.plugins.uidebugger.descriptors.Id
|
||||
import com.facebook.flipper.plugins.uidebugger.descriptors.NodeDescriptor
|
||||
import com.facebook.flipper.plugins.uidebugger.model.Node
|
||||
@@ -23,21 +22,20 @@ import com.facebook.flipper.plugins.uidebugger.util.MaybeDeferred
|
||||
* - The first item in the pair is the visited nodes.
|
||||
* - The second item are any observable roots discovered.
|
||||
*/
|
||||
class PartialLayoutTraversal(
|
||||
class LayoutTraversal(
|
||||
private val context: UIDContext,
|
||||
) {
|
||||
|
||||
@Suppress("unchecked_cast")
|
||||
private fun NodeDescriptor<*>.asAny(): NodeDescriptor<Any> = this as NodeDescriptor<Any>
|
||||
|
||||
fun traverse(root: Any, parentId: Id?): Pair<List<MaybeDeferred<Node>>, List<Pair<Any, Id?>>> {
|
||||
fun traverse(root: Any): MutableList<MaybeDeferred<Node>> {
|
||||
|
||||
val visited = mutableListOf<MaybeDeferred<Node>>()
|
||||
val observableRoots = mutableListOf<Pair<Any, Id?>>()
|
||||
|
||||
// cur and parent Id
|
||||
val stack = mutableListOf<Pair<Any, Id?>>()
|
||||
stack.add(Pair(root, parentId))
|
||||
stack.add(Pair(root, null))
|
||||
|
||||
val shallow = mutableSetOf<Any>()
|
||||
|
||||
@@ -45,11 +43,6 @@ class PartialLayoutTraversal(
|
||||
val (node, parentId) = stack.removeLast()
|
||||
|
||||
try {
|
||||
// If we encounter a node that has it own observer, don't traverse
|
||||
if (node != root && context.observerFactory.hasObserverFor(node)) {
|
||||
observableRoots.add((node to parentId))
|
||||
continue
|
||||
}
|
||||
|
||||
val descriptor =
|
||||
context.descriptorRegister.descriptorForClassUnsafe(node::class.java).asAny()
|
||||
@@ -126,6 +119,6 @@ class PartialLayoutTraversal(
|
||||
}
|
||||
}
|
||||
|
||||
return Pair(visited, observableRoots)
|
||||
return visited
|
||||
}
|
||||
}
|
||||
@@ -10,12 +10,14 @@ package com.facebook.flipper.plugins.uidebugger.core
|
||||
import android.annotation.SuppressLint
|
||||
import android.os.Build
|
||||
import android.view.View
|
||||
import com.facebook.flipper.plugins.uidebugger.util.WindowManagerCommon
|
||||
import java.lang.reflect.Field
|
||||
import java.lang.reflect.InvocationTargetException
|
||||
import java.lang.reflect.Modifier
|
||||
|
||||
/**
|
||||
* Provides access to all root views in an application.
|
||||
* Provides access to all root views in an application, as well as the ability to listen to changes
|
||||
* in root views
|
||||
*
|
||||
* 95% of the time this is unnecessary and we can operate solely on current Activity's root view as
|
||||
* indicated by getWindow().getDecorView(). However in the case of popup windows, menus, and dialogs
|
||||
@@ -67,15 +69,13 @@ class RootViewResolver {
|
||||
private fun initialize() {
|
||||
|
||||
initialized = true
|
||||
val accessClass =
|
||||
if (Build.VERSION.SDK_INT > 16) WINDOW_MANAGER_GLOBAL_CLAZZ else WINDOW_MANAGER_IMPL_CLAZZ
|
||||
val instanceMethod = if (Build.VERSION.SDK_INT > 16) GET_GLOBAL_INSTANCE else GET_DEFAULT_IMPL
|
||||
try {
|
||||
val clazz = Class.forName(accessClass)
|
||||
val getMethod = clazz.getMethod(instanceMethod)
|
||||
windowManagerObj = getMethod.invoke(null)
|
||||
|
||||
val viewsField: Field = clazz.getDeclaredField(VIEWS_FIELD)
|
||||
val (windowManager, windowManagerClas) =
|
||||
WindowManagerCommon.getGlobalWindowManager() ?: return
|
||||
windowManagerObj = windowManager
|
||||
|
||||
val viewsField: Field = windowManagerClas.getDeclaredField(VIEWS_FIELD)
|
||||
|
||||
viewsField.let { vf ->
|
||||
vf.isAccessible = true
|
||||
@@ -98,11 +98,7 @@ class RootViewResolver {
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val WINDOW_MANAGER_IMPL_CLAZZ = "android.view.WindowManagerImpl"
|
||||
private const val WINDOW_MANAGER_GLOBAL_CLAZZ = "android.view.WindowManagerGlobal"
|
||||
private const val VIEWS_FIELD = "mViews"
|
||||
private const val GET_DEFAULT_IMPL = "getDefault"
|
||||
private const val GET_GLOBAL_INSTANCE = "getInstance"
|
||||
}
|
||||
|
||||
class ObservableViewArrayList : ArrayList<View>() {
|
||||
|
||||
@@ -0,0 +1,168 @@
|
||||
/*
|
||||
* 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.core
|
||||
|
||||
import android.app.Activity
|
||||
import android.graphics.Canvas
|
||||
import android.os.Build
|
||||
import android.os.Handler
|
||||
import android.os.Looper
|
||||
import android.util.Log
|
||||
import android.view.PixelCopy
|
||||
import android.view.View
|
||||
import androidx.annotation.RequiresApi
|
||||
import com.facebook.flipper.plugins.uidebugger.LogTag
|
||||
import kotlin.coroutines.Continuation
|
||||
import kotlin.coroutines.resume
|
||||
import kotlin.coroutines.suspendCoroutine
|
||||
|
||||
interface Snapshotter {
|
||||
suspend fun takeSnapshot(view: View): BitmapPool.ReusableBitmap?
|
||||
}
|
||||
|
||||
/**
|
||||
* Takes a snapshot by redrawing the view into a bitmap backed canvas, Since this is software
|
||||
* rendering there can be discrepancies between the real image and the snapshot:
|
||||
* 1. It can be unreliable when snapshotting views that are added directly to window manager
|
||||
* 2. It doesn't include certain types of content (video / images)
|
||||
*/
|
||||
class CanvasSnapshotter(private val bitmapPool: BitmapPool) : Snapshotter {
|
||||
override suspend fun takeSnapshot(view: View): BitmapPool.ReusableBitmap? {
|
||||
|
||||
return SnapshotCommon.doSnapshotWithErrorHandling(bitmapPool, view, fallback = null) { bitmap ->
|
||||
val canvas = Canvas(bitmap.bitmap)
|
||||
view.draw(canvas)
|
||||
true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Uses the new api to snapshot any view regardless whether its attached to a activity or not,
|
||||
* requires no hacks
|
||||
*/
|
||||
@RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
|
||||
class ModernPixelCopySnapshotter(
|
||||
private val bitmapPool: BitmapPool,
|
||||
private val fallback: Snapshotter
|
||||
) : Snapshotter {
|
||||
private var handler = Handler(Looper.getMainLooper())
|
||||
|
||||
override suspend fun takeSnapshot(view: View): BitmapPool.ReusableBitmap? {
|
||||
|
||||
return if (view.isHardwareAccelerated) {
|
||||
SnapshotCommon.doSnapshotWithErrorHandling(bitmapPool, view, fallback) { reusableBitmap ->
|
||||
suspendCoroutine { continuation ->
|
||||
// Since android U this api is actually async
|
||||
val request =
|
||||
PixelCopy.Request.Builder.ofWindow(view)
|
||||
.setDestinationBitmap(reusableBitmap.bitmap)
|
||||
.build()
|
||||
PixelCopy.request(
|
||||
request,
|
||||
{ handler.post(it) },
|
||||
{ continuation.resume(it.status == PixelCopy.SUCCESS) })
|
||||
}
|
||||
}
|
||||
} else {
|
||||
fallback.takeSnapshot(view)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Uses pixel copy api to do a snapshot, this is accurate but prior to android U we have to use a
|
||||
* bit of hack to get the surface for root views not associated to an activity (added directly to
|
||||
* the window manager)
|
||||
*/
|
||||
@RequiresApi(Build.VERSION_CODES.O)
|
||||
class PixelCopySnapshotter(
|
||||
private val bitmapPool: BitmapPool,
|
||||
private val applicationRef: ApplicationRef,
|
||||
private val fallback: Snapshotter
|
||||
) : Snapshotter {
|
||||
private var handler = Handler(Looper.getMainLooper())
|
||||
|
||||
override suspend fun takeSnapshot(view: View): BitmapPool.ReusableBitmap? {
|
||||
|
||||
return if (view.isHardwareAccelerated) {
|
||||
SnapshotCommon.doSnapshotWithErrorHandling(bitmapPool, view, fallback) {
|
||||
tryCopyViaActivityWindow(view, it) || tryCopyViaInternalSurface(view, it)
|
||||
}
|
||||
} else {
|
||||
fallback.takeSnapshot(view)
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun tryCopyViaActivityWindow(
|
||||
view: View,
|
||||
bitmap: BitmapPool.ReusableBitmap
|
||||
): Boolean {
|
||||
|
||||
val decorViewToActivity: Map<View, Activity> = ActivityTracker.decorViewToActivityMap
|
||||
|
||||
val activityForDecorView = decorViewToActivity[view] ?: return false
|
||||
|
||||
return suspendCoroutine { continuation ->
|
||||
PixelCopy.request(
|
||||
activityForDecorView.window, bitmap.bitmap, pixelCopyCallback(continuation), handler)
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun tryCopyViaInternalSurface(
|
||||
view: View,
|
||||
bitmap: BitmapPool.ReusableBitmap
|
||||
): Boolean {
|
||||
val surface = applicationRef.windowManagerUtility.surfaceForRootView(view) ?: return false
|
||||
|
||||
return suspendCoroutine { continuation ->
|
||||
PixelCopy.request(surface, bitmap.bitmap, pixelCopyCallback(continuation), handler)
|
||||
}
|
||||
}
|
||||
|
||||
private fun pixelCopyCallback(continuation: Continuation<Boolean>): (Int) -> Unit =
|
||||
{ result: Int ->
|
||||
if (result == PixelCopy.SUCCESS) {
|
||||
continuation.resume(true)
|
||||
} else {
|
||||
Log.w(LogTag, "Pixel copy failed, code $result")
|
||||
continuation.resume(false)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal object SnapshotCommon {
|
||||
|
||||
internal suspend fun doSnapshotWithErrorHandling(
|
||||
bitmapPool: BitmapPool,
|
||||
view: View,
|
||||
fallback: Snapshotter?,
|
||||
snapshotStrategy: suspend (reuseableBitmap: BitmapPool.ReusableBitmap) -> Boolean
|
||||
): BitmapPool.ReusableBitmap? {
|
||||
if (view.width <= 0 || view.height <= 0) {
|
||||
return null
|
||||
}
|
||||
var reusableBitmap: BitmapPool.ReusableBitmap? = null
|
||||
try {
|
||||
reusableBitmap = bitmapPool.getBitmap(view.width, view.height)
|
||||
if (snapshotStrategy(reusableBitmap)) {
|
||||
return reusableBitmap
|
||||
}
|
||||
} catch (e: OutOfMemoryError) {
|
||||
Log.e(LogTag, "OOM when taking snapshot")
|
||||
} catch (e: Exception) {
|
||||
// there was some problem with the pixel copy, fall back to canvas impl
|
||||
Log.e(LogTag, "Exception when taking snapshot", e)
|
||||
}
|
||||
|
||||
// something went wrong, use fallback, make sure to give bitmap back to pool first
|
||||
Log.i(LogTag, "Using fallback snapshot method")
|
||||
reusableBitmap?.readyForReuse()
|
||||
return fallback?.takeSnapshot(view)
|
||||
}
|
||||
}
|
||||
@@ -8,16 +8,14 @@
|
||||
package com.facebook.flipper.plugins.uidebugger.core
|
||||
|
||||
import android.app.Application
|
||||
import android.os.Build
|
||||
import android.util.Log
|
||||
import com.facebook.flipper.core.FlipperConnection
|
||||
import com.facebook.flipper.plugins.uidebugger.common.BitmapPool
|
||||
import com.facebook.flipper.plugins.uidebugger.LogTag
|
||||
import com.facebook.flipper.plugins.uidebugger.descriptors.DescriptorRegister
|
||||
import com.facebook.flipper.plugins.uidebugger.model.FrameworkEvent
|
||||
import com.facebook.flipper.plugins.uidebugger.model.FrameworkEventMetadata
|
||||
import com.facebook.flipper.plugins.uidebugger.model.TraversalError
|
||||
import com.facebook.flipper.plugins.uidebugger.observers.TreeObserverFactory
|
||||
import com.facebook.flipper.plugins.uidebugger.observers.TreeObserverManager
|
||||
import com.facebook.flipper.plugins.uidebugger.scheduler.SharedThrottle
|
||||
import com.facebook.flipper.plugins.uidebugger.traversal.PartialLayoutTraversal
|
||||
import kotlinx.serialization.json.Json
|
||||
|
||||
interface ConnectionListener {
|
||||
@@ -30,17 +28,29 @@ class UIDContext(
|
||||
val applicationRef: ApplicationRef,
|
||||
val connectionRef: ConnectionRef,
|
||||
val descriptorRegister: DescriptorRegister,
|
||||
val observerFactory: TreeObserverFactory,
|
||||
val frameworkEventMetadata: MutableList<FrameworkEventMetadata>,
|
||||
val connectionListeners: MutableList<ConnectionListener>,
|
||||
private val pendingFrameworkEvents: MutableList<FrameworkEvent>
|
||||
) {
|
||||
|
||||
val layoutTraversal: PartialLayoutTraversal = PartialLayoutTraversal(this)
|
||||
|
||||
val treeObserverManager = TreeObserverManager(this)
|
||||
val sharedThrottle: SharedThrottle = SharedThrottle()
|
||||
val bitmapPool = BitmapPool()
|
||||
private val canvasSnapshotter = CanvasSnapshotter(bitmapPool)
|
||||
|
||||
private val snapshotter =
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) {
|
||||
ModernPixelCopySnapshotter(bitmapPool, canvasSnapshotter)
|
||||
} else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||
PixelCopySnapshotter(bitmapPool, applicationRef, canvasSnapshotter)
|
||||
} else {
|
||||
Log.w(
|
||||
LogTag,
|
||||
"Using legacy snapshot mode, use device with API level >=26 to for pixel copy snapshot ")
|
||||
canvasSnapshotter
|
||||
}
|
||||
|
||||
val decorViewTracker: DecorViewTracker = DecorViewTracker(this, snapshotter)
|
||||
val updateQueue: UpdateQueue = UpdateQueue(this)
|
||||
val layoutTraversal: LayoutTraversal = LayoutTraversal(this)
|
||||
|
||||
fun addFrameworkEvent(frameworkEvent: FrameworkEvent) {
|
||||
synchronized(pendingFrameworkEvents) { pendingFrameworkEvents.add(frameworkEvent) }
|
||||
@@ -69,7 +79,6 @@ class UIDContext(
|
||||
ApplicationRef(application),
|
||||
ConnectionRef(null),
|
||||
descriptorRegister = DescriptorRegister.withDefaults(),
|
||||
observerFactory = TreeObserverFactory.withDefaults(),
|
||||
frameworkEventMetadata = mutableListOf(),
|
||||
connectionListeners = mutableListOf(),
|
||||
pendingFrameworkEvents = mutableListOf())
|
||||
|
||||
@@ -0,0 +1,163 @@
|
||||
/*
|
||||
* 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.core
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.graphics.Bitmap
|
||||
import android.util.Base64
|
||||
import android.util.Base64OutputStream
|
||||
import android.util.Log
|
||||
import com.facebook.flipper.plugins.uidebugger.LogTag
|
||||
import com.facebook.flipper.plugins.uidebugger.descriptors.Id
|
||||
import com.facebook.flipper.plugins.uidebugger.descriptors.MetadataRegister
|
||||
import com.facebook.flipper.plugins.uidebugger.model.FrameScanEvent
|
||||
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.Snapshot
|
||||
import com.facebook.flipper.plugins.uidebugger.model.TraversalError
|
||||
import com.facebook.flipper.plugins.uidebugger.util.MaybeDeferred
|
||||
import com.facebook.flipper.plugins.uidebugger.util.StopWatch
|
||||
import java.io.ByteArrayOutputStream
|
||||
import kotlinx.coroutines.*
|
||||
import kotlinx.coroutines.channels.Channel
|
||||
import kotlinx.coroutines.channels.Channel.Factory.CONFLATED
|
||||
import kotlinx.serialization.json.Json
|
||||
|
||||
data class Update(
|
||||
val snapshotNode: Id,
|
||||
val deferredNodes: List<MaybeDeferred<Node>>,
|
||||
val startTimestamp: Long,
|
||||
val traversalMS: Long,
|
||||
val snapshotMS: Long,
|
||||
val queuedTimestamp: Long,
|
||||
val snapshotBitmap: BitmapPool.ReusableBitmap?
|
||||
)
|
||||
|
||||
/**
|
||||
* Holds an update and manages a coroutine which serially reads from queue and sends to flipper
|
||||
* desktop
|
||||
*/
|
||||
class UpdateQueue(val context: UIDContext) {
|
||||
|
||||
// conflated channel means we only hold 1 item and newer values override older ones,
|
||||
// there is no point processing frames that the desktop cant keep up with since we only display
|
||||
// the latest
|
||||
private val frameChannel = Channel<Update>(CONFLATED)
|
||||
|
||||
private var job: Job? = null
|
||||
private val workerScope = CoroutineScope(Dispatchers.IO)
|
||||
private val stopWatch = StopWatch()
|
||||
|
||||
fun enqueueUpdate(update: Update) {
|
||||
frameChannel.trySend(update)
|
||||
}
|
||||
|
||||
/**
|
||||
* 1. Sets up the root observer
|
||||
* 2. Starts worker to listen to channel, which serializers and sends data over connection
|
||||
*/
|
||||
@SuppressLint("NewApi")
|
||||
fun start() {
|
||||
|
||||
job =
|
||||
workerScope.launch {
|
||||
while (isActive) {
|
||||
try {
|
||||
val update = frameChannel.receive()
|
||||
sendUpdate(update)
|
||||
} catch (e: CancellationException) {} catch (e: java.lang.Exception) {
|
||||
Log.e(LogTag, "Unexpected Error in channel ", e)
|
||||
}
|
||||
}
|
||||
Log.i(LogTag, "Shutting down worker")
|
||||
}
|
||||
}
|
||||
|
||||
fun stop() {
|
||||
job?.cancel()
|
||||
job = null
|
||||
// drain channel
|
||||
frameChannel.tryReceive()
|
||||
}
|
||||
|
||||
private fun sendUpdate(update: Update) {
|
||||
|
||||
val queuingTimeMs = System.currentTimeMillis() - update.queuedTimestamp
|
||||
|
||||
stopWatch.start()
|
||||
val nodes =
|
||||
try {
|
||||
update.deferredNodes.map { it.value() }
|
||||
} catch (exception: Exception) {
|
||||
context.onError(
|
||||
TraversalError(
|
||||
"DeferredProcessing",
|
||||
exception.javaClass.simpleName,
|
||||
exception.message ?: "",
|
||||
exception.stackTraceToString()))
|
||||
return
|
||||
}
|
||||
|
||||
val deferredComputationEndTimestamp = stopWatch.stop()
|
||||
|
||||
val frameworkEvents = context.extractPendingFrameworkEvents()
|
||||
|
||||
var snapshot: Snapshot? = null
|
||||
if (update.snapshotBitmap != null) {
|
||||
val stream = ByteArrayOutputStream()
|
||||
val base64Stream = Base64OutputStream(stream, Base64.DEFAULT)
|
||||
update.snapshotBitmap.bitmap.compress(Bitmap.CompressFormat.PNG, 100, base64Stream)
|
||||
snapshot = Snapshot(update.snapshotNode, stream.toString())
|
||||
update.snapshotBitmap.readyForReuse()
|
||||
}
|
||||
|
||||
// it is important this comes after deferred processing since the deferred processing can create
|
||||
// metadata
|
||||
sendMetadata()
|
||||
|
||||
val (serialized, serializationTimeMs) =
|
||||
StopWatch.time {
|
||||
Json.encodeToString(
|
||||
FrameScanEvent.serializer(),
|
||||
FrameScanEvent(update.startTimestamp, nodes, snapshot, frameworkEvents))
|
||||
}
|
||||
|
||||
val (_, sendTimeMs) =
|
||||
StopWatch.time { context.connectionRef.connection?.send(FrameScanEvent.name, serialized) }
|
||||
|
||||
// Note about payload size:
|
||||
// Payload size is an approximation as it assumes all characters
|
||||
// are ASCII encodable, this should be true for most of the payload content.
|
||||
// So, assume each character will at most occupy one byte.
|
||||
val perfStats =
|
||||
PerfStatsEvent(
|
||||
txId = update.startTimestamp,
|
||||
nodesCount = nodes.size,
|
||||
start = update.startTimestamp,
|
||||
traversalMS = update.traversalMS,
|
||||
snapshotMS = update.snapshotMS,
|
||||
queuingMS = queuingTimeMs,
|
||||
deferredComputationMS = deferredComputationEndTimestamp,
|
||||
serializationMS = serializationTimeMs,
|
||||
socketMS = sendTimeMs,
|
||||
payloadSize = serialized.length)
|
||||
|
||||
context.connectionRef.connection?.send(
|
||||
PerfStatsEvent.name, Json.encodeToString(PerfStatsEvent.serializer(), perfStats))
|
||||
}
|
||||
|
||||
private fun sendMetadata() {
|
||||
val metadata = MetadataRegister.extractPendingMetadata()
|
||||
if (metadata.isNotEmpty()) {
|
||||
context.connectionRef.connection?.send(
|
||||
MetadataUpdateEvent.name,
|
||||
Json.encodeToString(MetadataUpdateEvent.serializer(), MetadataUpdateEvent(metadata)))
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,93 @@
|
||||
/*
|
||||
* 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.core
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.util.Log
|
||||
import android.view.Surface
|
||||
import android.view.View
|
||||
import com.facebook.flipper.plugins.uidebugger.LogTag
|
||||
import com.facebook.flipper.plugins.uidebugger.util.WindowManagerCommon
|
||||
import java.lang.reflect.Field
|
||||
|
||||
/**
|
||||
* This class is related to root view resolver, it also accesses parts of the global window manager
|
||||
*/
|
||||
class WindowManagerUtility {
|
||||
|
||||
private var initialized = false
|
||||
|
||||
// type is RootViewImpl
|
||||
private var rootsImpls: ArrayList<*>? = null
|
||||
private var mSurfaceField: Field? = null
|
||||
private var mViewField: Field? = null
|
||||
|
||||
/**
|
||||
* Find the surface for a given root view to allow snapshotting with pixelcopy. In the window
|
||||
* manager there exists 2 arrays that contain similar data
|
||||
* 1. mViews (this is what we track in the observable array in the root view resolver), these are
|
||||
* the root decor views
|
||||
* 2. mRoots - this is an internal class that holds a reference to the decor view as well as other
|
||||
* internal bits (including the surface).
|
||||
*
|
||||
* Therefore we go through the roots and check for a view that matches the target, if it
|
||||
* matches we return the surface. It is possible for us to observe these 2 arrays slightly out
|
||||
* of sync with each other which is why we do this equality matching on the view field
|
||||
*
|
||||
* The reason we do this and not just grab the last view is because sometimes there is a
|
||||
* 'empty' root view at the top we need to ignore. The decision to decide which view to
|
||||
* snapshot is done else where as it needs to be synced with the observation and traversal
|
||||
*/
|
||||
fun surfaceForRootView(rootView: View): Surface? {
|
||||
if (!initialized) {
|
||||
initialize()
|
||||
}
|
||||
|
||||
val roots = rootsImpls ?: return null
|
||||
for (i in roots.size - 1 downTo 0) {
|
||||
val rootViewImpl = roots[i]
|
||||
val view = mViewField?.get(rootViewImpl)
|
||||
if (view == rootView) {
|
||||
return mSurfaceField?.get(rootViewImpl) as? Surface
|
||||
}
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
@SuppressLint("PrivateApi")
|
||||
private fun initialize() {
|
||||
|
||||
try {
|
||||
val (windowManager, windowManagerClass) =
|
||||
WindowManagerCommon.getGlobalWindowManager() ?: return
|
||||
|
||||
val rootsField: Field = windowManagerClass.getDeclaredField(ROOTS_FIELD)
|
||||
rootsField.isAccessible = true
|
||||
rootsImpls = rootsField.get(windowManager) as ArrayList<*>?
|
||||
|
||||
val rootViewImplClass = Class.forName(VIEW_ROOT_IMPL_CLAZZ)
|
||||
mSurfaceField = rootViewImplClass.getDeclaredField(SURFACE_FIELD)
|
||||
mSurfaceField?.isAccessible = true
|
||||
|
||||
mViewField = rootViewImplClass.getDeclaredField(VIEW_FIELD)
|
||||
mViewField?.isAccessible = true
|
||||
|
||||
initialized = true
|
||||
} catch (exception: Exception) {
|
||||
Log.e(LogTag, "Failed to initialize WindowManagerUtility", exception)
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val VIEW_ROOT_IMPL_CLAZZ = "android.view.ViewRootImpl"
|
||||
private const val SURFACE_FIELD = "mSurface"
|
||||
private const val VIEW_FIELD = "mView"
|
||||
private const val ROOTS_FIELD = "mRoots"
|
||||
}
|
||||
}
|
||||
@@ -10,6 +10,7 @@ package com.facebook.flipper.plugins.uidebugger.descriptors
|
||||
import android.app.Activity
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import com.facebook.flipper.plugins.uidebugger.core.ActivityTracker
|
||||
import com.facebook.flipper.plugins.uidebugger.core.ApplicationRef
|
||||
import com.facebook.flipper.plugins.uidebugger.model.Bounds
|
||||
import com.facebook.flipper.plugins.uidebugger.util.DisplayMetrics
|
||||
@@ -18,15 +19,7 @@ object ApplicationRefDescriptor : ChainedDescriptor<ApplicationRef>() {
|
||||
|
||||
override fun onGetActiveChild(node: ApplicationRef): Any? {
|
||||
val children = onGetChildren(node)
|
||||
if (children.isNotEmpty()) {
|
||||
val last = children.last()
|
||||
if (last.javaClass.simpleName.contains("OverlayHandlerView")) {
|
||||
return children.getOrNull(children.size - 2)
|
||||
}
|
||||
return last
|
||||
}
|
||||
|
||||
return null
|
||||
return children.lastOrNull(ApplicationRefDescriptor::isUsefulRoot)
|
||||
}
|
||||
|
||||
override fun onGetBounds(node: ApplicationRef): Bounds = DisplayMetrics.getDisplayBounds()
|
||||
@@ -41,26 +34,51 @@ object ApplicationRefDescriptor : ChainedDescriptor<ApplicationRef>() {
|
||||
override fun onGetChildren(node: ApplicationRef): List<Any> {
|
||||
val children = mutableListOf<Any>()
|
||||
|
||||
val activeRoots = node.rootsResolver.rootViews()
|
||||
val rootViews = node.rootsResolver.rootViews()
|
||||
|
||||
val decorViewToActivity: Map<View, Activity> =
|
||||
node.activitiesStack.toList().map { it.window.decorView to it }.toMap()
|
||||
val decorViewToActivity: Map<View, Activity> = ActivityTracker.decorViewToActivityMap
|
||||
|
||||
for (root in activeRoots) {
|
||||
for (root in rootViews) {
|
||||
// if there is an activity for this root view use that,
|
||||
// if not just return the mystery floating decor view
|
||||
// if not just return the root view that was added directly to the window manager
|
||||
val activity = decorViewToActivity[root]
|
||||
if (activity != null) {
|
||||
children.add(activity)
|
||||
} else {
|
||||
if (root is ViewGroup && root.childCount > 0) {
|
||||
// sometimes there is a root view on top that has no children and we dont want to add
|
||||
// these as they will become active
|
||||
children.add(root)
|
||||
}
|
||||
children.add(root)
|
||||
}
|
||||
}
|
||||
|
||||
return children
|
||||
}
|
||||
|
||||
/**
|
||||
* arg is either an acitivity if the root view has one other views the root view attached to the
|
||||
* window manager returns boolean indicating whether we are interested in it and whether we should
|
||||
* track, traverse and snapshot it
|
||||
*/
|
||||
fun isUsefulRoot(rootViewOrActivity: Any): Boolean {
|
||||
val className = rootViewOrActivity.javaClass.name
|
||||
|
||||
if (className.contains("mediagallery.ui.MediaGalleryActivity")) {
|
||||
// this activity doesn't contain the content and its actually in the decor view behind it, so
|
||||
// skip it :/
|
||||
return false
|
||||
}
|
||||
|
||||
if (rootViewOrActivity is Activity) {
|
||||
// in general we want views attached to activities
|
||||
return true
|
||||
}
|
||||
|
||||
val isFoldableOverlayInfraView = className.contains("OverlayHandlerView")
|
||||
return if (isFoldableOverlayInfraView) {
|
||||
false
|
||||
} else if (rootViewOrActivity is ViewGroup) {
|
||||
// sometimes there is a root view on top that has no children that isn't useful to inspect
|
||||
rootViewOrActivity.childCount > 0
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,7 +7,6 @@
|
||||
|
||||
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
|
||||
@@ -133,15 +132,6 @@ abstract class ChainedDescriptor<T> : NodeDescriptor<T> {
|
||||
*/
|
||||
open fun onGetAttributes(node: T, attributeSections: MutableMap<MetadataId, InspectableObject>) {}
|
||||
|
||||
/** Get a snapshot of the node. */
|
||||
final override fun getSnapshot(node: T, bitmap: Bitmap?): Bitmap? {
|
||||
return onGetSnapshot(node, bitmap) ?: mSuper?.onGetSnapshot(node, bitmap)
|
||||
}
|
||||
|
||||
open fun onGetSnapshot(node: T, bitmap: Bitmap?): Bitmap? {
|
||||
return null
|
||||
}
|
||||
|
||||
final override fun getInlineAttributes(node: T): Map<String, String> {
|
||||
|
||||
val builder = mutableMapOf<String, String>()
|
||||
|
||||
@@ -16,8 +16,8 @@ import android.view.Window
|
||||
import android.widget.ImageView
|
||||
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
|
||||
import com.facebook.flipper.plugins.uidebugger.util.UIDebuggerException
|
||||
|
||||
class DescriptorRegister {
|
||||
private val register: MutableMap<Class<*>, NodeDescriptor<*>> = HashMap()
|
||||
|
||||
@@ -9,12 +9,12 @@ package com.facebook.flipper.plugins.uidebugger.descriptors
|
||||
|
||||
import android.widget.ImageView
|
||||
import android.widget.ImageView.ScaleType
|
||||
import com.facebook.flipper.plugins.uidebugger.common.EnumMapping
|
||||
import com.facebook.flipper.plugins.uidebugger.common.enumMapping
|
||||
import com.facebook.flipper.plugins.uidebugger.common.enumToInspectableSet
|
||||
import com.facebook.flipper.plugins.uidebugger.model.Inspectable
|
||||
import com.facebook.flipper.plugins.uidebugger.model.InspectableObject
|
||||
import com.facebook.flipper.plugins.uidebugger.model.MetadataId
|
||||
import com.facebook.flipper.plugins.uidebugger.util.EnumMapping
|
||||
import com.facebook.flipper.plugins.uidebugger.util.enumMapping
|
||||
import com.facebook.flipper.plugins.uidebugger.util.enumToInspectableSet
|
||||
|
||||
object ImageViewDescriptor : ChainedDescriptor<ImageView>() {
|
||||
|
||||
|
||||
@@ -7,7 +7,6 @@
|
||||
|
||||
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
|
||||
@@ -58,13 +57,6 @@ interface NodeDescriptor<T> {
|
||||
/** The children this node exposes in the inspector. */
|
||||
fun getChildren(node: T): List<Any>
|
||||
|
||||
/**
|
||||
* Get a snapshot of the node. Bitmaps are not cheap to create, so accept one as an optional
|
||||
* parameter. If a bitmap is provided, it will be used by the canvas to draw on it. Otherwise, a
|
||||
* bitmap will be created.
|
||||
*/
|
||||
fun getSnapshot(node: T, bitmap: Bitmap?): Bitmap? = null
|
||||
|
||||
/**
|
||||
* 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'
|
||||
|
||||
@@ -7,7 +7,6 @@
|
||||
|
||||
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
|
||||
@@ -34,6 +33,4 @@ object ObjectDescriptor : NodeDescriptor<Any> {
|
||||
override fun getBounds(node: Any): Bounds = Bounds(0, 0, 0, 0)
|
||||
|
||||
override fun getTags(node: Any): Set<String> = setOf(BaseTags.Unknown)
|
||||
|
||||
override fun getSnapshot(node: Any, bitmap: Bitmap?): Bitmap? = null
|
||||
}
|
||||
|
||||
@@ -7,7 +7,6 @@
|
||||
|
||||
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
|
||||
@@ -42,7 +41,4 @@ object OffsetChildDescriptor : NodeDescriptor<OffsetChild> {
|
||||
node.descriptor.getAttributes(node.child)
|
||||
|
||||
override fun getTags(node: OffsetChild): Set<String> = node.descriptor.getTags(node.child)
|
||||
|
||||
override fun getSnapshot(node: OffsetChild, bitmap: Bitmap?): Bitmap? =
|
||||
node.descriptor.getSnapshot(node.child, bitmap)
|
||||
}
|
||||
|
||||
@@ -8,8 +8,6 @@
|
||||
package com.facebook.flipper.plugins.uidebugger.descriptors
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.graphics.Bitmap
|
||||
import android.graphics.Canvas
|
||||
import android.graphics.Rect
|
||||
import android.graphics.drawable.ColorDrawable
|
||||
import android.graphics.drawable.Drawable
|
||||
@@ -21,8 +19,8 @@ import android.view.ViewGroup
|
||||
import android.widget.FrameLayout
|
||||
import android.widget.LinearLayout
|
||||
import androidx.viewpager.widget.ViewPager
|
||||
import com.facebook.flipper.plugins.uidebugger.common.*
|
||||
import com.facebook.flipper.plugins.uidebugger.model.*
|
||||
import com.facebook.flipper.plugins.uidebugger.util.EnumMapping
|
||||
import com.facebook.flipper.plugins.uidebugger.util.ResourcesUtil
|
||||
import java.lang.reflect.Field
|
||||
import kotlinx.serialization.json.JsonElement
|
||||
@@ -380,30 +378,6 @@ object ViewDescriptor : ChainedDescriptor<View>() {
|
||||
attributes["id"] = value
|
||||
}
|
||||
|
||||
override fun onGetSnapshot(node: View, bitmap: Bitmap?): Bitmap? {
|
||||
if (node.width <= 0 || node.height <= 0) {
|
||||
return null
|
||||
}
|
||||
var workingBitmap = bitmap
|
||||
|
||||
try {
|
||||
val differentSize =
|
||||
if (bitmap != null) (node.width != bitmap.width || node.height != bitmap.height)
|
||||
else false
|
||||
if (workingBitmap == null || differentSize) {
|
||||
val viewWidth: Int = node.width
|
||||
val viewHeight: Int = node.height
|
||||
|
||||
workingBitmap = BitmapPool.createBitmapWithDefaultConfig(viewWidth, viewHeight)
|
||||
}
|
||||
|
||||
val canvas = Canvas(workingBitmap)
|
||||
node.draw(canvas)
|
||||
} catch (e: OutOfMemoryError) {}
|
||||
|
||||
return workingBitmap
|
||||
}
|
||||
|
||||
private fun fromDrawable(d: Drawable?): Inspectable? {
|
||||
return if (d is ColorDrawable) {
|
||||
InspectableValue.Color(Color.fromColor(d.color))
|
||||
|
||||
@@ -11,9 +11,9 @@ import android.os.Build
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import androidx.core.view.ViewGroupCompat
|
||||
import com.facebook.flipper.plugins.uidebugger.common.EnumMapping
|
||||
import com.facebook.flipper.plugins.uidebugger.core.FragmentTracker
|
||||
import com.facebook.flipper.plugins.uidebugger.model.*
|
||||
import com.facebook.flipper.plugins.uidebugger.util.EnumMapping
|
||||
|
||||
object ViewGroupDescriptor : ChainedDescriptor<ViewGroup>() {
|
||||
|
||||
|
||||
@@ -53,7 +53,6 @@ class TraversalError(
|
||||
@kotlinx.serialization.Serializable
|
||||
class PerfStatsEvent(
|
||||
val txId: Long,
|
||||
val observerType: String,
|
||||
val nodesCount: Int,
|
||||
val start: Long,
|
||||
val traversalMS: Long,
|
||||
|
||||
@@ -1,60 +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.observers
|
||||
|
||||
import android.util.Log
|
||||
import android.view.View
|
||||
import com.facebook.flipper.plugins.uidebugger.LogTag
|
||||
import com.facebook.flipper.plugins.uidebugger.core.ApplicationRef
|
||||
import com.facebook.flipper.plugins.uidebugger.core.RootViewResolver
|
||||
import com.facebook.flipper.plugins.uidebugger.core.UIDContext
|
||||
import com.facebook.flipper.plugins.uidebugger.descriptors.Id
|
||||
import com.facebook.flipper.plugins.uidebugger.util.objectIdentity
|
||||
|
||||
/**
|
||||
* Responsible for observing the activity stack and managing the subscription to the top most
|
||||
* content view (decor view)
|
||||
*/
|
||||
class ApplicationTreeObserver(val context: UIDContext) : TreeObserver<ApplicationRef>() {
|
||||
|
||||
override val type = "Application"
|
||||
|
||||
override fun subscribe(node: Any, parentId: Id?) {
|
||||
Log.i(LogTag, "Subscribing activity / root view changes")
|
||||
|
||||
val applicationRef = node as ApplicationRef
|
||||
|
||||
val rootViewListener =
|
||||
object : RootViewResolver.Listener {
|
||||
override fun onRootViewAdded(rootView: View) {}
|
||||
|
||||
override fun onRootViewRemoved(rootView: View) {}
|
||||
|
||||
override fun onRootViewsChanged(rootViews: List<View>) {
|
||||
Log.i(LogTag, "Root views updated, num ${rootViews.size}")
|
||||
context.sharedThrottle.trigger()
|
||||
}
|
||||
}
|
||||
|
||||
context.sharedThrottle.registerCallback(this.objectIdentity()) {
|
||||
traverseAndSend(null, context, applicationRef)
|
||||
}
|
||||
|
||||
context.applicationRef.rootsResolver.attachListener(rootViewListener)
|
||||
// On subscribe, trigger a traversal on whatever roots we have
|
||||
rootViewListener.onRootViewsChanged(applicationRef.rootsResolver.rootViews())
|
||||
|
||||
Log.i(LogTag, "${context.applicationRef.rootsResolver.rootViews().size} root views")
|
||||
Log.i(LogTag, "${context.applicationRef.activitiesStack.size} activities")
|
||||
}
|
||||
|
||||
override fun unsubscribe() {
|
||||
context.applicationRef.rootsResolver.attachListener(null)
|
||||
context.sharedThrottle.deregisterCallback(this.objectIdentity())
|
||||
}
|
||||
}
|
||||
@@ -1,91 +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.observers
|
||||
|
||||
import android.util.Log
|
||||
import android.view.View
|
||||
import android.view.ViewTreeObserver
|
||||
import com.facebook.flipper.plugins.uidebugger.LogTag
|
||||
import com.facebook.flipper.plugins.uidebugger.common.BitmapPool
|
||||
import com.facebook.flipper.plugins.uidebugger.core.UIDContext
|
||||
import com.facebook.flipper.plugins.uidebugger.descriptors.Id
|
||||
import com.facebook.flipper.plugins.uidebugger.util.objectIdentity
|
||||
import java.lang.ref.WeakReference
|
||||
|
||||
typealias DecorView = View
|
||||
|
||||
/** Responsible for subscribing to updates to the content view of an activity */
|
||||
class DecorViewObserver(val context: UIDContext) : TreeObserver<DecorView>() {
|
||||
|
||||
private var nodeRef: WeakReference<View>? = null
|
||||
private var preDrawListener: ViewTreeObserver.OnPreDrawListener? = null
|
||||
|
||||
override val type = "DecorView"
|
||||
|
||||
override fun subscribe(node: Any, parentId: Id?) {
|
||||
node as View
|
||||
nodeRef = WeakReference(node)
|
||||
|
||||
Log.i(LogTag, "Subscribing to decor view changes")
|
||||
|
||||
context.sharedThrottle.registerCallback(this.objectIdentity()) {
|
||||
nodeRef?.get()?.let { traverseAndSendWithSnapshot(parentId) }
|
||||
}
|
||||
|
||||
preDrawListener =
|
||||
ViewTreeObserver.OnPreDrawListener {
|
||||
context.sharedThrottle.trigger()
|
||||
true
|
||||
}
|
||||
|
||||
node.viewTreeObserver.addOnPreDrawListener(preDrawListener)
|
||||
|
||||
// It can be the case that the DecorView the current observer owns has already
|
||||
// drawn. In this case, manually trigger an update.
|
||||
traverseAndSendWithSnapshot(parentId)
|
||||
}
|
||||
|
||||
private fun traverseAndSendWithSnapshot(parentId: Id?) {
|
||||
nodeRef?.get()?.let { view ->
|
||||
var snapshotBitmap: BitmapPool.ReusableBitmap? = null
|
||||
if (view.width > 0 && view.height > 0) {
|
||||
snapshotBitmap = context.bitmapPool.getBitmap(view.width, view.height)
|
||||
}
|
||||
traverseAndSend(
|
||||
parentId,
|
||||
context,
|
||||
view,
|
||||
snapshotBitmap,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
override fun unsubscribe() {
|
||||
Log.i(LogTag, "Unsubscribing from decor view changes")
|
||||
|
||||
preDrawListener.let {
|
||||
nodeRef?.get()?.viewTreeObserver?.removeOnPreDrawListener(it)
|
||||
preDrawListener = null
|
||||
}
|
||||
|
||||
context.sharedThrottle.deregisterCallback(this.objectIdentity())
|
||||
nodeRef?.clear()
|
||||
nodeRef = null
|
||||
}
|
||||
}
|
||||
|
||||
object DecorViewTreeObserverBuilder : TreeObserverBuilder<DecorView> {
|
||||
override fun canBuildFor(node: Any): Boolean {
|
||||
return node.javaClass.simpleName.contains("DecorView")
|
||||
}
|
||||
|
||||
override fun build(context: UIDContext): TreeObserver<DecorView> {
|
||||
Log.i(LogTag, "Building DecorView observer")
|
||||
return DecorViewObserver(context)
|
||||
}
|
||||
}
|
||||
@@ -1,115 +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.observers
|
||||
|
||||
import android.util.Log
|
||||
import com.facebook.flipper.plugins.uidebugger.LogTag
|
||||
import com.facebook.flipper.plugins.uidebugger.common.BitmapPool
|
||||
import com.facebook.flipper.plugins.uidebugger.core.UIDContext
|
||||
import com.facebook.flipper.plugins.uidebugger.descriptors.Id
|
||||
import com.facebook.flipper.plugins.uidebugger.descriptors.NodeDescriptor
|
||||
import com.facebook.flipper.plugins.uidebugger.model.FrameworkEvent
|
||||
import com.facebook.flipper.plugins.uidebugger.util.objectIdentity
|
||||
|
||||
/*
|
||||
* Represents a stateful observer that manages some subtree in the UI Hierarchy.
|
||||
* It is responsible for:
|
||||
* 1. Listening to the relevant framework events
|
||||
* 2. Traversing the hierarchy of the managed nodes
|
||||
* 3. Diffing to previous state (optional)
|
||||
* 4. Pushing out updates for its entire set of managed nodes
|
||||
*
|
||||
* If while traversing it encounters a node type which has its own TreeObserver, it
|
||||
* does not traverse that, instead it sets up a Tree observer responsible for that subtree
|
||||
*
|
||||
* The parent is responsible for detecting when a child observer needs to be cleaned up.
|
||||
*/
|
||||
abstract class TreeObserver<T> {
|
||||
|
||||
protected val children: MutableMap<Int, TreeObserver<*>> = mutableMapOf()
|
||||
|
||||
abstract val type: String
|
||||
|
||||
abstract fun subscribe(node: Any, parentId: Id?)
|
||||
|
||||
abstract fun unsubscribe()
|
||||
|
||||
/** Traverses the layout hierarchy while managing any encountered child observers. */
|
||||
fun traverseAndSend(
|
||||
parentId: Id?,
|
||||
context: UIDContext,
|
||||
root: Any,
|
||||
snapshotBitmap: BitmapPool.ReusableBitmap? = null,
|
||||
frameworkEvents: List<FrameworkEvent>? = null
|
||||
) {
|
||||
val traversalStartTimestamp = System.currentTimeMillis()
|
||||
val (visitedNodes, observableRoots) = context.layoutTraversal.traverse(root, parentId)
|
||||
|
||||
// Add any new observers
|
||||
observableRoots.forEach { (observable, parentId) ->
|
||||
if (!children.containsKey(observable.objectIdentity())) {
|
||||
context.observerFactory.createObserver(observable, context)?.let { observer ->
|
||||
Log.d(
|
||||
LogTag,
|
||||
"Observer ${this.type} discovered new child of type ${observer.type} Node ID ${observable.objectIdentity()}")
|
||||
observer.subscribe(observable, parentId)
|
||||
children[observable.objectIdentity()] = observer
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Remove any old observers
|
||||
val observableRootsIdentifiers =
|
||||
observableRoots.map { (observable, _) -> observable.objectIdentity() }
|
||||
val removables = mutableListOf<Id>()
|
||||
children.keys.forEach { key ->
|
||||
if (!observableRootsIdentifiers.contains(key)) {
|
||||
children[key]?.let { observer ->
|
||||
Log.d(
|
||||
LogTag,
|
||||
"Observer ${this.type} cleaning up child of type ${observer.type} Node ID $key")
|
||||
|
||||
observer.cleanUpRecursive()
|
||||
}
|
||||
removables.add(key)
|
||||
}
|
||||
}
|
||||
removables.forEach { key -> children.remove(key) }
|
||||
|
||||
val traversalEndTimestamp = System.currentTimeMillis()
|
||||
|
||||
if (snapshotBitmap != null) {
|
||||
@Suppress("unchecked_cast")
|
||||
val descriptor =
|
||||
context.descriptorRegister.descriptorForClassUnsafe(root::class.java)
|
||||
as NodeDescriptor<Any>
|
||||
descriptor.getSnapshot(root, snapshotBitmap.bitmap)
|
||||
}
|
||||
|
||||
val snapshotEndTimestamp = System.currentTimeMillis()
|
||||
|
||||
context.treeObserverManager.enqueueUpdate(
|
||||
SubtreeUpdate(
|
||||
type,
|
||||
root.objectIdentity(),
|
||||
visitedNodes,
|
||||
traversalStartTimestamp,
|
||||
snapshotEndTimestamp,
|
||||
(traversalEndTimestamp - traversalStartTimestamp),
|
||||
(snapshotEndTimestamp - traversalEndTimestamp),
|
||||
frameworkEvents,
|
||||
snapshotBitmap))
|
||||
}
|
||||
|
||||
fun cleanUpRecursive() {
|
||||
Log.i(LogTag, "Cleaning up observer $this")
|
||||
children.values.forEach { it.cleanUpRecursive() }
|
||||
unsubscribe()
|
||||
children.clear()
|
||||
}
|
||||
}
|
||||
@@ -1,45 +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.observers
|
||||
|
||||
import com.facebook.flipper.plugins.uidebugger.core.UIDContext
|
||||
|
||||
interface TreeObserverBuilder<T> {
|
||||
fun canBuildFor(node: Any): Boolean
|
||||
|
||||
fun build(context: UIDContext): TreeObserver<T>
|
||||
}
|
||||
|
||||
class TreeObserverFactory {
|
||||
|
||||
private val builders = mutableListOf<TreeObserverBuilder<*>>()
|
||||
|
||||
fun <T> register(builder: TreeObserverBuilder<T>) {
|
||||
builders.add(builder)
|
||||
}
|
||||
|
||||
// TODO: Not very efficient, need to cache this. Builders cannot be removed
|
||||
fun hasObserverFor(node: Any): Boolean {
|
||||
return builders.any { it.canBuildFor(node) }
|
||||
}
|
||||
|
||||
// TODO: Not very efficient, need to cache this. Builders cannot be removed.
|
||||
fun createObserver(node: Any, context: UIDContext): TreeObserver<*>? {
|
||||
return builders.find { it.canBuildFor(node) }?.build(context)
|
||||
}
|
||||
|
||||
companion object {
|
||||
fun withDefaults(): TreeObserverFactory {
|
||||
val factory = TreeObserverFactory()
|
||||
// TODO: Only builder for DecorView, maybe more are needed.
|
||||
factory.register(DecorViewTreeObserverBuilder)
|
||||
|
||||
return factory
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,203 +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.observers
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.graphics.Bitmap
|
||||
import android.os.Looper
|
||||
import android.util.Base64
|
||||
import android.util.Base64OutputStream
|
||||
import android.util.Log
|
||||
import android.view.Choreographer
|
||||
import com.facebook.flipper.plugins.uidebugger.LogTag
|
||||
import com.facebook.flipper.plugins.uidebugger.common.BitmapPool
|
||||
import com.facebook.flipper.plugins.uidebugger.core.UIDContext
|
||||
import com.facebook.flipper.plugins.uidebugger.descriptors.Id
|
||||
import com.facebook.flipper.plugins.uidebugger.descriptors.MetadataRegister
|
||||
import com.facebook.flipper.plugins.uidebugger.model.FrameScanEvent
|
||||
import com.facebook.flipper.plugins.uidebugger.model.FrameworkEvent
|
||||
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.Snapshot
|
||||
import com.facebook.flipper.plugins.uidebugger.model.TraversalError
|
||||
import com.facebook.flipper.plugins.uidebugger.util.MaybeDeferred
|
||||
import java.io.ByteArrayOutputStream
|
||||
import java.util.concurrent.atomic.AtomicInteger
|
||||
import kotlinx.coroutines.*
|
||||
import kotlinx.coroutines.channels.Channel
|
||||
import kotlinx.serialization.json.Json
|
||||
|
||||
data class SubtreeUpdate(
|
||||
val observerType: String,
|
||||
val rootId: Id,
|
||||
val deferredNodes: List<MaybeDeferred<Node>>,
|
||||
val timestamp: Long,
|
||||
val queuedTimestamp: Long,
|
||||
val traversalMS: Long,
|
||||
val snapshotMS: Long,
|
||||
val frameworkEvents: List<FrameworkEvent>?,
|
||||
val snapshot: BitmapPool.ReusableBitmap?
|
||||
)
|
||||
|
||||
data class BatchedUpdate(val updates: List<SubtreeUpdate>, val frameTimeMs: Long)
|
||||
|
||||
/** Holds the root observer and manages sending updates to desktop */
|
||||
class TreeObserverManager(val context: UIDContext) {
|
||||
|
||||
private val rootObserver = ApplicationTreeObserver(context)
|
||||
private lateinit var batchedUpdates: Channel<BatchedUpdate>
|
||||
|
||||
private val subtreeUpdateBuffer = SubtreeUpdateBuffer(this::enqueueBatch)
|
||||
|
||||
private var job: Job? = null
|
||||
private val workerScope = CoroutineScope(Dispatchers.IO)
|
||||
private val mainScope = CoroutineScope(Dispatchers.Main)
|
||||
private val txId = AtomicInteger()
|
||||
|
||||
fun enqueueUpdate(update: SubtreeUpdate) {
|
||||
subtreeUpdateBuffer.bufferUpdate(update)
|
||||
}
|
||||
|
||||
private fun enqueueBatch(batchedUpdate: BatchedUpdate) {
|
||||
batchedUpdates.trySend(batchedUpdate)
|
||||
}
|
||||
|
||||
/**
|
||||
* 1. Sets up the root observer
|
||||
* 2. Starts worker to listen to channel, which serializers and sends data over connection
|
||||
*/
|
||||
@SuppressLint("NewApi")
|
||||
fun start() {
|
||||
|
||||
if (Looper.myLooper() != Looper.getMainLooper()) {
|
||||
mainScope.launch { start() }
|
||||
}
|
||||
batchedUpdates = Channel(Channel.UNLIMITED)
|
||||
rootObserver.subscribe(context.applicationRef, null)
|
||||
|
||||
job =
|
||||
workerScope.launch {
|
||||
while (isActive) {
|
||||
try {
|
||||
val update = batchedUpdates.receive()
|
||||
sendBatchedUpdate(update)
|
||||
} catch (e: CancellationException) {} catch (e: java.lang.Exception) {
|
||||
Log.e(LogTag, "Unexpected Error in channel ", e)
|
||||
}
|
||||
}
|
||||
Log.i(LogTag, "Shutting down worker")
|
||||
}
|
||||
}
|
||||
|
||||
fun stop() {
|
||||
rootObserver.cleanUpRecursive()
|
||||
job?.cancel()
|
||||
batchedUpdates.cancel()
|
||||
}
|
||||
|
||||
private fun sendBatchedUpdate(batchedUpdate: BatchedUpdate) {
|
||||
Log.i(
|
||||
LogTag,
|
||||
"Got update from ${batchedUpdate.updates.size} observers at time ${batchedUpdate.frameTimeMs}")
|
||||
|
||||
val workerThreadStartTimestamp = System.currentTimeMillis()
|
||||
|
||||
val nodes =
|
||||
try {
|
||||
batchedUpdate.updates.flatMap { it.deferredNodes.map { it.value() } }
|
||||
} catch (exception: Exception) {
|
||||
context.onError(
|
||||
TraversalError(
|
||||
"DeferredProcessing",
|
||||
exception.javaClass.simpleName,
|
||||
exception.message ?: "",
|
||||
exception.stackTraceToString()))
|
||||
return
|
||||
}
|
||||
|
||||
val frameworkEvents = context.extractPendingFrameworkEvents()
|
||||
val snapshotUpdate = batchedUpdate.updates.find { it.snapshot != null }
|
||||
val deferredComputationEndTimestamp = System.currentTimeMillis()
|
||||
|
||||
var snapshot: Snapshot? = null
|
||||
if (snapshotUpdate?.snapshot != null) {
|
||||
val stream = ByteArrayOutputStream()
|
||||
val base64Stream = Base64OutputStream(stream, Base64.DEFAULT)
|
||||
snapshotUpdate.snapshot.bitmap?.compress(Bitmap.CompressFormat.PNG, 100, base64Stream)
|
||||
snapshot = Snapshot(snapshotUpdate.rootId, stream.toString())
|
||||
snapshotUpdate.snapshot.readyForReuse()
|
||||
}
|
||||
|
||||
// it is important this comes after deferred processing since the deferred processing can create
|
||||
// metadata
|
||||
sendMetadata()
|
||||
|
||||
val serialized =
|
||||
Json.encodeToString(
|
||||
FrameScanEvent.serializer(),
|
||||
FrameScanEvent(batchedUpdate.frameTimeMs, nodes, snapshot, frameworkEvents))
|
||||
|
||||
val serialisationEndTimestamp = System.currentTimeMillis()
|
||||
|
||||
context.connectionRef.connection?.send(FrameScanEvent.name, serialized)
|
||||
|
||||
val socketEndTimestamp = System.currentTimeMillis()
|
||||
Log.i(LogTag, "Sent event for batched subtree update with nodes with ${nodes.size}")
|
||||
|
||||
// Note about payload size:
|
||||
// Payload size is an approximation as it assumes all characters
|
||||
// are ASCII encodable, this should be true for most of the payload content.
|
||||
// So, assume each character will at most occupy one byte.
|
||||
val perfStats =
|
||||
PerfStatsEvent(
|
||||
txId = batchedUpdate.frameTimeMs,
|
||||
observerType = "batched",
|
||||
nodesCount = nodes.size,
|
||||
start = batchedUpdate.updates.minOf { it.timestamp },
|
||||
traversalMS = batchedUpdate.updates.maxOf { it.traversalMS },
|
||||
snapshotMS = batchedUpdate.updates.maxOf { it.snapshotMS },
|
||||
queuingMS =
|
||||
workerThreadStartTimestamp - batchedUpdate.updates.minOf { it.queuedTimestamp },
|
||||
deferredComputationMS = (deferredComputationEndTimestamp - workerThreadStartTimestamp),
|
||||
serializationMS = (serialisationEndTimestamp - deferredComputationEndTimestamp),
|
||||
socketMS = (socketEndTimestamp - serialisationEndTimestamp),
|
||||
payloadSize = serialized.length)
|
||||
|
||||
context.connectionRef.connection?.send(
|
||||
PerfStatsEvent.name, Json.encodeToString(PerfStatsEvent.serializer(), perfStats))
|
||||
}
|
||||
|
||||
private fun sendMetadata() {
|
||||
val metadata = MetadataRegister.extractPendingMetadata()
|
||||
if (metadata.isNotEmpty()) {
|
||||
context.connectionRef.connection?.send(
|
||||
MetadataUpdateEvent.name,
|
||||
Json.encodeToString(MetadataUpdateEvent.serializer(), MetadataUpdateEvent(metadata)))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** Buffers up subtree updates until the frame is complete, should only be called on main thread */
|
||||
private class SubtreeUpdateBuffer(private val onBatchReady: (BatchedUpdate) -> Unit) {
|
||||
|
||||
private val bufferedSubtreeUpdates = mutableListOf<SubtreeUpdate>()
|
||||
|
||||
fun bufferUpdate(update: SubtreeUpdate) {
|
||||
if (bufferedSubtreeUpdates.isEmpty()) {
|
||||
|
||||
Choreographer.getInstance().postFrameCallback { frameTime ->
|
||||
val updatesCopy = bufferedSubtreeUpdates.toList()
|
||||
bufferedSubtreeUpdates.clear()
|
||||
|
||||
onBatchReady(BatchedUpdate(updatesCopy, frameTime / 1000000))
|
||||
}
|
||||
}
|
||||
bufferedSubtreeUpdates.add(update)
|
||||
}
|
||||
}
|
||||
@@ -1,74 +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.scheduler
|
||||
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.Job
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
/**
|
||||
* The class makes the following guarantees
|
||||
* 1. All registered callbacks will be called on the same frame at the same time
|
||||
* 2. The callbacks will never be called more often than the min interval
|
||||
* 3. If it has been > min interval since the callbacks was last called we will call the callbacks
|
||||
* immediately
|
||||
* 4. If an event comes in within the min interval of the last firing we will schedule another
|
||||
* firing at the next possible moment
|
||||
*
|
||||
* The reason we need this is because with an independent throttle per observer you end up with
|
||||
* updates occurring across different frames,
|
||||
*
|
||||
* WARNING: Not thread safe, should only be called on main thread. Also It is important to
|
||||
* deregister to avoid leaks
|
||||
*/
|
||||
class SharedThrottle(
|
||||
private val executionScope: CoroutineScope = CoroutineScope(Dispatchers.Main)
|
||||
) {
|
||||
|
||||
private var job: Job? = null
|
||||
private val callbacks = mutableMapOf<Int, () -> Unit>()
|
||||
private var latestInvocationId: Long = 0
|
||||
|
||||
fun registerCallback(id: Int, callback: () -> Unit) {
|
||||
callbacks[id] = callback
|
||||
}
|
||||
|
||||
fun deregisterCallback(id: Int) {
|
||||
callbacks.remove(id)
|
||||
}
|
||||
|
||||
fun trigger() {
|
||||
latestInvocationId += 1
|
||||
|
||||
if (job == null || job?.isCompleted == true) {
|
||||
job =
|
||||
executionScope.launch {
|
||||
var i = 0
|
||||
do {
|
||||
val thisInvocationId = latestInvocationId
|
||||
|
||||
callbacks.values.toList().forEach { callback -> callback() }
|
||||
|
||||
val delayTime = exponentialBackOff(base = 250, exp = 1.5, max = 1000, i = i).toLong()
|
||||
delay(delayTime)
|
||||
i++
|
||||
|
||||
// if we haven't received an call since we executed break out and let a new job be
|
||||
// created which, otherwise we loop which executes again at the next appropriate time
|
||||
// since we have already waited
|
||||
} while (thisInvocationId != latestInvocationId)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun exponentialBackOff(base: Long, exp: Double, max: Long, i: Int): Double {
|
||||
return Math.min(base * Math.pow(exp, i.toDouble()), max.toDouble())
|
||||
}
|
||||
}
|
||||
@@ -5,10 +5,8 @@
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
package com.facebook.flipper.plugins.uidebugger.common
|
||||
package com.facebook.flipper.plugins.uidebugger.util
|
||||
|
||||
import android.util.Log
|
||||
import com.facebook.flipper.plugins.uidebugger.LogTag
|
||||
import com.facebook.flipper.plugins.uidebugger.model.InspectableValue
|
||||
|
||||
// Maintains 2 way mapping between some enum value and a readable string representation
|
||||
@@ -19,9 +17,6 @@ open class EnumMapping<T>(private val mapping: Map<String, T>) {
|
||||
return if (entry != null) {
|
||||
entry.key
|
||||
} else {
|
||||
Log.v(
|
||||
LogTag,
|
||||
"Could not convert enum value ${enumValue.toString()} to string, known values ${mapping.entries}")
|
||||
NoMapping
|
||||
}
|
||||
}
|
||||
@@ -5,6 +5,6 @@
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
package com.facebook.flipper.plugins.uidebugger.common
|
||||
package com.facebook.flipper.plugins.uidebugger.util
|
||||
|
||||
class UIDebuggerException(message: String) : Exception(message)
|
||||
@@ -0,0 +1,38 @@
|
||||
/*
|
||||
* 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.util
|
||||
|
||||
class StopWatch() {
|
||||
private var startTime: Long = 0
|
||||
|
||||
fun start() {
|
||||
startTime = System.currentTimeMillis()
|
||||
}
|
||||
|
||||
fun stop(): Long {
|
||||
return System.currentTimeMillis() - startTime
|
||||
}
|
||||
|
||||
companion object {
|
||||
|
||||
fun <T> time(fn: () -> T): Pair<T, Long> {
|
||||
|
||||
val start = System.currentTimeMillis()
|
||||
val result = fn()
|
||||
val elapsed = System.currentTimeMillis() - start
|
||||
return Pair(result, elapsed)
|
||||
}
|
||||
|
||||
suspend fun <T> timeSuspend(fn: suspend () -> T): Pair<T, Long> {
|
||||
val start = System.currentTimeMillis()
|
||||
val result = fn()
|
||||
val elapsed = System.currentTimeMillis() - start
|
||||
return Pair(result, elapsed)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
/*
|
||||
* 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.util
|
||||
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.Job
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
/**
|
||||
* This class will throttle calls into a callback. E.g if interval is 500ms and you receive triggers
|
||||
* at t=0, 100, 300 400, the callback will only be triggered at t=500
|
||||
*/
|
||||
class Throttler<T>(private val intervalMs: Long, val callback: suspend () -> T) {
|
||||
|
||||
private val executionScope: CoroutineScope = CoroutineScope(Dispatchers.Main)
|
||||
private var throttleJob: Job? = null
|
||||
|
||||
fun trigger() {
|
||||
if (throttleJob == null || throttleJob?.isCompleted == true) {
|
||||
throttleJob =
|
||||
executionScope.launch {
|
||||
delay(intervalMs)
|
||||
executionScope.launch { callback() }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
/*
|
||||
* 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.util
|
||||
|
||||
import android.os.Build
|
||||
import android.util.Log
|
||||
import com.facebook.flipper.plugins.uidebugger.LogTag
|
||||
|
||||
object WindowManagerCommon {
|
||||
|
||||
// provides access to
|
||||
// https://android.googlesource.com/platform/frameworks/base/+/refs/heads/main/core/java/android/view/WindowManagerGlobal.java
|
||||
fun getGlobalWindowManager(): Pair<Any, Class<*>>? {
|
||||
val accessClass =
|
||||
if (Build.VERSION.SDK_INT > 16) WINDOW_MANAGER_GLOBAL_CLAZZ else WINDOW_MANAGER_IMPL_CLAZZ
|
||||
val instanceMethod = if (Build.VERSION.SDK_INT > 16) GET_GLOBAL_INSTANCE else GET_DEFAULT_IMPL
|
||||
|
||||
try {
|
||||
val clazz = Class.forName(accessClass)
|
||||
val getMethod = clazz.getMethod(instanceMethod)
|
||||
return Pair(getMethod.invoke(null), clazz)
|
||||
} catch (exception: Exception) {
|
||||
Log.e(LogTag, "Unable to get global window manager handle", exception)
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
private const val GET_DEFAULT_IMPL = "getDefault"
|
||||
private const val GET_GLOBAL_INSTANCE = "getInstance"
|
||||
private const val WINDOW_MANAGER_IMPL_CLAZZ = "android.view.WindowManagerImpl"
|
||||
private const val WINDOW_MANAGER_GLOBAL_CLAZZ = "android.view.WindowManagerGlobal"
|
||||
}
|
||||
@@ -8,8 +8,8 @@
|
||||
package com.facebook.flipper.plugins.uidebugger
|
||||
|
||||
import android.view.View
|
||||
import com.facebook.flipper.plugins.uidebugger.common.EnumMapping
|
||||
import com.facebook.flipper.plugins.uidebugger.model.InspectableValue
|
||||
import com.facebook.flipper.plugins.uidebugger.util.EnumMapping
|
||||
import org.hamcrest.CoreMatchers.*
|
||||
import org.hamcrest.MatcherAssert.assertThat
|
||||
import org.junit.Test
|
||||
|
||||
20
build.gradle
20
build.gradle
@@ -49,8 +49,8 @@ subprojects {
|
||||
|
||||
ext {
|
||||
minSdkVersion = 15
|
||||
targetSdkVersion = 33
|
||||
compileSdkVersion = 33
|
||||
targetSdkVersion = 34
|
||||
compileSdkVersion = 34
|
||||
buildToolsVersion = "33.0.2"
|
||||
ndkVersion = "25.1.8937393"
|
||||
javaTargetVersion = JavaVersion.VERSION_17
|
||||
@@ -113,12 +113,12 @@ ext.deps = [
|
||||
testCore : 'androidx.test:core:1.4.0',
|
||||
testRules : 'androidx.test:rules:1.5.0',
|
||||
// Plugin dependencies
|
||||
flipperFrescoPlugin: 'com.facebook.fresco:flipper-fresco-plugin:3.1.0',
|
||||
frescoFlipper : 'com.facebook.fresco:flipper:3.1.0',
|
||||
frescoStetho : 'com.facebook.fresco:stetho:3.1.0',
|
||||
fresco : 'com.facebook.fresco:fresco:3.1.0',
|
||||
frescoUiCommon : 'com.facebook.fresco:ui-common:3.1.0',
|
||||
frescoVito : 'com.facebook.fresco:vito:3.1.0',
|
||||
frescoVitoCore : 'com.facebook.fresco:vito-core:3.1.0',
|
||||
frescoVitoLitho : 'com.facebook.fresco:vito-litho:3.1.0',
|
||||
flipperFrescoPlugin: "com.facebook.fresco:flipper-fresco-plugin:$FRESCO_VERSION",
|
||||
frescoFlipper : "com.facebook.fresco:flipper:$FRESCO_VERSION",
|
||||
frescoStetho : "com.facebook.fresco:stetho:$FRESCO_VERSION",
|
||||
fresco : "com.facebook.fresco:fresco:$FRESCO_VERSION",
|
||||
frescoUiCommon : "com.facebook.fresco:ui-common:$FRESCO_VERSION",
|
||||
frescoVito : "com.facebook.fresco:vito:$FRESCO_VERSION",
|
||||
frescoVitoCore : "com.facebook.fresco:vito-core:$FRESCO_VERSION",
|
||||
frescoVitoLitho : "com.facebook.fresco:vito-litho:$FRESCO_VERSION",
|
||||
]
|
||||
|
||||
@@ -214,11 +214,35 @@ module.exports = {
|
||||
],
|
||||
'@typescript-eslint/naming-convention': [
|
||||
2,
|
||||
{
|
||||
selector: 'default',
|
||||
format: ['camelCase'],
|
||||
leadingUnderscore: 'allow',
|
||||
trailingUnderscore: 'allow',
|
||||
},
|
||||
{
|
||||
selector: 'variable',
|
||||
format: ['camelCase', 'UPPER_CASE', 'PascalCase', 'snake_case'],
|
||||
leadingUnderscore: 'allowSingleOrDouble',
|
||||
trailingUnderscore: 'allowSingleOrDouble',
|
||||
},
|
||||
{
|
||||
selector: 'function',
|
||||
format: ['camelCase', 'PascalCase'],
|
||||
leadingUnderscore: 'allow',
|
||||
trailingUnderscore: 'allow',
|
||||
},
|
||||
{
|
||||
selector: 'typeLike',
|
||||
format: ['PascalCase', 'UPPER_CASE'],
|
||||
leadingUnderscore: 'allow',
|
||||
},
|
||||
{
|
||||
selector: ['property', 'method', 'memberLike', 'parameter'],
|
||||
// do not enforce naming convention for properties
|
||||
// no support for kebab-case
|
||||
format: null,
|
||||
},
|
||||
],
|
||||
'@typescript-eslint/no-non-null-assertion': 'warn',
|
||||
},
|
||||
|
||||
@@ -57,7 +57,7 @@ export function plugin(client: PluginClient<Events, Methods>) {
|
||||
});
|
||||
});
|
||||
|
||||
function _unused_JustTypeChecks() {
|
||||
function _unusedJustTypeChecks() {
|
||||
// @ts-expect-error Argument of type '"bla"' is not assignable
|
||||
client.send('bla', {});
|
||||
// @ts-expect-error Argument of type '{ stuff: string; }' is not assignable to parameter of type
|
||||
|
||||
@@ -35,6 +35,7 @@ test('Correct top level API exposed', () => {
|
||||
"DataList",
|
||||
"DataSource",
|
||||
"DataTable",
|
||||
"DataTableLegacy",
|
||||
"DetailSidebar",
|
||||
"Dialog",
|
||||
"ElementsInspector",
|
||||
@@ -44,6 +45,7 @@ test('Correct top level API exposed', () => {
|
||||
"Layout",
|
||||
"MarkerTimeline",
|
||||
"MasterDetail",
|
||||
"MasterDetailLegacy",
|
||||
"NUX",
|
||||
"Panel",
|
||||
"PowerSearch",
|
||||
@@ -97,7 +99,9 @@ test('Correct top level API exposed', () => {
|
||||
"DataInspectorExpanded",
|
||||
"DataSourceVirtualizer",
|
||||
"DataTableColumn",
|
||||
"DataTableColumnLegacy",
|
||||
"DataTableManager",
|
||||
"DataTableManagerLegacy",
|
||||
"DataValueExtractor",
|
||||
"DefaultKeyboardAction",
|
||||
"Device",
|
||||
|
||||
@@ -37,7 +37,9 @@ export {
|
||||
export {Sidebar as _Sidebar} from './ui/Sidebar';
|
||||
export {DetailSidebar} from './ui/DetailSidebar';
|
||||
export {Toolbar} from './ui/Toolbar';
|
||||
|
||||
export {MasterDetail} from './ui/MasterDetail';
|
||||
export {MasterDetail as MasterDetailLegacy} from './ui/MasterDetail';
|
||||
export {MasterDetailWithPowerSearch as _MasterDetailWithPowerSearch} from './ui/MasterDetailWithPowerSearch';
|
||||
export {CodeBlock} from './ui/CodeBlock';
|
||||
|
||||
@@ -58,7 +60,12 @@ export {DataFormatter} from './ui/DataFormatter';
|
||||
export {useLogger, _LoggerContext} from './utils/useLogger';
|
||||
|
||||
export {DataTable, DataTableColumn} from './ui/data-table/DataTable';
|
||||
export {
|
||||
DataTable as DataTableLegacy,
|
||||
DataTableColumn as DataTableColumnLegacy,
|
||||
} from './ui/data-table/DataTable';
|
||||
export {DataTableManager} from './ui/data-table/DataTableManager';
|
||||
export {DataTableManager as DataTableManagerLegacy} from './ui/data-table/DataTableManager';
|
||||
export {
|
||||
DataTable as _DataTableWithPowerSearch,
|
||||
DataTableColumn as _DataTableColumnWithPowerSearch,
|
||||
|
||||
@@ -65,20 +65,20 @@ export const PowerSearchTermFinder = React.forwardRef<
|
||||
onChange={setSearchTermFinderValue}
|
||||
onBlur={() => {
|
||||
setSearchTermFinderValue(null);
|
||||
}}
|
||||
onInputKeyDown={(event) => {
|
||||
if (event.key === 'Enter') {
|
||||
if (searchTermFinderValue && onConfirmUnknownOption) {
|
||||
onConfirmUnknownOption(searchTermFinderValue);
|
||||
}
|
||||
setSearchTermFinderValue(null);
|
||||
}
|
||||
if (event.key === 'Backspace' && !searchTermFinderValue) {
|
||||
onBackspacePressWhileEmpty();
|
||||
}
|
||||
}}>
|
||||
<Input
|
||||
bordered={false}
|
||||
onKeyUp={(event) => {
|
||||
if (event.key === 'Enter') {
|
||||
if (searchTermFinderValue && onConfirmUnknownOption) {
|
||||
onConfirmUnknownOption(searchTermFinderValue);
|
||||
}
|
||||
setSearchTermFinderValue(null);
|
||||
}
|
||||
if (event.key === 'Backspace' && !searchTermFinderValue) {
|
||||
onBackspacePressWhileEmpty();
|
||||
}
|
||||
}}
|
||||
onPasteCapture={(event) => {
|
||||
const text = event.clipboardData.getData('text/plain');
|
||||
|
||||
|
||||
@@ -223,6 +223,7 @@ export const dataTableManagerReducer = produce<
|
||||
break;
|
||||
}
|
||||
case 'setSearchValue': {
|
||||
getFlipperLib().logger.track('usage', 'data-table:filter:search');
|
||||
draft.searchValue = action.value;
|
||||
draft.previousSearchValue = '';
|
||||
draft.filterExceptions = undefined;
|
||||
@@ -240,6 +241,7 @@ export const dataTableManagerReducer = produce<
|
||||
break;
|
||||
}
|
||||
case 'toggleSearchValue': {
|
||||
getFlipperLib().logger.track('usage', 'data-table:filter:toggle-search');
|
||||
draft.filterExceptions = undefined;
|
||||
if (draft.searchValue) {
|
||||
draft.previousSearchValue = draft.searchValue;
|
||||
@@ -297,6 +299,7 @@ export const dataTableManagerReducer = produce<
|
||||
break;
|
||||
}
|
||||
case 'addColumnFilter': {
|
||||
getFlipperLib().logger.track('usage', 'data-table:filter:add-column');
|
||||
draft.filterExceptions = undefined;
|
||||
addColumnFilter(
|
||||
draft.columns,
|
||||
@@ -307,6 +310,7 @@ export const dataTableManagerReducer = produce<
|
||||
break;
|
||||
}
|
||||
case 'removeColumnFilter': {
|
||||
getFlipperLib().logger.track('usage', 'data-table:filter:remove-column');
|
||||
draft.filterExceptions = undefined;
|
||||
const column = draft.columns.find((c) => c.key === action.column)!;
|
||||
const index =
|
||||
@@ -321,6 +325,7 @@ export const dataTableManagerReducer = produce<
|
||||
break;
|
||||
}
|
||||
case 'toggleColumnFilter': {
|
||||
getFlipperLib().logger.track('usage', 'data-table:filter:toggle-column');
|
||||
draft.filterExceptions = undefined;
|
||||
const column = draft.columns.find((c) => c.key === action.column)!;
|
||||
const index =
|
||||
@@ -486,7 +491,6 @@ export function createDataTableManager<T>(
|
||||
dispatch({type: 'sortColumn', column, direction});
|
||||
},
|
||||
setSearchValue(value, addToHistory = false) {
|
||||
getFlipperLib().logger.track('usage', 'data-table:filter:search');
|
||||
dispatch({type: 'setSearchValue', value, addToHistory});
|
||||
},
|
||||
toggleSearchValue() {
|
||||
@@ -508,11 +512,9 @@ export function createDataTableManager<T>(
|
||||
dispatch({type: 'setShowNumberedHistory', showNumberedHistory});
|
||||
},
|
||||
addColumnFilter(column, value, options = {}) {
|
||||
getFlipperLib().logger.track('usage', 'data-table:filter:add-column');
|
||||
dispatch({type: 'addColumnFilter', column, value, options});
|
||||
},
|
||||
removeColumnFilter(column, label) {
|
||||
getFlipperLib().logger.track('usage', 'data-table:filter:remove-column');
|
||||
dispatch({type: 'removeColumnFilter', column, label});
|
||||
},
|
||||
setFilterExceptions(exceptions: string[] | undefined) {
|
||||
|
||||
@@ -15,6 +15,7 @@ import produce, {castDraft, immerable, original} from 'immer';
|
||||
import {DataSource, getFlipperLib, _DataSourceView} from 'flipper-plugin-core';
|
||||
import {SearchExpressionTerm} from '../PowerSearch';
|
||||
import {PowerSearchOperatorProcessorConfig} from './DataTableDefaultPowerSearchOperators';
|
||||
import {DataTableManager as DataTableManagerLegacy} from './DataTableManager';
|
||||
|
||||
export type OnColumnResize = (id: string, size: number | Percentage) => void;
|
||||
export type Sorting<T = any> = {
|
||||
@@ -259,6 +260,16 @@ export type DataTableManager<T> = {
|
||||
stateRef: RefObject<Readonly<DataManagerState<T>>>;
|
||||
toggleSideBySide(): void;
|
||||
setFilterExceptions(exceptions: string[] | undefined): void;
|
||||
} & Omit<DataTableManagerLegacy<T>, 'stateRef'>;
|
||||
|
||||
const showPowerSearchMigrationWarning = () => {
|
||||
console.warn(
|
||||
'Flipper is migrating to the new power search (see https://fburl.com/workplace/eewxik3o). Your plugin uses tableManagerRef which is partially incompatible with the new API. THIS API CALL DOES NOTHING AT THIS POINT! Please, migrate to the new API by explicitly using _MasterDetailWithPowerSearch, _DataTableWithPowerSearch, _DataTableWithPowerSearchManager (see https://fburl.com/code/dpawdt69). As a temporary workaround, feel free to use legacy MasterDetailLegacy, DataTableLegacy, DataTableManagerLegacy components to force the usage of the old search.',
|
||||
);
|
||||
getFlipperLib().logger.track(
|
||||
'usage',
|
||||
'data-table:filter:power-search-legacy-api-access',
|
||||
);
|
||||
};
|
||||
|
||||
export function createDataTableManager<T>(
|
||||
@@ -303,7 +314,6 @@ export function createDataTableManager<T>(
|
||||
dispatch({type: 'sortColumn', column, direction});
|
||||
},
|
||||
setSearchExpression(searchExpression) {
|
||||
getFlipperLib().logger.track('usage', 'data-table:power-search:search');
|
||||
dispatch({type: 'setSearchExpression', searchExpression});
|
||||
},
|
||||
toggleSideBySide() {
|
||||
@@ -314,6 +324,30 @@ export function createDataTableManager<T>(
|
||||
},
|
||||
dataView,
|
||||
stateRef,
|
||||
showSearchDropdown() {
|
||||
showPowerSearchMigrationWarning();
|
||||
},
|
||||
setSearchValue() {
|
||||
showPowerSearchMigrationWarning();
|
||||
},
|
||||
setSearchHighlightColor() {
|
||||
showPowerSearchMigrationWarning();
|
||||
},
|
||||
setShowNumberedHistory() {
|
||||
showPowerSearchMigrationWarning();
|
||||
},
|
||||
toggleSearchValue() {
|
||||
showPowerSearchMigrationWarning();
|
||||
},
|
||||
toggleHighlightSearch() {
|
||||
showPowerSearchMigrationWarning();
|
||||
},
|
||||
addColumnFilter() {
|
||||
showPowerSearchMigrationWarning();
|
||||
},
|
||||
removeColumnFilter() {
|
||||
showPowerSearchMigrationWarning();
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
* @format
|
||||
*/
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/naming-convention
|
||||
export const unstable_batchedUpdates = (cb: () => void) => {
|
||||
return cb();
|
||||
};
|
||||
|
||||
@@ -29,6 +29,7 @@ import {Server} from 'net';
|
||||
import {serializeError} from 'serialize-error';
|
||||
import {WSCloseCode} from '../utils/WSCloseCode';
|
||||
import {recorder} from '../recorder';
|
||||
import {tracker} from '../tracker';
|
||||
|
||||
export interface ConnectionCtx {
|
||||
clientQuery?: ClientQuery;
|
||||
@@ -63,11 +64,11 @@ class ServerWebSocket extends ServerWebSocketBase {
|
||||
// We do not need to listen to http server's `error` because it is propagated to WS
|
||||
// https://github.com/websockets/ws/blob/a3a22e4ed39c1a3be8e727e9c630dd440edc61dd/lib/websocket-server.js#L109
|
||||
const onConnectionError = (error: Error) => {
|
||||
const message = JSON.stringify(serializeError(error));
|
||||
tracker.track('server-ws-server-error', {port, error: message});
|
||||
reject(
|
||||
new Error(
|
||||
`Unable to start server at port ${port} due to ${JSON.stringify(
|
||||
serializeError(error),
|
||||
)}`,
|
||||
`Unable to start server at port ${port} due to ${message}`,
|
||||
),
|
||||
);
|
||||
};
|
||||
|
||||
@@ -16,6 +16,7 @@ import {promisify} from 'util';
|
||||
import child_process from 'child_process';
|
||||
import fs from 'fs-extra';
|
||||
import {recorder} from '../../recorder';
|
||||
import {isFBBuild} from '../../fb-stubs/constants';
|
||||
const exec = promisify(child_process.exec);
|
||||
|
||||
export type IdbConfig = {
|
||||
@@ -173,8 +174,12 @@ async function queryTargetsWithIdb(
|
||||
const cmd = `${idbPath} list-targets --json`;
|
||||
const description = `Query available devices with idb. idb is aware of the companions that you have
|
||||
manually connected, as well as other iOS targets that do not yet have companions.`;
|
||||
const troubleshoot = `Either idb is not installed or needs to be reset.
|
||||
let troubleshoot = `Either idb is not installed or needs to be reset.
|
||||
Run 'idb kill' from terminal.`;
|
||||
if (isFBBuild) {
|
||||
troubleshoot += ` If the steps above do not fix the issue, try re-installing idb by running these commands on the terminal 'sudo microdnf remove fb-idb fb-idb-companion'
|
||||
and 'sudo microdnf install fb-idb fb-idb-companion'.`;
|
||||
}
|
||||
|
||||
try {
|
||||
const {stdout} = await unsafeExec(cmd);
|
||||
|
||||
@@ -110,6 +110,7 @@ export async function startServer(
|
||||
}> {
|
||||
setTimeout(() => {
|
||||
if (!isReady && isProduction()) {
|
||||
tracker.track('server-ready-timeout', {timeout: timeoutSeconds});
|
||||
console.error(
|
||||
`[flipper-server] Unable to become ready within ${timeoutSeconds} seconds, exit`,
|
||||
);
|
||||
|
||||
@@ -47,6 +47,9 @@ type TrackerEvents = {
|
||||
error?: string;
|
||||
};
|
||||
'server-socket-already-in-use': {};
|
||||
'server-open-ui': {browser: boolean; hasToken: boolean};
|
||||
'server-ws-server-error': {port: number; error: string};
|
||||
'server-ready-timeout': {timeout: number};
|
||||
'browser-connection-created': {
|
||||
successful: boolean;
|
||||
timeMS: number;
|
||||
|
||||
@@ -313,6 +313,11 @@ async function launch() {
|
||||
console.info(`[flipper-server] Go to: ${chalk.blue(url.toString())}`);
|
||||
|
||||
open(url.toString(), {app: {name: open.apps.chrome}});
|
||||
|
||||
tracker.track('server-open-ui', {
|
||||
browser: true,
|
||||
hasToken: token?.length != 0,
|
||||
});
|
||||
};
|
||||
|
||||
if (argv.bundler) {
|
||||
@@ -320,6 +325,10 @@ async function launch() {
|
||||
} else {
|
||||
const path = await findInstallation();
|
||||
if (path) {
|
||||
tracker.track('server-open-ui', {
|
||||
browser: false,
|
||||
hasToken: token?.length != 0,
|
||||
});
|
||||
open(path);
|
||||
} else {
|
||||
await openInBrowser();
|
||||
|
||||
@@ -27,6 +27,16 @@ let cachedDeepLinkURL: string | undefined;
|
||||
const logger = initLogger();
|
||||
|
||||
async function start() {
|
||||
/**
|
||||
* The following is used to ensure only one instance of Flipper is running at a time.
|
||||
* The event will not be fired for the current tab.
|
||||
*/
|
||||
window.addEventListener('storage', function (event) {
|
||||
if (event.key === 'flipper-kill-window') {
|
||||
window.close();
|
||||
}
|
||||
});
|
||||
|
||||
// @ts-ignore
|
||||
electronRequire = function (path: string) {
|
||||
console.error(
|
||||
@@ -87,11 +97,18 @@ async function start() {
|
||||
|
||||
getLogger().info('[flipper-client][ui-browser] Create WS client');
|
||||
|
||||
let lastStateChangeMS = performance.now();
|
||||
const flipperServer = await createFlipperServer(
|
||||
location.hostname,
|
||||
parseInt(location.port, 10),
|
||||
tokenProvider,
|
||||
(state: FlipperServerState) => {
|
||||
const timestamp = performance.now();
|
||||
getLogger().track('usage', 'browser-server-state-changed', {
|
||||
state,
|
||||
timeElapsedMS: timestamp - lastStateChangeMS,
|
||||
});
|
||||
lastStateChangeMS = timestamp;
|
||||
switch (state) {
|
||||
case FlipperServerState.CONNECTING:
|
||||
getLogger().info('[flipper-client] Connecting to server');
|
||||
@@ -142,6 +159,12 @@ async function start() {
|
||||
require('flipper-ui-core').startFlipperDesktop(flipperServer);
|
||||
window.flipperHideMessage?.();
|
||||
|
||||
/**
|
||||
* At this stage, the current client has established a connection with the server.
|
||||
* So, it is safe to 'set' into local storage so that other clients close.
|
||||
*/
|
||||
localStorage.setItem('flipper-kill-window', Date.now().toString());
|
||||
|
||||
getLogger().info('[flipper-client][ui-browser] UI initialised');
|
||||
logger.track('success-rate', 'flipper-ui-browser-started', {value: 1});
|
||||
}
|
||||
|
||||
@@ -52,7 +52,7 @@ export function plugin(client: PluginClient<Events, Methods>) {
|
||||
});
|
||||
});
|
||||
|
||||
function _unused_JustTypeChecks() {
|
||||
function _unusedJustTypeChecks() {
|
||||
// @ts-expect-error Argument of type '"bla"' is not assignable
|
||||
client.send('bla', {});
|
||||
// @ts-expect-error Argument of type '{ stuff: string; }' is not assignable to parameter of type
|
||||
|
||||
@@ -12,3 +12,5 @@ export {RenderHost, getRenderHostInstance} from 'flipper-frontend-core';
|
||||
export {startFlipperDesktop} from './startFlipperDesktop';
|
||||
|
||||
export {Icon} from './utils/icons';
|
||||
|
||||
export {isPWA} from './utils/pwa';
|
||||
|
||||
@@ -120,6 +120,7 @@ export default class Orderable extends React.Component<
|
||||
return !this.state.movingOrder;
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/naming-convention
|
||||
UNSAFE_componentWillReceiveProps(nextProps: OrderableProps) {
|
||||
this.setState({
|
||||
order: nextProps.order,
|
||||
|
||||
@@ -120,6 +120,7 @@ class SearchableManagedTable extends PureComponent<Props, State> {
|
||||
this.props.defaultFilters.map(this.props.addFilter);
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/naming-convention
|
||||
UNSAFE_componentWillReceiveProps(nextProps: Props) {
|
||||
if (
|
||||
nextProps.searchTerm !== this.props.searchTerm ||
|
||||
|
||||
@@ -229,6 +229,7 @@ export class ManagedTable extends React.Component<
|
||||
}
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/naming-convention
|
||||
UNSAFE_componentWillReceiveProps(nextProps: ManagedTableProps) {
|
||||
// if columnSizes has changed
|
||||
if (nextProps.columnSizes !== this.props.columnSizes) {
|
||||
|
||||
12
desktop/flipper-ui-core/src/utils/pwa.tsx
Normal file
12
desktop/flipper-ui-core/src/utils/pwa.tsx
Normal file
@@ -0,0 +1,12 @@
|
||||
/**
|
||||
* 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.
|
||||
*
|
||||
* @format
|
||||
*/
|
||||
|
||||
export const isPWA = () => {
|
||||
return window.matchMedia('(display-mode: standalone)').matches;
|
||||
};
|
||||
@@ -171,7 +171,7 @@
|
||||
"npm": "use yarn instead",
|
||||
"yarn": "^1.16"
|
||||
},
|
||||
"version": "0.233.0",
|
||||
"version": "0.236.0",
|
||||
"workspaces": {
|
||||
"packages": [
|
||||
"scripts",
|
||||
|
||||
@@ -27,7 +27,7 @@ You also need to compile in the `litho-annotations` package, as Flipper reflects
|
||||
|
||||
```groovy
|
||||
dependencies {
|
||||
debugImplementation 'com.facebook.flipper:flipper-litho-plugin:0.233.0'
|
||||
debugImplementation 'com.facebook.flipper:flipper-litho-plugin:0.236.0'
|
||||
debugImplementation 'com.facebook.litho:litho-annotations:0.19.0'
|
||||
// ...
|
||||
}
|
||||
|
||||
@@ -8,7 +8,7 @@ To setup the <Link to={useBaseUrl("/docs/features/plugins/leak-canary")}>LeakCan
|
||||
|
||||
```groovy
|
||||
dependencies {
|
||||
debugImplementation 'com.facebook.flipper:flipper-leakcanary2-plugin:0.233.0'
|
||||
debugImplementation 'com.facebook.flipper:flipper-leakcanary2-plugin:0.236.0'
|
||||
debugImplementation 'com.squareup.leakcanary:leakcanary-android:2.8.1'
|
||||
}
|
||||
```
|
||||
|
||||
@@ -12,7 +12,7 @@ The network plugin is shipped as a separate Maven artifact, as follows:
|
||||
|
||||
```groovy
|
||||
dependencies {
|
||||
debugImplementation 'com.facebook.flipper:flipper-network-plugin:0.233.0'
|
||||
debugImplementation 'com.facebook.flipper:flipper-network-plugin:0.236.0'
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
@@ -28,9 +28,9 @@ import {
|
||||
usePlugin,
|
||||
useValue,
|
||||
createDataSource,
|
||||
DataTable,
|
||||
DataTableColumn,
|
||||
DataTableManager,
|
||||
DataTableLegacy as DataTable,
|
||||
DataTableColumnLegacy as DataTableColumn,
|
||||
DataTableManagerLegacy as DataTableManager,
|
||||
theme,
|
||||
renderReactRoot,
|
||||
batch,
|
||||
|
||||
@@ -7,7 +7,11 @@
|
||||
* @format
|
||||
*/
|
||||
|
||||
import {Atom, DataTableManager, getFlipperLib} from 'flipper-plugin';
|
||||
import {
|
||||
Atom,
|
||||
DataTableManagerLegacy as DataTableManager,
|
||||
getFlipperLib,
|
||||
} from 'flipper-plugin';
|
||||
import {createContext} from 'react';
|
||||
import {Header, Request} from '../types';
|
||||
|
||||
|
||||
@@ -220,6 +220,16 @@ async function buildDist(buildFolder: string) {
|
||||
distDir,
|
||||
process.arch === 'arm64' ? 'mac-arm64' : 'mac',
|
||||
);
|
||||
if (argv['react-native-only']) {
|
||||
targetsRaw.push(Platform.MAC.createTarget(['pkg'], Arch.x64));
|
||||
// macPath = path.join(distDir, 'mac');
|
||||
} else {
|
||||
targetsRaw.push(Platform.MAC.createTarget(['dir']));
|
||||
// You can build mac apps on Linux but can't build dmgs, so we separate those.
|
||||
if (argv['mac-dmg']) {
|
||||
targetsRaw.push(Platform.MAC.createTarget(['dmg']));
|
||||
}
|
||||
}
|
||||
postBuildCallbacks.push(() =>
|
||||
spawn('zip', ['-qyr9', '../Flipper-mac.zip', 'Flipper.app'], {
|
||||
cwd: macPath,
|
||||
|
||||
@@ -1,3 +1,9 @@
|
||||
# 0.234.0 (1/11/2023)
|
||||
|
||||
* [D50595987](https://github.com/facebook/flipper/search?q=D50595987&type=Commits) - UIDebugger, new sidebar design
|
||||
* [D50599215](https://github.com/facebook/flipper/search?q=D50599215&type=Commits) - Android SDK is now built against SDK 34
|
||||
|
||||
|
||||
# 0.223.0 (2/10/2023)
|
||||
|
||||
* [D49704398](https://github.com/facebook/flipper/search?q=D49704398&type=Commits) - UIDebugger: improvements to iOS Accessibility mode
|
||||
|
||||
@@ -36,36 +36,6 @@
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.console {
|
||||
font-family: 'Fira Mono';
|
||||
width: 600px;
|
||||
height: 250px;
|
||||
box-sizing: border-box;
|
||||
margin: auto;
|
||||
}
|
||||
|
||||
.console header {
|
||||
border-top-left-radius: 15px;
|
||||
border-top-right-radius: 15px;
|
||||
background-color: #9254de;
|
||||
height: 45px;
|
||||
line-height: 45px;
|
||||
text-align: center;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.console .consolebody {
|
||||
border-bottom-left-radius: 15px;
|
||||
border-bottom-right-radius: 15px;
|
||||
box-sizing: border-box;
|
||||
padding: 0px 10px;
|
||||
height: calc(100% - 40px);
|
||||
overflow: scroll;
|
||||
background-color: #000;
|
||||
color: white;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
input[type="submit"] {
|
||||
background-color: #9254de;
|
||||
color: white;
|
||||
@@ -107,17 +77,7 @@
|
||||
<p>
|
||||
Flipper will automatically reload once the connection is re-established.
|
||||
</p>
|
||||
<p>If is taking a bit longer than it should, you can manually start Flipper.</p>
|
||||
<p>From the terminal, please run:</p>
|
||||
|
||||
<div class="console">
|
||||
<header>
|
||||
<p>shell</p>
|
||||
</header>
|
||||
<div class="consolebody">
|
||||
<p>> open -a 'Flipper' --args '--server'</p>
|
||||
</div>
|
||||
</div>
|
||||
<p>Note: if is taking a bit longer than it should, you can manually start Flipper by launching it from the <b>Applications</b> folder.</p>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
// OFFLINE_VERSION is used as an update marker so that on subsequent installations
|
||||
// the newer version of the file gets updated.
|
||||
// eslint-disable-next-line header/header, no-unused-vars
|
||||
const OFFLINE_VERSION = 1.1;
|
||||
const OFFLINE_VERSION = 1.2;
|
||||
const CACHE_NAME = 'offline';
|
||||
const OFFLINE_URL = 'offline.html';
|
||||
|
||||
|
||||
@@ -982,6 +982,10 @@ function HighlightedText(props: {text: string}) {
|
||||
|
||||
### dataTablePowerSearchOperators
|
||||
|
||||
### MasterDetailLegacy
|
||||
|
||||
### DataTableLegacy
|
||||
|
||||
Coming soon
|
||||
|
||||
### MasterDetail
|
||||
|
||||
@@ -24,10 +24,10 @@ repositories {
|
||||
}
|
||||
|
||||
dependencies {
|
||||
debugImplementation 'com.facebook.flipper:flipper:0.233.0'
|
||||
debugImplementation 'com.facebook.flipper:flipper:0.236.0'
|
||||
debugImplementation 'com.facebook.soloader:soloader:0.10.5'
|
||||
|
||||
releaseImplementation 'com.facebook.flipper:flipper-noop:0.233.0'
|
||||
releaseImplementation 'com.facebook.flipper:flipper-noop:0.236.0'
|
||||
}
|
||||
```
|
||||
|
||||
@@ -124,10 +124,10 @@ repositories {
|
||||
}
|
||||
|
||||
dependencies {
|
||||
debugImplementation 'com.facebook.flipper:flipper:0.233.1-SNAPSHOT'
|
||||
debugImplementation 'com.facebook.flipper:flipper:0.236.1-SNAPSHOT'
|
||||
debugImplementation 'com.facebook.soloader:soloader:0.10.5'
|
||||
|
||||
releaseImplementation 'com.facebook.flipper:flipper-noop:0.233.1-SNAPSHOT'
|
||||
releaseImplementation 'com.facebook.flipper:flipper-noop:0.236.1-SNAPSHOT'
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
@@ -19,7 +19,7 @@ The following configuration assumes CocoaPods 1.9+:
|
||||
|
||||
```ruby
|
||||
project 'MyApp.xcodeproj'
|
||||
flipperkit_version = '0.222.0'
|
||||
flipperkit_version = '0.233.0'
|
||||
|
||||
target 'MyApp' do
|
||||
platform :ios, '10.0'
|
||||
|
||||
@@ -51,7 +51,7 @@ Add all of the code below to your `ios/Podfile`:
|
||||
platform :ios, '9.0'
|
||||
|
||||
def flipper_pods()
|
||||
flipperkit_version = '0.233.0' # should match the version of your Flipper client app
|
||||
flipperkit_version = '0.236.0' # should match the version of your Flipper client app
|
||||
pod 'FlipperKit', '~>' + flipperkit_version, :configuration => 'Debug'
|
||||
pod 'FlipperKit/FlipperKitLayoutPlugin', '~>' + flipperkit_version, :configuration => 'Debug'
|
||||
pod 'FlipperKit/SKIOSNetworkPlugin', '~>' + flipperkit_version, :configuration => 'Debug'
|
||||
|
||||
@@ -34,7 +34,7 @@ Latest version of Flipper requires react-native 0.69+! If you use react-native <
|
||||
|
||||
Android:
|
||||
|
||||
1. Bump the `FLIPPER_VERSION` variable in `android/gradle.properties`, for example: `FLIPPER_VERSION=0.233.0`.
|
||||
1. Bump the `FLIPPER_VERSION` variable in `android/gradle.properties`, for example: `FLIPPER_VERSION=0.236.0`.
|
||||
2. Run `./gradlew clean` in the `android` directory.
|
||||
|
||||
iOS:
|
||||
@@ -44,7 +44,7 @@ react-native version => 0.69.0
|
||||
2. Run `pod install --repo-update` in the `ios` directory.
|
||||
|
||||
react-native version < 0.69.0
|
||||
1. Call `use_flipper` with a specific version in `ios/Podfile`, for example: `use_flipper!({ 'Flipper' => '0.233.0' })`.
|
||||
1. Call `use_flipper` with a specific version in `ios/Podfile`, for example: `use_flipper!({ 'Flipper' => '0.236.0' })`.
|
||||
2. Run `pod install --repo-update` in the `ios` directory.
|
||||
|
||||
## Manual Setup
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
# This source code is licensed under the MIT license found in the
|
||||
# LICENSE file in the root directory of this source tree.
|
||||
# POM publishing constants
|
||||
VERSION_NAME=0.233.1-SNAPSHOT
|
||||
VERSION_NAME=0.236.1-SNAPSHOT
|
||||
GROUP=com.facebook.flipper
|
||||
SONATYPE_STAGING_PROFILE=comfacebook
|
||||
POM_URL=https://github.com/facebook/flipper
|
||||
@@ -18,6 +18,7 @@ POM_DEVELOPER_NAME=facebook
|
||||
POM_ISSUES_URL=https://github.com/facebook/flipper/issues/
|
||||
# Shared version numbers
|
||||
LITHO_VERSION=0.48.0
|
||||
FRESCO_VERSION=3.1.3
|
||||
ANDROIDX_VERSION=1.3.0
|
||||
KOTLIN_VERSION=1.8.20
|
||||
FBJNI_VERSION=0.3.0
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
PODS:
|
||||
- CocoaAsyncSocket (7.6.5)
|
||||
- Flipper (0.222.0):
|
||||
- Flipper (0.233.0):
|
||||
- Flipper-Folly (~> 2.6)
|
||||
- Flipper-Boost-iOSX (1.76.0.1.11)
|
||||
- Flipper-DoubleConversion (3.2.0.1)
|
||||
@@ -14,50 +14,50 @@ PODS:
|
||||
- OpenSSL-Universal (= 1.1.1100)
|
||||
- Flipper-Glog (0.5.0.5)
|
||||
- Flipper-PeerTalk (0.0.4)
|
||||
- FlipperKit (0.222.0):
|
||||
- FlipperKit/Core (= 0.222.0)
|
||||
- FlipperKit/Core (0.222.0):
|
||||
- Flipper (~> 0.222.0)
|
||||
- FlipperKit (0.233.0):
|
||||
- FlipperKit/Core (= 0.233.0)
|
||||
- FlipperKit/Core (0.233.0):
|
||||
- Flipper (~> 0.233.0)
|
||||
- FlipperKit/CppBridge
|
||||
- FlipperKit/FBCxxFollyDynamicConvert
|
||||
- FlipperKit/FBDefines
|
||||
- FlipperKit/FKPortForwarding
|
||||
- SocketRocket (~> 0.7.0)
|
||||
- FlipperKit/CppBridge (0.222.0):
|
||||
- Flipper (~> 0.222.0)
|
||||
- FlipperKit/FBCxxFollyDynamicConvert (0.222.0):
|
||||
- FlipperKit/CppBridge (0.233.0):
|
||||
- Flipper (~> 0.233.0)
|
||||
- FlipperKit/FBCxxFollyDynamicConvert (0.233.0):
|
||||
- Flipper-Folly (~> 2.6)
|
||||
- FlipperKit/FBDefines (0.222.0)
|
||||
- FlipperKit/FKPortForwarding (0.222.0):
|
||||
- FlipperKit/FBDefines (0.233.0)
|
||||
- FlipperKit/FKPortForwarding (0.233.0):
|
||||
- CocoaAsyncSocket (~> 7.6)
|
||||
- Flipper-PeerTalk (~> 0.0.4)
|
||||
- FlipperKit/FlipperKitExamplePlugin (0.222.0):
|
||||
- FlipperKit/FlipperKitExamplePlugin (0.233.0):
|
||||
- FlipperKit/Core
|
||||
- FlipperKit/FlipperKitHighlightOverlay (0.222.0)
|
||||
- FlipperKit/FlipperKitLayoutHelpers (0.222.0):
|
||||
- FlipperKit/FlipperKitHighlightOverlay (0.233.0)
|
||||
- FlipperKit/FlipperKitLayoutHelpers (0.233.0):
|
||||
- FlipperKit/Core
|
||||
- FlipperKit/FlipperKitHighlightOverlay
|
||||
- FlipperKit/FlipperKitLayoutTextSearchable
|
||||
- FlipperKit/FlipperKitLayoutIOSDescriptors (0.222.0):
|
||||
- FlipperKit/FlipperKitLayoutIOSDescriptors (0.233.0):
|
||||
- FlipperKit/Core
|
||||
- FlipperKit/FlipperKitHighlightOverlay
|
||||
- FlipperKit/FlipperKitLayoutHelpers
|
||||
- FlipperKit/FlipperKitLayoutPlugin (0.222.0):
|
||||
- FlipperKit/FlipperKitLayoutPlugin (0.233.0):
|
||||
- FlipperKit/Core
|
||||
- FlipperKit/FlipperKitHighlightOverlay
|
||||
- FlipperKit/FlipperKitLayoutHelpers
|
||||
- FlipperKit/FlipperKitLayoutIOSDescriptors
|
||||
- FlipperKit/FlipperKitLayoutTextSearchable
|
||||
- FlipperKit/FlipperKitLayoutTextSearchable (0.222.0)
|
||||
- FlipperKit/FlipperKitNetworkPlugin (0.222.0):
|
||||
- FlipperKit/FlipperKitLayoutTextSearchable (0.233.0)
|
||||
- FlipperKit/FlipperKitNetworkPlugin (0.233.0):
|
||||
- FlipperKit/Core
|
||||
- FlipperKit/FlipperKitReactPlugin (0.222.0):
|
||||
- FlipperKit/FlipperKitReactPlugin (0.233.0):
|
||||
- FlipperKit/Core
|
||||
- FlipperKit/FlipperKitUIDebuggerPlugin (0.222.0):
|
||||
- FlipperKit/FlipperKitUIDebuggerPlugin (0.233.0):
|
||||
- FlipperKit/Core
|
||||
- FlipperKit/FlipperKitUserDefaultsPlugin (0.222.0):
|
||||
- FlipperKit/FlipperKitUserDefaultsPlugin (0.233.0):
|
||||
- FlipperKit/Core
|
||||
- FlipperKit/SKIOSNetworkPlugin (0.222.0):
|
||||
- FlipperKit/SKIOSNetworkPlugin (0.233.0):
|
||||
- FlipperKit/Core
|
||||
- FlipperKit/FlipperKitNetworkPlugin
|
||||
- libevent (2.1.12)
|
||||
@@ -104,14 +104,14 @@ EXTERNAL SOURCES:
|
||||
|
||||
SPEC CHECKSUMS:
|
||||
CocoaAsyncSocket: 065fd1e645c7abab64f7a6a2007a48038fdc6a99
|
||||
Flipper: 2134ddbde14751be413e22c2677d743dffaa9945
|
||||
Flipper: 78cb4b9cf280b4ad4230a4f3b5c21ff657f3c147
|
||||
Flipper-Boost-iOSX: fd1e2b8cbef7e662a122412d7ac5f5bea715403c
|
||||
Flipper-DoubleConversion: 2dc99b02f658daf147069aad9dbd29d8feb06d30
|
||||
Flipper-Fmt: 60cbdd92fc254826e61d669a5d87ef7015396a9b
|
||||
Flipper-Folly: 584845625005ff068a6ebf41f857f468decd26b3
|
||||
Flipper-Glog: 70c50ce58ddaf67dc35180db05f191692570f446
|
||||
Flipper-PeerTalk: 116d8f857dc6ef55c7a5a75ea3ceaafe878aadc9
|
||||
FlipperKit: 347167ea72c9d718edb15757184384cf31a2cf27
|
||||
FlipperKit: 825a05d0e628a8533599bff069d8325c0c577b27
|
||||
libevent: 4049cae6c81cdb3654a443be001fb9bdceff7913
|
||||
OpenSSL-Universal: ebc357f1e6bc71fa463ccb2fe676756aff50e88c
|
||||
SocketRocket: abac6f5de4d4d62d24e11868d7a2f427e0ef940d
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
PODS:
|
||||
- boost-for-react-native (1.63.0)
|
||||
- CocoaAsyncSocket (7.6.5)
|
||||
- Flipper (0.222.0):
|
||||
- Flipper (0.233.0):
|
||||
- Flipper-Folly (~> 2.6)
|
||||
- Flipper-Boost-iOSX (1.76.0.1.11)
|
||||
- Flipper-DoubleConversion (3.2.0.1)
|
||||
@@ -15,46 +15,46 @@ PODS:
|
||||
- OpenSSL-Universal (= 1.1.1100)
|
||||
- Flipper-Glog (0.5.0.5)
|
||||
- Flipper-PeerTalk (0.0.4)
|
||||
- FlipperKit (0.222.0):
|
||||
- FlipperKit/Core (= 0.222.0)
|
||||
- FlipperKit/Core (0.222.0):
|
||||
- Flipper (~> 0.222.0)
|
||||
- FlipperKit (0.233.0):
|
||||
- FlipperKit/Core (= 0.233.0)
|
||||
- FlipperKit/Core (0.233.0):
|
||||
- Flipper (~> 0.233.0)
|
||||
- FlipperKit/CppBridge
|
||||
- FlipperKit/FBCxxFollyDynamicConvert
|
||||
- FlipperKit/FBDefines
|
||||
- FlipperKit/FKPortForwarding
|
||||
- SocketRocket (~> 0.7.0)
|
||||
- FlipperKit/CppBridge (0.222.0):
|
||||
- Flipper (~> 0.222.0)
|
||||
- FlipperKit/FBCxxFollyDynamicConvert (0.222.0):
|
||||
- FlipperKit/CppBridge (0.233.0):
|
||||
- Flipper (~> 0.233.0)
|
||||
- FlipperKit/FBCxxFollyDynamicConvert (0.233.0):
|
||||
- Flipper-Folly (~> 2.6)
|
||||
- FlipperKit/FBDefines (0.222.0)
|
||||
- FlipperKit/FKPortForwarding (0.222.0):
|
||||
- FlipperKit/FBDefines (0.233.0)
|
||||
- FlipperKit/FKPortForwarding (0.233.0):
|
||||
- CocoaAsyncSocket (~> 7.6)
|
||||
- Flipper-PeerTalk (~> 0.0.4)
|
||||
- FlipperKit/FlipperKitExamplePlugin (0.222.0):
|
||||
- FlipperKit/FlipperKitExamplePlugin (0.233.0):
|
||||
- FlipperKit/Core
|
||||
- FlipperKit/FlipperKitHighlightOverlay (0.222.0)
|
||||
- FlipperKit/FlipperKitLayoutHelpers (0.222.0):
|
||||
- FlipperKit/FlipperKitHighlightOverlay (0.233.0)
|
||||
- FlipperKit/FlipperKitLayoutHelpers (0.233.0):
|
||||
- FlipperKit/Core
|
||||
- FlipperKit/FlipperKitHighlightOverlay
|
||||
- FlipperKit/FlipperKitLayoutTextSearchable
|
||||
- FlipperKit/FlipperKitLayoutIOSDescriptors (0.222.0):
|
||||
- FlipperKit/FlipperKitLayoutIOSDescriptors (0.233.0):
|
||||
- FlipperKit/Core
|
||||
- FlipperKit/FlipperKitHighlightOverlay
|
||||
- FlipperKit/FlipperKitLayoutHelpers
|
||||
- FlipperKit/FlipperKitLayoutPlugin (0.222.0):
|
||||
- FlipperKit/FlipperKitLayoutPlugin (0.233.0):
|
||||
- FlipperKit/Core
|
||||
- FlipperKit/FlipperKitHighlightOverlay
|
||||
- FlipperKit/FlipperKitLayoutHelpers
|
||||
- FlipperKit/FlipperKitLayoutIOSDescriptors
|
||||
- FlipperKit/FlipperKitLayoutTextSearchable
|
||||
- FlipperKit/FlipperKitLayoutTextSearchable (0.222.0)
|
||||
- FlipperKit/FlipperKitNetworkPlugin (0.222.0):
|
||||
- FlipperKit/FlipperKitLayoutTextSearchable (0.233.0)
|
||||
- FlipperKit/FlipperKitNetworkPlugin (0.233.0):
|
||||
- FlipperKit/Core
|
||||
- FlipperKit/FlipperKitUserDefaultsPlugin (0.222.0):
|
||||
- FlipperKit/FlipperKitUserDefaultsPlugin (0.233.0):
|
||||
- FlipperKit/Core
|
||||
- FlipperKit/SKIOSNetworkPlugin (0.222.0):
|
||||
- FlipperKit/SKIOSNetworkPlugin (0.233.0):
|
||||
- FlipperKit/Core
|
||||
- FlipperKit/FlipperKitNetworkPlugin
|
||||
- libevent (2.1.12)
|
||||
@@ -100,14 +100,14 @@ EXTERNAL SOURCES:
|
||||
SPEC CHECKSUMS:
|
||||
boost-for-react-native: 39c7adb57c4e60d6c5479dd8623128eb5b3f0f2c
|
||||
CocoaAsyncSocket: 065fd1e645c7abab64f7a6a2007a48038fdc6a99
|
||||
Flipper: 2134ddbde14751be413e22c2677d743dffaa9945
|
||||
Flipper: 78cb4b9cf280b4ad4230a4f3b5c21ff657f3c147
|
||||
Flipper-Boost-iOSX: fd1e2b8cbef7e662a122412d7ac5f5bea715403c
|
||||
Flipper-DoubleConversion: 2dc99b02f658daf147069aad9dbd29d8feb06d30
|
||||
Flipper-Fmt: 60cbdd92fc254826e61d669a5d87ef7015396a9b
|
||||
Flipper-Folly: 584845625005ff068a6ebf41f857f468decd26b3
|
||||
Flipper-Glog: 70c50ce58ddaf67dc35180db05f191692570f446
|
||||
Flipper-PeerTalk: 116d8f857dc6ef55c7a5a75ea3ceaafe878aadc9
|
||||
FlipperKit: 347167ea72c9d718edb15757184384cf31a2cf27
|
||||
FlipperKit: 825a05d0e628a8533599bff069d8325c0c577b27
|
||||
libevent: 4049cae6c81cdb3654a443be001fb9bdceff7913
|
||||
OpenSSL-Universal: ebc357f1e6bc71fa463ccb2fe676756aff50e88c
|
||||
SocketRocket: abac6f5de4d4d62d24e11868d7a2f427e0ef940d
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
project 'Tutorial.xcodeproj'
|
||||
swift_version = "4.1"
|
||||
flipperkit_version = '0.222.0'
|
||||
flipperkit_version = '0.233.0'
|
||||
use_frameworks!
|
||||
|
||||
target 'Tutorial' do
|
||||
|
||||
@@ -3,7 +3,7 @@ PODS:
|
||||
- ComponentKit (0.31):
|
||||
- RenderCore (= 0.31)
|
||||
- Yoga (~> 1.14)
|
||||
- Flipper (0.222.0):
|
||||
- Flipper (0.233.0):
|
||||
- Flipper-Folly (~> 2.6)
|
||||
- Flipper-Boost-iOSX (1.76.0.1.11)
|
||||
- Flipper-DoubleConversion (3.2.0.1)
|
||||
@@ -17,25 +17,25 @@ PODS:
|
||||
- OpenSSL-Universal (= 1.1.1100)
|
||||
- Flipper-Glog (0.5.0.5)
|
||||
- Flipper-PeerTalk (0.0.4)
|
||||
- FlipperKit (0.222.0):
|
||||
- FlipperKit/Core (= 0.222.0)
|
||||
- FlipperKit/Core (0.222.0):
|
||||
- Flipper (~> 0.222.0)
|
||||
- FlipperKit (0.233.0):
|
||||
- FlipperKit/Core (= 0.233.0)
|
||||
- FlipperKit/Core (0.233.0):
|
||||
- Flipper (~> 0.233.0)
|
||||
- FlipperKit/CppBridge
|
||||
- FlipperKit/FBCxxFollyDynamicConvert
|
||||
- FlipperKit/FBDefines
|
||||
- FlipperKit/FKPortForwarding
|
||||
- SocketRocket (~> 0.7.0)
|
||||
- FlipperKit/CppBridge (0.222.0):
|
||||
- Flipper (~> 0.222.0)
|
||||
- FlipperKit/FBCxxFollyDynamicConvert (0.222.0):
|
||||
- FlipperKit/CppBridge (0.233.0):
|
||||
- Flipper (~> 0.233.0)
|
||||
- FlipperKit/FBCxxFollyDynamicConvert (0.233.0):
|
||||
- Flipper-Folly (~> 2.6)
|
||||
- FlipperKit/FBDefines (0.222.0)
|
||||
- FlipperKit/FKPortForwarding (0.222.0):
|
||||
- FlipperKit/FBDefines (0.233.0)
|
||||
- FlipperKit/FKPortForwarding (0.233.0):
|
||||
- CocoaAsyncSocket (~> 7.6)
|
||||
- Flipper-PeerTalk (~> 0.0.4)
|
||||
- FlipperKit/FlipperKitHighlightOverlay (0.222.0)
|
||||
- FlipperKit/FlipperKitLayoutComponentKitSupport (0.222.0):
|
||||
- FlipperKit/FlipperKitHighlightOverlay (0.233.0)
|
||||
- FlipperKit/FlipperKitLayoutComponentKitSupport (0.233.0):
|
||||
- ComponentKit (= 0.31)
|
||||
- FlipperKit/Core
|
||||
- FlipperKit/FlipperKitHighlightOverlay
|
||||
@@ -43,26 +43,26 @@ PODS:
|
||||
- FlipperKit/FlipperKitLayoutPlugin
|
||||
- FlipperKit/FlipperKitLayoutTextSearchable
|
||||
- RenderCore (= 0.31)
|
||||
- FlipperKit/FlipperKitLayoutHelpers (0.222.0):
|
||||
- FlipperKit/FlipperKitLayoutHelpers (0.233.0):
|
||||
- FlipperKit/Core
|
||||
- FlipperKit/FlipperKitHighlightOverlay
|
||||
- FlipperKit/FlipperKitLayoutTextSearchable
|
||||
- FlipperKit/FlipperKitLayoutIOSDescriptors (0.222.0):
|
||||
- FlipperKit/FlipperKitLayoutIOSDescriptors (0.233.0):
|
||||
- FlipperKit/Core
|
||||
- FlipperKit/FlipperKitHighlightOverlay
|
||||
- FlipperKit/FlipperKitLayoutHelpers
|
||||
- FlipperKit/FlipperKitLayoutPlugin (0.222.0):
|
||||
- FlipperKit/FlipperKitLayoutPlugin (0.233.0):
|
||||
- FlipperKit/Core
|
||||
- FlipperKit/FlipperKitHighlightOverlay
|
||||
- FlipperKit/FlipperKitLayoutHelpers
|
||||
- FlipperKit/FlipperKitLayoutIOSDescriptors
|
||||
- FlipperKit/FlipperKitLayoutTextSearchable
|
||||
- FlipperKit/FlipperKitLayoutTextSearchable (0.222.0)
|
||||
- FlipperKit/FlipperKitNetworkPlugin (0.222.0):
|
||||
- FlipperKit/FlipperKitLayoutTextSearchable (0.233.0)
|
||||
- FlipperKit/FlipperKitNetworkPlugin (0.233.0):
|
||||
- FlipperKit/Core
|
||||
- FlipperKit/FlipperKitUserDefaultsPlugin (0.222.0):
|
||||
- FlipperKit/FlipperKitUserDefaultsPlugin (0.233.0):
|
||||
- FlipperKit/Core
|
||||
- FlipperKit/SKIOSNetworkPlugin (0.222.0):
|
||||
- FlipperKit/SKIOSNetworkPlugin (0.233.0):
|
||||
- FlipperKit/Core
|
||||
- FlipperKit/FlipperKitNetworkPlugin
|
||||
- libevent (2.1.12)
|
||||
@@ -72,10 +72,10 @@ PODS:
|
||||
- Yoga (1.14.0)
|
||||
|
||||
DEPENDENCIES:
|
||||
- FlipperKit (~> 0.222.0)
|
||||
- FlipperKit/FlipperKitLayoutComponentKitSupport (~> 0.222.0)
|
||||
- FlipperKit/FlipperKitUserDefaultsPlugin (~> 0.222.0)
|
||||
- FlipperKit/SKIOSNetworkPlugin (~> 0.222.0)
|
||||
- FlipperKit (~> 0.233.0)
|
||||
- FlipperKit/FlipperKitLayoutComponentKitSupport (~> 0.233.0)
|
||||
- FlipperKit/FlipperKitUserDefaultsPlugin (~> 0.233.0)
|
||||
- FlipperKit/SKIOSNetworkPlugin (~> 0.233.0)
|
||||
|
||||
SPEC REPOS:
|
||||
trunk:
|
||||
@@ -98,20 +98,20 @@ SPEC REPOS:
|
||||
SPEC CHECKSUMS:
|
||||
CocoaAsyncSocket: 065fd1e645c7abab64f7a6a2007a48038fdc6a99
|
||||
ComponentKit: 7bf7048b9814afc6b6641645a14177f95fd9b9ae
|
||||
Flipper: 2134ddbde14751be413e22c2677d743dffaa9945
|
||||
Flipper: 78cb4b9cf280b4ad4230a4f3b5c21ff657f3c147
|
||||
Flipper-Boost-iOSX: fd1e2b8cbef7e662a122412d7ac5f5bea715403c
|
||||
Flipper-DoubleConversion: 2dc99b02f658daf147069aad9dbd29d8feb06d30
|
||||
Flipper-Fmt: 60cbdd92fc254826e61d669a5d87ef7015396a9b
|
||||
Flipper-Folly: 584845625005ff068a6ebf41f857f468decd26b3
|
||||
Flipper-Glog: 70c50ce58ddaf67dc35180db05f191692570f446
|
||||
Flipper-PeerTalk: 116d8f857dc6ef55c7a5a75ea3ceaafe878aadc9
|
||||
FlipperKit: 347167ea72c9d718edb15757184384cf31a2cf27
|
||||
FlipperKit: 825a05d0e628a8533599bff069d8325c0c577b27
|
||||
libevent: 4049cae6c81cdb3654a443be001fb9bdceff7913
|
||||
OpenSSL-Universal: ebc357f1e6bc71fa463ccb2fe676756aff50e88c
|
||||
RenderCore: 090beb17b5bff80b86929a7ceb49df789923d23a
|
||||
SocketRocket: abac6f5de4d4d62d24e11868d7a2f427e0ef940d
|
||||
Yoga: cff67a400f6b74dc38eb0bad4f156673d9aa980c
|
||||
|
||||
PODFILE CHECKSUM: 8f946f0af7af514b0220472d59f2b9d79b44d357
|
||||
PODFILE CHECKSUM: 21ac489846903706fab579f97b09e3927902f123
|
||||
|
||||
COCOAPODS: 1.12.1
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "js-flipper",
|
||||
"title": "JS Flipper Bindings for Web-Socket based clients",
|
||||
"version": "0.233.0",
|
||||
"version": "0.236.0",
|
||||
"main": "lib/index.js",
|
||||
"browser": {
|
||||
"os": false
|
||||
@@ -39,34 +39,34 @@
|
||||
"licenseFilename": "LICENSE",
|
||||
"readmeFilename": "README.md",
|
||||
"devDependencies": {
|
||||
"@babel/core": "^7.22.10",
|
||||
"@babel/eslint-parser": "7.22.10",
|
||||
"@types/jest": "^29.5.3",
|
||||
"@types/node": "^20.5.0",
|
||||
"@types/sinon": "^10.0.16",
|
||||
"@types/ws": "^8.5.5",
|
||||
"@typescript-eslint/eslint-plugin": "6.4.0",
|
||||
"@typescript-eslint/parser": "6.4.0",
|
||||
"@babel/core": "^7.23.2",
|
||||
"@babel/eslint-parser": "7.22.15",
|
||||
"@types/jest": "^29.5.6",
|
||||
"@types/node": "^20.8.8",
|
||||
"@types/sinon": "^10.0.20",
|
||||
"@types/ws": "^8.5.8",
|
||||
"@typescript-eslint/eslint-plugin": "6.9.0",
|
||||
"@typescript-eslint/parser": "6.9.0",
|
||||
"ansi-to-html": "^0.7.2",
|
||||
"cross-env": "^7.0.3",
|
||||
"eslint": "8.47.0",
|
||||
"eslint": "8.52.0",
|
||||
"eslint-config-fbjs": "^4.0.0",
|
||||
"eslint-config-prettier": "^9.0.0",
|
||||
"eslint-import-resolver-typescript": "^3.6.0",
|
||||
"eslint-import-resolver-typescript": "^3.6.1",
|
||||
"eslint-plugin-babel": "^5.3.0",
|
||||
"eslint-plugin-ft-flow": "^3.0.0",
|
||||
"eslint-plugin-ft-flow": "^3.0.1",
|
||||
"eslint-plugin-header": "^3.0.0",
|
||||
"eslint-plugin-import": "^2.28.0",
|
||||
"eslint-plugin-import": "^2.29.0",
|
||||
"eslint-plugin-jsx-a11y": "^6.7.1",
|
||||
"eslint-plugin-node": "^11.1.0",
|
||||
"eslint-plugin-prettier": "^5.0.0",
|
||||
"eslint-plugin-prettier": "^5.0.1",
|
||||
"eslint-plugin-react": "^7.33.2",
|
||||
"eslint-plugin-react-hooks": "^4.5.0",
|
||||
"jest": "^29.6.2",
|
||||
"prettier": "^3.0.2",
|
||||
"sinon": "^15.2.0",
|
||||
"jest": "^29.7.0",
|
||||
"prettier": "^3.0.3",
|
||||
"sinon": "^17.0.0",
|
||||
"ts-jest": "^29.1.0",
|
||||
"typescript": "^5.1.6",
|
||||
"ws": "^8.12.1"
|
||||
"typescript": "^5.2.2",
|
||||
"ws": "^8.14.2"
|
||||
}
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -5,7 +5,7 @@ source 'https://github.com/CocoaPods/Specs'
|
||||
platform :ios, '12.4'
|
||||
|
||||
# used for automatic bumping
|
||||
flipperkit_version = '0.222.0'
|
||||
flipperkit_version = '0.233.0'
|
||||
|
||||
target 'ReactNativeFlipperExample' do
|
||||
config = use_native_modules!
|
||||
|
||||
@@ -10,7 +10,7 @@ PODS:
|
||||
- React-Core (= 0.69.7)
|
||||
- React-jsi (= 0.69.7)
|
||||
- ReactCommon/turbomodule/core (= 0.69.7)
|
||||
- Flipper (0.222.0):
|
||||
- Flipper (0.233.0):
|
||||
- Flipper-Folly (~> 2.6)
|
||||
- Flipper-Boost-iOSX (1.76.0.1.11)
|
||||
- Flipper-DoubleConversion (3.2.0)
|
||||
@@ -26,46 +26,46 @@ PODS:
|
||||
- Flipper-PeerTalk (0.0.4)
|
||||
- Flipper-RSocket (1.4.3):
|
||||
- Flipper-Folly (~> 2.6)
|
||||
- FlipperKit (0.222.0):
|
||||
- FlipperKit/Core (= 0.222.0)
|
||||
- FlipperKit/Core (0.222.0):
|
||||
- Flipper (~> 0.222.0)
|
||||
- FlipperKit (0.233.0):
|
||||
- FlipperKit/Core (= 0.233.0)
|
||||
- FlipperKit/Core (0.233.0):
|
||||
- Flipper (~> 0.233.0)
|
||||
- FlipperKit/CppBridge
|
||||
- FlipperKit/FBCxxFollyDynamicConvert
|
||||
- FlipperKit/FBDefines
|
||||
- FlipperKit/FKPortForwarding
|
||||
- SocketRocket (~> 0.7.0)
|
||||
- FlipperKit/CppBridge (0.222.0):
|
||||
- Flipper (~> 0.222.0)
|
||||
- FlipperKit/FBCxxFollyDynamicConvert (0.222.0):
|
||||
- FlipperKit/CppBridge (0.233.0):
|
||||
- Flipper (~> 0.233.0)
|
||||
- FlipperKit/FBCxxFollyDynamicConvert (0.233.0):
|
||||
- Flipper-Folly (~> 2.6)
|
||||
- FlipperKit/FBDefines (0.222.0)
|
||||
- FlipperKit/FKPortForwarding (0.222.0):
|
||||
- FlipperKit/FBDefines (0.233.0)
|
||||
- FlipperKit/FKPortForwarding (0.233.0):
|
||||
- CocoaAsyncSocket (~> 7.6)
|
||||
- Flipper-PeerTalk (~> 0.0.4)
|
||||
- FlipperKit/FlipperKitHighlightOverlay (0.222.0)
|
||||
- FlipperKit/FlipperKitLayoutHelpers (0.222.0):
|
||||
- FlipperKit/FlipperKitHighlightOverlay (0.233.0)
|
||||
- FlipperKit/FlipperKitLayoutHelpers (0.233.0):
|
||||
- FlipperKit/Core
|
||||
- FlipperKit/FlipperKitHighlightOverlay
|
||||
- FlipperKit/FlipperKitLayoutTextSearchable
|
||||
- FlipperKit/FlipperKitLayoutIOSDescriptors (0.222.0):
|
||||
- FlipperKit/FlipperKitLayoutIOSDescriptors (0.233.0):
|
||||
- FlipperKit/Core
|
||||
- FlipperKit/FlipperKitHighlightOverlay
|
||||
- FlipperKit/FlipperKitLayoutHelpers
|
||||
- FlipperKit/FlipperKitLayoutPlugin (0.222.0):
|
||||
- FlipperKit/FlipperKitLayoutPlugin (0.233.0):
|
||||
- FlipperKit/Core
|
||||
- FlipperKit/FlipperKitHighlightOverlay
|
||||
- FlipperKit/FlipperKitLayoutHelpers
|
||||
- FlipperKit/FlipperKitLayoutIOSDescriptors
|
||||
- FlipperKit/FlipperKitLayoutTextSearchable
|
||||
- FlipperKit/FlipperKitLayoutTextSearchable (0.222.0)
|
||||
- FlipperKit/FlipperKitNetworkPlugin (0.222.0):
|
||||
- FlipperKit/FlipperKitLayoutTextSearchable (0.233.0)
|
||||
- FlipperKit/FlipperKitNetworkPlugin (0.233.0):
|
||||
- FlipperKit/Core
|
||||
- FlipperKit/FlipperKitReactPlugin (0.222.0):
|
||||
- FlipperKit/FlipperKitReactPlugin (0.233.0):
|
||||
- FlipperKit/Core
|
||||
- FlipperKit/FlipperKitUserDefaultsPlugin (0.222.0):
|
||||
- FlipperKit/FlipperKitUserDefaultsPlugin (0.233.0):
|
||||
- FlipperKit/Core
|
||||
- FlipperKit/SKIOSNetworkPlugin (0.222.0):
|
||||
- FlipperKit/SKIOSNetworkPlugin (0.233.0):
|
||||
- FlipperKit/Core
|
||||
- FlipperKit/FlipperKitNetworkPlugin
|
||||
- fmt (6.2.1)
|
||||
@@ -375,7 +375,7 @@ DEPENDENCIES:
|
||||
- DoubleConversion (from `../node_modules/react-native/third-party-podspecs/DoubleConversion.podspec`)
|
||||
- FBLazyVector (from `../node_modules/react-native/Libraries/FBLazyVector`)
|
||||
- FBReactNativeSpec (from `../node_modules/react-native/React/FBReactNativeSpec`)
|
||||
- Flipper (= 0.222.0)
|
||||
- Flipper (= 0.233.0)
|
||||
- Flipper-Boost-iOSX (= 1.76.0.1.11)
|
||||
- Flipper-DoubleConversion (= 3.2.0)
|
||||
- Flipper-Fmt (= 7.1.7)
|
||||
@@ -383,19 +383,19 @@ DEPENDENCIES:
|
||||
- Flipper-Glog (= 0.5.0.3)
|
||||
- Flipper-PeerTalk (= 0.0.4)
|
||||
- Flipper-RSocket (= 1.4.3)
|
||||
- FlipperKit (= 0.222.0)
|
||||
- FlipperKit/Core (= 0.222.0)
|
||||
- FlipperKit/CppBridge (= 0.222.0)
|
||||
- FlipperKit/FBCxxFollyDynamicConvert (= 0.222.0)
|
||||
- FlipperKit/FBDefines (= 0.222.0)
|
||||
- FlipperKit/FKPortForwarding (= 0.222.0)
|
||||
- FlipperKit/FlipperKitHighlightOverlay (= 0.222.0)
|
||||
- FlipperKit/FlipperKitLayoutPlugin (= 0.222.0)
|
||||
- FlipperKit/FlipperKitLayoutTextSearchable (= 0.222.0)
|
||||
- FlipperKit/FlipperKitNetworkPlugin (= 0.222.0)
|
||||
- FlipperKit/FlipperKitReactPlugin (= 0.222.0)
|
||||
- FlipperKit/FlipperKitUserDefaultsPlugin (= 0.222.0)
|
||||
- FlipperKit/SKIOSNetworkPlugin (= 0.222.0)
|
||||
- FlipperKit (= 0.233.0)
|
||||
- FlipperKit/Core (= 0.233.0)
|
||||
- FlipperKit/CppBridge (= 0.233.0)
|
||||
- FlipperKit/FBCxxFollyDynamicConvert (= 0.233.0)
|
||||
- FlipperKit/FBDefines (= 0.233.0)
|
||||
- FlipperKit/FKPortForwarding (= 0.233.0)
|
||||
- FlipperKit/FlipperKitHighlightOverlay (= 0.233.0)
|
||||
- FlipperKit/FlipperKitLayoutPlugin (= 0.233.0)
|
||||
- FlipperKit/FlipperKitLayoutTextSearchable (= 0.233.0)
|
||||
- FlipperKit/FlipperKitNetworkPlugin (= 0.233.0)
|
||||
- FlipperKit/FlipperKitReactPlugin (= 0.233.0)
|
||||
- FlipperKit/FlipperKitUserDefaultsPlugin (= 0.233.0)
|
||||
- FlipperKit/SKIOSNetworkPlugin (= 0.233.0)
|
||||
- glog (from `../node_modules/react-native/third-party-podspecs/glog.podspec`)
|
||||
- hermes-engine (from `../node_modules/react-native/sdks/hermes/hermes-engine.podspec`)
|
||||
- libevent (~> 2.1.12)
|
||||
@@ -527,7 +527,7 @@ SPEC CHECKSUMS:
|
||||
DoubleConversion: 5189b271737e1565bdce30deb4a08d647e3f5f54
|
||||
FBLazyVector: 6b7f5692909b4300d50e7359cdefbcd09dd30faa
|
||||
FBReactNativeSpec: affcf71d996f6b0c01f68883482588297b9d5e6e
|
||||
Flipper: 2134ddbde14751be413e22c2677d743dffaa9945
|
||||
Flipper: 78cb4b9cf280b4ad4230a4f3b5c21ff657f3c147
|
||||
Flipper-Boost-iOSX: fd1e2b8cbef7e662a122412d7ac5f5bea715403c
|
||||
Flipper-DoubleConversion: 3d3d04a078d4f3a1b6c6916587f159dc11f232c4
|
||||
Flipper-Fmt: 60cbdd92fc254826e61d669a5d87ef7015396a9b
|
||||
@@ -535,7 +535,7 @@ SPEC CHECKSUMS:
|
||||
Flipper-Glog: 7761f5362d23ead28c19afc2dd1d819f00e40df9
|
||||
Flipper-PeerTalk: 116d8f857dc6ef55c7a5a75ea3ceaafe878aadc9
|
||||
Flipper-RSocket: d9d9ade67cbecf6ac10730304bf5607266dd2541
|
||||
FlipperKit: 347167ea72c9d718edb15757184384cf31a2cf27
|
||||
FlipperKit: 825a05d0e628a8533599bff069d8325c0c577b27
|
||||
fmt: ff9d55029c625d3757ed641535fd4a75fedc7ce9
|
||||
glog: 3d02b25ca00c2d456734d0bcff864cbc62f6ae1a
|
||||
hermes-engine: 51aaceb1f6dc1aed44b8bbf866f52ec146c00a89
|
||||
@@ -572,6 +572,6 @@ SPEC CHECKSUMS:
|
||||
SocketRocket: abac6f5de4d4d62d24e11868d7a2f427e0ef940d
|
||||
Yoga: 0b84a956f7393ef1f37f3bb213c516184e4a689d
|
||||
|
||||
PODFILE CHECKSUM: 4e948bc99291e6ee9db9ffa2c78a53e28aab3012
|
||||
PODFILE CHECKSUM: 5e5d992e623055876f27684fc962e3d0e2b2fa37
|
||||
|
||||
COCOAPODS: 1.12.1
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "react-native-flipper",
|
||||
"title": "React Native Flipper Bindings",
|
||||
"version": "0.233.0",
|
||||
"version": "0.236.0",
|
||||
"description": "Flipper bindings for React Native",
|
||||
"main": "index.js",
|
||||
"types": "index.d.ts",
|
||||
|
||||
Reference in New Issue
Block a user