Refactor WM transformations to use flickerlib object representation

Test: Run winscope and make sure there are no errors and everything seems to still work...
Change-Id: I1aef5c469d4323af502580e03a310d76da739d4d
This commit is contained in:
Pablo Gamito
2020-07-17 12:36:18 +01:00
parent c98e7e7ba5
commit 335b3e14a5
35 changed files with 1359 additions and 527 deletions

View File

@@ -27,6 +27,7 @@
"@babel/preset-env": "^7.10.4",
"@babel/register": "^7.10.5",
"@jetbrains/kotlin-webpack-plugin": "^3.0.2",
"@types/lodash": "^4.14.158",
"babel-loader": "^8.1.0",
"compression-webpack-plugin": "^4.0.0",
"cross-env": "^7.0.2",
@@ -47,6 +48,8 @@
"protobufjs": "^6.10.0",
"source-map-loader": "^1.0.1",
"style-loader": "^1.2.1",
"ts-loader": "^8.0.1",
"typescript": "^3.9.7",
"uglifyjs-webpack-plugin": "^2.2.0",
"vue-loader": "^15.9.3",
"vue-style-loader": "^4.1.2",

View File

@@ -35,6 +35,13 @@
:title="c.long"
:class="chipClassForChip(c)"
>{{c.short}} <!-- No line break on purpose -->
<md-tooltip
md-delay="300"
md-direction="top"
style="margin-bottom: -10px"
>
{{c.long}}
</md-tooltip>
</div>
</span>
</template>

View File

