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:
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user