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:
@@ -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
|
||||
)!;
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
);
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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[];
|
||||
}
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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];
|
||||
|
||||
@@ -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
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -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];
|
||||
|
||||
@@ -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 = '';
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -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[] {
|
||||
|
||||
@@ -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};
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
});
|
||||
};
|
||||
});
|
||||
|
||||
@@ -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[] {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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));
|
||||
};
|
||||
});
|
||||
|
||||
@@ -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[] {
|
||||
|
||||
@@ -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
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
});
|
||||
};
|
||||
});
|
||||
|
||||
@@ -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[] {
|
||||
|
||||
Reference in New Issue
Block a user