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

View File

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

View File

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

View File

@@ -209,7 +209,7 @@ export default {
itemSelected(item) { itemSelected(item) {
this.hierarchySelected = item; this.hierarchySelected = item;
this.selectedTree = this.getTransformedProperties(item); this.selectedTree = this.getTransformedProperties(item);
this.highlight = item.highlight; this.highlight = item.rect;
this.lastSelectedStableId = item.stableId; this.lastSelectedStableId = item.stableId;
this.$emit('focus'); 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 jsonProtoDefsLauncher from 'packages/apps/Launcher3/protos/launcher_trace_file.proto';
import protobuf from 'protobufjs'; import protobuf from 'protobufjs';
import {transformLayers, transformLayersTrace} from './transform_sf.js'; 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_transaction_trace} from './transform_transaction.js';
import {transform_wl_outputstate, transform_wayland_trace} from './transform_wl.js'; import {transform_wl_outputstate, transform_wayland_trace} from './transform_wl.js';
import {transformProtolog} from './transform_protolog.js'; import {transformProtolog} from './transform_protolog.js';
@@ -211,7 +210,7 @@ const FILE_DECODERS = {
decoderParams: { decoderParams: {
type: FILE_TYPES.WINDOW_MANAGER_TRACE, type: FILE_TYPES.WINDOW_MANAGER_TRACE,
protoType: WmTraceMessage, protoType: WmTraceMessage,
transform: transform_window_trace, transform: WindowManagerTrace.fromProto,
timeline: true, timeline: true,
}, },
}, },
@@ -255,7 +254,7 @@ const FILE_DECODERS = {
type: FILE_TYPES.WINDOW_MANAGER_DUMP, type: FILE_TYPES.WINDOW_MANAGER_DUMP,
mime: 'application/octet-stream', mime: 'application/octet-stream',
protoType: WmDumpMessage, protoType: WmDumpMessage,
transform: transform_window_service, transform: WindowManagerDump.fromProto,
timeline: false, timeline: false,
}, },
}, },
@@ -380,7 +379,7 @@ function protoDecoder(buffer, params, fileName, store) {
const transformed = decodeAndTransformProto(buffer, params, store.displayDefaults); const transformed = decodeAndTransformProto(buffer, params, store.displayDefaults);
let data; let data;
if (params.timeline) { if (params.timeline) {
data = transformed.children; data = transformed.entries ?? transformed.children;
} else { } else {
data = [transformed]; data = [transformed];
} }

View File

@@ -17,6 +17,8 @@
import { FILE_TYPES, DUMP_TYPES } from "@/decode.js"; import { FILE_TYPES, DUMP_TYPES } from "@/decode.js";
import DumpBase from "./DumpBase"; import DumpBase from "./DumpBase";
import { WindowManagerTraceEntry } from '@/flickerlib';
export default class WindowManager extends DumpBase { export default class WindowManager extends DumpBase {
wmDumpFile: any; wmDumpFile: any;
@@ -29,4 +31,8 @@ export default class WindowManager extends DumpBase {
get type() { get type() {
return DUMP_TYPES.WINDOW_MANAGER; 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 { FILE_TYPES, TRACE_TYPES } from '@/decode.js';
import TraceBase from './TraceBase'; import TraceBase from './TraceBase';
import { WindowManagerTrace } from '@/flickerlib';
export default class WindowManager extends TraceBase { export default class WindowManager extends TraceBase {
wmTraceFile: Object; wmTraceFile: Object;
@@ -30,4 +32,8 @@ export default class WindowManager extends TraceBase {
get type() { get type() {
return TRACE_TYPES.WINDOW_MANAGER; 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 = { const CompatibleFeatures = {
DiffVisualization: true, 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: * Should be kept in sync with ENUM is in Google3 under:
* google3/wireless/android/tools/android_bug_tool/extension/common/actions * google3/wireless/android/tools/android_bug_tool/extension/common/actions

View File

@@ -1,6 +1,23 @@
/*
* 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'; import cloneDeep from 'lodash.clonedeep';
const DiffType = Object.freeze({ export const DiffType = Object.freeze({
NONE: 'none', NONE: 'none',
ADDED: 'added', ADDED: 'added',
DELETED: 'deleted', DELETED: 'deleted',
@@ -9,7 +26,7 @@ const DiffType = Object.freeze({
MODIFIED: 'modified', MODIFIED: 'modified',
}); });
function defaultModifiedCheck(newNode, oldNode) { export function defaultModifiedCheck(newNode, oldNode) {
if (!newNode && !oldNode) { if (!newNode && !oldNode) {
return false; return false;
} }
@@ -23,24 +40,33 @@ function defaultModifiedCheck(newNode, oldNode) {
function isPrimitive(test) { function isPrimitive(test) {
return test !== Object(test); return test !== Object(test);
}; }
class DiffGenerator { export class DiffGenerator {
constructor(tree) { constructor(tree) {
if (tree.asRawTreeViewObject) {
this.tree = tree.asRawTreeViewObject();
} else {
this.tree = tree; this.tree = tree;
} }
}
compareWith(tree) { compareWith(tree) {
if (tree?.asRawTreeViewObject) {
this.diffWithTree = tree.asRawTreeViewObject();
} else {
this.diffWithTree = tree; this.diffWithTree = tree;
}
return this; return this;
} }
withUniqueNodeId(getNodeId) { withUniqueNodeId(getNodeId) {
this.getNodeId = node => { this.getNodeId = (node) => {
const id = getNodeId(node); const id = getNodeId(node);
if (id === null || id === undefined) { if (id === null || id === undefined) {
console.error("Null node ID for node", node); console.error('Null node ID for node', node);
throw new Error("Node ID can't be null or undefined"); throw new Error('Node ID can\'t be null or undefined');
} }
return id; return id;
}; };
@@ -54,7 +80,8 @@ class DiffGenerator {
generateDiffTree() { generateDiffTree() {
this.newMapping = this._generateIdToNodeMapping(this.tree); this.newMapping = this._generateIdToNodeMapping(this.tree);
this.oldMapping = this.diffWithTree ? this._generateIdToNodeMapping(this.diffWithTree) : {}; this.oldMapping = this.diffWithTree ?
this._generateIdToNodeMapping(this.diffWithTree) : {};
const diffTrees = const diffTrees =
this._generateDiffTree(this.tree, this.diffWithTree, [], []); this._generateDiffTree(this.tree, this.diffWithTree, [], []);
@@ -62,9 +89,10 @@ class DiffGenerator {
let diffTree; let diffTree;
if (diffTrees.length > 1) { if (diffTrees.length > 1) {
diffTree = { diffTree = {
kind: "", kind: '',
name: "DiffTree", name: 'DiffTree',
children: diffTrees, children: diffTrees,
stableId: 'DiffTree',
}; };
} else { } else {
diffTree = diffTrees[0]; diffTree = diffTrees[0];
@@ -127,7 +155,7 @@ class DiffGenerator {
const diffTree = this._cloneNodeWithoutChildren(newTree); const diffTree = this._cloneNodeWithoutChildren(newTree);
// Default to no changes // Default to no changes
diffTree.diff = { type: DiffType.NONE }; diffTree.diff = {type: DiffType.NONE};
if (newId !== oldId) { if (newId !== oldId) {
// A move, addition, or deletion has occurred // A move, addition, or deletion has occurred
@@ -139,14 +167,14 @@ class DiffGenerator {
// Objected existed in old tree, the counter party // Objected existed in old tree, the counter party
// DELETED_MOVE will be/has been flagged and added to the // DELETED_MOVE will be/has been flagged and added to the
// diffTree when visiting it in the oldTree. // diffTree when visiting it in the oldTree.
diffTree.diff = { type: DiffType.ADDED_MOVE }; diffTree.diff = {type: DiffType.ADDED_MOVE};
// TODO: Figure out if oldTree is ever visited now... // TODO: Figure out if oldTree is ever visited now...
// Switch out oldTree for new one to compare against // Switch out oldTree for new one to compare against
nextOldTree = this.oldMapping[newId]; nextOldTree = this.oldMapping[newId];
} else { } else {
diffTree.diff = { type: DiffType.ADDED }; diffTree.diff = {type: DiffType.ADDED};
// TODO: Figure out if oldTree is ever visited now... // TODO: Figure out if oldTree is ever visited now...
@@ -160,13 +188,13 @@ class DiffGenerator {
const deletedTreeDiff = this._cloneNodeWithoutChildren(oldTree); const deletedTreeDiff = this._cloneNodeWithoutChildren(oldTree);
if (this.newMapping[oldId]) { if (this.newMapping[oldId]) {
deletedTreeDiff.diff = { type: DiffType.DELETED_MOVE }; deletedTreeDiff.diff = {type: DiffType.DELETED_MOVE};
// Stop comparing against oldTree, will be/has been // Stop comparing against oldTree, will be/has been
// visited when object is seen in newTree // visited when object is seen in newTree
nextOldTree = null; nextOldTree = null;
} else { } else {
deletedTreeDiff.diff = { type: DiffType.DELETED }; deletedTreeDiff.diff = {type: DiffType.DELETED};
// Visit all children to check if they have been deleted or moved // Visit all children to check if they have been deleted or moved
deletedTreeDiff.children = this._visitChildren(null, oldTree); deletedTreeDiff.children = this._visitChildren(null, oldTree);
@@ -177,10 +205,11 @@ class DiffGenerator {
oldTree = nextOldTree; oldTree = nextOldTree;
} else { } else {
// TODO: Always have modified check and add modified tags on top of others // 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 // NOTE: Doesn't check for moved and modified at the same time
if (this.isModified && this.isModified(newTree, oldTree)) { if (this.isModified && this.isModified(newTree, oldTree)) {
diffTree.diff = { type: DiffType.MODIFIED }; diffTree.diff = {type: DiffType.MODIFIED};
} }
} }
@@ -193,16 +222,16 @@ class DiffGenerator {
// newTree doesn't exists, oldTree has either been moved or deleted. // newTree doesn't exists, oldTree has either been moved or deleted.
if (this.newMapping[oldId]) { if (this.newMapping[oldId]) {
diffTree.diff = { type: DiffType.DELETED_MOVE }; diffTree.diff = {type: DiffType.DELETED_MOVE};
} else { } else {
diffTree.diff = { type: DiffType.DELETED }; diffTree.diff = {type: DiffType.DELETED};
} }
diffTree.children = this._visitChildren(null, oldTree); diffTree.children = this._visitChildren(null, oldTree);
diffTrees.push(diffTree); diffTrees.push(diffTree);
} }
} else { } else {
throw new Error("Both newTree and oldTree are undefined..."); throw new Error('Both newTree and oldTree are undefined...');
} }
return diffTrees; return diffTrees;
@@ -224,7 +253,7 @@ class DiffGenerator {
const childDiffTrees = this._generateDiffTree( const childDiffTrees = this._generateDiffTree(
newChild, oldChild, newChild, oldChild,
newTree?.children ?? [], oldTree?.children ?? [] newTree?.children ?? [], oldTree?.children ?? [],
); );
diffChildren.push(...childDiffTrees); diffChildren.push(...childDiffTrees);
} }
@@ -232,5 +261,3 @@ class DiffGenerator {
return diffChildren; return diffChildren;
} }
} }
export { DiffGenerator, DiffType, defaultModifiedCheck }

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 // Returns just the class name and root package information
function getComponentClassName(componentFullName) { function getComponentClassName(componentFullName) {
const classParts = componentFullName.split('.'); const classParts = componentFullName.split('.');

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.
*/
// Find the index of the last element matching the predicate in a sorted array // Find the index of the last element matching the predicate in a sorted array
function findLastMatchingSorted(array, predicate) { function findLastMatchingSorted(array, predicate) {
let a = 0; let a = 0;
@@ -30,4 +46,42 @@ const DIRECTION = Object.freeze({
FORWARD: 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', polyfill: '@babel/polyfill',
main: './src/main.js', main: './src/main.js',
}, },
externals: {
_: 'lodash',
},
resolve: { resolve: {
extensions: ['.tsx', '.ts', '.js', '.vue'], extensions: ['.tsx', '.ts', '.js', '.vue'],
alias: { alias: {

View File

@@ -995,6 +995,11 @@
resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.6.tgz#f4c7ec43e81b319a9815115031709f26987891f0" resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.6.tgz#f4c7ec43e81b319a9815115031709f26987891f0"
integrity sha512-3c+yGKvVP5Y9TYBEibGNR+kLtijnj7mYrXRg+WpFb2X9xm04g/DXYkfg4hmzJQosc9snFNUPkbYIhu+KAm6jJw== 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": "@types/long@^4.0.1":
version "4.0.1" version "4.0.1"
resolved "https://registry.yarnpkg.com/@types/long/-/long-4.0.1.tgz#459c65fa1867dafe6a8f322c4c51695663cc55e9" 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" memory-fs "^0.5.0"
tapable "^1.0.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: enquirer@^2.3.5, enquirer@^2.3.6:
version "2.3.6" version "2.3.6"
resolved "https://registry.yarnpkg.com/enquirer/-/enquirer-2.3.6.tgz#2a7fe5dd634a1e4125a975ec994ff5456dc3734d" 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" micromatch "^4.0.0"
semver "^6.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" version "1.13.0"
resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.13.0.tgz#c881e13cc7015894ed914862d276436fa9a47043" resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.13.0.tgz#c881e13cc7015894ed914862d276436fa9a47043"
integrity sha512-i/6DQjL8Xf3be4K/E6Wgpekn5Qasl1usyw++dAA35Ue5orEn65VIxOA+YvNNl9HV3qv70T7CNwjODHZrLwvd1Q== integrity sha512-i/6DQjL8Xf3be4K/E6Wgpekn5Qasl1usyw++dAA35Ue5orEn65VIxOA+YvNNl9HV3qv70T7CNwjODHZrLwvd1Q==