Integrate "UI Traces API" with viewers

Bug: b/256564627
Test: npm run build:all && npm run test:all
Change-Id: Ia60ceb479ced5c547579dc67336314c1d22d13fe
This commit is contained in:
Kean Mariotti
2023-03-06 15:59:03 +00:00
parent 8777d32f37
commit f54172dadd
23 changed files with 743 additions and 584 deletions

View File

@@ -21,7 +21,7 @@ describe('ImeUtils', () => {
it('processes WindowManager trace entry', async () => {
const entries = await UnitTestUtils.getImeTraceEntries();
const processed = ImeUtils.processWindowManagerTraceEntry(
entries.get(TraceType.WINDOW_MANAGER)[0]
entries.get(TraceType.WINDOW_MANAGER)
);
expect(processed.focusedApp).toEqual(
@@ -59,10 +59,10 @@ describe('ImeUtils', () => {
it('processes SurfaceFlinger trace entry', async () => {
const entries = await UnitTestUtils.getImeTraceEntries();
const processedWindowManagerState = ImeUtils.processWindowManagerTraceEntry(
entries.get(TraceType.WINDOW_MANAGER)[0]
entries.get(TraceType.WINDOW_MANAGER)
);
const layers = ImeUtils.getImeLayers(
entries.get(TraceType.SURFACE_FLINGER)[0],
entries.get(TraceType.SURFACE_FLINGER),
processedWindowManagerState
)!;

View File

@@ -13,8 +13,15 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import {PersistentStoreProxy} from 'common/persistent_store_proxy';
import {FilterType, TreeUtils} from 'common/tree_utils';
import {LayerTraceEntry} from 'trace/flickerlib/layers/LayerTraceEntry';
import {WindowManagerState} from 'trace/flickerlib/windows/WindowManagerState';
import {Trace, TraceEntry} from 'trace/trace';
import {Traces} from 'trace/traces';
import {TraceEntryFinder} from 'trace/trace_entry_finder';
import {TracePosition} from 'trace/trace_position';
import {TraceTreeNode} from 'trace/trace_tree_node';
import {TraceType} from 'trace/trace_type';
import {ImeAdditionalProperties} from 'viewers/common/ime_additional_properties';
@@ -29,17 +36,91 @@ import {UserOptions} from 'viewers/common/user_options';
type NotifyImeViewCallbackType = (uiData: ImeUiData) => void;
export abstract class PresenterInputMethod {
private readonly imeTrace: Trace<object>;
private readonly wmTrace?: Trace<WindowManagerState>;
private readonly sfTrace?: Trace<LayerTraceEntry>;
private hierarchyFilter: FilterType = TreeUtils.makeNodeFilter('');
private propertiesFilter: FilterType = TreeUtils.makeNodeFilter('');
private pinnedItems: HierarchyTreeNode[] = [];
private pinnedIds: string[] = [];
private selectedHierarchyTree: HierarchyTreeNode | null = null;
readonly notifyViewCallback: NotifyImeViewCallbackType;
protected readonly dependencies: TraceType[];
protected uiData: ImeUiData;
protected highlightedItems: string[] = [];
protected entry: TraceTreeNode | null = null;
protected additionalPropertyEntry: TraceTreeNode | null = null;
protected hierarchyUserOptions: UserOptions = PersistentStoreProxy.new<UserOptions>(
'ImeHierarchyOptions',
{
simplifyNames: {
name: 'Simplify names',
enabled: true,
},
onlyVisible: {
name: 'Only visible',
enabled: false,
},
flat: {
name: 'Flat',
enabled: false,
},
},
this.storage
);
protected propertiesUserOptions: UserOptions = PersistentStoreProxy.new<UserOptions>(
'ImePropertiesOptions',
{
showDefaults: {
name: 'Show defaults',
enabled: false,
tooltip: `
If checked, shows the value of all properties.
Otherwise, hides all properties whose value is
the default for its data type.
`,
},
},
this.storage
);
constructor(
notifyViewCallback: NotifyImeViewCallbackType,
traces: Traces,
private storage: Storage,
dependencies: TraceType[],
private storage: Storage
notifyViewCallback: NotifyImeViewCallbackType
) {
this.notifyViewCallback = notifyViewCallback;
this.imeTrace = traces.getTrace(dependencies[0]) as Trace<TraceTreeNode>;
this.sfTrace = traces.getTrace(TraceType.SURFACE_FLINGER);
this.wmTrace = traces.getTrace(TraceType.WINDOW_MANAGER);
this.dependencies = dependencies;
this.notifyViewCallback = notifyViewCallback;
this.uiData = new ImeUiData(dependencies);
this.notifyViewCallback(this.uiData);
}
onTracePositionUpdate(position: TracePosition) {
this.uiData = new ImeUiData(this.dependencies);
this.uiData.hierarchyUserOptions = this.hierarchyUserOptions;
this.uiData.propertiesUserOptions = this.propertiesUserOptions;
const [imeEntry, sfEntry, wmEntry] = this.findTraceEntries(position);
if (imeEntry) {
this.entry = imeEntry.getValue() as TraceTreeNode;
this.uiData.highlightedItems = this.highlightedItems;
this.uiData.additionalProperties = this.getAdditionalProperties(
wmEntry?.getValue(),
sfEntry?.getValue()
);
this.uiData.tree = this.generateTree();
this.uiData.hierarchyTableProperties = this.updateHierarchyTableProperties();
}
this.notifyViewCallback(this.uiData);
}
updatePinnedItems(pinnedItem: HierarchyTreeNode) {
const pinnedId = `${pinnedItem.id}`;
if (this.pinnedItems.map((item) => `${item.id}`).includes(pinnedId)) {
@@ -109,30 +190,6 @@ export abstract class PresenterInputMethod {
this.updateSelectedTreeUiData();
}
notifyCurrentTraceEntries(entries: Map<TraceType, [any, any]>) {
this.uiData = new ImeUiData(this.dependencies);
this.uiData.hierarchyUserOptions = this.hierarchyUserOptions;
this.uiData.propertiesUserOptions = this.propertiesUserOptions;
const imEntries = entries.get(this.dependencies[0]);
if (imEntries && imEntries[0]) {
this.entry = imEntries[0];
this.uiData.highlightedItems = this.highlightedItems;
const wmEntries = entries.get(TraceType.WINDOW_MANAGER);
const sfEntries = entries.get(TraceType.SURFACE_FLINGER);
this.uiData.additionalProperties = this.getAdditionalProperties(
wmEntries ? wmEntries[0] : undefined,
sfEntries ? sfEntries[0] : undefined
);
this.uiData.tree = this.generateTree();
this.uiData.hierarchyTableProperties = this.updateHierarchyTableProperties();
}
this.notifyViewCallback(this.uiData);
}
protected getAdditionalProperties(
wmEntry: TraceTreeNode | undefined,
sfEntry: TraceTreeNode | undefined
@@ -201,51 +258,33 @@ export abstract class PresenterInputMethod {
return transformedTree;
}
private hierarchyFilter: FilterType = TreeUtils.makeNodeFilter('');
private propertiesFilter: FilterType = TreeUtils.makeNodeFilter('');
private pinnedItems: HierarchyTreeNode[] = [];
private pinnedIds: string[] = [];
private selectedHierarchyTree: HierarchyTreeNode | null = null;
private findTraceEntries(
position: TracePosition
): [
TraceEntry<object> | undefined,
TraceEntry<LayerTraceEntry> | undefined,
TraceEntry<WindowManagerState> | undefined
] {
const imeEntry = TraceEntryFinder.findCorrespondingEntry(this.imeTrace, position);
if (!imeEntry) {
return [undefined, undefined, undefined];
}
readonly notifyViewCallback: NotifyImeViewCallbackType;
protected readonly dependencies: TraceType[];
protected uiData: ImeUiData;
protected highlightedItems: string[] = [];
protected entry: TraceTreeNode | null = null;
protected additionalPropertyEntry: TraceTreeNode | null = null;
protected hierarchyUserOptions: UserOptions = PersistentStoreProxy.new<UserOptions>(
'ImeHierarchyOptions',
{
simplifyNames: {
name: 'Simplify names',
enabled: true,
},
onlyVisible: {
name: 'Only visible',
enabled: false,
},
flat: {
name: 'Flat',
enabled: false,
},
},
this.storage
);
protected propertiesUserOptions: UserOptions = PersistentStoreProxy.new<UserOptions>(
'ImePropertiesOptions',
{
showDefaults: {
name: 'Show defaults',
enabled: false,
tooltip: `
If checked, shows the value of all properties.
Otherwise, hides all properties whose value is
the default for its data type.
`,
},
},
this.storage
);
if (!this.imeTrace.hasFrameInfo()) {
return [imeEntry, undefined, undefined];
}
const frames = imeEntry.getFramesRange();
if (!frames || frames.start === frames.end) {
return [imeEntry, undefined, undefined];
}
const frame = frames.start;
const sfEntry = this.sfTrace?.getFrame(frame)?.findClosestEntry(imeEntry.getTimestamp());
const wmEntry = this.wmTrace?.getFrame(frame)?.findClosestEntry(imeEntry.getTimestamp());
return [imeEntry, sfEntry, wmEntry];
}
protected abstract updateHierarchyTableProperties(): TableProperties;
}

View File

@@ -14,9 +14,14 @@
* limitations under the License.d
*/
import {assertDefined} from 'common/assert_utils';
import {HierarchyTreeBuilder} from 'test/unit/hierarchy_tree_builder';
import {MockStorage} from 'test/unit/mock_storage';
import {TracesBuilder} from 'test/unit/traces_builder';
import {TraceBuilder} from 'test/unit/trace_builder';
import {UnitTestUtils} from 'test/unit/utils';
import {Traces} from 'trace/traces';
import {TracePosition} from 'trace/trace_position';
import {TraceType} from 'trace/trace_type';
import {ImeUiData} from 'viewers/common/ime_ui_data';
import {HierarchyTreeNode, PropertiesTreeNode} from 'viewers/common/ui_tree_utils';
@@ -35,64 +40,66 @@ export function executePresenterInputMethodTests(
| typeof PresenterInputMethodClients
| typeof PresenterInputMethodService
| typeof PresenterInputMethodManagerService,
traceType: TraceType
imeTraceType: TraceType
) {
describe('PresenterInputMethod', () => {
let presenter: PresenterInputMethod;
let uiData: ImeUiData;
let entries: Map<TraceType, any>;
let position: TracePosition;
let selectedTree: HierarchyTreeNode;
beforeEach(async () => {
entries = await UnitTestUtils.getImeTraceEntries();
selectedTree = selected;
presenter = new PresenterInputMethod(
(newData: ImeUiData) => {
uiData = newData;
},
[traceType],
new MockStorage()
);
await setUpTestEnvironment([
imeTraceType,
TraceType.SURFACE_FLINGER,
TraceType.WINDOW_MANAGER,
]);
});
it('can notify current trace entries', () => {
presenter.notifyCurrentTraceEntries(entries);
it('is robust to empty trace', () => {
const traces = new TracesBuilder().setEntries(imeTraceType, []).build();
presenter = createPresenter(traces);
expect(uiData.hierarchyUserOptions).toBeTruthy();
expect(uiData.propertiesUserOptions).toBeTruthy();
expect(Object.keys(uiData.tree!).length > 0).toBeTrue();
});
it('is robust to trace entry without SF', () => {
entries.delete(TraceType.SURFACE_FLINGER);
presenter.notifyCurrentTraceEntries(entries);
expect(uiData.hierarchyUserOptions).toBeTruthy();
expect(uiData.propertiesUserOptions).toBeTruthy();
expect(Object.keys(uiData.tree!).length > 0).toBeTrue();
});
it('is robust to trace entry without WM', () => {
entries.delete(TraceType.WINDOW_MANAGER);
presenter.notifyCurrentTraceEntries(entries);
expect(uiData.hierarchyUserOptions).toBeTruthy();
expect(uiData.propertiesUserOptions).toBeTruthy();
expect(Object.keys(uiData.tree!).length > 0).toBeTrue();
});
it('is robust to trace entry without WM and SF', () => {
entries.delete(TraceType.SURFACE_FLINGER);
entries.delete(TraceType.WINDOW_MANAGER);
presenter.notifyCurrentTraceEntries(entries);
expect(uiData.hierarchyUserOptions).toBeTruthy();
expect(uiData.propertiesUserOptions).toBeTruthy();
expect(Object.keys(uiData.tree!).length > 0).toBeTrue();
});
it('can handle unavailable trace entry', () => {
presenter.notifyCurrentTraceEntries(entries);
expect(Object.keys(uiData.tree!).length > 0).toBeTrue();
const emptyEntries = new Map<TraceType, any>();
presenter.notifyCurrentTraceEntries(emptyEntries);
expect(uiData.tree).toBeFalsy();
presenter.onTracePositionUpdate(position);
expect(uiData.hierarchyUserOptions).toBeTruthy();
expect(uiData.propertiesUserOptions).toBeTruthy();
expect(uiData.tree).toBeFalsy();
});
it('is robust to traces without SF', async () => {
await setUpTestEnvironment([imeTraceType, TraceType.WINDOW_MANAGER]);
presenter.onTracePositionUpdate(position);
expect(uiData.hierarchyUserOptions).toBeTruthy();
expect(uiData.propertiesUserOptions).toBeTruthy();
expect(Object.keys(uiData.tree!).length > 0).toBeTrue();
});
it('is robust to traces without WM', async () => {
await setUpTestEnvironment([imeTraceType, TraceType.SURFACE_FLINGER]);
presenter.onTracePositionUpdate(position);
expect(uiData.hierarchyUserOptions).toBeTruthy();
expect(uiData.propertiesUserOptions).toBeTruthy();
expect(Object.keys(uiData.tree!).length > 0).toBeTrue();
});
it('is robust to traces without WM and SF', async () => {
await setUpTestEnvironment([imeTraceType]);
presenter.onTracePositionUpdate(position);
expect(uiData.hierarchyUserOptions).toBeTruthy();
expect(uiData.propertiesUserOptions).toBeTruthy();
expect(Object.keys(uiData.tree!).length > 0).toBeTrue();
});
it('processes trace position updates', () => {
presenter.onTracePositionUpdate(position);
expect(uiData.hierarchyUserOptions).toBeTruthy();
expect(uiData.propertiesUserOptions).toBeTruthy();
expect(Object.keys(uiData.tree!).length > 0).toBeTrue();
});
it('can update pinned items', () => {
@@ -131,7 +138,7 @@ export function executePresenterInputMethodTests(
};
let expectedChildren = expectHierarchyTreeWithSfSubtree ? 2 : 1;
presenter.notifyCurrentTraceEntries(entries);
presenter.onTracePositionUpdate(position);
expect(uiData.tree?.children.length).toEqual(expectedChildren);
// Filter out non-visible child
@@ -158,7 +165,7 @@ export function executePresenterInputMethodTests(
};
const expectedChildren = expectHierarchyTreeWithSfSubtree ? 12 : 1;
presenter.notifyCurrentTraceEntries(entries);
presenter.onTracePositionUpdate(position);
presenter.updateHierarchyTree(userOptions);
expect(uiData.tree?.children.length).toEqual(expectedChildren);
@@ -168,14 +175,14 @@ export function executePresenterInputMethodTests(
});
it('can set new properties tree and associated ui data', () => {
presenter.notifyCurrentTraceEntries(entries);
presenter.onTracePositionUpdate(position);
presenter.newPropertiesTree(selectedTree);
// does not check specific tree values as tree transformation method may change
expect(uiData.propertiesTree).toBeTruthy();
});
it('can filter properties tree', () => {
presenter.notifyCurrentTraceEntries(entries);
presenter.onTracePositionUpdate(position);
presenter.newPropertiesTree(selectedTree);
let nonTerminalChildren =
uiData.propertiesTree?.children?.filter(
@@ -191,5 +198,35 @@ export function executePresenterInputMethodTests(
) ?? [];
expect(nonTerminalChildren.length).toEqual(expectedChildren[1]);
});
const setUpTestEnvironment = async (traceTypes: TraceType[]) => {
const traces = new Traces();
const entries = await UnitTestUtils.getImeTraceEntries();
traceTypes.forEach((traceType) => {
const trace = new TraceBuilder<object>()
.setEntries([entries.get(traceType)])
.setFrame(0, 0)
.build();
traces.setTrace(traceType, trace);
});
presenter = createPresenter(traces);
position = TracePosition.fromTraceEntry(
assertDefined(traces.getTrace(imeTraceType)).getEntry(0)
);
};
const createPresenter = (traces: Traces): PresenterInputMethod => {
return new PresenterInputMethod(
traces,
new MockStorage(),
[imeTraceType],
(newData: ImeUiData) => {
uiData = newData;
}
);
};
});
}

View File

@@ -13,6 +13,9 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import {Traces} from 'trace/traces';
import {TracePosition} from 'trace/trace_position';
import {TraceType} from 'trace/trace_type';
import {ImeUiData} from 'viewers/common/ime_ui_data';
import {PresenterInputMethod} from 'viewers/common/presenter_input_method';
@@ -20,14 +23,14 @@ import {ViewerEvents} from 'viewers/common/viewer_events';
import {View, Viewer} from 'viewers/viewer';
abstract class ViewerInputMethod implements Viewer {
constructor(storage: Storage) {
constructor(traces: Traces, storage: Storage) {
this.htmlElement = document.createElement('viewer-input-method');
this.presenter = this.initialisePresenter(storage);
this.presenter = this.initialisePresenter(traces, storage);
this.addViewerEventListeners();
}
notifyCurrentTraceEntries(entries: Map<TraceType, any>): void {
this.presenter.notifyCurrentTraceEntries(entries);
onTracePositionUpdate(position: TracePosition) {
this.presenter.onTracePositionUpdate(position);
}
abstract getViews(): View[];
@@ -68,7 +71,7 @@ abstract class ViewerInputMethod implements Viewer {
);
}
protected abstract initialisePresenter(storage: Storage): PresenterInputMethod;
protected abstract initialisePresenter(traces: Traces, storage: Storage): PresenterInputMethod;
protected htmlElement: HTMLElement;
protected presenter: PresenterInputMethod;

View File

@@ -13,6 +13,8 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import {TracePosition} from 'trace/trace_position';
import {TraceType} from 'trace/trace_type';
enum ViewType {
@@ -30,7 +32,7 @@ class View {
}
interface Viewer {
notifyCurrentTraceEntries(entries: Map<TraceType, any>): void;
onTracePositionUpdate(position: TracePosition): void;
getViews(): View[];
getDependencies(): TraceType[];
}

View File

@@ -13,6 +13,8 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import {Traces} from 'trace/traces';
import {TraceType} from 'trace/trace_type';
import {Viewer} from './viewer';
import {ViewerInputMethodClients} from './viewer_input_method_clients/viewer_input_method_clients';
@@ -39,7 +41,7 @@ class ViewerFactory {
ViewerScreenRecording,
];
createViewers(activeTraceTypes: Set<TraceType>, storage: Storage): Viewer[] {
createViewers(activeTraceTypes: Set<TraceType>, traces: Traces, storage: Storage): Viewer[] {
const viewers: Viewer[] = [];
for (const Viewer of ViewerFactory.VIEWERS) {
@@ -48,7 +50,7 @@ class ViewerFactory {
);
if (areViewerDepsSatisfied) {
viewers.push(new Viewer(storage));
viewers.push(new Viewer(traces, storage));
}
}

View File

@@ -13,6 +13,8 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import {Traces} from 'trace/traces';
import {TraceType} from 'trace/trace_type';
import {ViewerInputMethod} from 'viewers/common/viewer_input_method';
import {View, ViewType} from 'viewers/viewer';
@@ -29,8 +31,13 @@ class ViewerInputMethodClients extends ViewerInputMethod {
return ViewerInputMethodClients.DEPENDENCIES;
}
override initialisePresenter(storage: Storage) {
return new PresenterInputMethodClients(this.imeUiCallback, this.getDependencies(), storage);
override initialisePresenter(traces: Traces, storage: Storage): PresenterInputMethodClients {
return new PresenterInputMethodClients(
traces,
storage,
this.getDependencies(),
this.imeUiCallback
);
}
static readonly DEPENDENCIES: TraceType[] = [TraceType.INPUT_METHOD_CLIENTS];

View File

@@ -13,6 +13,8 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import {Traces} from 'trace/traces';
import {TraceType} from 'trace/trace_type';
import {ViewerInputMethod} from 'viewers/common/viewer_input_method';
import {View, ViewType} from 'viewers/viewer';
@@ -34,11 +36,12 @@ class ViewerInputMethodManagerService extends ViewerInputMethod {
return ViewerInputMethodManagerService.DEPENDENCIES;
}
override initialisePresenter(storage: Storage) {
override initialisePresenter(traces: Traces, storage: Storage) {
return new PresenterInputMethodManagerService(
this.imeUiCallback,
traces,
storage,
this.getDependencies(),
storage
this.imeUiCallback
);
}

View File

@@ -13,6 +13,8 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import {Traces} from 'trace/traces';
import {TraceType} from 'trace/trace_type';
import {ViewerInputMethod} from 'viewers/common/viewer_input_method';
import {View, ViewType} from 'viewers/viewer';
@@ -29,8 +31,13 @@ class ViewerInputMethodService extends ViewerInputMethod {
return ViewerInputMethodService.DEPENDENCIES;
}
override initialisePresenter(storage: Storage) {
return new PresenterInputMethodService(this.imeUiCallback, this.getDependencies(), storage);
override initialisePresenter(traces: Traces, storage: Storage) {
return new PresenterInputMethodService(
traces,
storage,
this.getDependencies(),
this.imeUiCallback
);
}
static readonly DEPENDENCIES: TraceType[] = [TraceType.INPUT_METHOD_SERVICE];

View File

@@ -13,25 +13,39 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import {ArrayUtils} from 'common/array_utils';
import {LogMessage, ProtoLogTraceEntry} from 'trace/protolog';
import {LogMessage} from 'trace/protolog';
import {Trace, TraceEntry} from 'trace/trace';
import {Traces} from 'trace/traces';
import {TraceEntryFinder} from 'trace/trace_entry_finder';
import {TracePosition} from 'trace/trace_position';
import {TraceType} from 'trace/trace_type';
import {assertDefined} from '../../common/assert_utils';
import {UiData} from './ui_data';
export class Presenter {
constructor(notifyUiDataCallback: (data: UiData) => void) {
private readonly trace: Trace<LogMessage>;
private readonly notifyUiDataCallback: (data: UiData) => void;
private entry?: TraceEntry<LogMessage>;
private originalIndicesOfFilteredOutputMessages: number[];
private uiData = UiData.EMPTY;
private tags: string[] = [];
private files: string[] = [];
private levels: string[] = [];
private searchString = '';
constructor(traces: Traces, notifyUiDataCallback: (data: UiData) => void) {
this.trace = assertDefined(traces.getTrace(TraceType.PROTO_LOG));
this.notifyUiDataCallback = notifyUiDataCallback;
this.originalIndicesOfFilteredOutputMessages = [];
this.uiData = UiData.EMPTY;
this.computeUiDataMessages();
this.notifyUiDataCallback(this.uiData);
}
//TODO: replace input with something like iterator/cursor (same for other viewers/presenters)
notifyCurrentTraceEntries(entries: Map<TraceType, any>): void {
this.entry = entries.get(TraceType.PROTO_LOG) ? entries.get(TraceType.PROTO_LOG)[0] : undefined;
if (this.uiData === UiData.EMPTY) {
this.computeUiDataMessages();
}
onTracePositionUpdate(position: TracePosition) {
this.entry = TraceEntryFinder.findCorrespondingEntry(this.trace, position);
this.computeUiDataCurrentMessageIndex();
this.notifyUiDataCallback(this.uiData);
}
@@ -65,26 +79,14 @@ export class Presenter {
}
private computeUiDataMessages() {
if (!this.entry) {
return;
}
const allLogLevels = this.getUniqueMessageValues((message: LogMessage) => message.level);
const allTags = this.getUniqueMessageValues((message: LogMessage) => message.tag);
const allSourceFiles = this.getUniqueMessageValues((message: LogMessage) => message.at);
const allLogLevels = this.getUniqueMessageValues(
this.entry!.messages,
(message: LogMessage) => message.level
);
const allTags = this.getUniqueMessageValues(
this.entry!.messages,
(message: LogMessage) => message.tag
);
const allSourceFiles = this.getUniqueMessageValues(
this.entry!.messages,
(message: LogMessage) => message.at
);
let filteredMessagesAndOriginalIndex: Array<[number, LogMessage]> = [
...this.entry!.messages.entries(),
];
let filteredMessagesAndOriginalIndex = new Array<[number, LogMessage]>();
this.trace.forEachEntry((entry) => {
filteredMessagesAndOriginalIndex.push([entry.getIndex(), entry.getValue()]);
});
if (this.levels.length > 0) {
filteredMessagesAndOriginalIndex = filteredMessagesAndOriginalIndex.filter((value) =>
@@ -113,40 +115,34 @@ export class Presenter {
);
const filteredMessages = filteredMessagesAndOriginalIndex.map((value) => value[1]);
this.uiData = new UiData(allLogLevels, allTags, allSourceFiles, filteredMessages, 0);
this.uiData = new UiData(allLogLevels, allTags, allSourceFiles, filteredMessages, undefined);
}
private computeUiDataCurrentMessageIndex() {
if (!this.entry) {
this.uiData.currentMessageIndex = undefined;
return;
}
this.uiData.currentMessageIndex = ArrayUtils.binarySearchLowerOrEqual(
this.originalIndicesOfFilteredOutputMessages,
this.entry.currentMessageIndex
);
if (this.originalIndicesOfFilteredOutputMessages.length === 0) {
this.uiData.currentMessageIndex = undefined;
return;
}
this.uiData.currentMessageIndex =
ArrayUtils.binarySearchFirstGreaterOrEqual(
this.originalIndicesOfFilteredOutputMessages,
this.entry.getIndex()
) ?? this.originalIndicesOfFilteredOutputMessages.length - 1;
}
private getUniqueMessageValues(
messages: LogMessage[],
getValue: (message: LogMessage) => string
): string[] {
private getUniqueMessageValues(getValue: (message: LogMessage) => string): string[] {
const uniqueValues = new Set<string>();
messages.forEach((message) => {
uniqueValues.add(getValue(message));
this.trace.forEachEntry((entry) => {
uniqueValues.add(getValue(entry.getValue()));
});
const result = [...uniqueValues];
result.sort();
return result;
}
private entry?: ProtoLogTraceEntry;
private originalIndicesOfFilteredOutputMessages: number[];
private uiData: UiData;
private readonly notifyUiDataCallback: (data: UiData) => void;
private tags: string[] = [];
private files: string[] = [];
private levels: string[] = [];
private searchString = '';
}

View File

@@ -13,7 +13,14 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import {LogMessage, ProtoLogTraceEntry} from 'trace/protolog';
import {TracesBuilder} from 'test/unit/traces_builder';
import {TraceBuilder} from 'test/unit/trace_builder';
import {LogMessage} from 'trace/protolog';
import {RealTimestamp} from 'trace/timestamp';
import {Trace} from 'trace/trace';
import {Traces} from 'trace/traces';
import {TracePosition} from 'trace/trace_position';
import {TraceType} from 'trace/trace_type';
import {Presenter} from './presenter';
import {UiData} from './ui_data';
@@ -21,41 +28,56 @@ import {UiData} from './ui_data';
describe('ViewerProtoLogPresenter', () => {
let presenter: Presenter;
let inputMessages: LogMessage[];
let inputTraceEntries: Map<TraceType, any>;
let trace: Trace<LogMessage>;
let position10: TracePosition;
let position11: TracePosition;
let position12: TracePosition;
let outputUiData: undefined | UiData;
beforeEach(async () => {
const time10 = new RealTimestamp(10n);
const time11 = new RealTimestamp(11n);
const time12 = new RealTimestamp(12n);
inputMessages = [
new LogMessage('text0', 'time', 'tag0', 'level0', 'sourcefile0', 10n),
new LogMessage('text1', 'time', 'tag1', 'level1', 'sourcefile1', 10n),
new LogMessage('text2', 'time', 'tag2', 'level2', 'sourcefile2', 10n),
new LogMessage('text1', 'time', 'tag1', 'level1', 'sourcefile1', 11n),
new LogMessage('text2', 'time', 'tag2', 'level2', 'sourcefile2', 12n),
];
inputTraceEntries = new Map<TraceType, any>();
inputTraceEntries.set(TraceType.PROTO_LOG, [new ProtoLogTraceEntry(inputMessages, 0)]);
trace = new TraceBuilder<LogMessage>()
.setEntries(inputMessages)
.setTimestamps([time10, time11, time12])
.build();
position10 = TracePosition.fromTimestamp(time10);
position11 = TracePosition.fromTimestamp(time11);
position12 = TracePosition.fromTimestamp(time12);
outputUiData = undefined;
presenter = new Presenter((data: UiData) => {
const traces = new Traces();
traces.setTrace(TraceType.PROTO_LOG, trace);
presenter = new Presenter(traces, (data: UiData) => {
outputUiData = data;
});
});
it('is robust to undefined trace entry', () => {
presenter.notifyCurrentTraceEntries(new Map<TraceType, any>());
it('is robust to empty trace', () => {
const traces = new TracesBuilder().setEntries(TraceType.PROTO_LOG, []).build();
presenter = new Presenter(traces, (data: UiData) => {
outputUiData = data;
});
expect(outputUiData!.messages).toEqual([]);
expect(outputUiData!.currentMessageIndex).toBeUndefined();
presenter.onTracePositionUpdate(position10);
expect(outputUiData!.messages).toEqual([]);
expect(outputUiData!.currentMessageIndex).toBeUndefined();
});
it("ignores undefined trace entry and doesn't discard displayed messages", () => {
presenter.notifyCurrentTraceEntries(inputTraceEntries);
expect(outputUiData!.messages).toEqual(inputMessages);
presenter.notifyCurrentTraceEntries(new Map<TraceType, any>());
expect(outputUiData!.messages).toEqual(inputMessages);
});
it('processes current trace entries', () => {
presenter.notifyCurrentTraceEntries(inputTraceEntries);
it('processes trace position updates', () => {
presenter.onTracePositionUpdate(position10);
expect(outputUiData!.allLogLevels).toEqual(['level0', 'level1', 'level2']);
expect(outputUiData!.allTags).toEqual(['tag0', 'tag1', 'tag2']);
@@ -64,8 +86,7 @@ describe('ViewerProtoLogPresenter', () => {
expect(outputUiData!.currentMessageIndex).toEqual(0);
});
it('updated displayed messages according to log levels filter', () => {
presenter.notifyCurrentTraceEntries(inputTraceEntries);
it('updates displayed messages according to log levels filter', () => {
expect(outputUiData!.messages).toEqual(inputMessages);
presenter.onLogLevelsFilterChanged([]);
@@ -79,7 +100,6 @@ describe('ViewerProtoLogPresenter', () => {
});
it('updates displayed messages according to tags filter', () => {
presenter.notifyCurrentTraceEntries(inputTraceEntries);
expect(outputUiData!.messages).toEqual(inputMessages);
presenter.onTagsFilterChanged([]);
@@ -93,7 +113,6 @@ describe('ViewerProtoLogPresenter', () => {
});
it('updates displayed messages according to source files filter', () => {
presenter.notifyCurrentTraceEntries(inputTraceEntries);
expect(outputUiData!.messages).toEqual(inputMessages);
presenter.onSourceFilesFilterChanged([]);
@@ -107,7 +126,6 @@ describe('ViewerProtoLogPresenter', () => {
});
it('updates displayed messages according to search string filter', () => {
presenter.notifyCurrentTraceEntries(inputTraceEntries);
expect(outputUiData!.messages).toEqual(inputMessages);
presenter.onSearchStringFilterChanged('');
@@ -124,7 +142,8 @@ describe('ViewerProtoLogPresenter', () => {
});
it('computes current message index', () => {
presenter.notifyCurrentTraceEntries(inputTraceEntries);
// Position -> entry #0
presenter.onTracePositionUpdate(position10);
presenter.onLogLevelsFilterChanged([]);
expect(outputUiData!.currentMessageIndex).toEqual(0);
@@ -134,8 +153,8 @@ describe('ViewerProtoLogPresenter', () => {
presenter.onLogLevelsFilterChanged([]);
expect(outputUiData!.currentMessageIndex).toEqual(0);
(inputTraceEntries.get(TraceType.PROTO_LOG)[0] as ProtoLogTraceEntry).currentMessageIndex = 1;
presenter.notifyCurrentTraceEntries(inputTraceEntries);
// Position -> entry #1
presenter.onTracePositionUpdate(position11);
presenter.onLogLevelsFilterChanged([]);
expect(outputUiData!.currentMessageIndex).toEqual(1);
@@ -147,5 +166,10 @@ describe('ViewerProtoLogPresenter', () => {
presenter.onLogLevelsFilterChanged(['level0', 'level1']);
expect(outputUiData!.currentMessageIndex).toEqual(1);
// Position -> entry #2
presenter.onTracePositionUpdate(position12);
presenter.onLogLevelsFilterChanged([]);
expect(outputUiData!.currentMessageIndex).toEqual(2);
});
});

View File

@@ -13,6 +13,9 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import {Traces} from 'trace/traces';
import {TracePosition} from 'trace/trace_position';
import {TraceType} from 'trace/trace_type';
import {View, Viewer, ViewType} from 'viewers/viewer';
import {Events} from './events';
@@ -20,10 +23,10 @@ import {Presenter} from './presenter';
import {UiData} from './ui_data';
class ViewerProtoLog implements Viewer {
constructor() {
constructor(traces: Traces) {
this.htmlElement = document.createElement('viewer-protolog');
this.presenter = new Presenter((data: UiData) => {
this.presenter = new Presenter(traces, (data: UiData) => {
(this.htmlElement as any).inputData = data;
});
@@ -41,8 +44,8 @@ class ViewerProtoLog implements Viewer {
});
}
notifyCurrentTraceEntries(entries: Map<TraceType, any>): void {
this.presenter.notifyCurrentTraceEntries(entries);
onTracePositionUpdate(position: TracePosition) {
this.presenter.onTracePositionUpdate(position);
}
getViews(): View[] {

View File

@@ -13,21 +13,31 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import {assertDefined} from 'common/assert_utils';
import {ScreenRecordingTraceEntry} from 'trace/screen_recording';
import {Trace} from 'trace/trace';
import {Traces} from 'trace/traces';
import {TraceEntryFinder} from 'trace/trace_entry_finder';
import {TracePosition} from 'trace/trace_position';
import {TraceType} from 'trace/trace_type';
import {View, Viewer, ViewType} from 'viewers/viewer';
import {ViewerScreenRecordingComponent} from './viewer_screen_recording_component';
class ViewerScreenRecording implements Viewer {
constructor() {
static readonly DEPENDENCIES: TraceType[] = [TraceType.SCREEN_RECORDING];
private readonly trace: Trace<ScreenRecordingTraceEntry>;
private readonly htmlElement: HTMLElement;
constructor(traces: Traces) {
this.trace = assertDefined(traces.getTrace(TraceType.SCREEN_RECORDING));
this.htmlElement = document.createElement('viewer-screen-recording');
}
notifyCurrentTraceEntries(entries: Map<TraceType, any>): void {
const entry: undefined | ScreenRecordingTraceEntry = entries.get(TraceType.SCREEN_RECORDING)
? entries.get(TraceType.SCREEN_RECORDING)[0]
: undefined;
(this.htmlElement as any).currentTraceEntry = entry;
onTracePositionUpdate(position: TracePosition) {
const entry = TraceEntryFinder.findCorrespondingEntry(this.trace, position);
(this.htmlElement as unknown as ViewerScreenRecordingComponent).currentTraceEntry =
entry?.getValue();
}
getViews(): View[] {
@@ -39,9 +49,6 @@ class ViewerScreenRecording implements Viewer {
getDependencies(): TraceType[] {
return ViewerScreenRecording.DEPENDENCIES;
}
static readonly DEPENDENCIES: TraceType[] = [TraceType.SCREEN_RECORDING];
private htmlElement: HTMLElement;
}
export {ViewerScreenRecording};

View File

@@ -13,6 +13,8 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import {TracePosition} from 'trace/trace_position';
import {View, Viewer, ViewType} from './viewer';
class ViewerStub implements Viewer {
@@ -27,7 +29,7 @@ class ViewerStub implements Viewer {
}
}
notifyCurrentTraceEntries(entries: any) {
onTracePositionUpdate(position: TracePosition) {
// do nothing
}

View File

@@ -13,9 +13,16 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import {assertDefined} from 'common/assert_utils';
import {PersistentStoreProxy} from 'common/persistent_store_proxy';
import {FilterType, TreeUtils} from 'common/tree_utils';
import {Layer, LayerTraceEntry} from 'trace/flickerlib/common';
import {Layer} from 'trace/flickerlib/layers/Layer';
import {LayerTraceEntry} from 'trace/flickerlib/layers/LayerTraceEntry';
import {Trace} from 'trace/trace';
import {Traces} from 'trace/traces';
import {TraceEntryFinder} from 'trace/trace_entry_finder';
import {TracePosition} from 'trace/trace_position';
import {TraceType} from 'trace/trace_type';
import {Rectangle, RectMatrix, RectTransform} from 'viewers/common/rectangle';
import {TreeGenerator} from 'viewers/common/tree_generator';
@@ -27,12 +34,94 @@ import {UiData} from './ui_data';
type NotifyViewCallbackType = (uiData: UiData) => void;
export class Presenter {
constructor(notifyViewCallback: NotifyViewCallbackType, private storage: Storage) {
private readonly notifyViewCallback: NotifyViewCallbackType;
private readonly trace: Trace<LayerTraceEntry>;
private uiData: UiData;
private hierarchyFilter: FilterType = TreeUtils.makeNodeFilter('');
private propertiesFilter: FilterType = TreeUtils.makeNodeFilter('');
private highlightedItems: string[] = [];
private displayIds: number[] = [];
private pinnedItems: HierarchyTreeNode[] = [];
private pinnedIds: string[] = [];
private selectedHierarchyTree: HierarchyTreeNode | null = null;
private selectedLayer: LayerTraceEntry | Layer | null = null;
private previousEntry: LayerTraceEntry | null = null;
private entry: LayerTraceEntry | null = null;
private hierarchyUserOptions: UserOptions = PersistentStoreProxy.new<UserOptions>(
'SfHierarchyOptions',
{
showDiff: {
name: 'Show diff', // TODO: PersistentStoreObject.Ignored("Show diff") or something like that to instruct to not store this info
enabled: false,
},
simplifyNames: {
name: 'Simplify names',
enabled: true,
},
onlyVisible: {
name: 'Only visible',
enabled: false,
},
flat: {
name: 'Flat',
enabled: false,
},
},
this.storage
);
private propertiesUserOptions: UserOptions = PersistentStoreProxy.new<UserOptions>(
'SfPropertyOptions',
{
showDiff: {
name: 'Show diff',
enabled: false,
},
showDefaults: {
name: 'Show defaults',
enabled: false,
tooltip: `
If checked, shows the value of all properties.
Otherwise, hides all properties whose value is
the default for its data type.
`,
},
},
this.storage
);
constructor(
traces: Traces,
private readonly storage: Storage,
notifyViewCallback: NotifyViewCallbackType
) {
this.trace = assertDefined(traces.getTrace(TraceType.SURFACE_FLINGER));
this.notifyViewCallback = notifyViewCallback;
this.uiData = new UiData([TraceType.SURFACE_FLINGER]);
this.copyUiDataAndNotifyView();
}
onTracePositionUpdate(position: TracePosition) {
this.uiData = new UiData();
this.uiData.hierarchyUserOptions = this.hierarchyUserOptions;
this.uiData.propertiesUserOptions = this.propertiesUserOptions;
const entry = TraceEntryFinder.findCorrespondingEntry(this.trace, position);
const prevEntry =
entry && entry.getIndex() > 0 ? this.trace.getEntry(entry.getIndex() - 1) : undefined;
this.entry = entry?.getValue() ?? null;
this.previousEntry = prevEntry?.getValue() ?? null;
if (this.entry) {
this.uiData.highlightedItems = this.highlightedItems;
this.uiData.rects = this.generateRects();
this.uiData.displayIds = this.displayIds;
this.uiData.tree = this.generateTree();
}
this.copyUiDataAndNotifyView();
}
updatePinnedItems(pinnedItem: HierarchyTreeNode) {
const pinnedId = `${pinnedItem.id}`;
if (this.pinnedItems.map((item) => `${item.id}`).includes(pinnedId)) {
@@ -85,24 +174,6 @@ export class Presenter {
this.updateSelectedTreeUiData();
}
notifyCurrentTraceEntries(entries: Map<TraceType, [any, any]>) {
this.uiData = new UiData();
this.uiData.hierarchyUserOptions = this.hierarchyUserOptions;
this.uiData.propertiesUserOptions = this.propertiesUserOptions;
const sfEntries = entries.get(TraceType.SURFACE_FLINGER);
if (sfEntries) {
[this.entry, this.previousEntry] = sfEntries;
if (this.entry) {
this.uiData.highlightedItems = this.highlightedItems;
this.uiData.rects = this.generateRects();
this.uiData.displayIds = this.displayIds;
this.uiData.tree = this.generateTree();
}
}
this.copyUiDataAndNotifyView();
}
private generateRects(): Rectangle[] {
const displayRects =
this.entry.displays.map((display: any) => {
@@ -273,59 +344,4 @@ export class Presenter {
const copy = Object.assign({}, this.uiData);
this.notifyViewCallback(copy);
}
private readonly notifyViewCallback: NotifyViewCallbackType;
private uiData: UiData;
private hierarchyFilter: FilterType = TreeUtils.makeNodeFilter('');
private propertiesFilter: FilterType = TreeUtils.makeNodeFilter('');
private highlightedItems: string[] = [];
private displayIds: number[] = [];
private pinnedItems: HierarchyTreeNode[] = [];
private pinnedIds: string[] = [];
private selectedHierarchyTree: HierarchyTreeNode | null = null;
private selectedLayer: LayerTraceEntry | Layer | null = null;
private previousEntry: LayerTraceEntry | null = null;
private entry: LayerTraceEntry | null = null;
private hierarchyUserOptions: UserOptions = PersistentStoreProxy.new<UserOptions>(
'SfHierarchyOptions',
{
showDiff: {
name: 'Show diff', // TODO: PersistentStoreObject.Ignored("Show diff") or something like that to instruct to not store this info
enabled: false,
},
simplifyNames: {
name: 'Simplify names',
enabled: true,
},
onlyVisible: {
name: 'Only visible',
enabled: false,
},
flat: {
name: 'Flat',
enabled: false,
},
},
this.storage
);
private propertiesUserOptions: UserOptions = PersistentStoreProxy.new<UserOptions>(
'SfPropertyOptions',
{
showDiff: {
name: 'Show diff',
enabled: false,
},
showDefaults: {
name: 'Show defaults',
enabled: false,
tooltip: `
If checked, shows the value of all properties.
Otherwise, hides all properties whose value is
the default for its data type.
`,
},
},
this.storage
);
}

View File

@@ -13,10 +13,16 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import {HierarchyTreeBuilder} from 'test/unit/hierarchy_tree_builder';
import {MockStorage} from 'test/unit/mock_storage';
import {TraceBuilder} from 'test/unit/trace_builder';
import {UnitTestUtils} from 'test/unit/utils';
import {LayerTraceEntry} from 'trace/flickerlib/common';
import {LayerTraceEntry} from 'trace/flickerlib/layers/LayerTraceEntry';
import {RealTimestamp} from 'trace/timestamp';
import {Trace} from 'trace/trace';
import {Traces} from 'trace/traces';
import {TracePosition} from 'trace/trace_position';
import {TraceType} from 'trace/trace_type';
import {HierarchyTreeNode, PropertiesTreeNode} from 'viewers/common/ui_tree_utils';
import {UserOptions} from 'viewers/common/user_options';
@@ -24,14 +30,23 @@ import {Presenter} from './presenter';
import {UiData} from './ui_data';
describe('PresenterSurfaceFlinger', () => {
let trace: Trace<LayerTraceEntry>;
let position: TracePosition;
let positionMultiDisplayEntry: TracePosition;
let presenter: Presenter;
let uiData: UiData;
let entries: Map<TraceType, any>;
let selectedTree: HierarchyTreeNode;
beforeAll(async () => {
entries = new Map<TraceType, any>();
const entry: LayerTraceEntry = await UnitTestUtils.getLayerTraceEntry();
trace = new TraceBuilder<LayerTraceEntry>()
.setEntries([
await UnitTestUtils.getLayerTraceEntry(),
await UnitTestUtils.getMultiDisplayLayerTraceEntry(),
])
.build();
position = TracePosition.fromTraceEntry(trace.getEntry(0));
positionMultiDisplayEntry = TracePosition.fromTraceEntry(trace.getEntry(1));
selectedTree = new HierarchyTreeBuilder()
.setName('Dim layer#53')
@@ -41,18 +56,24 @@ describe('PresenterSurfaceFlinger', () => {
.setDiffType('EffectLayer')
.setId(53)
.build();
entries.set(TraceType.SURFACE_FLINGER, [entry, null]);
});
beforeEach(async () => {
presenter = new Presenter((newData: UiData) => {
uiData = newData;
}, new MockStorage());
presenter = createPresenter(trace);
});
it('processes current trace entries', () => {
presenter.notifyCurrentTraceEntries(entries);
it('is robust to empty trace', () => {
const emptyTrace = new TraceBuilder<LayerTraceEntry>().setEntries([]).build();
const presenter = createPresenter(emptyTrace);
const positionWithoutTraceEntry = TracePosition.fromTimestamp(new RealTimestamp(0n));
presenter.onTracePositionUpdate(positionWithoutTraceEntry);
expect(uiData.hierarchyUserOptions).toBeTruthy();
expect(uiData.tree).toBeFalsy();
});
it('processes trace position updates', () => {
presenter.onTracePositionUpdate(position);
expect(uiData.rects.length).toBeGreaterThan(0);
expect(uiData.highlightedItems?.length).toEqual(0);
@@ -68,18 +89,8 @@ describe('PresenterSurfaceFlinger', () => {
expect(Object.keys(uiData.tree!).length > 0).toBeTrue();
});
it('handles unavailable trace entry', () => {
presenter.notifyCurrentTraceEntries(entries);
expect(uiData.tree).toBeDefined();
expect(Object.keys(uiData.tree!).length > 0).toBeTrue();
const emptyEntries = new Map<TraceType, any>();
presenter.notifyCurrentTraceEntries(emptyEntries);
expect(uiData.tree).toBeFalsy();
});
it('creates input data for rects view', () => {
presenter.notifyCurrentTraceEntries(entries);
presenter.onTracePositionUpdate(position);
expect(uiData.rects.length).toBeGreaterThan(0);
expect(uiData.rects[0].topLeft).toEqual({x: 0, y: 0});
expect(uiData.rects[0].bottomRight).toEqual({x: 1080, y: 118});
@@ -126,7 +137,7 @@ describe('PresenterSurfaceFlinger', () => {
},
};
presenter.notifyCurrentTraceEntries(entries);
presenter.onTracePositionUpdate(position);
expect(uiData.tree?.children.length).toEqual(3);
presenter.updateHierarchyTree(userOptions);
@@ -154,7 +165,7 @@ describe('PresenterSurfaceFlinger', () => {
enabled: true,
},
};
presenter.notifyCurrentTraceEntries(entries);
presenter.onTracePositionUpdate(position);
presenter.updateHierarchyTree(userOptions);
expect(uiData.tree?.children.length).toEqual(94);
presenter.filterHierarchyTree('Wallpaper');
@@ -163,7 +174,7 @@ describe('PresenterSurfaceFlinger', () => {
});
it('sets properties tree and associated ui data', () => {
presenter.notifyCurrentTraceEntries(entries);
presenter.onTracePositionUpdate(position);
presenter.newPropertiesTree(selectedTree);
// does not check specific tree values as tree transformation method may change
expect(uiData.propertiesTree).toBeTruthy();
@@ -187,7 +198,7 @@ describe('PresenterSurfaceFlinger', () => {
},
};
presenter.notifyCurrentTraceEntries(entries);
presenter.onTracePositionUpdate(position);
presenter.newPropertiesTree(selectedTree);
expect(uiData.propertiesTree?.diffType).toBeFalsy();
@@ -197,7 +208,7 @@ describe('PresenterSurfaceFlinger', () => {
});
it('filters properties tree', () => {
presenter.notifyCurrentTraceEntries(entries);
presenter.onTracePositionUpdate(position);
presenter.newPropertiesTree(selectedTree);
let nonTerminalChildren =
uiData.propertiesTree?.children?.filter(
@@ -215,18 +226,17 @@ describe('PresenterSurfaceFlinger', () => {
});
it('handles displays with no visible layers', async () => {
presenter.notifyCurrentTraceEntries(await getEntriesWithMultiDisplaySfTrace());
presenter.onTracePositionUpdate(positionMultiDisplayEntry);
expect(uiData.displayIds.length).toEqual(5);
// we want the ids to be sorted
expect(uiData.displayIds).toEqual([0, 2, 3, 4, 5]);
});
async function getEntriesWithMultiDisplaySfTrace(): Promise<Map<TraceType, any>> {
const entries = new Map<TraceType, any>();
const entry: LayerTraceEntry = await UnitTestUtils.getMultiDisplayLayerTraceEntry();
entries.set(TraceType.SURFACE_FLINGER, [entry, null]);
return entries;
}
const createPresenter = (trace: Trace<LayerTraceEntry>): Presenter => {
const traces = new Traces();
traces.setTrace(TraceType.SURFACE_FLINGER, trace);
return new Presenter(traces, new MockStorage(), (newData: UiData) => {
uiData = newData;
});
};
});

View File

@@ -13,6 +13,9 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import {Traces} from 'trace/traces';
import {TracePosition} from 'trace/trace_position';
import {TraceType} from 'trace/trace_type';
import {ViewerEvents} from 'viewers/common/viewer_events';
import {View, Viewer, ViewType} from 'viewers/viewer';
@@ -21,15 +24,15 @@ import {UiData} from './ui_data';
class ViewerSurfaceFlinger implements Viewer {
static readonly DEPENDENCIES: TraceType[] = [TraceType.SURFACE_FLINGER];
private htmlElement: HTMLElement;
private presenter: Presenter;
private readonly htmlElement: HTMLElement;
private readonly presenter: Presenter;
constructor(storage: Storage) {
constructor(traces: Traces, storage: Storage) {
this.htmlElement = document.createElement('viewer-surface-flinger');
this.presenter = new Presenter((uiData: UiData) => {
this.presenter = new Presenter(traces, storage, (uiData: UiData) => {
(this.htmlElement as any).inputData = uiData;
}, storage);
});
this.htmlElement.addEventListener(ViewerEvents.HierarchyPinnedChange, (event) =>
this.presenter.updatePinnedItems((event as CustomEvent).detail.pinnedItem)
@@ -54,8 +57,8 @@ class ViewerSurfaceFlinger implements Viewer {
);
}
notifyCurrentTraceEntries(entries: Map<TraceType, any>): void {
this.presenter.notifyCurrentTraceEntries(entries);
onTracePositionUpdate(position: TracePosition) {
this.presenter.onTracePositionUpdate(position);
}
getViews(): View[] {

View File

@@ -13,20 +13,25 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import {ArrayUtils} from 'common/array_utils';
import {assertDefined} from 'common/assert_utils';
import {TimeUtils} from 'common/time_utils';
import {ObjectFormatter} from 'trace/flickerlib/ObjectFormatter';
import {ElapsedTimestamp, RealTimestamp, TimestampType} from 'trace/timestamp';
import {Trace, TraceEntry} from 'trace/trace';
import {Traces} from 'trace/traces';
import {TraceEntryFinder} from 'trace/trace_entry_finder';
import {TracePosition} from 'trace/trace_position';
import {TraceType} from 'trace/trace_type';
import {TransactionsTraceEntry} from 'trace/transactions';
import {PropertiesTreeGenerator} from 'viewers/common/properties_tree_generator';
import {PropertiesTreeNode} from 'viewers/common/ui_tree_utils';
import {UiData, UiDataEntry, UiDataEntryType} from './ui_data';
export class Presenter {
private entry?: TransactionsTraceEntry;
private trace: Trace<object>;
private entry?: TraceEntry<object>;
private originalIndicesOfUiDataEntries: number[];
private uiData: UiData;
private uiData = UiData.EMPTY;
private readonly notifyUiDataCallback: (data: UiData) => void;
private static readonly VALUE_NA = 'N/A';
private vsyncIdFilter: string[] = [];
@@ -36,31 +41,26 @@ export class Presenter {
private idFilter: string[] = [];
private whatSearchString = '';
constructor(notifyUiDataCallback: (data: UiData) => void) {
constructor(traces: Traces, notifyUiDataCallback: (data: UiData) => void) {
this.trace = assertDefined(traces.getTrace(TraceType.TRANSACTIONS));
this.notifyUiDataCallback = notifyUiDataCallback;
this.originalIndicesOfUiDataEntries = [];
this.uiData = UiData.EMPTY;
this.computeUiData();
this.notifyUiDataCallback(this.uiData);
}
//TODO: replace input with something like iterator/cursor (same for other viewers/presenters)
notifyCurrentTraceEntries(entries: Map<TraceType, any>): void {
this.entry = entries.get(TraceType.TRANSACTIONS)
? entries.get(TraceType.TRANSACTIONS)[0]
: undefined;
if (this.uiData === UiData.EMPTY) {
this.computeUiData();
} else {
// update only "position" data
this.uiData.currentEntryIndex = this.computeCurrentEntryIndex();
this.uiData.selectedEntryIndex = undefined;
this.uiData.scrollToIndex = this.uiData.currentEntryIndex;
this.uiData.currentPropertiesTree = this.computeCurrentPropertiesTree(
this.uiData.entries,
this.uiData.currentEntryIndex,
this.uiData.selectedEntryIndex
);
}
onTracePositionUpdate(position: TracePosition) {
this.entry = TraceEntryFinder.findCorrespondingEntry(this.trace, position);
this.uiData.currentEntryIndex = this.computeCurrentEntryIndex();
this.uiData.selectedEntryIndex = undefined;
this.uiData.scrollToIndex = this.uiData.currentEntryIndex;
this.uiData.currentPropertiesTree = this.computeCurrentPropertiesTree(
this.uiData.entries,
this.uiData.currentEntryIndex,
this.uiData.selectedEntryIndex
);
this.notifyUiDataCallback(this.uiData);
}
@@ -119,11 +119,7 @@ export class Presenter {
}
private computeUiData() {
if (!this.entry) {
return;
}
const entries = this.makeUiDataEntries(this.entry!);
const entries = this.makeUiDataEntries();
const allVSyncIds = this.getUniqueUiDataEntryValues(entries, (entry: UiDataEntry) =>
entry.vsyncId.toString()
@@ -190,9 +186,15 @@ export class Presenter {
return undefined;
}
return ArrayUtils.binarySearchLowerOrEqual(
this.originalIndicesOfUiDataEntries,
this.entry.currentEntryIndex
if (this.originalIndicesOfUiDataEntries.length === 0) {
return undefined;
}
return (
ArrayUtils.binarySearchFirstGreaterOrEqual(
this.originalIndicesOfUiDataEntries,
this.entry.getIndex()
) ?? this.originalIndicesOfUiDataEntries.length - 1
);
}
@@ -210,23 +212,23 @@ export class Presenter {
return undefined;
}
private makeUiDataEntries(entry: TransactionsTraceEntry): UiDataEntry[] {
const entriesProto: any[] = entry.entriesProto;
const timestampType = entry.timestampType;
const realToElapsedTimeOffsetNs = entry.realToElapsedTimeOffsetNs;
private makeUiDataEntries(): UiDataEntry[] {
const treeGenerator = new PropertiesTreeGenerator();
const entries: UiDataEntry[] = [];
const formattingOptions = ObjectFormatter.displayDefaults;
ObjectFormatter.displayDefaults = false;
for (const [originalIndex, entryProto] of entriesProto.entries()) {
this.trace.forEachEntry((entry, originalIndex) => {
const timestampType = entry.getTimestamp().getType();
const entryProto = entry.getValue() as any;
const realToElapsedTimeOffsetNs = entryProto.realToElapsedTimeOffsetNs;
for (const transactionStateProto of entryProto.transactions) {
for (const layerStateProto of transactionStateProto.layerChanges) {
entries.push(
new UiDataEntry(
originalIndex,
this.formatTime(entryProto, timestampType, realToElapsedTimeOffsetNs),
TimeUtils.format(entry.getTimestamp()),
Number(entryProto.vsyncId),
transactionStateProto.pid.toString(),
transactionStateProto.uid.toString(),
@@ -242,7 +244,7 @@ export class Presenter {
entries.push(
new UiDataEntry(
originalIndex,
this.formatTime(entryProto, timestampType, realToElapsedTimeOffsetNs),
TimeUtils.format(entry.getTimestamp()),
Number(entryProto.vsyncId),
transactionStateProto.pid.toString(),
transactionStateProto.uid.toString(),
@@ -259,7 +261,7 @@ export class Presenter {
entries.push(
new UiDataEntry(
originalIndex,
this.formatTime(entryProto, timestampType, realToElapsedTimeOffsetNs),
TimeUtils.format(entry.getTimestamp()),
Number(entryProto.vsyncId),
Presenter.VALUE_NA,
Presenter.VALUE_NA,
@@ -278,7 +280,7 @@ export class Presenter {
entries.push(
new UiDataEntry(
originalIndex,
this.formatTime(entryProto, timestampType, realToElapsedTimeOffsetNs),
TimeUtils.format(entry.getTimestamp()),
Number(entryProto.vsyncId),
Presenter.VALUE_NA,
Presenter.VALUE_NA,
@@ -294,7 +296,7 @@ export class Presenter {
entries.push(
new UiDataEntry(
originalIndex,
this.formatTime(entryProto, timestampType, realToElapsedTimeOffsetNs),
TimeUtils.format(entry.getTimestamp()),
Number(entryProto.vsyncId),
Presenter.VALUE_NA,
Presenter.VALUE_NA,
@@ -310,7 +312,7 @@ export class Presenter {
entries.push(
new UiDataEntry(
originalIndex,
this.formatTime(entryProto, timestampType, realToElapsedTimeOffsetNs),
TimeUtils.format(entry.getTimestamp()),
Number(entryProto.vsyncId),
Presenter.VALUE_NA,
Presenter.VALUE_NA,
@@ -326,7 +328,7 @@ export class Presenter {
entries.push(
new UiDataEntry(
originalIndex,
this.formatTime(entryProto, timestampType, realToElapsedTimeOffsetNs),
TimeUtils.format(entry.getTimestamp()),
Number(entryProto.vsyncId),
Presenter.VALUE_NA,
Presenter.VALUE_NA,
@@ -340,26 +342,13 @@ export class Presenter {
)
);
}
}
});
ObjectFormatter.displayDefaults = formattingOptions;
return entries;
}
private formatTime(
entryProto: any,
timestampType: TimestampType,
realToElapsedTimeOffsetNs: bigint | undefined
): string {
if (timestampType === TimestampType.REAL && realToElapsedTimeOffsetNs !== undefined) {
return TimeUtils.format(
new RealTimestamp(BigInt(entryProto.elapsedRealtimeNanos) + realToElapsedTimeOffsetNs)
);
} else {
return TimeUtils.format(new ElapsedTimestamp(BigInt(entryProto.elapsedRealtimeNanos)));
}
}
private getUniqueUiDataEntryValues<T>(
entries: UiDataEntry[],
getValue: (entry: UiDataEntry) => T

View File

@@ -13,21 +13,24 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import {Parser} from 'parsers/parser';
import {TracesBuilder} from 'test/unit/traces_builder';
import {TraceBuilder} from 'test/unit/trace_builder';
import {UnitTestUtils} from 'test/unit/utils';
import {Timestamp, TimestampType} from 'trace/timestamp';
import {Parser} from 'trace/parser';
import {RealTimestamp, TimestampType} from 'trace/timestamp';
import {Trace} from 'trace/trace';
import {Traces} from 'trace/traces';
import {TracePosition} from 'trace/trace_position';
import {TraceType} from 'trace/trace_type';
import {TransactionsTraceEntry} from 'trace/transactions';
import {Presenter} from './presenter';
import {UiData, UiDataEntryType} from './ui_data';
describe('ViewerTransactionsPresenter', () => {
let parser: Parser;
describe('PresenterTransactions', () => {
let parser: Parser<object>;
let trace: Trace<object>;
let traces: Traces;
let presenter: Presenter;
let inputTraceEntryElapsed: TransactionsTraceEntry;
let inputTraceEntriesElapsed: Map<TraceType, any>;
let inputTraceEntryReal: TransactionsTraceEntry;
let inputTraceEntriesReal: Map<TraceType, any>;
let outputUiData: undefined | UiData;
const TOTAL_OUTPUT_ENTRIES = 1504;
@@ -36,31 +39,24 @@ describe('ViewerTransactionsPresenter', () => {
});
beforeEach(() => {
const elapsedTimestamp = new Timestamp(TimestampType.ELAPSED, 2450981445n);
inputTraceEntryElapsed = parser.getTraceEntry(elapsedTimestamp)!;
inputTraceEntriesElapsed = new Map<TraceType, any>();
inputTraceEntriesElapsed.set(TraceType.TRANSACTIONS, [inputTraceEntryElapsed]);
const realTimestamp = new Timestamp(TimestampType.REAL, 16595075386004995520n);
inputTraceEntryReal = parser.getTraceEntry(realTimestamp)!;
inputTraceEntriesReal = new Map<TraceType, any>();
inputTraceEntriesReal.set(TraceType.TRANSACTIONS, [inputTraceEntryReal]);
outputUiData = undefined;
presenter = new Presenter((data: UiData) => {
outputUiData = data;
});
setUpTestEnvironment(TimestampType.ELAPSED);
});
it('is robust to undefined trace entry', () => {
inputTraceEntriesElapsed = new Map<TraceType, any>();
presenter.notifyCurrentTraceEntries(inputTraceEntriesElapsed);
it('is robust to empty trace', () => {
const traces = new TracesBuilder().setEntries(TraceType.TRANSACTIONS, []).build();
presenter = new Presenter(traces, (data: UiData) => {
outputUiData = data;
});
expect(outputUiData).toEqual(UiData.EMPTY);
presenter.onTracePositionUpdate(TracePosition.fromTimestamp(new RealTimestamp(10n)));
expect(outputUiData).toEqual(UiData.EMPTY);
});
it('processes trace entry and computes output UI data', () => {
presenter.notifyCurrentTraceEntries(inputTraceEntriesElapsed);
it('processes trace position update and computes output UI data', () => {
presenter.onTracePositionUpdate(createTracePosition(0));
expect(outputUiData!.allPids).toEqual([
'N/A',
@@ -90,30 +86,17 @@ describe('ViewerTransactionsPresenter', () => {
expect(outputUiData?.currentPropertiesTree).toBeDefined();
});
it("ignores undefined trace entry and doesn't discard previously computed UI data", () => {
presenter.notifyCurrentTraceEntries(inputTraceEntriesElapsed);
expect(outputUiData!.entries.length).toEqual(TOTAL_OUTPUT_ENTRIES);
presenter.notifyCurrentTraceEntries(new Map<TraceType, any>());
expect(outputUiData!.entries.length).toEqual(TOTAL_OUTPUT_ENTRIES);
});
it('processes trace entry and updates current entry and scroll position', () => {
presenter.notifyCurrentTraceEntries(inputTraceEntriesElapsed);
it('processes trace position update and updates current entry and scroll position', () => {
presenter.onTracePositionUpdate(createTracePosition(0));
expect(outputUiData!.currentEntryIndex).toEqual(0);
expect(outputUiData!.scrollToIndex).toEqual(0);
(
inputTraceEntriesElapsed.get(TraceType.TRANSACTIONS)[0] as TransactionsTraceEntry
).currentEntryIndex = 10;
presenter.notifyCurrentTraceEntries(inputTraceEntriesElapsed);
presenter.onTracePositionUpdate(createTracePosition(10));
expect(outputUiData!.currentEntryIndex).toEqual(13);
expect(outputUiData!.scrollToIndex).toEqual(13);
});
it('filters entries according to VSYNC ID filter', () => {
presenter.notifyCurrentTraceEntries(inputTraceEntriesElapsed);
presenter.onVSyncIdFilterChanged([]);
expect(outputUiData!.entries.length).toEqual(TOTAL_OUTPUT_ENTRIES);
@@ -127,8 +110,6 @@ describe('ViewerTransactionsPresenter', () => {
});
it('filters entries according to PID filter', () => {
presenter.notifyCurrentTraceEntries(inputTraceEntriesElapsed);
presenter.onPidFilterChanged([]);
expect(new Set(outputUiData!.entries.map((entry) => entry.pid))).toEqual(
new Set(['N/A', '0', '515', '1593', '2022', '2322', '2463', '3300'])
@@ -142,8 +123,6 @@ describe('ViewerTransactionsPresenter', () => {
});
it('filters entries according to UID filter', () => {
presenter.notifyCurrentTraceEntries(inputTraceEntriesElapsed);
presenter.onUidFilterChanged([]);
expect(new Set(outputUiData!.entries.map((entry) => entry.uid))).toEqual(
new Set(['N/A', '1000', '1003', '10169', '10235', '10239'])
@@ -159,8 +138,6 @@ describe('ViewerTransactionsPresenter', () => {
});
it('filters entries according to type filter', () => {
presenter.notifyCurrentTraceEntries(inputTraceEntriesElapsed);
presenter.onTypeFilterChanged([]);
expect(new Set(outputUiData!.entries.map((entry) => entry.type))).toEqual(
new Set([
@@ -184,8 +161,6 @@ describe('ViewerTransactionsPresenter', () => {
});
it('filters entries according to ID filter', () => {
presenter.notifyCurrentTraceEntries(inputTraceEntriesElapsed);
presenter.onIdFilterChanged([]);
expect(new Set(outputUiData!.entries.map((entry) => entry.id)).size).toBeGreaterThan(20);
@@ -197,7 +172,6 @@ describe('ViewerTransactionsPresenter', () => {
});
it('filters entries according to "what" search string', () => {
presenter.notifyCurrentTraceEntries(inputTraceEntriesElapsed);
expect(outputUiData!.entries.length).toEqual(TOTAL_OUTPUT_ENTRIES);
presenter.onWhatSearchStringChanged('');
@@ -211,7 +185,7 @@ describe('ViewerTransactionsPresenter', () => {
});
it('updates selected entry and properties tree when entry is clicked', () => {
presenter.notifyCurrentTraceEntries(inputTraceEntriesElapsed);
presenter.onTracePositionUpdate(createTracePosition(0));
expect(outputUiData!.currentEntryIndex).toEqual(0);
expect(outputUiData!.selectedEntryIndex).toBeUndefined();
expect(outputUiData!.scrollToIndex).toEqual(0);
@@ -232,21 +206,15 @@ describe('ViewerTransactionsPresenter', () => {
});
it('computes current entry index', () => {
presenter.notifyCurrentTraceEntries(inputTraceEntriesElapsed);
presenter.onTracePositionUpdate(createTracePosition(0));
expect(outputUiData!.currentEntryIndex).toEqual(0);
(
inputTraceEntriesElapsed.get(TraceType.TRANSACTIONS)[0] as TransactionsTraceEntry
).currentEntryIndex = 10;
presenter.notifyCurrentTraceEntries(inputTraceEntriesElapsed);
presenter.onTracePositionUpdate(createTracePosition(10));
expect(outputUiData!.currentEntryIndex).toEqual(13);
});
it('updates current entry index when filters change', () => {
(
inputTraceEntriesElapsed.get(TraceType.TRANSACTIONS)[0] as TransactionsTraceEntry
).currentEntryIndex = 10;
presenter.notifyCurrentTraceEntries(inputTraceEntriesElapsed);
presenter.onTracePositionUpdate(createTracePosition(10));
presenter.onPidFilterChanged([]);
expect(outputUiData!.currentEntryIndex).toEqual(13);
@@ -262,20 +230,27 @@ describe('ViewerTransactionsPresenter', () => {
});
it('formats real time', () => {
(
inputTraceEntriesReal.get(TraceType.TRANSACTIONS)[0] as TransactionsTraceEntry
).currentEntryIndex = 10;
presenter.notifyCurrentTraceEntries(inputTraceEntriesReal);
setUpTestEnvironment(TimestampType.REAL);
expect(outputUiData!.entries[0].time).toEqual('2022-08-03T06:19:01.051480997');
});
it('formats elapsed time', () => {
(
inputTraceEntriesElapsed.get(TraceType.TRANSACTIONS)[0] as TransactionsTraceEntry
).currentEntryIndex = 10;
presenter.notifyCurrentTraceEntries(inputTraceEntriesElapsed);
setUpTestEnvironment(TimestampType.ELAPSED);
expect(outputUiData!.entries[0].time).toEqual('2s450ms981445ns');
});
const setUpTestEnvironment = (timestampType: TimestampType) => {
trace = new TraceBuilder<object>().setParser(parser).setTimestampType(timestampType).build();
traces = new Traces();
traces.setTrace(TraceType.TRANSACTIONS, trace);
presenter = new Presenter(traces, (data: UiData) => {
outputUiData = data;
});
};
const createTracePosition = (entryIndex: number): TracePosition => {
return TracePosition.fromTraceEntry(trace.getEntry(entryIndex));
};
});

View File

@@ -13,6 +13,9 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import {Traces} from 'trace/traces';
import {TracePosition} from 'trace/trace_position';
import {TraceType} from 'trace/trace_type';
import {View, Viewer, ViewType} from 'viewers/viewer';
import {Events} from './events';
@@ -20,10 +23,10 @@ import {Presenter} from './presenter';
import {UiData} from './ui_data';
class ViewerTransactions implements Viewer {
constructor() {
constructor(traces: Traces) {
this.htmlElement = document.createElement('viewer-transactions');
this.presenter = new Presenter((data: UiData) => {
this.presenter = new Presenter(traces, (data: UiData) => {
(this.htmlElement as any).inputData = data;
});
@@ -56,8 +59,8 @@ class ViewerTransactions implements Viewer {
});
}
notifyCurrentTraceEntries(entries: Map<TraceType, any>): void {
this.presenter.notifyCurrentTraceEntries(entries);
onTracePositionUpdate(position: TracePosition) {
this.presenter.onTracePositionUpdate(position);
}
getViews(): View[] {

View File

@@ -13,9 +13,16 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import {assertDefined} from 'common/assert_utils';
import {PersistentStoreProxy} from 'common/persistent_store_proxy';
import {FilterType, TreeUtils} from 'common/tree_utils';
import {DisplayContent} from 'trace/flickerlib/windows/DisplayContent';
import {WindowManagerState} from 'trace/flickerlib/windows/WindowManagerState';
import {Trace} from 'trace/trace';
import {Traces} from 'trace/traces';
import {TraceEntryFinder} from 'trace/trace_entry_finder';
import {TracePosition} from 'trace/trace_position';
import {TraceTreeNode} from 'trace/trace_tree_node';
import {TraceType} from 'trace/trace_type';
import {Rectangle, RectMatrix, RectTransform} from 'viewers/common/rectangle';
@@ -28,7 +35,66 @@ import {UiData} from './ui_data';
type NotifyViewCallbackType = (uiData: UiData) => void;
export class Presenter {
constructor(notifyViewCallback: NotifyViewCallbackType, private storage: Storage) {
private readonly trace: Trace<WindowManagerState>;
private readonly notifyViewCallback: NotifyViewCallbackType;
private uiData: UiData;
private hierarchyFilter: FilterType = TreeUtils.makeNodeFilter('');
private propertiesFilter: FilterType = TreeUtils.makeNodeFilter('');
private highlightedItems: string[] = [];
private displayIds: number[] = [];
private pinnedItems: HierarchyTreeNode[] = [];
private pinnedIds: string[] = [];
private selectedHierarchyTree: HierarchyTreeNode | null = null;
private previousEntry: TraceTreeNode | null = null;
private entry: TraceTreeNode | null = null;
private hierarchyUserOptions: UserOptions = PersistentStoreProxy.new<UserOptions>(
'WmHierarchyOptions',
{
showDiff: {
name: 'Show diff',
enabled: false,
},
simplifyNames: {
name: 'Simplify names',
enabled: true,
},
onlyVisible: {
name: 'Only visible',
enabled: false,
},
flat: {
name: 'Flat',
enabled: false,
},
},
this.storage
);
private propertiesUserOptions: UserOptions = PersistentStoreProxy.new<UserOptions>(
'WmPropertyOptions',
{
showDiff: {
name: 'Show diff',
enabled: false,
},
showDefaults: {
name: 'Show defaults',
enabled: false,
tooltip: `
If checked, shows the value of all properties.
Otherwise, hides all properties whose value is
the default for its data type.
`,
},
},
this.storage
);
constructor(
traces: Traces,
private storage: Storage,
notifyViewCallback: NotifyViewCallbackType
) {
this.trace = assertDefined(traces.getTrace(TraceType.WINDOW_MANAGER));
this.notifyViewCallback = notifyViewCallback;
this.uiData = new UiData([TraceType.WINDOW_MANAGER]);
this.notifyViewCallback(this.uiData);
@@ -86,20 +152,22 @@ export class Presenter {
this.updateSelectedTreeUiData();
}
notifyCurrentTraceEntries(entries: Map<TraceType, [any, any]>) {
onTracePositionUpdate(position: TracePosition) {
this.uiData = new UiData();
this.uiData.hierarchyUserOptions = this.hierarchyUserOptions;
this.uiData.propertiesUserOptions = this.propertiesUserOptions;
const wmEntries = entries.get(TraceType.WINDOW_MANAGER);
if (wmEntries) {
[this.entry, this.previousEntry] = wmEntries;
if (this.entry) {
this.uiData.highlightedItems = this.highlightedItems;
this.uiData.rects = this.generateRects();
this.uiData.displayIds = this.displayIds;
this.uiData.tree = this.generateTree();
}
const entry = TraceEntryFinder.findCorrespondingEntry(this.trace, position);
const prevEntry =
entry && entry.getIndex() > 0 ? this.trace.getEntry(entry.getIndex() - 1) : undefined;
this.entry = entry?.getValue() ?? null;
this.previousEntry = prevEntry?.getValue() ?? null;
if (this.entry) {
this.uiData.highlightedItems = this.highlightedItems;
this.uiData.rects = this.generateRects();
this.uiData.displayIds = this.displayIds;
this.uiData.tree = this.generateTree();
}
this.notifyViewCallback(this.uiData);
@@ -224,57 +292,4 @@ export class Presenter {
const transformedTree = transformer.transform();
return transformedTree;
}
private readonly notifyViewCallback: NotifyViewCallbackType;
private uiData: UiData;
private hierarchyFilter: FilterType = TreeUtils.makeNodeFilter('');
private propertiesFilter: FilterType = TreeUtils.makeNodeFilter('');
private highlightedItems: string[] = [];
private displayIds: number[] = [];
private pinnedItems: HierarchyTreeNode[] = [];
private pinnedIds: string[] = [];
private selectedHierarchyTree: HierarchyTreeNode | null = null;
private previousEntry: TraceTreeNode | null = null;
private entry: TraceTreeNode | null = null;
private hierarchyUserOptions: UserOptions = PersistentStoreProxy.new<UserOptions>(
'WmHierarchyOptions',
{
showDiff: {
name: 'Show diff',
enabled: false,
},
simplifyNames: {
name: 'Simplify names',
enabled: true,
},
onlyVisible: {
name: 'Only visible',
enabled: false,
},
flat: {
name: 'Flat',
enabled: false,
},
},
this.storage
);
private propertiesUserOptions: UserOptions = PersistentStoreProxy.new<UserOptions>(
'WmPropertyOptions',
{
showDiff: {
name: 'Show diff',
enabled: false,
},
showDefaults: {
name: 'Show defaults',
enabled: false,
tooltip: `
If checked, shows the value of all properties.
Otherwise, hides all properties whose value is
the default for its data type.
`,
},
},
this.storage
);
}

View File

@@ -13,10 +13,16 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import {HierarchyTreeBuilder} from 'test/unit/hierarchy_tree_builder';
import {MockStorage} from 'test/unit/mock_storage';
import {TraceBuilder} from 'test/unit/trace_builder';
import {UnitTestUtils} from 'test/unit/utils';
import {WindowManagerState} from 'trace/flickerlib/common';
import {RealTimestamp} from 'trace/timestamp';
import {Trace} from 'trace/trace';
import {Traces} from 'trace/traces';
import {TracePosition} from 'trace/trace_position';
import {TraceType} from 'trace/trace_type';
import {VISIBLE_CHIP} from 'viewers/common/chip';
import {HierarchyTreeNode, PropertiesTreeNode} from 'viewers/common/ui_tree_utils';
@@ -25,14 +31,18 @@ import {Presenter} from './presenter';
import {UiData} from './ui_data';
describe('PresenterWindowManager', () => {
let trace: Trace<WindowManagerState>;
let position: TracePosition;
let presenter: Presenter;
let uiData: UiData;
let entries: Map<TraceType, any>;
let selectedTree: HierarchyTreeNode;
beforeAll(async () => {
entries = new Map<TraceType, any>();
const entry: WindowManagerState = await UnitTestUtils.getWindowManagerState();
trace = new TraceBuilder<WindowManagerState>()
.setEntries([await UnitTestUtils.getWindowManagerState()])
.build();
position = TracePosition.fromTraceEntry(trace.getEntry(0));
selectedTree = new HierarchyTreeBuilder()
.setName('ScreenDecorOverlayBottom')
@@ -45,18 +55,26 @@ describe('PresenterWindowManager', () => {
.setIsVisible(true)
.setChips([VISIBLE_CHIP])
.build();
entries.set(TraceType.WINDOW_MANAGER, [entry, null]);
});
beforeEach(async () => {
presenter = new Presenter((newData: UiData) => {
uiData = newData;
}, new MockStorage());
presenter = createPresenter(trace);
});
it('processes current trace entries', () => {
presenter.notifyCurrentTraceEntries(entries);
it('is robust to empty trace', () => {
const emptyTrace = new TraceBuilder<WindowManagerState>().setEntries([]).build();
const presenter = createPresenter(emptyTrace);
expect(uiData.hierarchyUserOptions).toBeTruthy();
expect(uiData.tree).toBeFalsy();
const positionWithoutTraceEntry = TracePosition.fromTimestamp(new RealTimestamp(0n));
presenter.onTracePositionUpdate(positionWithoutTraceEntry);
expect(uiData.hierarchyUserOptions).toBeTruthy();
expect(uiData.tree).toBeFalsy();
});
it('processes trace position update', () => {
presenter.onTracePositionUpdate(position);
const filteredUiDataRectLabels = uiData.rects
?.filter((rect) => rect.isVisible !== undefined)
.map((rect) => rect.label);
@@ -76,23 +94,15 @@ describe('PresenterWindowManager', () => {
expect(Object.keys(uiData.tree!).length > 0).toBeTrue();
});
it('handles unavailable trace entry', () => {
presenter.notifyCurrentTraceEntries(entries);
expect(Object.keys(uiData.tree!).length > 0).toBeTrue();
const emptyEntries = new Map<TraceType, any>();
presenter.notifyCurrentTraceEntries(emptyEntries);
expect(uiData.tree).toBeFalsy();
});
it('creates input data for rects view', () => {
presenter.notifyCurrentTraceEntries(entries);
presenter.onTracePositionUpdate(position);
expect(uiData.rects.length).toBeGreaterThan(0);
expect(uiData.rects[0].topLeft).toEqual({x: 0, y: 2326});
expect(uiData.rects[0].bottomRight).toEqual({x: 1080, y: 2400});
});
it('updates pinned items', () => {
presenter.notifyCurrentTraceEntries(entries);
presenter.onTracePositionUpdate(position);
expect(uiData.pinnedItems).toEqual([]);
const pinnedItem = new HierarchyTreeBuilder()
@@ -133,7 +143,7 @@ describe('PresenterWindowManager', () => {
},
};
presenter.notifyCurrentTraceEntries(entries);
presenter.onTracePositionUpdate(position);
expect(uiData.tree?.children.length).toEqual(1);
presenter.updateHierarchyTree(userOptions);
expect(uiData.hierarchyUserOptions).toEqual(userOptions);
@@ -160,7 +170,7 @@ describe('PresenterWindowManager', () => {
enabled: true,
},
};
presenter.notifyCurrentTraceEntries(entries);
presenter.onTracePositionUpdate(position);
presenter.updateHierarchyTree(userOptions);
expect(uiData.tree?.children.length).toEqual(72);
presenter.filterHierarchyTree('ScreenDecor');
@@ -169,7 +179,7 @@ describe('PresenterWindowManager', () => {
});
it('sets properties tree and associated ui data', () => {
presenter.notifyCurrentTraceEntries(entries);
presenter.onTracePositionUpdate(position);
presenter.newPropertiesTree(selectedTree);
// does not check specific tree values as tree transformation method may change
expect(uiData.propertiesTree).toBeTruthy();
@@ -193,7 +203,7 @@ describe('PresenterWindowManager', () => {
},
};
presenter.notifyCurrentTraceEntries(entries);
presenter.onTracePositionUpdate(position);
presenter.newPropertiesTree(selectedTree);
expect(uiData.propertiesTree?.diffType).toBeFalsy();
presenter.updatePropertiesTree(userOptions);
@@ -203,7 +213,7 @@ describe('PresenterWindowManager', () => {
});
it('filters properties tree', () => {
presenter.notifyCurrentTraceEntries(entries);
presenter.onTracePositionUpdate(position);
presenter.newPropertiesTree(selectedTree);
let nonTerminalChildren =
@@ -220,4 +230,12 @@ describe('PresenterWindowManager', () => {
) ?? [];
expect(nonTerminalChildren.length).toEqual(1);
});
const createPresenter = (trace: Trace<WindowManagerState>): Presenter => {
const traces = new Traces();
traces.setTrace(TraceType.WINDOW_MANAGER, trace);
return new Presenter(traces, new MockStorage(), (newData: UiData) => {
uiData = newData;
});
};
});

View File

@@ -13,7 +13,9 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import {MockStorage} from 'test/unit/mock_storage';
import {Traces} from 'trace/traces';
import {TracePosition} from 'trace/trace_position';
import {TraceType} from 'trace/trace_type';
import {ViewerEvents} from 'viewers/common/viewer_events';
import {View, Viewer, ViewType} from 'viewers/viewer';
@@ -21,15 +23,11 @@ import {Presenter} from './presenter';
import {UiData} from './ui_data';
class ViewerWindowManager implements Viewer {
constructor() {
constructor(traces: Traces, storage: Storage) {
this.htmlElement = document.createElement('viewer-window-manager');
this.presenter = new Presenter((uiData: UiData) => {
// Angular does not deep watch @Input properties. Clearing inputData to null before repopulating
// automatically ensures that the UI will change via the Angular change detection cycle. Without
// resetting, Angular does not auto-detect that inputData has changed.
(this.htmlElement as any).inputData = null;
this.presenter = new Presenter(traces, storage, (uiData: UiData) => {
(this.htmlElement as any).inputData = uiData;
}, new MockStorage());
});
this.htmlElement.addEventListener(ViewerEvents.HierarchyPinnedChange, (event) =>
this.presenter.updatePinnedItems((event as CustomEvent).detail.pinnedItem)
);
@@ -53,8 +51,8 @@ class ViewerWindowManager implements Viewer {
);
}
notifyCurrentTraceEntries(entries: Map<TraceType, any>): void {
this.presenter.notifyCurrentTraceEntries(entries);
onTracePositionUpdate(position: TracePosition) {
this.presenter.onTracePositionUpdate(position);
}
getViews(): View[] {