Migrate Fresco plugin to Sandy
Summary: Removed the "notifications for leaks" test since we're no longer storing notifications but showing them immediately. Will do UI updates separately. Reviewed By: mweststrate Differential Revision: D28025507 fbshipit-source-id: 58db2504c29bba441b2c2d86cd3e2b8b5c010757
This commit is contained in:
committed by
Facebook GitHub Bot
parent
dd9af302cc
commit
8a5b2c5981
@@ -1,32 +0,0 @@
|
|||||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
|
||||||
|
|
||||||
exports[`notifications for leaks 1`] = `
|
|
||||||
<React.Fragment>
|
|
||||||
<Styled(div)>
|
|
||||||
CloseableReference leaked for
|
|
||||||
|
|
||||||
<Text
|
|
||||||
code={true}
|
|
||||||
>
|
|
||||||
com.facebook.imagepipeline.memory.NativeMemoryChunk
|
|
||||||
</Text>
|
|
||||||
(identity hashcode:
|
|
||||||
deadbeef
|
|
||||||
).
|
|
||||||
</Styled(div)>
|
|
||||||
<Styled(div)>
|
|
||||||
<Text
|
|
||||||
bold={true}
|
|
||||||
>
|
|
||||||
Stacktrace:
|
|
||||||
</Text>
|
|
||||||
</Styled(div)>
|
|
||||||
<Styled(div)>
|
|
||||||
<Text
|
|
||||||
code={true}
|
|
||||||
>
|
|
||||||
<unavailable>
|
|
||||||
</Text>
|
|
||||||
</Styled(div)>
|
|
||||||
</React.Fragment>
|
|
||||||
`;
|
|
||||||
@@ -1,99 +0,0 @@
|
|||||||
/**
|
|
||||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
|
||||||
*
|
|
||||||
* This source code is licensed under the MIT license found in the
|
|
||||||
* LICENSE file in the root directory of this source tree.
|
|
||||||
*
|
|
||||||
* @format
|
|
||||||
*/
|
|
||||||
|
|
||||||
import FrescoPlugin from '../index';
|
|
||||||
import {PersistedState, ImageEventWithId} from '../index';
|
|
||||||
import {AndroidCloseableReferenceLeakEvent} from '../api';
|
|
||||||
import {Notification} from 'flipper';
|
|
||||||
import {ImagesMap} from '../ImagePool';
|
|
||||||
|
|
||||||
type ScanDisplayTime = {[scan_number: number]: number};
|
|
||||||
|
|
||||||
function mockPersistedState(
|
|
||||||
imageSizes: Array<{
|
|
||||||
width: number;
|
|
||||||
height: number;
|
|
||||||
}> = [],
|
|
||||||
viewport: {
|
|
||||||
width: number;
|
|
||||||
height: number;
|
|
||||||
} = {width: 150, height: 150},
|
|
||||||
): PersistedState {
|
|
||||||
const scanDisplayTime: ScanDisplayTime = {};
|
|
||||||
scanDisplayTime[1] = 3;
|
|
||||||
const events: Array<ImageEventWithId> = [
|
|
||||||
{
|
|
||||||
imageIds: [...Array(imageSizes.length).keys()].map(String),
|
|
||||||
eventId: 0,
|
|
||||||
attribution: [],
|
|
||||||
startTime: 1,
|
|
||||||
endTime: 2,
|
|
||||||
source: 'source',
|
|
||||||
coldStart: true,
|
|
||||||
viewport: {...viewport, scanDisplayTime},
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
const imagesMap = imageSizes.reduce((acc, val, index) => {
|
|
||||||
acc[index] = {
|
|
||||||
imageId: String(index),
|
|
||||||
width: val.width,
|
|
||||||
height: val.height,
|
|
||||||
sizeBytes: 10,
|
|
||||||
data: 'undefined',
|
|
||||||
};
|
|
||||||
return acc;
|
|
||||||
}, {} as ImagesMap);
|
|
||||||
|
|
||||||
return {
|
|
||||||
surfaceList: new Set(),
|
|
||||||
images: [],
|
|
||||||
events,
|
|
||||||
imagesMap,
|
|
||||||
closeableReferenceLeaks: [],
|
|
||||||
isLeakTrackingEnabled: false,
|
|
||||||
nextEventId: 0,
|
|
||||||
showDiskImages: false,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
test('notifications for leaks', () => {
|
|
||||||
const notificationReducer: (
|
|
||||||
persistedState: PersistedState,
|
|
||||||
) => Array<Notification> = FrescoPlugin.getActiveNotifications;
|
|
||||||
const closeableReferenceLeaks: Array<AndroidCloseableReferenceLeakEvent> = [
|
|
||||||
{
|
|
||||||
identityHashCode: 'deadbeef',
|
|
||||||
className: 'com.facebook.imagepipeline.memory.NativeMemoryChunk',
|
|
||||||
stacktrace: null,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
identityHashCode: 'f4c3b00c',
|
|
||||||
className: 'com.facebook.flipper.SomeMemoryAbstraction',
|
|
||||||
stacktrace: null,
|
|
||||||
},
|
|
||||||
];
|
|
||||||
const persistedStateWithoutTracking = {
|
|
||||||
...mockPersistedState(),
|
|
||||||
closeableReferenceLeaks,
|
|
||||||
isLeakTrackingEnabled: false,
|
|
||||||
};
|
|
||||||
const emptyNotifs = notificationReducer(persistedStateWithoutTracking);
|
|
||||||
expect(emptyNotifs).toHaveLength(0);
|
|
||||||
|
|
||||||
const persistedStateWithTracking = {
|
|
||||||
...mockPersistedState(),
|
|
||||||
closeableReferenceLeaks,
|
|
||||||
isLeakTrackingEnabled: true,
|
|
||||||
};
|
|
||||||
const notifs = notificationReducer(persistedStateWithTracking);
|
|
||||||
expect(notifs).toHaveLength(2);
|
|
||||||
expect(notifs[0].message).toMatchSnapshot();
|
|
||||||
expect(notifs[1].title).toContain('SomeMemoryAbstraction');
|
|
||||||
});
|
|
||||||
@@ -19,43 +19,23 @@ import {
|
|||||||
} from './api';
|
} from './api';
|
||||||
import {Fragment} from 'react';
|
import {Fragment} from 'react';
|
||||||
import {ImagesMap} from './ImagePool';
|
import {ImagesMap} from './ImagePool';
|
||||||
import {ReduxState} from 'flipper';
|
import {PluginClient, createState, usePlugin, useValue} from 'flipper-plugin';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import ImagesCacheOverview from './ImagesCacheOverview';
|
import ImagesCacheOverview from './ImagesCacheOverview';
|
||||||
import {
|
import {
|
||||||
FlipperPlugin,
|
|
||||||
FlexRow,
|
FlexRow,
|
||||||
Text,
|
Text,
|
||||||
DetailSidebar,
|
DetailSidebar,
|
||||||
colors,
|
colors,
|
||||||
styled,
|
styled,
|
||||||
isProduction,
|
isProduction,
|
||||||
Notification,
|
|
||||||
BaseAction,
|
|
||||||
} from 'flipper';
|
} from 'flipper';
|
||||||
import ImagesSidebar from './ImagesSidebar';
|
import ImagesSidebar from './ImagesSidebar';
|
||||||
import ImagePool from './ImagePool';
|
import ImagePool from './ImagePool';
|
||||||
|
|
||||||
export type ImageEventWithId = ImageEvent & {eventId: number};
|
export type ImageEventWithId = ImageEvent & {eventId: number};
|
||||||
|
export type AllImageEventsInfo = {
|
||||||
export type PersistedState = {
|
|
||||||
surfaceList: Set<string>;
|
|
||||||
images: ImagesList;
|
|
||||||
events: Array<ImageEventWithId>;
|
events: Array<ImageEventWithId>;
|
||||||
imagesMap: ImagesMap;
|
|
||||||
closeableReferenceLeaks: Array<AndroidCloseableReferenceLeakEvent>;
|
|
||||||
isLeakTrackingEnabled: boolean;
|
|
||||||
showDiskImages: boolean;
|
|
||||||
nextEventId: number;
|
|
||||||
};
|
|
||||||
|
|
||||||
type PluginState = {
|
|
||||||
selectedSurfaces: Set<string>;
|
|
||||||
selectedImage: ImageId | null;
|
|
||||||
isDebugOverlayEnabled: boolean;
|
|
||||||
isAutoRefreshEnabled: boolean;
|
|
||||||
images: ImagesList;
|
|
||||||
coldStartFilter: boolean;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const EmptySidebar = styled(FlexRow)({
|
const EmptySidebar = styled(FlexRow)({
|
||||||
@@ -79,141 +59,57 @@ const debugLog = (...args: any[]) => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
export default class FlipperImagesPlugin extends FlipperPlugin<
|
type Methods = {
|
||||||
PluginState,
|
getAllImageEventsInfo(params: {}): Promise<AllImageEventsInfo>;
|
||||||
BaseAction,
|
listImages(params: {showDiskImages: boolean}): Promise<ImagesListResponse>;
|
||||||
PersistedState
|
getImage(params: {imageId: string}): Promise<ImageData>;
|
||||||
> {
|
clear(params: {type: string}): Promise<void>;
|
||||||
static defaultPersistedState: PersistedState = {
|
trimMemory(params: {}): Promise<void>;
|
||||||
images: [],
|
enableDebugOverlay(params: {enabled: boolean}): Promise<void>;
|
||||||
events: [],
|
};
|
||||||
imagesMap: {},
|
|
||||||
surfaceList: new Set(),
|
|
||||||
closeableReferenceLeaks: [],
|
|
||||||
isLeakTrackingEnabled: false,
|
|
||||||
showDiskImages: false,
|
|
||||||
nextEventId: 0,
|
|
||||||
};
|
|
||||||
|
|
||||||
static exportPersistedState = (
|
type Events = {
|
||||||
callClient: undefined | ((method: string, params?: any) => Promise<any>),
|
closeable_reference_leak_event: AndroidCloseableReferenceLeakEvent;
|
||||||
persistedState: PersistedState,
|
events: ImageEvent;
|
||||||
store?: ReduxState,
|
debug_overlay_event: FrescoDebugOverlayEvent;
|
||||||
): Promise<PersistedState> => {
|
};
|
||||||
const defaultPromise = Promise.resolve(persistedState);
|
|
||||||
if (!persistedState) {
|
|
||||||
persistedState = FlipperImagesPlugin.defaultPersistedState;
|
|
||||||
}
|
|
||||||
if (!store || !callClient) {
|
|
||||||
return defaultPromise;
|
|
||||||
}
|
|
||||||
return Promise.all([
|
|
||||||
callClient('listImages', {showDiskImages: persistedState.showDiskImages}),
|
|
||||||
callClient('getAllImageEventsInfo'),
|
|
||||||
]).then(async ([responseImages, responseEvents]) => {
|
|
||||||
const levels: ImagesList = responseImages.levels;
|
|
||||||
const events: Array<ImageEventWithId> = responseEvents.events;
|
|
||||||
let pluginData: PersistedState = {
|
|
||||||
...persistedState,
|
|
||||||
images: persistedState ? [...persistedState.images, ...levels] : levels,
|
|
||||||
closeableReferenceLeaks:
|
|
||||||
(persistedState && persistedState.closeableReferenceLeaks) || [],
|
|
||||||
};
|
|
||||||
|
|
||||||
events.forEach((event: ImageEventWithId, index) => {
|
export function plugin(client: PluginClient<Events, Methods>) {
|
||||||
if (!event) {
|
const selectedSurfaces = createState<Set<string>>(
|
||||||
return;
|
new Set([surfaceDefaultText]),
|
||||||
}
|
);
|
||||||
const {attribution} = event;
|
const currentSelectedImage = createState<ImageId | null>(null);
|
||||||
if (
|
const isDebugOverlayEnabled = createState<boolean>(false);
|
||||||
attribution &&
|
const isAutoRefreshEnabled = createState<boolean>(false);
|
||||||
attribution instanceof Array &&
|
const currentImages = createState<ImagesList>([]);
|
||||||
attribution.length > 0
|
const coldStartFilter = createState<boolean>(false);
|
||||||
) {
|
const imagePool = createState<ImagePool | null>(null);
|
||||||
const surface = attribution[0] ? attribution[0].trim() : undefined;
|
|
||||||
if (surface && surface.length > 0) {
|
|
||||||
pluginData.surfaceList = new Set([
|
|
||||||
...pluginData.surfaceList,
|
|
||||||
surface,
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
pluginData = {
|
|
||||||
...pluginData,
|
|
||||||
events: [{...event, eventId: index}, ...pluginData.events],
|
|
||||||
};
|
|
||||||
});
|
|
||||||
const idSet: Set<string> = levels.reduce((acc, level: CacheInfo) => {
|
|
||||||
level.imageIds.forEach((id) => {
|
|
||||||
acc.add(id);
|
|
||||||
});
|
|
||||||
return acc;
|
|
||||||
}, new Set<string>());
|
|
||||||
const imageDataList: Array<ImageData> = [];
|
|
||||||
for (const id of idSet) {
|
|
||||||
try {
|
|
||||||
const imageData: ImageData = await callClient('getImage', {
|
|
||||||
imageId: id,
|
|
||||||
});
|
|
||||||
imageDataList.push(imageData);
|
|
||||||
} catch (e) {
|
|
||||||
console.error(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
imageDataList.forEach((data: ImageData) => {
|
|
||||||
const imagesMap = {...pluginData.imagesMap};
|
|
||||||
imagesMap[data.imageId] = data;
|
|
||||||
pluginData.imagesMap = imagesMap;
|
|
||||||
});
|
|
||||||
return pluginData;
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
static persistedStateReducer = (
|
const surfaceList = createState<Set<string>>(new Set(), {
|
||||||
persistedState: PersistedState,
|
persist: 'surfaceList',
|
||||||
method: string,
|
});
|
||||||
data: AndroidCloseableReferenceLeakEvent | ImageEvent,
|
const images = createState<ImagesList>([], {persist: 'images'});
|
||||||
): PersistedState => {
|
const events = createState<Array<ImageEventWithId>>([], {persist: 'events'});
|
||||||
if (method == 'closeable_reference_leak_event') {
|
const imagesMap = createState<ImagesMap>({}, {persist: 'imagesMap'});
|
||||||
const event: AndroidCloseableReferenceLeakEvent = data as AndroidCloseableReferenceLeakEvent;
|
const isLeakTrackingEnabled = createState<boolean>(false, {
|
||||||
return {
|
persist: 'isLeakTrackingEnabled',
|
||||||
...persistedState,
|
});
|
||||||
closeableReferenceLeaks: persistedState.closeableReferenceLeaks.concat(
|
const showDiskImages = createState<boolean>(false, {
|
||||||
event,
|
persist: 'showDiskImages',
|
||||||
),
|
});
|
||||||
};
|
const nextEventId = createState<number>(0, {persist: 'nextEventId'});
|
||||||
} else if (method == 'events') {
|
|
||||||
const event: ImageEvent = data as ImageEvent;
|
|
||||||
debugLog('Received events', event);
|
|
||||||
let {surfaceList} = persistedState;
|
|
||||||
const {attribution} = event;
|
|
||||||
if (attribution instanceof Array && attribution.length > 0) {
|
|
||||||
const surface = attribution[0] ? attribution[0].trim() : undefined;
|
|
||||||
if (surface && surface.length > 0) {
|
|
||||||
surfaceList = new Set([...surfaceList, surface]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return {
|
|
||||||
...persistedState,
|
|
||||||
surfaceList,
|
|
||||||
events: [
|
|
||||||
{eventId: persistedState.nextEventId, ...event},
|
|
||||||
...persistedState.events,
|
|
||||||
],
|
|
||||||
nextEventId: persistedState.nextEventId + 1,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
return persistedState;
|
client.onConnect(() => {
|
||||||
};
|
init();
|
||||||
|
});
|
||||||
|
|
||||||
static getActiveNotifications = ({
|
client.onDestroy(() => {
|
||||||
closeableReferenceLeaks = [],
|
imagePool?.get()?.clear();
|
||||||
isLeakTrackingEnabled = false,
|
});
|
||||||
}: PersistedState): Array<Notification> =>
|
|
||||||
closeableReferenceLeaks
|
client.onMessage('closeable_reference_leak_event', (event) => {
|
||||||
.filter((_) => isLeakTrackingEnabled)
|
if (isLeakTrackingEnabled) {
|
||||||
.map((event: AndroidCloseableReferenceLeakEvent) => ({
|
client.showNotification({
|
||||||
id: event.identityHashCode,
|
id: event.identityHashCode,
|
||||||
title: `Leaked CloseableReference: ${event.className}`,
|
title: `Leaked CloseableReference: ${event.className}`,
|
||||||
message: (
|
message: (
|
||||||
@@ -233,25 +129,183 @@ export default class FlipperImagesPlugin extends FlipperPlugin<
|
|||||||
),
|
),
|
||||||
severity: 'error',
|
severity: 'error',
|
||||||
category: 'closeablereference_leak',
|
category: 'closeablereference_leak',
|
||||||
}));
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
state: PluginState = {
|
client.onExport(async () => {
|
||||||
selectedSurfaces: new Set([surfaceDefaultText]),
|
const [responseImages, responseEvents] = await Promise.all([
|
||||||
selectedImage: null,
|
client.send('listImages', {showDiskImages: showDiskImages.get()}),
|
||||||
isDebugOverlayEnabled: false,
|
client.send('getAllImageEventsInfo', {}),
|
||||||
isAutoRefreshEnabled: false,
|
]);
|
||||||
images: [],
|
const levels: ImagesList = responseImages.levels;
|
||||||
coldStartFilter: false,
|
const newEvents: Array<ImageEventWithId> = responseEvents.events;
|
||||||
};
|
|
||||||
imagePool: ImagePool | undefined;
|
|
||||||
nextEventId: number = 1;
|
|
||||||
|
|
||||||
filterImages = (
|
images.set([...images.get(), ...levels]);
|
||||||
|
|
||||||
|
newEvents.forEach((event: ImageEventWithId, index) => {
|
||||||
|
if (!event) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const {attribution} = event;
|
||||||
|
if (
|
||||||
|
attribution &&
|
||||||
|
attribution instanceof Array &&
|
||||||
|
attribution.length > 0
|
||||||
|
) {
|
||||||
|
const surface = attribution[0] ? attribution[0].trim() : undefined;
|
||||||
|
if (surface && surface.length > 0) {
|
||||||
|
surfaceList.set(new Set([...surfaceList.get(), surface]));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
events.set([{...event, eventId: index}, ...events.get()]);
|
||||||
|
});
|
||||||
|
const idSet: Set<string> = levels.reduce((acc, level: CacheInfo) => {
|
||||||
|
level.imageIds.forEach((id) => {
|
||||||
|
acc.add(id);
|
||||||
|
});
|
||||||
|
return acc;
|
||||||
|
}, new Set<string>());
|
||||||
|
const imageDataList: Array<ImageData> = [];
|
||||||
|
for (const id of idSet) {
|
||||||
|
try {
|
||||||
|
const imageData: ImageData = await client.send('getImage', {
|
||||||
|
imageId: id,
|
||||||
|
});
|
||||||
|
imageDataList.push(imageData);
|
||||||
|
} catch (e) {
|
||||||
|
console.error(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const imagesMapCopy = {...imagesMap.get()};
|
||||||
|
imageDataList.forEach((data: ImageData) => {
|
||||||
|
imagesMapCopy[data.imageId] = data;
|
||||||
|
});
|
||||||
|
imagesMap.set(imagesMapCopy);
|
||||||
|
});
|
||||||
|
|
||||||
|
client.onMessage('debug_overlay_event', (event) => {
|
||||||
|
isDebugOverlayEnabled.set(event.enabled);
|
||||||
|
});
|
||||||
|
|
||||||
|
client.onMessage('events', (event) => {
|
||||||
|
debugLog('Received events', event);
|
||||||
|
const {attribution} = event;
|
||||||
|
if (attribution instanceof Array && attribution.length > 0) {
|
||||||
|
const surface = attribution[0] ? attribution[0].trim() : undefined;
|
||||||
|
if (surface && surface.length > 0) {
|
||||||
|
surfaceList.update((draft) => (draft = new Set([...draft, surface])));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
events.update((draft) => {
|
||||||
|
draft.unshift({
|
||||||
|
eventId: nextEventId.get(),
|
||||||
|
...event,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
nextEventId.set(nextEventId.get() + 1);
|
||||||
|
});
|
||||||
|
|
||||||
|
function onClear(type: string) {
|
||||||
|
client.send('clear', {type});
|
||||||
|
setTimeout(() => updateCaches('onClear'), 1000);
|
||||||
|
}
|
||||||
|
|
||||||
|
function onTrimMemory() {
|
||||||
|
client.send('trimMemory', {});
|
||||||
|
setTimeout(() => updateCaches('onTrimMemory'), 1000);
|
||||||
|
}
|
||||||
|
|
||||||
|
function onEnableDebugOverlay(enabled: boolean) {
|
||||||
|
client.send('enableDebugOverlay', {enabled});
|
||||||
|
}
|
||||||
|
|
||||||
|
function onEnableAutoRefresh(enabled: boolean) {
|
||||||
|
isAutoRefreshEnabled.set(enabled);
|
||||||
|
|
||||||
|
if (enabled) {
|
||||||
|
// Delay the call just enough to allow the state change to complete.
|
||||||
|
setTimeout(() => onAutoRefresh());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function onAutoRefresh() {
|
||||||
|
updateCaches('auto-refresh');
|
||||||
|
if (isAutoRefreshEnabled.get()) {
|
||||||
|
setTimeout(() => onAutoRefresh(), 1000);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function getImage(imageId: string) {
|
||||||
|
if (!client.isConnected) {
|
||||||
|
debugLog(`Cannot fetch image ${imageId}: disconnected`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
debugLog('<- getImage requested for ' + imageId);
|
||||||
|
client.send('getImage', {imageId}).then((image: ImageData) => {
|
||||||
|
debugLog('-> getImage ' + imageId + ' returned');
|
||||||
|
imagePool.get()?._fetchCompleted(image);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function onImageSelected(selectedImage: ImageId) {
|
||||||
|
currentSelectedImage.set(selectedImage);
|
||||||
|
}
|
||||||
|
|
||||||
|
function onSurfaceChange(surfaces: Set<string>) {
|
||||||
|
updateImagesOnUI(images.get(), surfaces, coldStartFilter.get());
|
||||||
|
}
|
||||||
|
|
||||||
|
function onColdStartChange(checked: boolean) {
|
||||||
|
updateImagesOnUI(images.get(), selectedSurfaces.get(), checked);
|
||||||
|
}
|
||||||
|
|
||||||
|
function onTrackLeaks(checked: boolean) {
|
||||||
|
client.logger.track('usage', 'fresco:onTrackLeaks', {
|
||||||
|
enabled: checked,
|
||||||
|
});
|
||||||
|
|
||||||
|
isLeakTrackingEnabled.set(checked);
|
||||||
|
}
|
||||||
|
|
||||||
|
function onShowDiskImages(checked: boolean) {
|
||||||
|
client.logger.track('usage', 'fresco:onShowDiskImages', {
|
||||||
|
enabled: checked,
|
||||||
|
});
|
||||||
|
|
||||||
|
showDiskImages.set(checked);
|
||||||
|
updateCaches('refresh');
|
||||||
|
}
|
||||||
|
|
||||||
|
function init() {
|
||||||
|
debugLog('init()');
|
||||||
|
if (client.isConnected) {
|
||||||
|
updateCaches('init');
|
||||||
|
} else {
|
||||||
|
debugLog(`not connected)`);
|
||||||
|
}
|
||||||
|
imagePool.set(
|
||||||
|
new ImagePool(getImage, (images: ImagesMap) => imagesMap.set(images)),
|
||||||
|
);
|
||||||
|
|
||||||
|
const filteredImages = filterImages(
|
||||||
|
images.get(),
|
||||||
|
events.get(),
|
||||||
|
selectedSurfaces.get(),
|
||||||
|
coldStartFilter.get(),
|
||||||
|
);
|
||||||
|
|
||||||
|
images.set(filteredImages);
|
||||||
|
}
|
||||||
|
|
||||||
|
function filterImages(
|
||||||
images: ImagesList,
|
images: ImagesList,
|
||||||
events: Array<ImageEventWithId>,
|
events: Array<ImageEventWithId>,
|
||||||
surfaces: Set<string>,
|
surfaces: Set<string>,
|
||||||
coldStart: boolean,
|
coldStart: boolean,
|
||||||
): ImagesList => {
|
): ImagesList {
|
||||||
if (!surfaces || (surfaces.has(surfaceDefaultText) && !coldStart)) {
|
if (!surfaces || (surfaces.has(surfaceDefaultText) && !coldStart)) {
|
||||||
return images;
|
return images;
|
||||||
}
|
}
|
||||||
@@ -280,216 +334,149 @@ export default class FlipperImagesPlugin extends FlipperPlugin<
|
|||||||
return {...image, imageIds: imageIdList};
|
return {...image, imageIds: imageIdList};
|
||||||
});
|
});
|
||||||
return imageList;
|
return imageList;
|
||||||
};
|
|
||||||
|
|
||||||
init() {
|
|
||||||
debugLog('init()');
|
|
||||||
if (this.client.isConnected) {
|
|
||||||
this.updateCaches('init');
|
|
||||||
this.client.subscribe(
|
|
||||||
'debug_overlay_event',
|
|
||||||
(event: FrescoDebugOverlayEvent) => {
|
|
||||||
this.setState({isDebugOverlayEnabled: event.enabled});
|
|
||||||
},
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
debugLog(`not connected)`);
|
|
||||||
}
|
|
||||||
this.imagePool = new ImagePool(this.getImage, (images: ImagesMap) =>
|
|
||||||
this.props.setPersistedState({imagesMap: images}),
|
|
||||||
);
|
|
||||||
|
|
||||||
const images = this.filterImages(
|
|
||||||
this.props.persistedState.images,
|
|
||||||
this.props.persistedState.events,
|
|
||||||
this.state.selectedSurfaces,
|
|
||||||
this.state.coldStartFilter,
|
|
||||||
);
|
|
||||||
|
|
||||||
this.setState({images});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
teardown() {
|
function updateImagesOnUI(
|
||||||
this.imagePool ? this.imagePool.clear() : undefined;
|
newImages: ImagesList,
|
||||||
}
|
|
||||||
|
|
||||||
updateImagesOnUI = (
|
|
||||||
images: ImagesList,
|
|
||||||
surfaces: Set<string>,
|
surfaces: Set<string>,
|
||||||
coldStart: boolean,
|
coldStart: boolean,
|
||||||
) => {
|
) {
|
||||||
const filteredImages = this.filterImages(
|
const filteredImages = filterImages(
|
||||||
images,
|
newImages,
|
||||||
this.props.persistedState.events,
|
events.get(),
|
||||||
surfaces,
|
surfaces,
|
||||||
coldStart,
|
coldStart,
|
||||||
);
|
);
|
||||||
|
|
||||||
this.setState({
|
selectedSurfaces.set(surfaces);
|
||||||
selectedSurfaces: surfaces,
|
images.set(filteredImages);
|
||||||
images: filteredImages,
|
coldStartFilter.set(coldStart);
|
||||||
coldStartFilter: coldStart,
|
}
|
||||||
});
|
|
||||||
};
|
function updateCaches(reason: string) {
|
||||||
updateCaches = (reason: string) => {
|
|
||||||
debugLog('Requesting images list (reason=' + reason + ')');
|
debugLog('Requesting images list (reason=' + reason + ')');
|
||||||
this.client
|
client
|
||||||
.call('listImages', {
|
.send('listImages', {
|
||||||
showDiskImages: this.props.persistedState.showDiskImages,
|
showDiskImages: showDiskImages.get(),
|
||||||
})
|
})
|
||||||
.then((response: ImagesListResponse) => {
|
.then((response: ImagesListResponse) => {
|
||||||
response.levels.forEach((data) =>
|
response.levels.forEach((data) =>
|
||||||
this.imagePool
|
imagePool?.get()?.fetchImages(data.imageIds),
|
||||||
? this.imagePool.fetchImages(data.imageIds)
|
|
||||||
: undefined,
|
|
||||||
);
|
);
|
||||||
this.props.setPersistedState({images: response.levels});
|
images.set(response.levels);
|
||||||
this.updateImagesOnUI(
|
updateImagesOnUI(
|
||||||
this.props.persistedState.images,
|
images.get(),
|
||||||
this.state.selectedSurfaces,
|
selectedSurfaces.get(),
|
||||||
this.state.coldStartFilter,
|
coldStartFilter.get(),
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
selectedSurfaces,
|
||||||
|
currentSelectedImage,
|
||||||
|
isDebugOverlayEnabled,
|
||||||
|
isAutoRefreshEnabled,
|
||||||
|
currentImages,
|
||||||
|
coldStartFilter,
|
||||||
|
surfaceList,
|
||||||
|
images,
|
||||||
|
events,
|
||||||
|
imagesMap,
|
||||||
|
isLeakTrackingEnabled,
|
||||||
|
showDiskImages,
|
||||||
|
nextEventId,
|
||||||
|
imagePool,
|
||||||
|
onSurfaceChange,
|
||||||
|
onColdStartChange,
|
||||||
|
onClear,
|
||||||
|
onTrimMemory,
|
||||||
|
updateCaches,
|
||||||
|
onEnableDebugOverlay,
|
||||||
|
onEnableAutoRefresh,
|
||||||
|
onImageSelected,
|
||||||
|
onTrackLeaks,
|
||||||
|
onShowDiskImages,
|
||||||
};
|
};
|
||||||
|
}
|
||||||
|
|
||||||
onClear = (type: string) => {
|
export function Component() {
|
||||||
this.client.call('clear', {type});
|
const instance = usePlugin(plugin);
|
||||||
setTimeout(() => this.updateCaches('onClear'), 1000);
|
|
||||||
};
|
|
||||||
|
|
||||||
onTrimMemory = () => {
|
let selectedSurfaces = useValue(instance.selectedSurfaces);
|
||||||
this.client.call('trimMemory', {});
|
const isDebugOverlayEnabled = useValue(instance.isDebugOverlayEnabled);
|
||||||
setTimeout(() => this.updateCaches('onTrimMemory'), 1000);
|
const isAutoRefreshEnabled = useValue(instance.isAutoRefreshEnabled);
|
||||||
};
|
const coldStartFilter = useValue(instance.coldStartFilter);
|
||||||
|
|
||||||
onEnableDebugOverlay = (enabled: boolean) => {
|
const surfaceList = useValue(instance.surfaceList);
|
||||||
this.client.call('enableDebugOverlay', {enabled});
|
const images = useValue(instance.images);
|
||||||
};
|
const events = useValue(instance.events);
|
||||||
|
const imagesMap = useValue(instance.imagesMap);
|
||||||
|
const isLeakTrackingEnabled = useValue(instance.isLeakTrackingEnabled);
|
||||||
|
const showDiskImages = useValue(instance.showDiskImages);
|
||||||
|
|
||||||
onEnableAutoRefresh = (enabled: boolean) => {
|
const options = [...surfaceList].reduce(
|
||||||
this.setState({isAutoRefreshEnabled: enabled});
|
(acc, item) => {
|
||||||
if (enabled) {
|
return [...acc, item];
|
||||||
// Delay the call just enough to allow the state change to complete.
|
},
|
||||||
setTimeout(() => this.onAutoRefresh());
|
[surfaceDefaultText],
|
||||||
}
|
);
|
||||||
};
|
|
||||||
|
|
||||||
onAutoRefresh = () => {
|
if (selectedSurfaces.has(surfaceDefaultText)) {
|
||||||
this.updateCaches('auto-refresh');
|
selectedSurfaces = new Set(options);
|
||||||
if (this.state.isAutoRefreshEnabled) {
|
}
|
||||||
setTimeout(() => this.onAutoRefresh(), 1000);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
getImage = (imageId: string) => {
|
return (
|
||||||
if (!this.client.isConnected) {
|
<React.Fragment>
|
||||||
debugLog(`Cannot fetch image ${imageId}: disconnected`);
|
<ImagesCacheOverview
|
||||||
return;
|
allSurfacesOption={surfaceDefaultText}
|
||||||
}
|
surfaceOptions={new Set(options)}
|
||||||
debugLog('<- getImage requested for ' + imageId);
|
selectedSurfaces={selectedSurfaces}
|
||||||
this.client.call('getImage', {imageId}).then((image: ImageData) => {
|
onChangeSurface={instance.onSurfaceChange}
|
||||||
debugLog('-> getImage ' + imageId + ' returned');
|
coldStartFilter={coldStartFilter}
|
||||||
this.imagePool ? this.imagePool._fetchCompleted(image) : undefined;
|
onColdStartChange={instance.onColdStartChange}
|
||||||
});
|
images={images}
|
||||||
};
|
onClear={instance.onClear}
|
||||||
|
onTrimMemory={instance.onTrimMemory}
|
||||||
|
onRefresh={() => instance.updateCaches('refresh')}
|
||||||
|
onEnableDebugOverlay={instance.onEnableDebugOverlay}
|
||||||
|
onEnableAutoRefresh={instance.onEnableAutoRefresh}
|
||||||
|
isDebugOverlayEnabled={isDebugOverlayEnabled}
|
||||||
|
isAutoRefreshEnabled={isAutoRefreshEnabled}
|
||||||
|
onImageSelected={instance.onImageSelected}
|
||||||
|
imagesMap={imagesMap}
|
||||||
|
events={events}
|
||||||
|
isLeakTrackingEnabled={isLeakTrackingEnabled}
|
||||||
|
onTrackLeaks={instance.onTrackLeaks}
|
||||||
|
showDiskImages={showDiskImages}
|
||||||
|
onShowDiskImages={instance.onShowDiskImages}
|
||||||
|
/>
|
||||||
|
<DetailSidebar>
|
||||||
|
<Sidebar />
|
||||||
|
</DetailSidebar>
|
||||||
|
</React.Fragment>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
onImageSelected = (selectedImage: ImageId) => this.setState({selectedImage});
|
function Sidebar() {
|
||||||
|
const instance = usePlugin(plugin);
|
||||||
renderSidebar = () => {
|
const events = useValue(instance.events);
|
||||||
const {selectedImage} = this.state;
|
const imagesMap = useValue(instance.imagesMap);
|
||||||
|
const currentSelectedImage = useValue(instance.currentSelectedImage);
|
||||||
if (selectedImage == null) {
|
|
||||||
return (
|
|
||||||
<EmptySidebar grow={true}>
|
|
||||||
<Text align="center">
|
|
||||||
Select an image to see the events associated with it.
|
|
||||||
</Text>
|
|
||||||
</EmptySidebar>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
const maybeImage = this.props.persistedState.imagesMap[selectedImage];
|
|
||||||
const events = this.props.persistedState.events.filter((e) =>
|
|
||||||
e.imageIds.includes(selectedImage),
|
|
||||||
);
|
|
||||||
return <ImagesSidebar image={maybeImage} events={events} />;
|
|
||||||
};
|
|
||||||
|
|
||||||
onSurfaceChange = (surfaces: Set<string>) => {
|
|
||||||
this.updateImagesOnUI(
|
|
||||||
this.props.persistedState.images,
|
|
||||||
surfaces,
|
|
||||||
this.state.coldStartFilter,
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
onColdStartChange = (checked: boolean) => {
|
|
||||||
this.updateImagesOnUI(
|
|
||||||
this.props.persistedState.images,
|
|
||||||
this.state.selectedSurfaces,
|
|
||||||
checked,
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
onTrackLeaks = (checked: boolean) => {
|
|
||||||
this.props.logger.track('usage', 'fresco:onTrackLeaks', {enabled: checked});
|
|
||||||
this.props.setPersistedState({
|
|
||||||
isLeakTrackingEnabled: checked,
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
onShowDiskImages = (checked: boolean) => {
|
|
||||||
this.props.logger.track('usage', 'fresco:onShowDiskImages', {
|
|
||||||
enabled: checked,
|
|
||||||
});
|
|
||||||
this.props.setPersistedState({
|
|
||||||
showDiskImages: checked,
|
|
||||||
});
|
|
||||||
this.updateCaches('refresh');
|
|
||||||
};
|
|
||||||
|
|
||||||
render() {
|
|
||||||
const options = [...this.props.persistedState.surfaceList].reduce(
|
|
||||||
(acc, item) => {
|
|
||||||
return [...acc, item];
|
|
||||||
},
|
|
||||||
[surfaceDefaultText],
|
|
||||||
);
|
|
||||||
let {selectedSurfaces} = this.state;
|
|
||||||
|
|
||||||
if (selectedSurfaces.has(surfaceDefaultText)) {
|
|
||||||
selectedSurfaces = new Set(options);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
if (currentSelectedImage == null) {
|
||||||
return (
|
return (
|
||||||
<React.Fragment>
|
<EmptySidebar grow={true}>
|
||||||
<ImagesCacheOverview
|
<Text align="center">
|
||||||
allSurfacesOption={surfaceDefaultText}
|
Select an image to see the events associated with it.
|
||||||
surfaceOptions={new Set(options)}
|
</Text>
|
||||||
selectedSurfaces={selectedSurfaces}
|
</EmptySidebar>
|
||||||
onChangeSurface={this.onSurfaceChange}
|
|
||||||
coldStartFilter={this.state.coldStartFilter}
|
|
||||||
onColdStartChange={this.onColdStartChange}
|
|
||||||
images={this.state.images}
|
|
||||||
onClear={this.onClear}
|
|
||||||
onTrimMemory={this.onTrimMemory}
|
|
||||||
onRefresh={() => this.updateCaches('refresh')}
|
|
||||||
onEnableDebugOverlay={this.onEnableDebugOverlay}
|
|
||||||
onEnableAutoRefresh={this.onEnableAutoRefresh}
|
|
||||||
isDebugOverlayEnabled={this.state.isDebugOverlayEnabled}
|
|
||||||
isAutoRefreshEnabled={this.state.isAutoRefreshEnabled}
|
|
||||||
onImageSelected={this.onImageSelected}
|
|
||||||
imagesMap={this.props.persistedState.imagesMap}
|
|
||||||
events={this.props.persistedState.events}
|
|
||||||
isLeakTrackingEnabled={
|
|
||||||
this.props.persistedState.isLeakTrackingEnabled
|
|
||||||
}
|
|
||||||
onTrackLeaks={this.onTrackLeaks}
|
|
||||||
showDiskImages={this.props.persistedState.showDiskImages}
|
|
||||||
onShowDiskImages={this.onShowDiskImages}
|
|
||||||
/>
|
|
||||||
<DetailSidebar>{this.renderSidebar()}</DetailSidebar>
|
|
||||||
</React.Fragment>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const maybeImage = imagesMap[currentSelectedImage];
|
||||||
|
const filteredEvents = events.filter((e) =>
|
||||||
|
e.imageIds.includes(currentSelectedImage),
|
||||||
|
);
|
||||||
|
return <ImagesSidebar image={maybeImage} events={filteredEvents} />;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,5 +13,8 @@
|
|||||||
"icon": "profile",
|
"icon": "profile",
|
||||||
"bugs": {
|
"bugs": {
|
||||||
"email": "oncall+fresco@xmail.facebook.com"
|
"email": "oncall+fresco@xmail.facebook.com"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"flipper-plugin": "*"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user