-
Notifications
You must be signed in to change notification settings - Fork 24.3k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add TraceUpdateOverlay native component to render highlights on trace…
… updates Summary: This diff adds `TraceUpdateOverlay` native component to render highlights when trace update is detected from React JS. This allows a highlight border to be rendered outside of the component with re-renders. - Created `TraceUpdateOverlay` native component and added to the `DebugCorePackage` - Added to C++ registry so it's compatible with Fabric - Added to `AppContainer` for all RN apps when global devtools hook is available Changelog: [Android][Internal] - Add trace update overlay to show re-render highlights Reviewed By: javache Differential Revision: D42831719 fbshipit-source-id: 30c2e24859a316c27700270087a0d7779d7ad8ed
- Loading branch information
1 parent
ceb1d0d
commit 6ac88a4
Showing
7 changed files
with
442 additions
and
1 deletion.
There are no files selected for viewing
163 changes: 163 additions & 0 deletions
163
Libraries/Components/TraceUpdateOverlay/TraceUpdateOverlay.js
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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. | ||
* | ||
* @flow strict-local | ||
* @format | ||
*/ | ||
|
||
import type {Overlay} from './TraceUpdateOverlayNativeComponent'; | ||
|
||
import processColor from '../../StyleSheet/processColor'; | ||
import StyleSheet from '../../StyleSheet/StyleSheet'; | ||
import View from '../View/View'; | ||
import TraceUpdateOverlayNativeComponent, { | ||
Commands, | ||
} from './TraceUpdateOverlayNativeComponent'; | ||
import * as React from 'react'; | ||
|
||
type AgentEvents = { | ||
drawTraceUpdates: [Array<{node: TraceNode, color: string}>], | ||
disableTraceUpdates: [], | ||
}; | ||
|
||
interface Agent { | ||
addListener<Event: $Keys<AgentEvents>>( | ||
event: Event, | ||
listener: (...AgentEvents[Event]) => void, | ||
): void; | ||
removeListener(event: $Keys<AgentEvents>, listener: () => void): void; | ||
} | ||
|
||
type TraceNode = { | ||
canonical?: TraceNode, | ||
measure?: ( | ||
( | ||
x: number, | ||
y: number, | ||
width: number, | ||
height: number, | ||
left: number, | ||
top: number, | ||
) => void, | ||
) => void, | ||
}; | ||
|
||
type ReactDevToolsGlobalHook = { | ||
on: (eventName: string, (agent: Agent) => void) => void, | ||
off: (eventName: string, (agent: Agent) => void) => void, | ||
reactDevtoolsAgent: Agent, | ||
}; | ||
|
||
const {useEffect, useRef, useState} = React; | ||
const hook: ReactDevToolsGlobalHook = window.__REACT_DEVTOOLS_GLOBAL_HOOK__; | ||
let devToolsAgent: ?Agent; | ||
|
||
export default function TraceUpdateOverlay(): React.Node { | ||
const [overlayDisabled, setOverlayDisabled] = useState(false); | ||
// This effect is designed to be explictly shown here to avoid re-subscribe from the same | ||
// overlay component. | ||
useEffect(() => { | ||
function attachToDevtools(agent: Agent) { | ||
devToolsAgent = agent; | ||
agent.addListener('drawTraceUpdates', onAgentDrawTraceUpdates); | ||
agent.addListener('disableTraceUpdates', onAgentDisableTraceUpdates); | ||
} | ||
|
||
function subscribe() { | ||
hook?.on('react-devtools', attachToDevtools); | ||
if (hook?.reactDevtoolsAgent) { | ||
attachToDevtools(hook.reactDevtoolsAgent); | ||
} | ||
} | ||
|
||
function unsubscribe() { | ||
hook?.off('react-devtools', attachToDevtools); | ||
const agent = devToolsAgent; | ||
if (agent != null) { | ||
agent.removeListener('drawTraceUpdates', onAgentDrawTraceUpdates); | ||
agent.removeListener('disableTraceUpdates', onAgentDisableTraceUpdates); | ||
devToolsAgent = null; | ||
} | ||
} | ||
|
||
function onAgentDrawTraceUpdates( | ||
nodesToDraw: Array<{node: TraceNode, color: string}> = [], | ||
) { | ||
// If overlay is disabled before, now it's enabled. | ||
setOverlayDisabled(false); | ||
|
||
const newFramesToDraw: Array<Promise<Overlay>> = []; | ||
nodesToDraw.forEach(({node, color}) => { | ||
const component = node.canonical ?? node; | ||
if (!component || !component.measure) { | ||
return; | ||
} | ||
const frameToDrawPromise = new Promise<Overlay>(resolve => { | ||
// The if statement here is to make flow happy | ||
if (component.measure) { | ||
// TODO(T145522797): We should refactor this to use `getBoundingClientRect` when Paper is no longer supported. | ||
component.measure((x, y, width, height, left, top) => { | ||
resolve({ | ||
rect: {left, top, width, height}, | ||
color: processColor(color), | ||
}); | ||
}); | ||
} | ||
}); | ||
newFramesToDraw.push(frameToDrawPromise); | ||
}); | ||
Promise.all(newFramesToDraw).then( | ||
results => { | ||
if (nativeComponentRef.current != null) { | ||
Commands.draw( | ||
nativeComponentRef.current, | ||
JSON.stringify( | ||
results.filter( | ||
({rect, color}) => rect.width >= 0 && rect.height >= 0, | ||
), | ||
), | ||
); | ||
} | ||
}, | ||
err => { | ||
console.error(`Failed to measure updated traces. Error: ${err}`); | ||
}, | ||
); | ||
} | ||
|
||
function onAgentDisableTraceUpdates() { | ||
// When trace updates are disabled from the backend, we won't receive draw events until it's enabled by the next draw. We can safely remove the overlay as it's not needed now. | ||
setOverlayDisabled(true); | ||
} | ||
|
||
subscribe(); | ||
return unsubscribe; | ||
}, []); // Only run once when the overlay initially rendered | ||
|
||
const nativeComponentRef = | ||
useRef<?React.ElementRef<typeof TraceUpdateOverlayNativeComponent>>(null); | ||
|
||
return ( | ||
!overlayDisabled && ( | ||
<View pointerEvents="none" style={styles.overlay}> | ||
<TraceUpdateOverlayNativeComponent | ||
ref={nativeComponentRef} | ||
style={styles.overlay} | ||
/> | ||
</View> | ||
) | ||
); | ||
} | ||
|
||
const styles = StyleSheet.create({ | ||
overlay: { | ||
position: 'absolute', | ||
top: 0, | ||
bottom: 0, | ||
left: 0, | ||
right: 0, | ||
}, | ||
}); |
43 changes: 43 additions & 0 deletions
43
Libraries/Components/TraceUpdateOverlay/TraceUpdateOverlayNativeComponent.js
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,43 @@ | ||
/** | ||
* 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. | ||
* | ||
* @flow strict-local | ||
* @format | ||
*/ | ||
|
||
import type {HostComponent} from '../../Renderer/shims/ReactNativeTypes'; | ||
import type {ProcessedColorValue} from '../../StyleSheet/processColor'; | ||
import type {ViewProps} from '../View/ViewPropTypes'; | ||
|
||
import codegenNativeCommands from '../../Utilities/codegenNativeCommands'; | ||
import codegenNativeComponent from '../../Utilities/codegenNativeComponent'; | ||
import * as React from 'react'; | ||
|
||
type NativeProps = $ReadOnly<{| | ||
...ViewProps, | ||
|}>; | ||
export type TraceUpdateOverlayNativeComponentType = HostComponent<NativeProps>; | ||
export type Overlay = { | ||
rect: {left: number, top: number, width: number, height: number}, | ||
color: ?ProcessedColorValue, | ||
}; | ||
|
||
interface NativeCommands { | ||
+draw: ( | ||
viewRef: React.ElementRef<TraceUpdateOverlayNativeComponentType>, | ||
// TODO(T144046177): Ideally we can pass array of Overlay, but currently | ||
// Array type is not supported in RN codegen for building native commands. | ||
overlays: string, | ||
) => void; | ||
} | ||
|
||
export const Commands: NativeCommands = codegenNativeCommands<NativeCommands>({ | ||
supportedCommands: ['draw'], | ||
}); | ||
|
||
export default (codegenNativeComponent<NativeProps>( | ||
'TraceUpdateOverlay', | ||
): HostComponent<NativeProps>); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
29 changes: 29 additions & 0 deletions
29
ReactAndroid/src/main/java/com/facebook/react/views/traceupdateoverlay/BUCK
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,29 @@ | ||
load("//tools/build_defs/oss:rn_defs.bzl", "react_native_dep", "react_native_target", "rn_android_library") | ||
|
||
oncall("react_native") | ||
|
||
rn_android_library( | ||
name = "traceupdateoverlay", | ||
srcs = glob(["*.java"]), | ||
autoglob = False, | ||
labels = [ | ||
"pfh:ReactNative_CommonInfrastructurePlaceholder", | ||
], | ||
language = "JAVA", | ||
visibility = [ | ||
"PUBLIC", | ||
], | ||
deps = [ | ||
react_native_dep("libraries/fbcore/src/main/java/com/facebook/common/logging:logging"), | ||
react_native_dep("third-party/android/androidx:annotation"), | ||
react_native_dep("third-party/android/androidx:core"), | ||
react_native_dep("third-party/android/androidx:fragment"), | ||
react_native_dep("third-party/android/androidx:legacy-support-core-utils"), | ||
react_native_dep("third-party/java/jsr-305:jsr-305"), | ||
react_native_target("java/com/facebook/react/bridge:bridge"), | ||
react_native_target("java/com/facebook/react/module/annotations:annotations"), | ||
react_native_target("java/com/facebook/react/uimanager:uimanager"), | ||
react_native_target("java/com/facebook/react/uimanager/annotations:annotations"), | ||
react_native_target("java/com/facebook/react/util:util"), | ||
], | ||
) |
70 changes: 70 additions & 0 deletions
70
...Android/src/main/java/com/facebook/react/views/traceupdateoverlay/TraceUpdateOverlay.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,70 @@ | ||
/* | ||
* 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.react.views.traceupdateoverlay; | ||
|
||
import android.content.Context; | ||
import android.graphics.Canvas; | ||
import android.graphics.Paint; | ||
import android.graphics.RectF; | ||
import android.view.View; | ||
import androidx.annotation.UiThread; | ||
import com.facebook.react.uimanager.PixelUtil; | ||
import java.util.ArrayList; | ||
import java.util.List; | ||
|
||
public class TraceUpdateOverlay extends View { | ||
private final Paint mOverlayPaint = new Paint(); | ||
private List<Overlay> mOverlays = new ArrayList<Overlay>(); | ||
|
||
public static class Overlay { | ||
private final int mColor; | ||
private final RectF mRect; | ||
|
||
public Overlay(int color, RectF rect) { | ||
mColor = color; | ||
mRect = rect; | ||
} | ||
|
||
public int getColor() { | ||
return mColor; | ||
} | ||
|
||
public RectF getPixelRect() { | ||
return new RectF( | ||
PixelUtil.toPixelFromDIP(mRect.left), | ||
PixelUtil.toPixelFromDIP(mRect.top), | ||
PixelUtil.toPixelFromDIP(mRect.right), | ||
PixelUtil.toPixelFromDIP(mRect.bottom)); | ||
} | ||
} | ||
|
||
public TraceUpdateOverlay(Context context) { | ||
super(context); | ||
mOverlayPaint.setStyle(Paint.Style.STROKE); | ||
mOverlayPaint.setStrokeWidth(6); | ||
} | ||
|
||
@UiThread | ||
public void setOverlays(List<Overlay> overlays) { | ||
mOverlays = overlays; | ||
invalidate(); | ||
} | ||
|
||
@Override | ||
public void onDraw(Canvas canvas) { | ||
super.onDraw(canvas); | ||
|
||
if (!mOverlays.isEmpty()) { | ||
// Draw border outside of the given overlays to be aligned with web trace highlights | ||
for (Overlay overlay : mOverlays) { | ||
mOverlayPaint.setColor(overlay.getColor()); | ||
canvas.drawRect(overlay.getPixelRect(), mOverlayPaint); | ||
} | ||
} | ||
} | ||
} |
Oops, something went wrong.