Display transaction merge information in Winscope

Test: N/A
Change-Id: I04836e844e2aa3158c36c12e375eca365c003ada
diff --git a/tools/winscope/src/TransactionEntry.vue b/tools/winscope/src/TransactionEntry.vue
index 138bca1..fb0702b 100644
--- a/tools/winscope/src/TransactionEntry.vue
+++ b/tools/winscope/src/TransactionEntry.vue
@@ -18,18 +18,29 @@
       </div>
     </div>
     <div class="type-column">{{transactionTypeOf(source)}}</div>
-    <div class="origin-column">
-      <span style="white-space: pre;">{{formatOrigin(source)}}</span>
-    </div>
     <div class="affected-surfaces-column">
-      <span v-for="(surface, index) in sufacesAffectedBy(source)" v-bind:key="surface.id">
-        <span v-if="surface.name" class="surface-name"> {{ surface.name }}</span>
+      <span
+        v-for="(surface, index) in sufacesAffectedBy(source)"
+        v-bind:key="surface.id"
+      >
+        <span v-if="surface.name" class="surface-name">{{ surface.name }}</span>
         <span class="surface-id">
+          <!-- eslint-disable-next-line max-len -->
           <span v-if="surface.name">(</span>{{surface.id}}<span v-if="surface.name">)</span>
         </span>
         <span v-if="index + 1 < sufacesAffectedBy(source).length">,&nbsp;</span>
       </span>
     </div>
+    <div class="extra-info-column">
+      <span v-if="source.identifier">
+        <!-- eslint-disable-next-line max-len -->
+        Tx Id: <span class="light">{{ prettifyTransactionId(source.identifier) }}</span><br/>
+      </span>
+      <span v-if="source.origin">
+        PID: <span class="light">{{ source.origin.pid }}</span><br/>
+        TID: <span class="light">{{ source.origin.uid }}</span><br/>
+      </span>
+    </div>
   </div>
 </template>
 
@@ -42,16 +53,22 @@
     },
     source: {
       type: Object,
-      default () {
-        return {}
-      }
+      default() {
+        return {};
+      },
     },
     onClick: {
       type: Function,
     },
     selectedTransaction: {
       type: Object,
-    }
+    },
+    transactionsTrace: {
+      type: Object,
+    },
+    prettifyTransactionId: {
+      type: Function,
+    },
   },
   computed: {
     currentTimestamp() {
@@ -60,6 +77,27 @@
     isSelected() {
       return this.source === this.selectedTransaction;
     },
+    hasOverrideChangeDueToMerge() {
+      const transaction = this.source;
+
+      if (!transaction.identifier) {
+        return;
+      }
+
+      // console.log('transaction', transaction.identifier);
+
+      // const history = this.transactionsTrace.transactionHistory;
+
+      // const allTransactionsMergedInto = history
+      //     .allTransactionsMergedInto(transaction.identifier);
+      // console.log('All merges', allTransactionsMergedInto);
+
+      // console.log('Direct merges',
+      //     history.allDirectMergesInto(transaction.identifier));
+
+
+      return true;
+    },
   },
   methods: {
     setTimelineTime(timestamp) {
@@ -71,13 +109,13 @@
       }
 
       if (transaction.transactions.length === 0) {
-        return "Empty Transaction";
+        return 'Empty Transaction';
       }
 
       const types = new Set();
-      transaction.transactions.forEach(t => types.add(t.type));
+      transaction.transactions.forEach((t) => types.add(t.type));
 
-      return Array.from(types).join(", ");
+      return Array.from(types).join(', ');
     },
     sufacesAffectedBy(transaction) {
       if (transaction.type !== 'transaction') {
@@ -95,25 +133,10 @@
         }
       }
 
-      return affectedSurfaces
-    },
-    formatOrigin(transaction) {
-      if (!transaction.origin) {
-        return "unavailable";
-      }
-
-      const originString = [];
-      originString.push(`PID: ${transaction.origin.pid}`);
-      originString.push(`UID: ${transaction.origin.uid}`);
-
-      if (transaction.origin.appliedByMainThread) {
-        originString.push("Applied by main thread");
-      }
-
-      return originString.join("\n");
+      return affectedSurfaces;
     },
   },
-}
+};
 </script>
 <style scoped>
 .time-column {
@@ -138,6 +161,10 @@
   width: 30em;
 }
 
+.extra-info-column {
+  width: 20em;
+}
+
 .entry {
   display: inline-flex;
   cursor: pointer;
@@ -190,4 +217,12 @@
 .inactive .affected-surfaces-column .surface-id {
   color: #b4b4b4
 }
-</style>
\ No newline at end of file
+
+.light {
+  color: #999999
+}
+
+.inactive .light {
+  color: #b4b4b4
+}
+</style>
diff --git a/tools/winscope/src/TransactionsView.vue b/tools/winscope/src/TransactionsView.vue
index c89b52d..66002a3 100644
--- a/tools/winscope/src/TransactionsView.vue
+++ b/tools/winscope/src/TransactionsView.vue
@@ -81,7 +81,12 @@
         :data-key="'timestamp'"
         :data-sources="filteredData"
         :data-component="transactionEntryComponent"
-        :extra-props="{onClick: transactionSelected, selectedTransaction }"
+        :extra-props="{
+          onClick: transactionSelected,
+          selectedTransaction,
+          transactionsTrace,
+          prettifyTransactionId,
+        }"
         ref="loglist"
       />
     </flat-card>
