Merge branch 'main' of github.com:facebook/flipper into universalBuild

This commit is contained in:
2023-11-07 12:42:02 +01:00
89 changed files with 2006 additions and 1608 deletions

View File

@@ -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'

View File

@@ -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

View File

@@ -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)

View File

@@ -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")

View File

@@ -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

View File

@@ -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)
}

View File

@@ -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.*

View File

@@ -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

View File

@@ -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);

View File

@@ -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"),

View File

@@ -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()

View File

@@ -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:

View File

@@ -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

View File

@@ -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 {

View File

@@ -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))
}
}

View File

@@ -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
}
}

View File

@@ -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>() {

View File

@@ -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)
}
}

View File

@@ -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())

View File

@@ -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)))
}
}
}

View File

@@ -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"
}
}

View File

@@ -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)
}
}
}
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
}
}
}

View File

@@ -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>()

View File

@@ -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()

View File

@@ -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>() {

View File

@@ -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'

View File

@@ -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
}

View File

@@ -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)
}

View File

@@ -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))

View File

@@ -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>() {

View File

@@ -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,

View File

@@ -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())
}
}

View File

@@ -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)
}
}

View File

@@ -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()
}
}

View File

@@ -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
}
}
}

View File

@@ -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)
}
}

View File

@@ -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())
}
}

View File

@@ -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
}
}

View File

@@ -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)

View File

@@ -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)
}
}
}

View File

@@ -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() }
}
}
}
}

View File

@@ -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"
}

View File

@@ -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

View File

@@ -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",
]

View File

@@ -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',
},

View File

@@ -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

View File

@@ -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",

View File

@@ -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,

View File

@@ -65,8 +65,10 @@ export const PowerSearchTermFinder = React.forwardRef<
onChange={setSearchTermFinderValue}
onBlur={() => {
setSearchTermFinderValue(null);
}}
onInputKeyDown={(event) => {
}}>
<Input
bordered={false}
onKeyUp={(event) => {
if (event.key === 'Enter') {
if (searchTermFinderValue && onConfirmUnknownOption) {
onConfirmUnknownOption(searchTermFinderValue);
@@ -76,9 +78,7 @@ export const PowerSearchTermFinder = React.forwardRef<
if (event.key === 'Backspace' && !searchTermFinderValue) {
onBackspacePressWhileEmpty();
}
}}>
<Input
bordered={false}
}}
onPasteCapture={(event) => {
const text = event.clipboardData.getData('text/plain');

View File

@@ -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) {

View File

@@ -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();
},
};
}

View File

@@ -7,6 +7,7 @@
* @format
*/
// eslint-disable-next-line @typescript-eslint/naming-convention
export const unstable_batchedUpdates = (cb: () => void) => {
return cb();
};

View File

@@ -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}`,
),
);
};

View File

@@ -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);

View File

@@ -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`,
);

View File

@@ -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;

View File

@@ -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();

View File

@@ -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});
}

View File

@@ -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

View File

@@ -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';

View File

@@ -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,

View File

@@ -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 ||

View File

@@ -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) {

View 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;
};

View File

@@ -171,7 +171,7 @@
"npm": "use yarn instead",
"yarn": "^1.16"
},
"version": "0.233.0",
"version": "0.236.0",
"workspaces": {
"packages": [
"scripts",

View File

@@ -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'
// ...
}

View File

@@ -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'
}
```

View File

@@ -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'
}
```

View File

@@ -28,9 +28,9 @@ import {
usePlugin,
useValue,
createDataSource,
DataTable,
DataTableColumn,
DataTableManager,
DataTableLegacy as DataTable,
DataTableColumnLegacy as DataTableColumn,
DataTableManagerLegacy as DataTableManager,
theme,
renderReactRoot,
batch,

View File

@@ -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';

View File

@@ -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,

View File

@@ -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

View File

@@ -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>

View File

@@ -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';

View File

@@ -982,6 +982,10 @@ function HighlightedText(props: {text: string}) {
### dataTablePowerSearchOperators
### MasterDetailLegacy
### DataTableLegacy
Coming soon
### MasterDetail

View File

@@ -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'
}
```

View File

@@ -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'

View File

@@ -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'

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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!

View File

@@ -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

View File

@@ -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",