Move mixin() into FunctionUtils

Bug: 307906075
Test: npm run test:presubmit
Change-Id: I21a7a82e6aa31502262baf05b8cb924fb5ff13b4
diff --git a/tools/winscope/src/common/function_utils.ts b/tools/winscope/src/common/function_utils.ts
index c5c2b3f..66cc6b2 100644
--- a/tools/winscope/src/common/function_utils.ts
+++ b/tools/winscope/src/common/function_utils.ts
@@ -24,4 +24,22 @@
   static readonly DO_NOTHING_ASYNC = (): Promise<void> => {
     return Promise.resolve();
   };
+
+  static mixin<T extends object, U extends object>(a: T, b: U): T & U {
+    const ret = {};
+    Object.assign(ret, a);
+    Object.assign(ret, b);
+
+    const assignMethods = (dst: object, src: object) => {
+      for (const methodName of Object.getOwnPropertyNames(Object.getPrototypeOf(src))) {
+        const method = (src as any)[methodName];
+        (dst as any)[methodName] = method;
+      }
+    };
+
+    assignMethods(ret, a);
+    assignMethods(ret, b);
+
+    return ret as T & U;
+  }
 }
diff --git a/tools/winscope/src/common/function_utils_test.ts b/tools/winscope/src/common/function_utils_test.ts
new file mode 100644
index 0000000..bd5406c
--- /dev/null
+++ b/tools/winscope/src/common/function_utils_test.ts
@@ -0,0 +1,45 @@
+/*
+ * Copyright (C) 2023 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 {FunctionUtils} from './function_utils';
+
+describe('FunctionUtils', () => {
+  class A {
+    a = 'a';
+    foo(): string {
+      return 'a';
+    }
+  }
+
+  class B {
+    b = 'b';
+    bar(): string {
+      return 'b';
+    }
+  }
+
+  it('mixin()', () => {
+    const a = new A();
+    const b = new B();
+
+    const mixin = FunctionUtils.mixin(a, b);
+
+    expect(mixin.a).toEqual('a');
+    expect(mixin.b).toEqual('b');
+    expect(mixin.foo()).toEqual('a');
+    expect(mixin.bar()).toEqual('b');
+  });
+});
diff --git a/tools/winscope/src/trace/trace_data_utils.ts b/tools/winscope/src/trace/trace_data_utils.ts
index 273f427..a6aaff5 100644
--- a/tools/winscope/src/trace/trace_data_utils.ts
+++ b/tools/winscope/src/trace/trace_data_utils.ts
@@ -78,13 +78,6 @@
   new (...args: any[]): T;
 }
 
-function mixin<T, U>(a: T, b: U): T & U {
-  const ret = {};
-  Object.assign(ret, a);
-  Object.assign(ret, b);
-  return ret as T & U;
-}
-
 type PropertyTreeNode = TreeNode<AssociatedProperty> & AssociatedProperty;
 type HierarchyTreeNode = TreeNode<PropertiesGetter> & PropertiesGetter;
 
@@ -103,7 +96,6 @@
   PropertyDetails,
   AssociatedProperty,
   Constructor,
-  mixin,
   PropertyTreeNode,
   HierarchyTreeNode,
   EMPTY_OBJ_STRING,