@@ -95,12 +100,22 @@
         <h2 class="md-title" style="flex: 1">Changes</h2>
       </md-content>
       <div class="changes-content" v-if="selectedTree">
-        <div v-if="selectedTransaction.type === 'transaction'">
+        <div
+          v-if="selectedTransaction.type === 'transaction'"
+          class="transaction-events"
+        >
           <div
-            v-for="history in transactionHistory(selectedTransaction)"
-            v-bind:key="history.id"
+            v-for="(event, i) in transactionHistory(selectedTransaction)"
+            v-bind:key="`${selectedTransaction.identifier}-${i}`"
+            class="transaction-event"
           >
-            {{ history.type }}
+            <div v-if="event.type === 'apply'" class="applied-event">
+              applied
+            </div>
+            <div v-if="event.type === 'merge'" class="merged-event">
+              <!-- eslint-disable-next-line max-len -->
+              {{ prettifyTransactionId(event.mergedId) }}
+            </div>
           </div>
         </div>
         <tree-view
@@ -126,6 +141,7 @@
 import FlatCard from './components/FlatCard.vue';
 
 import {ObjectTransformer} from './transform.js';
+import {expandTransactionId} from '@/traces/Transactions.js';
 
 export default {
   name: 'transactionsview',
@@ -168,6 +184,7 @@
       selectedTransaction: null,
       transactionEntryComponent: TransactionEntry,
       transactionsTrace,
+      expandTransactionId,
     };
   },
   computed: {
@@ -411,27 +428,12 @@
       const history = this.transactionsTrace.transactionHistory
           .generateHistoryTreesOf(transactionId);
 
-      const historyElements = [];
-      for (const [i, event] of history.entries()) {
-        const elementId = transactionId + '.' + i;
+      return history;
+    },
 
-        switch (event.type) {
-          case 'apply':
-            break;
-          case 'merge':
-            break;
-          default:
-            throw new Error('Unhandled event type');
-        }
-
-        const element = {
-          id: elementId,
-          type: event.type,
-        };
-        historyElements.push(element);
-      }
-
-      return historyElements;
+    prettifyTransactionId(transactionId) {
+      const expandedId = expandTransactionId(transactionId);
+      return `${expandedId.pid}.${expandedId.id}`;
     },
   },
   components: {
diff --git a/tools/winscope/src/decode.js b/tools/winscope/src/decode.js
index 271d219..7b5dce2 100644
--- a/tools/winscope/src/decode.js
+++ b/tools/winscope/src/decode.js
@@ -14,82 +14,84 @@
  * limitations under the License.
  */
 
+/* eslint-disable max-len */
+/* eslint-disable camelcase */
 
-import jsonProtoDefsWm from 'frameworks/base/core/proto/android/server/windowmanagertrace.proto'
-import jsonProtoDefsProtoLog from 'frameworks/base/core/proto/android/internal/protolog.proto'
-import jsonProtoDefsSf from 'frameworks/native/services/surfaceflinger/layerproto/layerstrace.proto'
-import jsonProtoDefsTransaction from 'frameworks/native/cmds/surfacereplayer/proto/src/trace.proto'
-import jsonProtoDefsTransactionEvents from 'frameworks/native/libs/gui/proto/src/transactions.proto'
-import jsonProtoDefsWl from 'WaylandSafePath/waylandtrace.proto'
-import jsonProtoDefsSysUi from 'frameworks/base/packages/SystemUI/src/com/android/systemui/tracing/sysui_trace.proto'
-import jsonProtoDefsLauncher from 'packages/apps/Launcher3/protos/launcher_trace_file.proto'
-import protobuf from 'protobufjs'
-import { transform_layers, transform_layers_trace } from './transform_sf.js'
-import { transform_window_service, transform_window_trace } from './transform_wm.js'
-import { transform_transaction_trace, transform_TRANSACTION_EVENTS_TRACE } from './transform_transaction.js'
-import { transform_wl_outputstate, transform_wayland_trace } from './transform_wl.js'
-import { transform_protolog } from './transform_protolog.js'
-import { transform_sysui_trace } from './transform_sys_ui.js'
-import { transform_launcher_trace } from './transform_launcher.js'
-import { fill_transform_data } from './matrix_utils.js'
-import { mp4Decoder } from './decodeVideo.js'
+import jsonProtoDefsWm from 'frameworks/base/core/proto/android/server/windowmanagertrace.proto';
+import jsonProtoDefsProtoLog from 'frameworks/base/core/proto/android/internal/protolog.proto';
+import jsonProtoDefsSf from 'frameworks/native/services/surfaceflinger/layerproto/layerstrace.proto';
+import jsonProtoDefsTransaction from 'frameworks/native/cmds/surfacereplayer/proto/src/trace.proto';
+import jsonProtoDefsTransactionEvents from 'frameworks/native/libs/gui/proto/transactions.proto';
+import jsonProtoDefsWl from 'WaylandSafePath/waylandtrace.proto';
+import jsonProtoDefsSysUi from 'frameworks/base/packages/SystemUI/src/com/android/systemui/tracing/sysui_trace.proto';
+import jsonProtoDefsLauncher from 'packages/apps/Launcher3/protos/launcher_trace_file.proto';
+import protobuf from 'protobufjs';
+import {transform_layers, transform_layers_trace} from './transform_sf.js';
+import {transform_window_service, transform_window_trace} from './transform_wm.js';
+import {transform_transaction_trace, transform_TRANSACTION_EVENTS_TRACE} from './transform_transaction.js';
+import {transform_wl_outputstate, transform_wayland_trace} from './transform_wl.js';
+import {transform_protolog} from './transform_protolog.js';
+import {transform_sysui_trace} from './transform_sys_ui.js';
+import {transform_launcher_trace} from './transform_launcher.js';
+import {fill_transform_data} from './matrix_utils.js';
+import {mp4Decoder} from './decodeVideo.js';
 
-import SurfaceFlingerTrace from '@/traces/SurfaceFlinger.js'
-import WindowManagerTrace from '@/traces/WindowManager.js'
-import TransactionsTrace from '@/traces/Transactions.js'
-import ScreenRecordingTrace from '@/traces/ScreenRecording.js'
-import WaylandTrace from '@/traces/Wayland.js'
-import ProtoLogTrace from '@/traces/ProtoLog.js'
-import SystemUITrace from '@/traces/SystemUI.js'
-import LauncherTrace from '@/traces/Launcher.js'
+import SurfaceFlingerTrace from '@/traces/SurfaceFlinger.js';
+import WindowManagerTrace from '@/traces/WindowManager.js';
+import TransactionsTrace from '@/traces/Transactions.js';
+import ScreenRecordingTrace from '@/traces/ScreenRecording.js';
+import WaylandTrace from '@/traces/Wayland.js';
+import ProtoLogTrace from '@/traces/ProtoLog.js';
+import SystemUITrace from '@/traces/SystemUI.js';
+import LauncherTrace from '@/traces/Launcher.js';
 
-import SurfaceFlingerDump from '@/dumps/SurfaceFlinger.js'
-import WindowManagerDump from '@/dumps/WindowManager.js'
-import WaylandDump from '@/dumps/Wayland.js'
+import SurfaceFlingerDump from '@/dumps/SurfaceFlinger.js';
+import WindowManagerDump from '@/dumps/WindowManager.js';
+import WaylandDump from '@/dumps/Wayland.js';
 
-const WmTraceMessage = lookup_type(jsonProtoDefsWm, "com.android.server.wm.WindowManagerTraceFileProto");
-const WmDumpMessage = lookup_type(jsonProtoDefsWm, "com.android.server.wm.WindowManagerServiceDumpProto");
-const SfTraceMessage = lookup_type(jsonProtoDefsSf, "android.surfaceflinger.LayersTraceFileProto");
-const SfDumpMessage = lookup_type(jsonProtoDefsSf, "android.surfaceflinger.LayersProto");
-const SfTransactionTraceMessage = lookup_type(jsonProtoDefsTransaction, "Trace");
-const SfTransactionEventsTraceMessage = lookup_type(jsonProtoDefsTransactionEvents, "android.TransactionEventsProto");
-const WaylandTraceMessage = lookup_type(jsonProtoDefsWl, "org.chromium.arc.wayland_composer.TraceFileProto");
-const WaylandDumpMessage = lookup_type(jsonProtoDefsWl, "org.chromium.arc.wayland_composer.OutputStateProto");
-const ProtoLogMessage = lookup_type(jsonProtoDefsProtoLog, "com.android.internal.protolog.ProtoLogFileProto");
-const SystemUiTraceMessage = lookup_type(jsonProtoDefsSysUi, "com.android.systemui.tracing.SystemUiTraceFileProto");
-const LauncherTraceMessage = lookup_type(jsonProtoDefsLauncher, "com.android.launcher3.tracing.LauncherTraceFileProto");
+const WmTraceMessage = lookup_type(jsonProtoDefsWm, 'com.android.server.wm.WindowManagerTraceFileProto');
+const WmDumpMessage = lookup_type(jsonProtoDefsWm, 'com.android.server.wm.WindowManagerServiceDumpProto');
+const SfTraceMessage = lookup_type(jsonProtoDefsSf, 'android.surfaceflinger.LayersTraceFileProto');
+const SfDumpMessage = lookup_type(jsonProtoDefsSf, 'android.surfaceflinger.LayersProto');
+const SfTransactionTraceMessage = lookup_type(jsonProtoDefsTransaction, 'Trace');
+const SfTransactionEventsTraceMessage = lookup_type(jsonProtoDefsTransactionEvents, 'android.TransactionEventsProto');
+const WaylandTraceMessage = lookup_type(jsonProtoDefsWl, 'org.chromium.arc.wayland_composer.TraceFileProto');
+const WaylandDumpMessage = lookup_type(jsonProtoDefsWl, 'org.chromium.arc.wayland_composer.OutputStateProto');
+const ProtoLogMessage = lookup_type(jsonProtoDefsProtoLog, 'com.android.internal.protolog.ProtoLogFileProto');
+const SystemUiTraceMessage = lookup_type(jsonProtoDefsSysUi, 'com.android.systemui.tracing.SystemUiTraceFileProto');
+const LauncherTraceMessage = lookup_type(jsonProtoDefsLauncher, 'com.android.launcher3.tracing.LauncherTraceFileProto');
 
-const LAYER_TRACE_MAGIC_NUMBER = [0x09, 0x4c, 0x59, 0x52, 0x54, 0x52, 0x41, 0x43, 0x45] // .LYRTRACE
-const WINDOW_TRACE_MAGIC_NUMBER = [0x09, 0x57, 0x49, 0x4e, 0x54, 0x52, 0x41, 0x43, 0x45] // .WINTRACE
-const MPEG4_MAGIC_NMBER = [0x00, 0x00, 0x00, 0x18, 0x66, 0x74, 0x79, 0x70, 0x6d, 0x70, 0x34, 0x32] // ....ftypmp42
-const WAYLAND_TRACE_MAGIC_NUMBER = [0x09, 0x57, 0x59, 0x4c, 0x54, 0x52, 0x41, 0x43, 0x45] // .WYLTRACE
-const PROTO_LOG_MAGIC_NUMBER = [0x09, 0x50, 0x52, 0x4f, 0x54, 0x4f, 0x4c, 0x4f, 0x47] // .PROTOLOG
-const SYSTEM_UI_MAGIC_NUMBER = [0x09, 0x53, 0x59, 0x53, 0x55, 0x49, 0x54, 0x52, 0x43] // .SYSUITRC
-const LAUNCHER_MAGIC_NUMBER = [0x09, 0x4C, 0x4E, 0x43, 0x48, 0x52, 0x54, 0x52, 0x43] // .LNCHRTRC
+const LAYER_TRACE_MAGIC_NUMBER = [0x09, 0x4c, 0x59, 0x52, 0x54, 0x52, 0x41, 0x43, 0x45]; // .LYRTRACE
+const WINDOW_TRACE_MAGIC_NUMBER = [0x09, 0x57, 0x49, 0x4e, 0x54, 0x52, 0x41, 0x43, 0x45]; // .WINTRACE
+const MPEG4_MAGIC_NMBER = [0x00, 0x00, 0x00, 0x18, 0x66, 0x74, 0x79, 0x70, 0x6d, 0x70, 0x34, 0x32]; // ....ftypmp42
+const WAYLAND_TRACE_MAGIC_NUMBER = [0x09, 0x57, 0x59, 0x4c, 0x54, 0x52, 0x41, 0x43, 0x45]; // .WYLTRACE
+const PROTO_LOG_MAGIC_NUMBER = [0x09, 0x50, 0x52, 0x4f, 0x54, 0x4f, 0x4c, 0x4f, 0x47]; // .PROTOLOG
+const SYSTEM_UI_MAGIC_NUMBER = [0x09, 0x53, 0x59, 0x53, 0x55, 0x49, 0x54, 0x52, 0x43]; // .SYSUITRC
+const LAUNCHER_MAGIC_NUMBER = [0x09, 0x4C, 0x4E, 0x43, 0x48, 0x52, 0x54, 0x52, 0x43]; // .LNCHRTRC
 
 const FILE_TYPES = Object.freeze({
-  WINDOW_MANAGER_TRACE: "WindowManagerTrace",
-  SURFACE_FLINGER_TRACE: "SurfaceFlingerTrace",
-  WINDOW_MANAGER_DUMP: "WindowManagerDump",
-  SURFACE_FLINGER_DUMP: "SurfaceFlingerDump",
-  SCREEN_RECORDING: "ScreenRecording",
-  TRANSACTIONS_TRACE: "TransactionsTrace",
-  TRANSACTION_EVENTS_TRACE: "TransactionMergesTrace",
-  WAYLAND_TRACE: "WaylandTrace",
-  WAYLAND_DUMP: "WaylandDump",
-  PROTO_LOG: "ProtoLog",
-  SYSTEM_UI: "SystemUI",
-  LAUNCHER: "Launcher",
+  WINDOW_MANAGER_TRACE: 'WindowManagerTrace',
+  SURFACE_FLINGER_TRACE: 'SurfaceFlingerTrace',
+  WINDOW_MANAGER_DUMP: 'WindowManagerDump',
+  SURFACE_FLINGER_DUMP: 'SurfaceFlingerDump',
+  SCREEN_RECORDING: 'ScreenRecording',
+  TRANSACTIONS_TRACE: 'TransactionsTrace',
+  TRANSACTION_EVENTS_TRACE: 'TransactionMergesTrace',
+  WAYLAND_TRACE: 'WaylandTrace',
+  WAYLAND_DUMP: 'WaylandDump',
+  PROTO_LOG: 'ProtoLog',
+  SYSTEM_UI: 'SystemUI',
+  LAUNCHER: 'Launcher',
 });
 
-const WINDOW_MANAGER_ICON = "view_compact"
-const SURFACE_FLINGER_ICON = "filter_none"
-const SCREEN_RECORDING_ICON = "videocam"
-const TRANSACTION_ICON = "timeline"
-const WAYLAND_ICON = "filter_none"
-const PROTO_LOG_ICON = "notes"
-const SYSTEM_UI_ICON = "filter_none"
-const LAUNCHER_ICON = "filter_none"
+const WINDOW_MANAGER_ICON = 'view_compact';
+const SURFACE_FLINGER_ICON = 'filter_none';
+const SCREEN_RECORDING_ICON = 'videocam';
+const TRANSACTION_ICON = 'timeline';
+const WAYLAND_ICON = 'filter_none';
+const PROTO_LOG_ICON = 'notes';
+const SYSTEM_UI_ICON = 'filter_none';
+const LAUNCHER_ICON = 'filter_none';
 
 const FILE_ICONS = {
   [FILE_TYPES.WINDOW_MANAGER_TRACE]: WINDOW_MANAGER_ICON,
@@ -104,48 +106,48 @@
   [FILE_TYPES.PROTO_LOG]: PROTO_LOG_ICON,
   [FILE_TYPES.SYSTEM_UI]: SYSTEM_UI_ICON,
   [FILE_TYPES.LAUNCHER]: LAUNCHER_ICON,
-}
+};
 
 function oneOf(dataType) {
-  return { oneOf: true, type: dataType };
+  return {oneOf: true, type: dataType};
 }
 
 function manyOf(dataType, fold = null) {
-  return { manyOf: true, type: dataType, fold };
+  return {manyOf: true, type: dataType, fold};
 }
 
 const TRACE_TYPES = Object.freeze({
-  WINDOW_MANAGER: "WindowManagerTrace",
-  SURFACE_FLINGER: "SurfaceFlingerTrace",
-  SCREEN_RECORDING: "ScreenRecording",
-  TRANSACTION: "Transaction",
-  WAYLAND: "Wayland",
-  PROTO_LOG: "ProtoLog",
-  SYSTEM_UI: "SystemUI",
-  LAUNCHER: "Launcher"
+  WINDOW_MANAGER: 'WindowManagerTrace',
+  SURFACE_FLINGER: 'SurfaceFlingerTrace',
+  SCREEN_RECORDING: 'ScreenRecording',
+  TRANSACTION: 'Transaction',
+  WAYLAND: 'Wayland',
+  PROTO_LOG: 'ProtoLog',
+  SYSTEM_UI: 'SystemUI',
+  LAUNCHER: 'Launcher',
 });
 
 const TRACE_INFO = {
   [TRACE_TYPES.WINDOW_MANAGER]: {
-    name: "WindowManager",
+    name: 'WindowManager',
     icon: WINDOW_MANAGER_ICON,
     files: [oneOf(FILE_TYPES.WINDOW_MANAGER_TRACE)],
     constructor: WindowManagerTrace,
   },
   [TRACE_TYPES.SURFACE_FLINGER]: {
-    name: "SurfaceFlinger",
+    name: 'SurfaceFlinger',
     icon: SURFACE_FLINGER_ICON,
     files: [oneOf(FILE_TYPES.SURFACE_FLINGER_TRACE)],
     constructor: SurfaceFlingerTrace,
   },
   [TRACE_TYPES.SCREEN_RECORDING]: {
-    name: "Screen recording",
+    name: 'Screen recording',
     icon: SCREEN_RECORDING_ICON,
     files: [oneOf(FILE_TYPES.SCREEN_RECORDING)],
     constructor: ScreenRecordingTrace,
   },
   [TRACE_TYPES.TRANSACTION]: {
-    name: "Transaction",
+    name: 'Transaction',
     icon: TRANSACTION_ICON,
     files: [
       oneOf(FILE_TYPES.TRANSACTIONS_TRACE),
@@ -154,62 +156,62 @@
     constructor: TransactionsTrace,
   },
   [TRACE_TYPES.WAYLAND]: {
-    name: "Wayland",
+    name: 'Wayland',
     icon: WAYLAND_ICON,
     files: [oneOf(FILE_TYPES.WAYLAND_TRACE)],
     constructor: WaylandTrace,
   },
   [TRACE_TYPES.PROTO_LOG]: {
-    name: "ProtoLog",
+    name: 'ProtoLog',
     icon: PROTO_LOG_ICON,
     files: [oneOf(FILE_TYPES.PROTO_LOG)],
     constructor: ProtoLogTrace,
   },
   [TRACE_TYPES.SYSTEM_UI]: {
-    name: "SystemUI",
+    name: 'SystemUI',
     icon: SYSTEM_UI_ICON,
     files: [oneOf(FILE_TYPES.SYSTEM_UI)],
     constructor: SystemUITrace,
   },
   [TRACE_TYPES.LAUNCHER]: {
-    name: "Launcher",
+    name: 'Launcher',
     icon: LAUNCHER_ICON,
     files: [oneOf(FILE_TYPES.LAUNCHER)],
     constructor: LauncherTrace,
   },
-}
+};
 
 const DUMP_TYPES = Object.freeze({
-  WINDOW_MANAGER: "WindowManagerDump",
-  SURFACE_FLINGER: "SurfaceFlingerDump",
-  WAYLAND: "WaylandDump",
+  WINDOW_MANAGER: 'WindowManagerDump',
+  SURFACE_FLINGER: 'SurfaceFlingerDump',
+  WAYLAND: 'WaylandDump',
 });
 
 const DUMP_INFO = {
   [DUMP_TYPES.WINDOW_MANAGER]: {
-    name: "WindowManager",
+    name: 'WindowManager',
     icon: WINDOW_MANAGER_ICON,
     files: [oneOf(FILE_TYPES.WINDOW_MANAGER_DUMP)],
     constructor: WindowManagerDump,
   },
   [DUMP_TYPES.SURFACE_FLINGER]: {
-    name: "SurfaceFlinger",
+    name: 'SurfaceFlinger',
     icon: SURFACE_FLINGER_ICON,
     files: [oneOf(FILE_TYPES.SURFACE_FLINGER_DUMP)],
     constructor: SurfaceFlingerDump,
   },
   [DUMP_TYPES.WAYLAND]: {
-    name: "Wayland",
+    name: 'Wayland',
     icon: WAYLAND_ICON,
     files: [oneOf(FILE_TYPES.WAYLAND_DUMP)],
     constructor: WaylandDump,
   },
-}
+};
 
 // TODO: Rename name to defaultName
 const FILE_DECODERS = {
   [FILE_TYPES.WINDOW_MANAGER_TRACE]: {
-    name: "WindowManager trace",
+    name: 'WindowManager trace',
     decoder: protoDecoder,
     decoderParams: {
       type: FILE_TYPES.WINDOW_MANAGER_TRACE,
@@ -219,123 +221,123 @@
     },
   },
   [FILE_TYPES.SURFACE_FLINGER_TRACE]: {
-    name: "SurfaceFlinger trace",
+    name: 'SurfaceFlinger trace',
     decoder: protoDecoder,
     decoderParams: {
       type: FILE_TYPES.SURFACE_FLINGER_TRACE,
-      mime: "application/octet-stream",
+      mime: 'application/octet-stream',
       protoType: SfTraceMessage,
       transform: transform_layers_trace,
       timeline: true,
     },
   },
   [FILE_TYPES.WAYLAND_TRACE]: {
-    name: "Wayland trace",
+    name: 'Wayland trace',
     decoder: protoDecoder,
     decoderParams: {
       type: FILE_TYPES.WAYLAND_TRACE,
-      mime: "application/octet-stream",
+      mime: 'application/octet-stream',
       protoType: WaylandTraceMessage,
       transform: transform_wayland_trace,
       timeline: true,
     },
   },
   [FILE_TYPES.SURFACE_FLINGER_DUMP]: {
-    name: "SurfaceFlinger dump",
+    name: 'SurfaceFlinger dump',
     decoder: protoDecoder,
     decoderParams: {
       type: FILE_TYPES.SURFACE_FLINGER_DUMP,
-      mime: "application/octet-stream",
+      mime: 'application/octet-stream',
       protoType: SfDumpMessage,
-      transform: (decoded) => transform_layers(true /*includesCompositionState*/, decoded),
+      transform: (decoded) => transform_layers(true /* includesCompositionState*/, decoded),
       timeline: false,
     },
   },
   [FILE_TYPES.WINDOW_MANAGER_DUMP]: {
-    name: "WindowManager dump",
+    name: 'WindowManager dump',
     decoder: protoDecoder,
     decoderParams: {
       type: FILE_TYPES.WINDOW_MANAGER_DUMP,
-      mime: "application/octet-stream",
+      mime: 'application/octet-stream',
       protoType: WmDumpMessage,
       transform: transform_window_service,
       timeline: false,
     },
   },
   [FILE_TYPES.WAYLAND_DUMP]: {
-    name: "Wayland dump",
+    name: 'Wayland dump',
     decoder: protoDecoder,
     decoderParams: {
       type: FILE_TYPES.WAYLAND_DUMP,
-      mime: "application/octet-stream",
+      mime: 'application/octet-stream',
       protoType: WaylandDumpMessage,
       transform: transform_wl_outputstate,
       timeline: false,
     },
   },
   [FILE_TYPES.SCREEN_RECORDING]: {
-    name: "Screen recording",
+    name: 'Screen recording',
     decoder: videoDecoder,
     decoderParams: {
       type: FILE_TYPES.SCREEN_RECORDING,
-      mime: "video/mp4",
+      mime: 'video/mp4',
       videoDecoder: mp4Decoder,
     },
   },
   [FILE_TYPES.TRANSACTIONS_TRACE]: {
-    name: "Transaction",
+    name: 'Transaction',
     decoder: protoDecoder,
     decoderParams: {
       type: FILE_TYPES.TRANSACTIONS_TRACE,
-      mime: "application/octet-stream",
+      mime: 'application/octet-stream',
       protoType: SfTransactionTraceMessage,
       transform: transform_transaction_trace,
       timeline: true,
-    }
+    },
   },
   [FILE_TYPES.TRANSACTION_EVENTS_TRACE]: {
-    name: "Transaction merges",
+    name: 'Transaction merges',
     decoder: protoDecoder,
     decoderParams: {
       type: FILE_TYPES.TRANSACTION_EVENTS_TRACE,
-      mime: "application/octet-stream",
+      mime: 'application/octet-stream',
       protoType: SfTransactionEventsTraceMessage,
       transform: transform_TRANSACTION_EVENTS_TRACE,
       timeline: true,
-    }
+    },
   },
   [FILE_TYPES.PROTO_LOG]: {
-    name: "ProtoLog",
+    name: 'ProtoLog',
     decoder: protoDecoder,
     decoderParams: {
       type: FILE_TYPES.PROTO_LOG,
-      mime: "application/octet-stream",
+      mime: 'application/octet-stream',
       protoType: ProtoLogMessage,
       transform: transform_protolog,
       timeline: true,
-    }
+    },
   },
   [FILE_TYPES.SYSTEM_UI]: {
-    name: "SystemUI trace",
+    name: 'SystemUI trace',
     decoder: protoDecoder,
     decoderParams: {
       type: FILE_TYPES.SYSTEM_UI,
-      mime: "application/octet-stream",
+      mime: 'application/octet-stream',
       protoType: SystemUiTraceMessage,
       transform: transform_sysui_trace,
       timeline: true,
-    }
+    },
   },
   [FILE_TYPES.LAUNCHER]: {
-    name: "Launcher trace",
+    name: 'Launcher trace',
     decoder: protoDecoder,
     decoderParams: {
       type: FILE_TYPES.LAUNCHER,
-      mime: "application/octet-stream",
+      mime: 'application/octet-stream',
       protoType: LauncherTraceMessage,
       transform: transform_launcher_trace,
       timeline: true,
-    }
+    },
   },
 };
 