@@ -15,7 +15,7 @@
<template>
<div class="bounds" :style="boundsStyle">
<div
class="rect" v-for="r in rects"
class="rect" v-for="r in filteredRects"
:style="rectToStyle(r)"
@click="onClick(r)"
v-bind:key="`${r.left}-${r.right}-${r.top}-${r.bottom}-${r.ref.name}`"
@@ -55,6 +55,9 @@ export default {
return this.rectToStyle({top: 0, left: 0, right: this.boundsC.width,
bottom: this.boundsC.height});
},
filteredRects() {
return this.rects.filter((rect) => rect.ref.visible);
},
},
methods: {
s(sourceCoordinate) { // translate source into target coordinates

View File

@@ -209,7 +209,7 @@ export default {
itemSelected(item) {
this.hierarchySelected = item;
this.selectedTree = this.getTransformedProperties(item);
this.highlight = item.highlight;
this.highlight = item.rect;
this.lastSelectedStableId = item.stableId;
this.$emit('focus');
},

View File

@@ -26,7 +26,6 @@ import jsonProtoDefsSysUi from 'frameworks/base/packages/SystemUI/src/com/androi
import jsonProtoDefsLauncher from 'packages/apps/Launcher3/protos/launcher_trace_file.proto';
import protobuf from 'protobufjs';
import {transformLayers, transformLayersTrace} from './transform_sf.js';
import {transform_window_service, transform_window_trace} from './transform_wm.js';
import {transform_transaction_trace} from './transform_transaction.js';
import {transform_wl_outputstate, transform_wayland_trace} from './transform_wl.js';
import {transformProtolog} from './transform_protolog.js';
@@ -211,7 +210,7 @@ const FILE_DECODERS = {
decoderParams: {
type: FILE_TYPES.WINDOW_MANAGER_TRACE,
protoType: WmTraceMessage,
transform: transform_window_trace,
transform: WindowManagerTrace.fromProto,
timeline: true,
},
},
@@ -255,7 +254,7 @@ const FILE_DECODERS = {
type: FILE_TYPES.WINDOW_MANAGER_DUMP,
mime: 'application/octet-stream',
protoType: WmDumpMessage,
transform: transform_window_service,
transform: WindowManagerDump.fromProto,
timeline: false,
},
},
@@ -380,7 +379,7 @@ function protoDecoder(buffer, params, fileName, store) {
const transformed = decodeAndTransformProto(buffer, params, store.displayDefaults);
let data;
if (params.timeline) {
data = transformed.children;
data = transformed.entries ?? transformed.children;
} else {
data = [transformed];
}

View File

@@ -17,6 +17,8 @@
import { FILE_TYPES, DUMP_TYPES } from "@/decode.js";
import DumpBase from "./DumpBase";
import { WindowManagerTraceEntry } from '@/flickerlib';
export default class WindowManager extends DumpBase {
wmDumpFile: any;
@@ -29,4 +31,8 @@ export default class WindowManager extends DumpBase {
get type() {
return DUMP_TYPES.WINDOW_MANAGER;
}
static fromProto(proto): WindowManagerTraceEntry {
return WindowManagerTraceEntry.fromProto(proto);
}
}

View File

@@ -0,0 +1,12 @@
This directory contains all the code extending the common Flicker library
to make it fully compatible with Winscope. The common Flicker library is
written is Kotlin and compiled to JavaScript and then extended by the code in
this directory.
To use flickerlib in the rest of the Winscope source code use
`import { ... } from '@/flickerlib'` rather than importing the compiled
common Flicker library directly.
The flickerlib classes are extended through mixins (functions, getter, and
setters) that are injected into the original compiled common Flicker library
classes.

View File

@@ -0,0 +1,36 @@
/*
* Copyright 2020, The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import {
WindowManagerTrace,
} from "./common"
import WindowManagerTraceEntry from "./WindowManagerTraceEntry"
WindowManagerTrace.fromProto = function (proto) {
const entries = []
for (const entryProto of proto.entry) {
const transformedEntry = WindowManagerTraceEntry.fromProto(
entryProto.windowManagerService, entryProto.elapsedRealtimeNanos)
entries.push(transformedEntry)
}
const source = null;
const sourceChecksum = null;
return new WindowManagerTrace(entries, source, sourceChecksum)
}
export default WindowManagerTrace;

View File

@@ -0,0 +1,113 @@
/*
* Copyright 2020, The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { nanosToString, TimeUnits } from "../utils/utils.js"
import { WindowManagerTraceEntry } from "./common"
import { applyMixins } from "./mixin"
import ITreeViewElement from './treeview/IClickableTreeViewElement'
import IClickableTreeViewElement from './treeview/IClickableTreeViewElement'
import Chip from './treeview/Chip'
import WindowContainer from "./windows/WindowContainer"
class WindowManagerTraceEntryMixin implements IClickableTreeViewElement {
common: any
kind: string
name: string
shortName: string
stableId: string
chips: Array<Chip>
children: Array<ITreeViewElement>
obj: any
rawTreeViewObject
timestamp: number
rootWindow
mixinConstructor(obj) {
const name = this.timestamp ? nanosToString(this.timestamp, TimeUnits.MILLI_SECONDS) : ""
this.kind = "entry"
this.name = name
this.shortName = name
this.stableId = "entry"
this.children = this.rootWindow.children
this.chips = []
this.obj = obj
}
static fromProto(proto, timestamp) {
const rootWindow =
WindowContainer.fromProto(proto.rootWindowContainer.windowContainer)
const windowManagerTraceEntry =
new WindowManagerTraceEntry(rootWindow, timestamp)
windowManagerTraceEntry.kind = 'service'
windowManagerTraceEntry.focusedApp = proto.focusedApp
windowManagerTraceEntry.focusedDisplayId = proto.focusedDisplayId
windowManagerTraceEntry.lastOrientation = proto.lastOrientation
windowManagerTraceEntry.policy = proto.policy
windowManagerTraceEntry.rotation = proto.rotation
windowManagerTraceEntry.displayFrozen = proto.displayFrozen
windowManagerTraceEntry.inputMethodWindow = proto.inputMethodWindow
// Remove anything that is part of the children elements
// allows for faster loading of properties and less information cluttering
// this applied to anywhere the proto is passed to be saved as .obj
const obj = Object.assign({}, proto)
obj.rootWindowContainer = {};
Object.assign(obj.rootWindowContainer,
proto.rootWindowContainer)
obj.rootWindowContainer.windowContainer = {};
Object.assign(obj.rootWindowContainer.windowContainer,
proto.rootWindowContainer.windowContainer)
delete obj.rootWindowContainer.windowContainer.children
windowManagerTraceEntry.mixinConstructor(obj)
return windowManagerTraceEntry
}
attachObject(obj) {
this.obj = obj
}
asRawTreeViewObject() {
// IMPORTANT: We want to always return the same tree view object and not
// generate a new one every time this function is called.
if (!this.rawTreeViewObject) {
const children = this.children.map(child => child.asRawTreeViewObject())
this.rawTreeViewObject = {
kind: this.kind,
name: this.name,
shortName: this.shortName,
stableId: this.stableId,
chips: this.chips,
obj: this.obj,
children,
ref: this,
}
}
return this.rawTreeViewObject;
}
}
applyMixins(WindowManagerTraceEntry, [WindowManagerTraceEntryMixin])
export default WindowManagerTraceEntry;

View File

@@ -0,0 +1,58 @@
/*
* Copyright 2020, The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
// Imports all the compiled common Flicker library classes and exports them
// as clean es6 modules rather than having them be commonjs modules
const WindowManagerTrace = require('flicker').com.android.server.wm.flicker
.common.traces.windowmanager.WindowManagerTrace;
const WindowManagerTraceEntry = require('flicker').com.android.server.wm.
flicker.common.traces.windowmanager.WindowManagerTraceEntry;
const WindowContainer = require('flicker').com.android.server.wm.flicker.common.
traces.windowmanager.windows.WindowContainer;
const WindowState = require('flicker').com.android.server.wm.flicker.common.
traces.windowmanager.windows.WindowState;
const DisplayContent = require('flicker').com.android.server.wm.flicker.common.
traces.windowmanager.windows.DisplayContent;
const ActivityRecord = require('flicker').com.android.server.wm.flicker.common.
traces.windowmanager.windows.ActivityRecord;
const WindowToken = require('flicker').com.android.server.wm.flicker.common.
traces.windowmanager.windows.WindowToken;
const DisplayArea = require('flicker').com.android.server.wm.flicker.common.
traces.windowmanager.windows.DisplayArea;
const RootDisplayArea = require('flicker').com.android.server.wm.flicker.common.
traces.windowmanager.windows.RootDisplayArea;
const Task = require('flicker').com.android.server.wm.flicker.common.traces.
windowmanager.windows.Task;
const Rect = require('flicker').com.android.server.wm.flicker.common.Rect;
const Bounds = require('flicker').com.android.server.wm.flicker.common.Bounds;
export {
WindowManagerTrace,
WindowManagerTraceEntry,
WindowContainer,
WindowState,
DisplayContent,
ActivityRecord,
WindowToken,
DisplayArea,
RootDisplayArea,
Task,
Rect,
Bounds,
};

View File

@@ -0,0 +1,25 @@
/*
* Copyright 2020, The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import WindowManagerTraceEntry from './WindowManagerTraceEntry';
import WindowManagerTrace from './WindowManagerTrace';
/**
* Entry point into the flickerlib for Winscope.
* Expose everything we want Winscope to have access to here.
*/
export {WindowManagerTraceEntry, WindowManagerTrace};

View File

@@ -0,0 +1,23 @@
/**
* Injects all the properties (getters, setters, functions...) of a list of
* classes (baseCtors) into a class (derivedCtor).
* @param derivedCtor The constructor of the class we want to inject the
* properties into.
* @param baseCtors A list of consturctor of the classes we want to mixin the
* properties of into the derivedCtor.
*/
export function applyMixins(derivedCtor: any, baseCtors: any[]) {
baseCtors.forEach(baseCtor => {
Object.getOwnPropertyNames(baseCtor).forEach(name => {
if (['length', 'name', 'prototype'].includes(name)) {
return;
}
Object.defineProperty(derivedCtor, name, Object.getOwnPropertyDescriptor(baseCtor, name))
})
Object.getOwnPropertyNames(baseCtor.prototype).forEach(name => {
Object.defineProperty(derivedCtor.prototype, name, Object.getOwnPropertyDescriptor(baseCtor.prototype, name))
})
});
}

View File

@@ -0,0 +1,29 @@
/*
* Copyright 2020, The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import ChipType from "./ChipType"
export default class Chip {
short: String
long: String
type: ChipType
constructor(short: String, long: String, type: ChipType) {
this.short = short
this.long = long
this.type = type
}
}

View File

@@ -0,0 +1,21 @@
/*
* Copyright 2020, The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
enum ChipType {
DEFAULT = 'default'
}
export default ChipType

View File

@@ -0,0 +1,20 @@
/*
* Copyright 2020, The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import Chip from "./Chip"
import ChipType from "./ChipType"
export const VISIBLE_CHIP = new Chip("V", "visible", ChipType.DEFAULT)

View File

@@ -0,0 +1,21 @@
/*
* Copyright 2020, The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import ITreeViewElement from "./ITreeViewElement"
export default interface IClickableTreeViewElement extends ITreeViewElement {
obj: any
}

View File

@@ -0,0 +1,34 @@
/*
* Copyright 2020, The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import Chip from "./Chip"
import { TreeViewObject } from "./types"
export default interface ITreeViewElement {
kind: String
name: String
shortName: String
stableId: Number | String
chips: Chip[]
children: ITreeViewElement[]
// This is used for compatibility with the "legacy" Winscope infrastructure
// where a class object would cause things to not work properly so this should
// return a raw javascript object with the relevant information.
// IMPORTANT: The implementation of this function should always return the
// same object every time it is called and not generate a new object.
asRawTreeViewObject(): TreeViewObject
}

View File

@@ -0,0 +1,12 @@
import Chip from './Chip'
export type TreeViewObject = {
kind: String
name: String
shortName: String
stableId: String | Number
chips: Chip[]
obj: any
children: TreeViewObject[]
ref: any
}

View File

@@ -0,0 +1,46 @@
/*
* Copyright 2020, The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import {
ActivityRecord,
} from "../common"
import { applyMixins } from '../mixin'
import WindowToken from "./WindowToken"
export class ActivityRecordMixin {
get kind() {
return "DisplayArea"
}
static fromProto(proto) {
const windowToken = WindowToken.fromProto(proto.windowToken)
const activityRecord = new ActivityRecord(windowToken)
const obj = Object.assign({}, proto)
delete proto.windowToken
Object.assign(obj, windowToken.obj)
activityRecord.attachObject(obj)
return activityRecord
}
}
applyMixins(ActivityRecord, [ActivityRecordMixin])
export default ActivityRecord

View File

@@ -0,0 +1,46 @@
/*
* Copyright 2020, The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import {
DisplayArea,
} from "../common"
import { applyMixins } from '../mixin'
import WindowContainer from "./WindowContainer"
export class DisplayAreaMixin {
get kind() {
return "DisplayArea"
}
static fromProto(proto) {
const windowContainer = WindowContainer.fromProto(proto.windowContainer)
const displayArea = new DisplayArea(windowContainer)
const obj = Object.assign({}, proto)
delete obj.windowContainer
Object.assign(obj, windowContainer.obj)
displayArea.attachObject(obj)
return displayArea
}
}
applyMixins(DisplayArea, [DisplayAreaMixin])
export default DisplayArea

View File

@@ -0,0 +1,62 @@
/*
* Copyright 2020, The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import {
DisplayContent,
Bounds
} from "../common"
import { applyMixins } from '../mixin'
import WindowContainer from "./WindowContainer"
import DisplayArea from "./DisplayArea"
export class DisplayContentMixin {
get kind() {
return "DisplayContent"
}
static fromProto(proto) {
let rootDisplayArea;
if (proto.rootDisplayArea.windowContainer == null) {
// For backward compatibility
const windowContainer = WindowContainer.fromProto(proto.windowContainer)
rootDisplayArea = new DisplayArea(windowContainer)
} else {
// New protos should always be using this
rootDisplayArea = DisplayArea.fromProto(proto.rootDisplayArea)
}
const bounds = new Bounds(
proto.displayInfo.logicalWidth || 0,
proto.displayInfo.logicalHeight || 0,
)
const displayContent = new DisplayContent(rootDisplayArea, bounds)
const obj = Object.assign({}, proto)
delete obj.windowContainer
delete obj.rootDisplayArea
Object.assign(obj, rootDisplayArea.obj)
displayContent.attachObject(obj)
return displayContent
}
}
applyMixins(DisplayContent, [DisplayContentMixin])
export default DisplayContent

View File

@@ -0,0 +1,22 @@
/*
* Copyright 2020, The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { RootDisplayArea } from "../common"
import DisplayArea from "./DisplayArea"
RootDisplayArea.fromProto = DisplayArea.fromProto
export default DisplayArea

View File

@@ -0,0 +1,44 @@
/*
* Copyright 2020, The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { Task } from "../common"
import { applyMixins } from '../mixin'
import WindowContainer from "./WindowContainer"
export class TaskMixin {
get kind() {
return "Task"
}
static fromProto(proto) {
const windowContainer = WindowContainer.fromProto(proto.windowContainer)
const task = new Task(windowContainer)
const obj = Object.assign({}, proto)
delete obj.windowContainer
Object.assign(obj, windowContainer.obj)
task.attachObject(obj)
return task
}
}
applyMixins(Task, [TaskMixin])
export default Task

View File

@@ -0,0 +1,204 @@
/*
* Copyright 2020, The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { WindowContainer } from "../common"
import { applyMixins } from '../mixin'
import IClickableTreeViewElement from '../treeview/IClickableTreeViewElement'
import { TreeViewObject } from '../treeview/types'
import DisplayArea from "./DisplayArea"
import DisplayContent from "./DisplayContent"
import Task from "./Task"
import ActivityRecord from "./ActivityRecord"
import WindowToken from "./WindowToken"
import WindowState from "./WindowState"
import { CompatibleFeatures } from '@/utils/compatibility.js'
export class WindowContainerMixin implements IClickableTreeViewElement {
title: string
windowHashCode: number
childrenWindows
visible: boolean
rawTreeViewObject
_obj: { name: string }
_children
get kind() {
return "WindowContainer"
}
get name() {
if (this.obj && this.obj.name) {
return this.obj.name
}
if (["WindowContainer", "Task"].includes(this.title)) {
return null
}
return `${removeRedundancyInName(this.title)}@${this.windowHashCode}`
}
get shortName() {
return this.name ? shortenName(this.name) : null
}
get stableId() {
return this.windowHashCode
}
get children() {
return this._children ?? this.childrenWindows
}
set children(children) {
this._children = children
}
get chips() {
return []
}
set obj(obj) {
this._obj = obj
}
get obj() {
return this._obj
}
static fromProto(proto) {
const children = proto.children.map(
child => transformWindowContainerChildProto(child))
// A kind of hacky way to check if the proto is from a device which
// didn't have the changes required for the diff viewer to work properly
// We required that all elements have a stable identifier in order to do the
// diff properly. In theory properties diff would still work, but are
// currently disabled. If required in the future don't hesitate to make
// the required changes.
if (!proto.identifier) {
CompatibleFeatures.DiffVisualization = false;
}
const title = proto.identifier.title
const hashCode = proto.identifier.hashCode
const visible = proto.visible
const windowContainer =
new WindowContainer(children, title, hashCode, visible)
const obj = Object.assign({}, proto)
// we remove the children property from the object to avoid it showing the
// the properties view of the element as we can always see those elements'
// properties by changing the target element in the hierarchy tree view.
delete obj.children
windowContainer.attachObject(obj)
return windowContainer
}
attachObject(obj) {
this._obj = obj
}
asRawTreeViewObject(): TreeViewObject {
// IMPORTANT: We want to always return the same tree view object and not
// generate a new one every time this function is called.
if (!this.rawTreeViewObject) {
const children = this.children.map(child => child.asRawTreeViewObject())
this.rawTreeViewObject = {
kind: this.kind,
name: this.name,
shortName: this.shortName,
stableId: this.stableId,
chips: this.chips,
obj: this.obj,
children,
ref: this,
};
}
return this.rawTreeViewObject;
}
}
applyMixins(WindowContainer, [WindowContainerMixin])
function transformWindowContainerChildProto(proto) {
if (proto.displayArea != null) {
return DisplayArea.fromProto(proto.displayArea)
}
if (proto.displayContent != null) {
return DisplayContent.fromProto(proto.displayContent)
}
if (proto.task != null) {
return Task.fromProto(proto.task)
}
if (proto.activity != null) {
return ActivityRecord.fromProto(proto.activity)
}
if (proto.windowToken != null) {
return WindowToken.fromProto(proto.windowToken)
}
if (proto.window != null) {
return WindowState.fromProto(proto.window)
}
throw new Error("Unhandled WindowContainerChildProto case...")
}
function removeRedundancyInName(name: string): string {
if (!name.includes('/')) {
return name
}
const split = name.split('/')
const pkg = split[0]
var clazz = split.slice(1).join("/")
if (clazz.startsWith("$pkg.")) {
clazz = clazz.slice(pkg.length + 1)
return "$pkg/$clazz"
}
return name
}
function shortenName(name: string): string {
const classParts = name.split(".")
if (classParts.length <= 3) {
return name
}
const className = classParts.slice(-1)[0] // last element
return `${classParts[0]}.${classParts[1]}.(...).${className}`
}
export default WindowContainer

View File

@@ -0,0 +1,55 @@
/*
* Copyright 2020, The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { WindowState, Rect } from "../common"
import { VISIBLE_CHIP } from '../treeview/Chips'
import { applyMixins } from '../mixin'
import WindowContainer from "./WindowContainer"
export class WindowStateMixin {
visible: boolean
get kind() {
return "WindowState"
}
static fromProto(proto) {
const windowContainer = WindowContainer.fromProto(proto.windowContainer)
const frame = (proto.windowFrames ?? proto).frame ?? {}
const rect = new Rect(frame.left ?? 0, frame.top ?? 0, frame.right ?? 0, frame.bottom ?? 0)
const windowState =
new WindowState(windowContainer, /* childWindows */[], rect)
const obj = Object.assign({}, proto)
Object.assign(obj, windowContainer.obj)
delete obj.windowContainer
windowState.attachObject(obj)
return windowState
}
get chips() {
return this.visible ? [VISIBLE_CHIP] : []
}
}
applyMixins(WindowState, [WindowStateMixin])
export default WindowState

View File

@@ -0,0 +1,44 @@
/*
* Copyright 2020, The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { WindowToken } from "../common"
import { applyMixins } from '../mixin'
import WindowContainer from "./WindowContainer"
export class WindowContainerMixin {
get kind() {
return "WindowToken"
}
static fromProto(proto) {
const windowContainer = WindowContainer.fromProto(proto.windowContainer)
const windowToken = new WindowToken(windowContainer)
const obj = Object.assign({}, proto)
Object.assign(obj, windowContainer.obj)
delete obj.windowContainer
windowToken.attachObject(obj)
return windowToken
}
}
applyMixins(WindowToken, [WindowContainerMixin])
export default WindowToken

View File

@@ -17,6 +17,8 @@
import { FILE_TYPES, TRACE_TYPES } from '@/decode.js';
import TraceBase from './TraceBase';
import { WindowManagerTrace } from '@/flickerlib';
export default class WindowManager extends TraceBase {
wmTraceFile: Object;
@@ -30,4 +32,8 @@ export default class WindowManager extends TraceBase {
get type() {
return TRACE_TYPES.WINDOW_MANAGER;
}
static fromProto(proto): WindowManagerTrace {
return WindowManagerTrace.fromProto(proto);
}
}

View File

@@ -1,276 +0,0 @@
/*
* Copyright 2017, The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { transform, nanos_to_string, get_visible_chip } from './transform.js'
import { CompatibleFeatures } from './utils/compatibility.js'
import { getComponentClassName } from './utils/names';
function transform_window(entry) {
const chips = [];
const renderIdentifier = (id) => shortenComponentName(id.title) + "@" + id.hashCode;
const visible = entry.windowContainer.visible;
function transform_rect(rect, label) {
const r = rect || {};
return {
left: r.left || 0,
right: r.right || 0,
top: r.top || 0,
bottom: r.bottom || 0,
label,
}
}
// For backward compatibility
if (!entry.windowContainer?.identifier) {
CompatibleFeatures.DiffVisualization = false;
}
const identifier = entry.windowContainer?.identifier ?? entry.identifier;
const name = renderIdentifier(identifier);
const shortName = getComponentClassName(identifier.title);
let rect = transform_rect((entry.windowFrames || entry).frame, name);
if (visible) {
chips.push(get_visible_chip());
} else {
rect = undefined;
}
return transform({
obj: entry,
kind: 'window',
name,
shortName,
stableId: entry.windowContainer?.identifier?.hashCode,
children: [
[entry.childWindows, transform_window],
[entry.windowContainer.children.reverse(), transform_window_container_child],
],
rect,
highlight: rect,
chips: chips,
visible: visible,
});
}
function transform_activity_record(entry) {
return transform({
obj: entry,
kind: 'activityRecord',
name: entry.name,
shortName: getComponentClassName(entry.name),
stableId: entry.windowToken.windowContainer?.identifier?.hashCode,
children: [
[entry.windowToken.windows, transform_window],
[entry.windowToken.windowContainer.children.reverse(), transform_window_container_child],
],
});
}
function transform_task(entry) {
return transform({
obj: entry,
kind: 'task',
name: entry.id || 0,
stableId: entry.windowContainer?.identifier?.hashCode,
children: [
[entry.tasks, transform_task],
[entry.activities, transform_activity_record],
[entry.windowContainer.children.reverse(), transform_window_container_child],
],
});
}
function transform_stack(entry) {
return transform({
obj: entry,
kind: 'stack',
name: entry.id || 0,
children: [
[entry.tasks, transform_task],
],
});
}
function transform_window_token(entry) {
return transform({
obj: entry,
kind: 'winToken',
name: '',
stableId: entry.windowContainer?.identifier?.hashCode,
children: [
[entry.windows, transform_window],
[entry.windowContainer.children.reverse(), transform_window_container_child],
],
});
}
function transform_below(entry) {
return transform({
obj: entry,
kind: 'belowAppWindow',
name: '',
children: [
[entry.windows, transform_window],
],
});
}
function transform_above(entry) {
return transform({
obj: entry,
kind: 'aboveAppWindow',
name: '',
children: [
[entry.windows, transform_window],
],
});
}
function transform_ime(entry) {
return transform({
obj: entry,
kind: 'imeWindow',
name: '',
children: [
[entry.windows, transform_window],
],
});
}
function transform_window_container_child(entry) {
if (entry.displayArea != null) { return transform_display_area(entry.displayArea) }
if (entry.displayContent != null) { return transform_display_content(entry.displayContent) }
if (entry.task != null) { return transform_task(entry.task) }
if (entry.activity != null) { return transform_activity_record(entry.activity) }
if (entry.windowToken != null) { return transform_window_token(entry.windowToken) }
if (entry.window != null) { return transform_window(entry.window) }
// The WindowContainerChild may be unknown
return transform({
obj: entry,
kind: 'WindowContainerChild',
name: '',
stableId: entry.windowContainer?.identifier?.hashCode,
children: [[entry.windowContainer.children.reverse(), transform_window_container_child],]
});
}
function transform_display_area(entry) {
return transform({
obj: entry,
kind: 'DisplayArea',
name: entry.name,
stableId: entry.windowContainer?.identifier?.hashCode,
children: [
[entry.windowContainer.children.reverse(), transform_window_container_child],
],
});
}
function transform_display_content(entry) {
var bounds = {
width: entry.displayInfo.logicalWidth || 0,
height: entry.displayInfo.logicalHeight || 0,
};
var children = entry.windowContainer == null
? entry.rootDisplayArea.windowContainer.children.reverse()
: entry.windowContainer.children.reverse();
return transform({
obj: entry,
kind: 'display',
name: entry.id || 0,
stableId: entry.windowContainer?.identifier?.hashCode,
children: [
[entry.aboveAppWindows, transform_above],
[entry.imeWindows, transform_ime],
[entry.stacks, transform_stack],
[entry.tasks, transform_task],
[entry.belowAppWindows, transform_below],
[children, transform_window_container_child],
],
bounds,
});
}
function transform_policy(entry) {
return transform({
obj: entry,
kind: 'policy',
name: 'policy',
stableId: 'policy',
children: [],
});
}
function transform_window_service(entry) {
return transform({
obj: entry,
kind: 'service',
name: '',
children: [
[entry.rootWindowContainer.displays, transform_display_content],
[entry.rootWindowContainer.windowContainer.children.reverse(),
transform_window_container_child],
[[entry.policy], transform_policy],
],
timestamp: entry.elapsedRealtimeNanos,
});
}
function transform_entry(entry) {
return transform({
obj: entry,
kind: 'entry',
name: nanos_to_string(entry.elapsedRealtimeNanos),
children: [
[entry.windowManagerService.rootWindowContainer.displays, transform_display_content],
[entry.windowManagerService.rootWindowContainer.windowContainer.children.reverse(),
transform_window_container_child],
[[entry.windowManagerService.policy], transform_policy],
],
timestamp: entry.elapsedRealtimeNanos,
stableId: 'entry',
});
}
function transform_window_trace(entries) {
return transform({
obj: entries,
kind: 'entries',
name: 'entries',
children: [
[entries.entry, transform_entry],
],
});
}
function shortenComponentName(name) {
if (!name.includes('/')) {
return name
}
var split = name.split('/');
var pkg = split[0];
var clazz = split.slice(1).join('/');
if (clazz.startsWith(pkg + '.')) {
clazz = clazz.slice(pkg.length + 1);
return [pkg, clazz].join('/');
}
return name;
}
export { transform_window_service, transform_window_trace };

View File

@@ -1,3 +1,19 @@
/*
* Copyright 2020, The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
const CompatibleFeatures = {
DiffVisualization: true,
}

View File

@@ -1,3 +1,19 @@
/*
* Copyright 2020, The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* Should be kept in sync with ENUM is in Google3 under:
* google3/wireless/android/tools/android_bug_tool/extension/common/actions

View File

@@ -1,236 +1,263 @@
/*
* Copyright 2020, The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
// TODO (b/162300507): Get rid of cloning
import cloneDeep from 'lodash.clonedeep';
const DiffType = Object.freeze({
NONE: 'none',
ADDED: 'added',
DELETED: 'deleted',
ADDED_MOVE: 'addedMove',
DELETED_MOVE: 'deletedMove',
MODIFIED: 'modified',
export const DiffType = Object.freeze({
NONE: 'none',
ADDED: 'added',
DELETED: 'deleted',
ADDED_MOVE: 'addedMove',
DELETED_MOVE: 'deletedMove',
MODIFIED: 'modified',
});
function defaultModifiedCheck(newNode, oldNode) {
if (!newNode && !oldNode) {
return false;
}
export function defaultModifiedCheck(newNode, oldNode) {
if (!newNode && !oldNode) {
return false;
}
if ((newNode && !oldNode) || (!newNode && oldNode)) {
return true;
}
if ((newNode && !oldNode) || (!newNode && oldNode)) {
return true;
}
return JSON.stringify(newNode.obj) !== JSON.stringify(oldNode.obj);
return JSON.stringify(newNode.obj) !== JSON.stringify(oldNode.obj);
}
function isPrimitive(test) {
return test !== Object(test);
};
class DiffGenerator {
constructor(tree) {
this.tree = tree;
}
compareWith(tree) {
this.diffWithTree = tree;
return this;
}
withUniqueNodeId(getNodeId) {
this.getNodeId = node => {
const id = getNodeId(node);
if (id === null || id === undefined) {
console.error("Null node ID for node", node);
throw new Error("Node ID can't be null or undefined");
}
return id;
};
return this;
}
withModifiedCheck(isModified) {
this.isModified = isModified;
return this;
}
generateDiffTree() {
this.newMapping = this._generateIdToNodeMapping(this.tree);
this.oldMapping = this.diffWithTree ? this._generateIdToNodeMapping(this.diffWithTree) : {};
const diffTrees =
this._generateDiffTree(this.tree, this.diffWithTree, [], []);
let diffTree;
if (diffTrees.length > 1) {
diffTree = {
kind: "",
name: "DiffTree",
children: diffTrees,
};
} else {
diffTree = diffTrees[0];
}
return Object.freeze(diffTree);
}
_generateIdToNodeMapping(node, acc) {
acc = acc || {};
const nodeId = this.getNodeId(node);
if (acc[nodeId]) {
throw new Error(`Duplicate node id '${nodeId}' detected...`);
}
acc[this.getNodeId(node)] = node;
if (node.children) {
for (const child of node.children) {
this._generateIdToNodeMapping(child, acc);
}
}
return acc;
}
_objIsEmpty(obj) {
return Object.keys(obj).length === 0 && obj.constructor === Object;
}
_cloneNodeWithoutChildren(node) {
const clone = {};
for (const key in node) {
if (key !== 'children') {
const val = node[key];
clone[key] = isPrimitive(val) ? val : cloneDeep(val);
}
}
return clone;
}
_generateDiffTree(newTree, oldTree, newTreeSiblings, oldTreeSiblings) {
const diffTrees = [];
// NOTE: A null ID represents a non existent node.
const newId = newTree ? this.getNodeId(newTree) : null;
const oldId = oldTree ? this.getNodeId(oldTree) : null;
const newTreeSiblingIds = newTreeSiblings.map(this.getNodeId);
const oldTreeSiblingIds = oldTreeSiblings.map(this.getNodeId);
if (newTree) {
// Deep clone newTree omitting children field
// Clone is required because trees are frozen objects — we can't
// modify the original tree object. Also means there is no side effect.
const diffTree = this._cloneNodeWithoutChildren(newTree);
// Default to no changes
diffTree.diff = { type: DiffType.NONE };
if (newId !== oldId) {
// A move, addition, or deletion has occurred
let nextOldTree;
// Check if newTree has been added or moved
if (!oldTreeSiblingIds.includes(newId)) {
if (this.oldMapping[newId]) {
// Objected existed in old tree, the counter party
// DELETED_MOVE will be/has been flagged and added to the
// diffTree when visiting it in the oldTree.
diffTree.diff = { type: DiffType.ADDED_MOVE };
// TODO: Figure out if oldTree is ever visited now...
// Switch out oldTree for new one to compare against
nextOldTree = this.oldMapping[newId];
} else {
diffTree.diff = { type: DiffType.ADDED };
// TODO: Figure out if oldTree is ever visited now...
// Stop comparing against oldTree
nextOldTree = null;
}
}
// Check if oldTree has been deleted of moved
if (oldTree && !newTreeSiblingIds.includes(oldId)) {
const deletedTreeDiff = this._cloneNodeWithoutChildren(oldTree);
if (this.newMapping[oldId]) {
deletedTreeDiff.diff = { type: DiffType.DELETED_MOVE };
// Stop comparing against oldTree, will be/has been
// visited when object is seen in newTree
nextOldTree = null;
} else {
deletedTreeDiff.diff = { type: DiffType.DELETED };
// Visit all children to check if they have been deleted or moved
deletedTreeDiff.children = this._visitChildren(null, oldTree);
}
diffTrees.push(deletedTreeDiff);
}
oldTree = nextOldTree;
} else {
// TODO: Always have modified check and add modified tags on top of others
// NOTE: Doesn't check for moved and modified at the same time
if (this.isModified && this.isModified(newTree, oldTree)) {
diffTree.diff = { type: DiffType.MODIFIED };
}
}
diffTree.children = this._visitChildren(newTree, oldTree);
diffTrees.push(diffTree);
} else if (oldTree) {
if (!newTreeSiblingIds.includes(oldId)) {
// Deep clone oldTree omitting children field
const diffTree = this._cloneNodeWithoutChildren(oldTree);
// newTree doesn't exists, oldTree has either been moved or deleted.
if (this.newMapping[oldId]) {
diffTree.diff = { type: DiffType.DELETED_MOVE };
} else {
diffTree.diff = { type: DiffType.DELETED };
}
diffTree.children = this._visitChildren(null, oldTree);
diffTrees.push(diffTree);
}
} else {
throw new Error("Both newTree and oldTree are undefined...");
}
return diffTrees;
}
_visitChildren(newTree, oldTree) {
// Recursively traverse all children of new and old tree.
const diffChildren = [];
// TODO: Try replacing this with some sort of zipWith.
const numOfChildren = Math.max(
newTree?.children.length ?? 0, oldTree?.children.length ?? 0);
for (let i = 0; i < numOfChildren; i++) {
const newChild = i < newTree?.children.length ?
newTree.children[i] : null;
const oldChild = i < oldTree?.children.length ?
oldTree.children[i] : null;
const childDiffTrees = this._generateDiffTree(
newChild, oldChild,
newTree?.children ?? [], oldTree?.children ?? []
);
diffChildren.push(...childDiffTrees);
}
return diffChildren;
}
return test !== Object(test);
}
export { DiffGenerator, DiffType, defaultModifiedCheck }
export class DiffGenerator {
constructor(tree) {
if (tree.asRawTreeViewObject) {
this.tree = tree.asRawTreeViewObject();
} else {
this.tree = tree;
}
}
compareWith(tree) {
if (tree?.asRawTreeViewObject) {
this.diffWithTree = tree.asRawTreeViewObject();
} else {
this.diffWithTree = tree;
}
return this;
}
withUniqueNodeId(getNodeId) {
this.getNodeId = (node) => {
const id = getNodeId(node);
if (id === null || id === undefined) {
console.error('Null node ID for node', node);
throw new Error('Node ID can\'t be null or undefined');
}
return id;
};
return this;
}
withModifiedCheck(isModified) {
this.isModified = isModified;
return this;
}
generateDiffTree() {
this.newMapping = this._generateIdToNodeMapping(this.tree);
this.oldMapping = this.diffWithTree ?
this._generateIdToNodeMapping(this.diffWithTree) : {};
const diffTrees =
this._generateDiffTree(this.tree, this.diffWithTree, [], []);
let diffTree;
if (diffTrees.length > 1) {
diffTree = {
kind: '',
name: 'DiffTree',
children: diffTrees,
stableId: 'DiffTree',
};
} else {
diffTree = diffTrees[0];
}
return Object.freeze(diffTree);
}
_generateIdToNodeMapping(node, acc) {
acc = acc || {};
const nodeId = this.getNodeId(node);
if (acc[nodeId]) {
throw new Error(`Duplicate node id '${nodeId}' detected...`);
}
acc[this.getNodeId(node)] = node;
if (node.children) {
for (const child of node.children) {
this._generateIdToNodeMapping(child, acc);
}
}
return acc;
}
_objIsEmpty(obj) {
return Object.keys(obj).length === 0 && obj.constructor === Object;
}
_cloneNodeWithoutChildren(node) {
const clone = {};
for (const key in node) {
if (key !== 'children') {
const val = node[key];
clone[key] = isPrimitive(val) ? val : cloneDeep(val);
}
}
return clone;
}
_generateDiffTree(newTree, oldTree, newTreeSiblings, oldTreeSiblings) {
const diffTrees = [];
// NOTE: A null ID represents a non existent node.
const newId = newTree ? this.getNodeId(newTree) : null;
const oldId = oldTree ? this.getNodeId(oldTree) : null;
const newTreeSiblingIds = newTreeSiblings.map(this.getNodeId);
const oldTreeSiblingIds = oldTreeSiblings.map(this.getNodeId);
if (newTree) {
// Deep clone newTree omitting children field
// Clone is required because trees are frozen objects — we can't
// modify the original tree object. Also means there is no side effect.
const diffTree = this._cloneNodeWithoutChildren(newTree);
// Default to no changes
diffTree.diff = {type: DiffType.NONE};
if (newId !== oldId) {
// A move, addition, or deletion has occurred
let nextOldTree;
// Check if newTree has been added or moved
if (!oldTreeSiblingIds.includes(newId)) {
if (this.oldMapping[newId]) {
// Objected existed in old tree, the counter party
// DELETED_MOVE will be/has been flagged and added to the
// diffTree when visiting it in the oldTree.
diffTree.diff = {type: DiffType.ADDED_MOVE};
// TODO: Figure out if oldTree is ever visited now...
// Switch out oldTree for new one to compare against
nextOldTree = this.oldMapping[newId];
} else {
diffTree.diff = {type: DiffType.ADDED};
// TODO: Figure out if oldTree is ever visited now...
// Stop comparing against oldTree
nextOldTree = null;
}
}
// Check if oldTree has been deleted of moved
if (oldTree && !newTreeSiblingIds.includes(oldId)) {
const deletedTreeDiff = this._cloneNodeWithoutChildren(oldTree);
if (this.newMapping[oldId]) {
deletedTreeDiff.diff = {type: DiffType.DELETED_MOVE};
// Stop comparing against oldTree, will be/has been
// visited when object is seen in newTree
nextOldTree = null;
} else {
deletedTreeDiff.diff = {type: DiffType.DELETED};
// Visit all children to check if they have been deleted or moved
deletedTreeDiff.children = this._visitChildren(null, oldTree);
}
diffTrees.push(deletedTreeDiff);
}
oldTree = nextOldTree;
} else {
// TODO: Always have modified check and add modified tags on top of
// others
// NOTE: Doesn't check for moved and modified at the same time
if (this.isModified && this.isModified(newTree, oldTree)) {
diffTree.diff = {type: DiffType.MODIFIED};
}
}
diffTree.children = this._visitChildren(newTree, oldTree);
diffTrees.push(diffTree);
} else if (oldTree) {
if (!newTreeSiblingIds.includes(oldId)) {
// Deep clone oldTree omitting children field
const diffTree = this._cloneNodeWithoutChildren(oldTree);
// newTree doesn't exists, oldTree has either been moved or deleted.
if (this.newMapping[oldId]) {
diffTree.diff = {type: DiffType.DELETED_MOVE};
} else {
diffTree.diff = {type: DiffType.DELETED};
}
diffTree.children = this._visitChildren(null, oldTree);
diffTrees.push(diffTree);
}
} else {
throw new Error('Both newTree and oldTree are undefined...');
}
return diffTrees;
}
_visitChildren(newTree, oldTree) {
// Recursively traverse all children of new and old tree.
const diffChildren = [];
// TODO: Try replacing this with some sort of zipWith.
const numOfChildren = Math.max(
newTree?.children.length ?? 0, oldTree?.children.length ?? 0);
for (let i = 0; i < numOfChildren; i++) {
const newChild = i < newTree?.children.length ?
newTree.children[i] : null;
const oldChild = i < oldTree?.children.length ?
oldTree.children[i] : null;
const childDiffTrees = this._generateDiffTree(
newChild, oldChild,
newTree?.children ?? [], oldTree?.children ?? [],
);
diffChildren.push(...childDiffTrees);
}
return diffChildren;
}
}

View File

@@ -1,3 +1,19 @@
/*
* Copyright 2020, The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
// Returns just the class name and root package information
function getComponentClassName(componentFullName) {
const classParts = componentFullName.split('.');

View File

@@ -1,33 +1,87 @@
/*
* Copyright 2020, The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
// Find the index of the last element matching the predicate in a sorted array
function findLastMatchingSorted(array, predicate) {
let a = 0;
let b = array.length - 1;
while (b - a > 1) {
const m = Math.floor((a + b) / 2);
if (predicate(array, m)) {
a = m;
} else {
b = m - 1;
}
let a = 0;
let b = array.length - 1;
while (b - a > 1) {
const m = Math.floor((a + b) / 2);
if (predicate(array, m)) {
a = m;
} else {
b = m - 1;
}
}
return predicate(array, b) ? b : a;
return predicate(array, b) ? b : a;
}
// Make sure stableId is unique (makes old versions of proto compatible)
function stableIdCompatibilityFixup(item) {
// For backwards compatibility
// (the only item that doesn't have a unique stable ID in the tree)
if (item.stableId === 'winToken|-|') {
return item.stableId + item.children[0].stableId;
}
// For backwards compatibility
// (the only item that doesn't have a unique stable ID in the tree)
if (item.stableId === 'winToken|-|') {
return item.stableId + item.children[0].stableId;
}
return item.stableId;
return item.stableId;
}
const DIRECTION = Object.freeze({
BACKWARD: -1,
FORWARD: 1,
BACKWARD: -1,
FORWARD: 1,
});
export { DIRECTION, findLastMatchingSorted, stableIdCompatibilityFixup }
const TimeUnits = Object.freeze({
NANO_SECONDS: 'ns',
MILLI_SECONDS: 'ms',
SECONDS: 's',
MINUTES: 'm',
HOURS: 'h',
DAYS: 'd',
});
function nanosToString(elapsedRealtimeNanos, precision) {
const units = [
[1000000, TimeUnits.NANO_SECONDS],
[1000, TimeUnits.MILLI_SECONDS],
[60, TimeUnits.SECONDS],
[60, TimeUnits.MINUTES],
[24, TimeUnits.HOURS],
[Infinity, TimeUnits.DAYS],
];
const parts = [];
let precisionThresholdReached = false;
units.some(([div, timeUnit], i) => {
const part = (elapsedRealtimeNanos % div).toFixed()
if (timeUnit === precision) {
precisionThresholdReached = true;
}
if (precisionThresholdReached) {
parts.push(part + timeUnit);
}
elapsedRealtimeNanos = Math.floor(elapsedRealtimeNanos / div);
return elapsedRealtimeNanos == 0;
});
return parts.reverse().join('');
}
export { DIRECTION, findLastMatchingSorted, stableIdCompatibilityFixup, nanosToString, TimeUnits }

View File

@@ -44,6 +44,9 @@ const webpackConfig = {
polyfill: '@babel/polyfill',
main: './src/main.js',
},
externals: {
_: 'lodash',
},
resolve: {
extensions: ['.tsx', '.ts', '.js', '.vue'],
alias: {

View File

@@ -995,6 +995,11 @@
resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.6.tgz#f4c7ec43e81b319a9815115031709f26987891f0"
integrity sha512-3c+yGKvVP5Y9TYBEibGNR+kLtijnj7mYrXRg+WpFb2X9xm04g/DXYkfg4hmzJQosc9snFNUPkbYIhu+KAm6jJw==
"@types/lodash@^4.14.158":
version "4.14.158"
resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.14.158.tgz#b38ea8b6fe799acd076d7a8d7ab71c26ef77f785"
integrity sha512-InCEXJNTv/59yO4VSfuvNrZHt7eeNtWQEgnieIA+mIC+MOWM9arOWG2eQ8Vhk6NbOre6/BidiXhkZYeDY9U35w==
"@types/long@^4.0.1":
version "4.0.1"
resolved "https://registry.yarnpkg.com/@types/long/-/long-4.0.1.tgz#459c65fa1867dafe6a8f322c4c51695663cc55e9"
@@ -2791,6 +2796,15 @@ enhanced-resolve@^4.0.0, enhanced-resolve@^4.1.1, enhanced-resolve@^4.3.0:
memory-fs "^0.5.0"
tapable "^1.0.0"
enhanced-resolve@^4.1.0, enhanced-resolve@^4.1.1:
version "4.2.0"
resolved "https://registry.yarnpkg.com/enhanced-resolve/-/enhanced-resolve-4.2.0.tgz#5d43bda4a0fd447cb0ebbe71bef8deff8805ad0d"
integrity sha512-S7eiFb/erugyd1rLb6mQ3Vuq+EXHv5cpCkNqqIkYkBgN2QdFnyCZzFBleqwGEx4lgNGYij81BWnCrFNK7vxvjQ==
dependencies:
graceful-fs "^4.1.2"
memory-fs "^0.5.0"
tapable "^1.0.0"
enquirer@^2.3.5, enquirer@^2.3.6:
version "2.3.6"
resolved "https://registry.yarnpkg.com/enquirer/-/enquirer-2.3.6.tgz#2a7fe5dd634a1e4125a975ec994ff5456dc3734d"
@@ -7101,7 +7115,18 @@ ts-loader@^8.0.3:
micromatch "^4.0.0"
semver "^6.0.0"
tslib@^1.9.0, tslib@^1.9.3:
ts-loader@^8.0.1:
version "8.0.1"
resolved "https://registry.yarnpkg.com/ts-loader/-/ts-loader-8.0.1.tgz#9670dcbce2a8c8506d01a37fee042350d02c8c21"
integrity sha512-I9Nmly0ufJoZRMuAT9d5ijsC2B7oSPvUnOJt/GhgoATlPGYfa17VicDKPcqwUCrHpOkCxr/ybLYwbnS4cOxmvQ==
dependencies:
chalk "^2.3.0"
enhanced-resolve "^4.0.0"
loader-utils "^1.0.2"
micromatch "^4.0.0"
semver "^6.0.0"
tslib@^1.10.0, tslib@^1.9.0, tslib@^1.9.3:
version "1.13.0"
resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.13.0.tgz#c881e13cc7015894ed914862d276436fa9a47043"
integrity sha512-i/6DQjL8Xf3be4K/E6Wgpekn5Qasl1usyw++dAA35Ue5orEn65VIxOA+YvNNl9HV3qv70T7CNwjODHZrLwvd1Q==