@@ -351,31 +353,33 @@
   if (!protoObj || protoObj !== Object(protoObj) || !protoObj.$type) {
     return;
   }
-  for (var fieldName in protoObj.$type.fields) {
-    var fieldProperties = protoObj.$type.fields[fieldName];
-    var field = protoObj[fieldName];
+  for (const fieldName in protoObj.$type.fields) {
+    if (protoObj.$type.fields.hasOwnProperty(fieldName)) {
+      const fieldProperties = protoObj.$type.fields[fieldName];
+      const field = protoObj[fieldName];
 
-    if (Array.isArray(field)) {
-      field.forEach((item, _) => {
-        modifyProtoFields(item, displayDefaults);
-      })
-      continue;
-    }
+      if (Array.isArray(field)) {
+        field.forEach((item, _) => {
+          modifyProtoFields(item, displayDefaults);
+        });
+        continue;
+      }
 
-    if (displayDefaults && !(field)) {
-      protoObj[fieldName] = fieldProperties.defaultValue;
-    }
+      if (displayDefaults && !(field)) {
+        protoObj[fieldName] = fieldProperties.defaultValue;
+      }
 
-    if (fieldProperties.type === 'TransformProto') {
-      fill_transform_data(protoObj[fieldName]);
-      continue;
-    }
+      if (fieldProperties.type === 'TransformProto') {
+        fill_transform_data(protoObj[fieldName]);
+        continue;
+      }
 
-    if (fieldProperties.resolvedType && fieldProperties.resolvedType.valuesById) {
-      protoObj[fieldName] = fieldProperties.resolvedType.valuesById[protoObj[fieldProperties.name]];
-      continue;
+      if (fieldProperties.resolvedType && fieldProperties.resolvedType.valuesById) {
+        protoObj[fieldName] = fieldProperties.resolvedType.valuesById[protoObj[fieldProperties.name]];
+        continue;
+      }
+      modifyProtoFields(protoObj[fieldName], displayDefaults);
     }
-    modifyProtoFields(protoObj[fieldName], displayDefaults);
   }
 }
 
@@ -395,13 +399,13 @@
   } else {
     data = [transformed];
   }
-  let blobUrl = URL.createObjectURL(new Blob([buffer], { type: params.mime }));
-  return dataFile(fileName, data.map(x => x.timestamp), data, blobUrl, params.type);
+  const blobUrl = URL.createObjectURL(new Blob([buffer], {type: params.mime}));
+  return dataFile(fileName, data.map((x) => x.timestamp), data, blobUrl, params.type);
 }
 
 function videoDecoder(buffer, params, fileName, store) {
-  let [data, timeline] = params.videoDecoder(buffer);
-  let blobUrl = URL.createObjectURL(new Blob([data], { type: params.mime }));
+  const [data, timeline] = params.videoDecoder(buffer);
+  const blobUrl = URL.createObjectURL(new Blob([data], {type: params.mime}));
   return dataFile(fileName, timeline, blobUrl, blobUrl, params.type);
 }
 
@@ -418,14 +422,14 @@
     destroy() {
       URL.revokeObjectURL(this.blobUrl);
     },
-  }
+  };
 }
 
 function arrayEquals(a, b) {
   if (a.length !== b.length) {
     return false;
   }
-  for (var i = 0; i < a.length; i++) {
+  for (let i = 0; i < a.length; i++) {
     if (a[i] != b[i]) {
       return false;
     }
@@ -473,7 +477,7 @@
     [FILE_TYPES.TRANSACTION_EVENTS_TRACE], // TODO: Add magic number at begging of file for better auto detection
   ]) {
     try {
-      const [_, fileData] = decodedFile(filetype, buffer, fileName, store);
+      const [, fileData] = decodedFile(filetype, buffer, fileName, store);
 
       // A generic file will often wrongly be decoded as an empty wayland dump file
       if (condition && !condition(fileData)) {
@@ -495,4 +499,4 @@
  */
 class UndetectableFileType extends Error { }
 
-export { detectAndDecode, decodeAndTransformProto, FILE_TYPES, TRACE_INFO, TRACE_TYPES, DUMP_TYPES, DUMP_INFO, FILE_DECODERS, FILE_ICONS, UndetectableFileType };
+export {detectAndDecode, decodeAndTransformProto, FILE_TYPES, TRACE_INFO, TRACE_TYPES, DUMP_TYPES, DUMP_INFO, FILE_DECODERS, FILE_ICONS, UndetectableFileType};
diff --git a/tools/winscope/src/traces/Transactions.js b/tools/winscope/src/traces/Transactions.js
index 67be26e..5d08844 100644
--- a/tools/winscope/src/traces/Transactions.js
+++ b/tools/winscope/src/traces/Transactions.js
@@ -14,23 +14,19 @@
  * limitations under the License.
  */
 
-import { FILE_TYPES, TRACE_TYPES } from "@/decode.js";
+import {FILE_TYPES, TRACE_TYPES} from '@/decode.js';
 import TraceBase from './TraceBase.js';
 
 export default class Transactions extends TraceBase {
   constructor(files) {
     const transactionsFile = files[FILE_TYPES.TRANSACTIONS_TRACE];
 
-    // There should be one file for each process which recorded transaction events
+    // There should be one file for each process which recorded transaction
+    // events
     const transactionsEventsFiles = files[FILE_TYPES.TRANSACTION_EVENTS_TRACE];
 
     super(transactionsFile.data, transactionsFile.timeline);
 
-    const transactions = transactionsFile.data
-      .filter(e => e.type === "transaction")
-      .map(e => e.transactions)
-      .flat();
-
     this.transactionsFile = transactionsFile;
     this.transactionsEventsFiles = transactionsEventsFiles;
 
@@ -55,12 +51,13 @@
       for (const event of eventsFile.data) {
         if (event.merge) {
           const merge = event.merge;
-          const originalId = merge.originalTransaction.identifier.id;
-          const mergedId = merge.mergedTransaction.identifier.id;
+          const originalId = merge.originalTransaction.id;
+          const mergedId = merge.mergedTransaction.id;
 
           this.addMerge(originalId, mergedId);
         } else if (event.apply) {
-          this.addApply(event.apply.identifier.id);
+          console.log(event);
+          this.addApply(event.apply.tx_id);
         }
       }
     }
@@ -98,21 +95,60 @@
       const event = events[i];
 
       if (event instanceof Merge) {
-        const historyTree = this._generateHistoryTree(event.mergedId, event.mergedAt);
+        const historyTree = this.
+            _generateHistoryTree(event.mergedId, event.mergedAt);
         const mergeTreeNode = new MergeTreeNode(event.mergedId, historyTree);
         children.push(mergeTreeNode);
       } else if (event instanceof Apply) {
         children.push(new ApplyTreeNode());
       } else {
-        throw new Error("Unhandled event type");
+        throw new Error('Unhandled event type');
       }
     }
 
     return children;
   }
 
-  getMergeTreeOf(transactionId) {
-    return this.mergeTrees[transactionId];
+  /**
+   * Generates the list of all the transactions that have ever been merged into
+   * the target transaction directly or indirectly through the merges of
+   * transactions that ended up being merged into the transaction.
+   * This includes both merges that occur before and after the transaction is
+   * applied.
+   * @param {Number} transactionId - The id of the transaction we want the list
+   *                                 of transactions merged in for
+   * @return {Set<Number>} a set of all the transaction ids that are in the
+   *                       history of merges of the transaction
+   */
+  allTransactionsMergedInto(transactionId) {
+    const allTransactionsMergedIn = new Set();
+
+    let event;
+    const toVisit = this.generateHistoryTreesOf(transactionId);
+    while (event = toVisit.pop()) {
+      if (event instanceof MergeTreeNode) {
+        allTransactionsMergedIn.add(event.mergedId);
+        for (const child of event.children) {
+          toVisit.push(child);
+        }
+      }
+    }
+
+    return allTransactionsMergedIn;
+  }
+
+  /**
+   * Generated the list of transactions that have been directly merged into the
+   * target transaction those are transactions that have explicitly been merged
+   * in the code with a call to merge.
+   * @param {Number} transactionId - The id of the target transaction.
+   * @return {Array<Number>} an array of the transaction ids of the transactions
+   *                        directly merged into the target transaction
+   */
+  allDirectMergesInto(transactionId) {
+    return (this.history[transactionId] ?? [])
+        .filter((event) => event instanceof Merge)
+        .map((merge) => merge.mergedId);
   }
 }
 
@@ -124,7 +160,7 @@
   }
 
   get type() {
-    return "merge";
+    return 'merge';
   }
 }
 
@@ -134,7 +170,7 @@
   }
 
   get type() {
-    return "apply";
+    return 'apply';
   }
 }
 
@@ -142,7 +178,8 @@
   constructor(originalId, mergedId, history) {
     this.originalId = originalId;
     this.mergedId = mergedId;
-    // Specifies how long the merge chain of the merged transaction was at the time is was merged.
+    // Specifies how long the merge chain of the merged transaction was at the
+    // time is was merged.
     this.mergedAt = history[mergedId]?.length ?? 0;
   }
 }
@@ -151,4 +188,23 @@
   constructor(transactionId) {
     this.transactionId = transactionId;
   }
-}
\ No newline at end of file
+}
+
+/**
+ * Converts the transactionId to the values that compose the identifier.
+ * The top 32 bits is the PID of the process that created the transaction
+ * and the bottom 32 bits is the ID of the transaction unique within that
+ * process.
+ * @param {Number} transactionId
+ * @return {Object} An object containing the id and pid of the transaction.
+ */
+export function expandTransactionId(transactionId) {
+  console.log('Expanding', transactionId);
+  // Can't use bit shift operation because it isn't a 32 bit integer...
+  // Because js uses floating point numbers for everything, maths isn't 100%
+  // accurate so we need to round...
+  return Object.freeze({
+    id: Math.round(transactionId % Math.pow(2, 32)),
+    pid: Math.round(transactionId / Math.pow(2, 32)),
+  });
+}