diff --git a/.circleci/config.yml b/.circleci/config.yml
index c9beed1..2de3de1 100644
--- a/.circleci/config.yml
+++ b/.circleci/config.yml
@@ -10,8 +10,7 @@ parameters:
orbs:
macos: circleci/macos@2.5.4
- # android: circleci/android@3.1.0
- codecov: codecov/codecov@5.4.3
+ codecov: codecov/codecov@6.0.0
executors:
base:
diff --git a/Package.swift b/Package.swift
index 3f54ec5..695923f 100644
--- a/Package.swift
+++ b/Package.swift
@@ -12,6 +12,7 @@ let excluded = ["ViewInspector", "UITests", "Tests", "BUILD"]
let playerUIDependency: Target.Dependency = .product(name: "PlayerUI", package: "playerui-swift-package")
let playerUILoggerDependency: Target.Dependency = .product(name: "PlayerUILogger", package: "playerui-swift-package")
+let playerUISwiftUIDependency: Target.Dependency = .product(name: "PlayerUISwiftUI", package: "playerui-swift-package")
let swiftFlipperDependency: Target.Dependency = .product(name: "SwiftFlipper", package: "SwiftFlipper")
let utils: Target = .target(
@@ -105,6 +106,30 @@ let basicPlugin: Target = .target(
exclude: excluded
)
+let baseProfilerDevtoolsPlugin: Target = .target(
+ name: "PlayerUIDevtoolsBaseProfilerDevtoolsPlugin",
+ dependencies: [
+ playerUIDependency,
+ playerUILoggerDependency,
+ "PlayerUIDevtoolsPlugin",
+ "PlayerUIDevtoolsUtilsSwiftUI"
+ ],
+ path: "devtools/plugins/profiler/ios",
+ exclude: excluded,
+ resources: [.process("Resources")]
+)
+
+let profilerPlugin: Target = .target(
+ name: "PlayerUIDevtoolsProfilerPlugin",
+ dependencies: [
+ swiftFlipperDependency,
+ "PlayerUIDevtoolsSwiftUIPlugin",
+ "PlayerUIDevtoolsBaseProfilerDevtoolsPlugin"
+ ],
+ path: "devtools/plugins/profiler/swiftui",
+ exclude: excluded
+)
+
// --- END DECLARATIONS ---
let allTargets: [Target] = [
@@ -115,7 +140,9 @@ let allTargets: [Target] = [
plugin,
swiftUIPlugin,
baseBasicDevtoolsPlugin,
- basicPlugin
+ basicPlugin,
+ baseProfilerDevtoolsPlugin,
+ profilerPlugin
]
// This is the Package.swift for our SPM release.
diff --git a/devtools/plugin/core/src/helpers/getNowTime.ts b/devtools/plugin/core/src/helpers/getNowTime.ts
new file mode 100644
index 0000000..fd3192d
--- /dev/null
+++ b/devtools/plugin/core/src/helpers/getNowTime.ts
@@ -0,0 +1,3 @@
+export const getNowTime = globalThis.performance
+ ? () => globalThis.performance.now()
+ : () => Date.now();
diff --git a/devtools/plugin/core/src/helpers/index.ts b/devtools/plugin/core/src/helpers/index.ts
index 13ceb14..18dd223 100644
--- a/devtools/plugin/core/src/helpers/index.ts
+++ b/devtools/plugin/core/src/helpers/index.ts
@@ -1,2 +1,3 @@
export { generateUUID } from "./uuid";
export { genDataChangeTransaction } from "./genDataChangeTransaction";
+export { getNowTime } from "./getNowTime";
diff --git a/devtools/plugin/core/src/helpers/uuid.ts b/devtools/plugin/core/src/helpers/uuid.ts
index 0d29030..3c0419f 100644
--- a/devtools/plugin/core/src/helpers/uuid.ts
+++ b/devtools/plugin/core/src/helpers/uuid.ts
@@ -1,11 +1,10 @@
+import { getNowTime } from "./getNowTime";
+
+// TODO: Either polyfill crypto or use this (pulled from SO)
export function generateUUID(): string {
// Public Domain/MIT
let d = new Date().getTime(); //Timestamp
- let d2 =
- (typeof performance !== "undefined" &&
- performance.now &&
- performance.now() * 1000) ||
- 0; //Time in microseconds since page-load or 0 if unsupported
+ let d2 = getNowTime() * 1000;
return "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, function (c) {
let r = Math.random() * 16; //random number between 0 and 16
if (d > 0) {
diff --git a/devtools/plugins/profiler/android/BUILD b/devtools/plugins/profiler/android/BUILD
new file mode 100644
index 0000000..9626e3c
--- /dev/null
+++ b/devtools/plugins/profiler/android/BUILD
@@ -0,0 +1,34 @@
+# Android lib that consumes jvm similar to how react consumes core
+load("@rules_jvm_external//:defs.bzl", "artifact")
+load("//helpers:android.bzl", "kt_android")
+
+main_exports = [
+ "//devtools/plugin/android:plugin-android",
+]
+
+main_deps = main_exports + [
+ "//devtools/plugins/profiler/jvm:profiler-plugin",
+]
+
+main_resources = []
+
+test_deps = [
+ "//helpers:kotlin_serialization",
+ artifact("com.intuit.playerui:testutils"),
+ artifact("com.intuit.playerui:j2v8-all"),
+]
+
+kt_android(
+ name = "profiler-android",
+ group = "com.intuit.playerui.plugins.devtools.profiler",
+ main_deps = main_deps,
+ main_exports = main_exports,
+ main_resources = main_resources,
+ unit_test_deps = test_deps,
+ unit_test_package = "com.intuit.playerui.plugins.devtools.profiler",
+)
+
+alias(
+ name = "android",
+ actual = "profiler-android",
+)
diff --git a/devtools/plugins/profiler/android/src/main/AndroidManifest.xml b/devtools/plugins/profiler/android/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..d086f67
--- /dev/null
+++ b/devtools/plugins/profiler/android/src/main/AndroidManifest.xml
@@ -0,0 +1,14 @@
+
+
+
+
+
+
+
+
+
diff --git a/devtools/plugins/profiler/android/src/main/kotlin/com/intuit/playerui/plugins/devtools/profiler/ProfilerAndroidDevtoolsPlugin.kt b/devtools/plugins/profiler/android/src/main/kotlin/com/intuit/playerui/plugins/devtools/profiler/ProfilerAndroidDevtoolsPlugin.kt
new file mode 100644
index 0000000..a9145dd
--- /dev/null
+++ b/devtools/plugins/profiler/android/src/main/kotlin/com/intuit/playerui/plugins/devtools/profiler/ProfilerAndroidDevtoolsPlugin.kt
@@ -0,0 +1,29 @@
+package com.intuit.playerui.plugins.devtools.profiler
+
+import androidx.annotation.StyleRes
+import com.intuit.playerui.android.AndroidPlayer
+import com.intuit.playerui.core.bridge.runtime.Runtime
+import com.intuit.playerui.devtools.AndroidDevtoolsPlugin
+import com.intuit.playerui.plugins.devtools.profiler.ProfilerDevtoolsPlugin.Module.ProfilerDevtoolsPlugin
+
+public class ProfilerAndroidDevtoolsPlugin(
+ private val id: String,
+ @StyleRes private val overlayStyle: Int? = R.style.ProfilerAndroidDevtoolsPlugin,
+) : AndroidDevtoolsPlugin() {
+ override fun Runtime<*>.buildCorePlugin(): ProfilerDevtoolsPlugin =
+ ProfilerDevtoolsPlugin(
+ ProfilerDevtoolsPlugin.Options(id, this@ProfilerAndroidDevtoolsPlugin),
+ )
+
+ override fun apply(androidPlayer: AndroidPlayer) {
+ if (!checkIfDevtoolsIsActive()) return
+
+ super.apply(androidPlayer)
+
+ overlayStyle?.let(::listOf)?.let {
+ androidPlayer.hooks.context.tap(this::class.simpleName!!) { _, context ->
+ androidPlayer.getCachedStyledContext(context, it)
+ }
+ }
+ }
+}
diff --git a/devtools/plugins/profiler/android/src/main/res/values/styles.xml b/devtools/plugins/profiler/android/src/main/res/values/styles.xml
new file mode 100644
index 0000000..05f069f
--- /dev/null
+++ b/devtools/plugins/profiler/android/src/main/res/values/styles.xml
@@ -0,0 +1,6 @@
+
+
+
+
diff --git a/devtools/plugins/profiler/content/BUILD b/devtools/plugins/profiler/content/BUILD
new file mode 100644
index 0000000..396c5d4
--- /dev/null
+++ b/devtools/plugins/profiler/content/BUILD
@@ -0,0 +1,47 @@
+load("@npm//:defs.bzl", "npm_link_all_packages")
+load("@rules_player//javascript:defs.bzl", "js_pipeline")
+load("@rules_player//player:defs.bzl", "dsl_compile", create_base_dsl_config = "create_base_config")
+load("//helpers:defs.bzl", "tsup_config", "vitest_config")
+
+npm_link_all_packages(name = "node_modules")
+
+tsup_config(name = "tsup_config")
+
+vitest_config(name = "vitest_config")
+
+create_base_dsl_config(
+ name = "dsl_config",
+ presets = [],
+)
+
+entrypoint = ["src/flow.tsx"]
+
+dsl_compile(
+ name = "compiled_flow",
+ srcs = entrypoint,
+ config = ":dsl_config",
+ data = glob(
+ ["src/**"],
+ entrypoint,
+ ) + [
+ "//:node_modules/@devtools-ui/plugin",
+ "//:node_modules/@player-tools/dsl",
+ "//:node_modules/@player-ui/common-types-plugin",
+ "//:node_modules/react",
+ ],
+ input_dir = "src",
+ output_dir = "_generated",
+ skip_test = True,
+)
+
+js_pipeline(
+ package_name = "@player-devtools/profiler-plugin-content",
+ srcs = [
+ "src/constants.ts",
+ "src/index.ts",
+ ":compiled_flow",
+ ],
+ deps = [
+ ":node_modules/@player-devtools/types",
+ ],
+)
diff --git a/devtools/plugins/profiler/content/package.json b/devtools/plugins/profiler/content/package.json
new file mode 100644
index 0000000..7024d45
--- /dev/null
+++ b/devtools/plugins/profiler/content/package.json
@@ -0,0 +1,8 @@
+{
+ "name": "@player-devtools/profiler-plugin-content",
+ "version": "0.0.0-PLACEHOLDER",
+ "main": "src/index.ts",
+ "dependencies": {
+ "@player-devtools/types": "workspace:*"
+ }
+}
diff --git a/devtools/plugins/profiler/content/src/common/ProfilerFooter.tsx b/devtools/plugins/profiler/content/src/common/ProfilerFooter.tsx
new file mode 100644
index 0000000..fab225a
--- /dev/null
+++ b/devtools/plugins/profiler/content/src/common/ProfilerFooter.tsx
@@ -0,0 +1,30 @@
+import React from "react";
+import { Collection, Action, Text } from "@devtools-ui/plugin";
+import { expression as e } from "@player-tools/dsl";
+import type { Expression } from "@player-tools/dsl";
+import { INTERACTIONS } from "../constants";
+import { bindings } from "../schema";
+
+const toggleProfiling = e`conditional(${bindings.profiling} === true, publish('${INTERACTIONS.STOP_PROFILING}'), publish('${INTERACTIONS.START_PROFILING}'))`;
+
+const toggleLabel = e`conditional(${bindings.profiling} === true, 'Stop', 'Start')`;
+
+const reset = e`publish('${INTERACTIONS.RESET_PROFILING}')`;
+
+/** Shared footer with a Start/Stop toggle and a Reset action. */
+export const ProfilerFooter = (
+
+
+
+
+ {toggleLabel}
+
+
+
+
+ Reset
+
+
+
+
+);
diff --git a/devtools/plugins/profiler/content/src/common/Screen.tsx b/devtools/plugins/profiler/content/src/common/Screen.tsx
new file mode 100644
index 0000000..cf3df4e
--- /dev/null
+++ b/devtools/plugins/profiler/content/src/common/Screen.tsx
@@ -0,0 +1,41 @@
+import React from "react";
+import { Navigation, Action, Text, StackedView } from "@devtools-ui/plugin";
+import { VIEWS_IDS } from "../constants";
+
+/** Display labels for the navigation actions, keyed by view id. */
+const NAV_LABELS: Record = {
+ [VIEWS_IDS.PROFILE]: "Flame Graph",
+ [VIEWS_IDS.RAW]: "Raw",
+};
+
+const Nav = () => (
+
+
+ {Object.values(VIEWS_IDS).map((viewId) => (
+
+
+ {NAV_LABELS[viewId] ?? viewId}
+
+
+ ))}
+
+
+);
+
+export const Screen = ({
+ main,
+ footer,
+ id,
+}: {
+ id: string;
+ main: React.ReactNode;
+ footer?: React.ReactNode;
+}) => (
+
+
+
+
+ {main}
+ {footer && {footer}}
+
+);
diff --git a/devtools/plugins/profiler/content/src/common/index.ts b/devtools/plugins/profiler/content/src/common/index.ts
new file mode 100644
index 0000000..389cd67
--- /dev/null
+++ b/devtools/plugins/profiler/content/src/common/index.ts
@@ -0,0 +1,2 @@
+export * from "./Screen";
+export * from "./ProfilerFooter";
diff --git a/devtools/plugins/profiler/content/src/constants.ts b/devtools/plugins/profiler/content/src/constants.ts
new file mode 100644
index 0000000..3dfecec
--- /dev/null
+++ b/devtools/plugins/profiler/content/src/constants.ts
@@ -0,0 +1,12 @@
+export const PLUGIN_ID = "player-ui-profiler-plugin";
+
+export const VIEWS_IDS = {
+ PROFILE: "Profile",
+ RAW: "Raw",
+};
+
+export const INTERACTIONS = {
+ START_PROFILING: "start-profiling",
+ STOP_PROFILING: "stop-profiling",
+ RESET_PROFILING: "reset-profiling",
+};
diff --git a/devtools/plugins/profiler/content/src/flow.tsx b/devtools/plugins/profiler/content/src/flow.tsx
new file mode 100644
index 0000000..cd09bc8
--- /dev/null
+++ b/devtools/plugins/profiler/content/src/flow.tsx
@@ -0,0 +1,19 @@
+import { PLUGIN_ID } from "./constants";
+import { navigation } from "./navigation";
+import { schema } from "./schema";
+import { views } from "./views";
+
+export default {
+ id: PLUGIN_ID,
+ views,
+ navigation,
+ schema,
+ data: {
+ profiling: false,
+ displayFlameGraph: false,
+ rootNode: {
+ value: 0,
+ },
+ rawNodes: [],
+ },
+};
diff --git a/devtools/plugins/profiler/content/src/index.ts b/devtools/plugins/profiler/content/src/index.ts
new file mode 100644
index 0000000..e2f489c
--- /dev/null
+++ b/devtools/plugins/profiler/content/src/index.ts
@@ -0,0 +1,22 @@
+import type { PluginData } from "@player-devtools/types";
+import { PLUGIN_ID } from "./constants";
+
+// Generated via dsl_compile target
+import flow from "../_generated/flow.json";
+
+declare global {
+ const __VERSION__: string;
+}
+
+const PLUGIN_VERSION =
+ typeof __VERSION__ !== "undefined" ? __VERSION__ : "unstamped";
+
+export const ProfilerPluginData: PluginData = {
+ id: PLUGIN_ID,
+ name: "Player UI Profiler",
+ description: "Standard Player UI Profiler",
+ version: PLUGIN_VERSION,
+ flow,
+};
+
+export * from "./constants";
diff --git a/devtools/plugins/profiler/content/src/navigation/index.tsx b/devtools/plugins/profiler/content/src/navigation/index.tsx
new file mode 100644
index 0000000..e846fa4
--- /dev/null
+++ b/devtools/plugins/profiler/content/src/navigation/index.tsx
@@ -0,0 +1,27 @@
+import { VIEWS_IDS } from "../constants";
+
+const transitions = Object.entries(VIEWS_IDS).reduce(
+ (acc, [key, value]) => ({
+ ...acc,
+ [value]: key,
+ }),
+ {} as Record,
+);
+
+export const navigation = {
+ BEGIN: "Plugin",
+ Plugin: {
+ startState: Object.keys(VIEWS_IDS)[0],
+ ...Object.entries(VIEWS_IDS).reduce(
+ (acc, [key, value]) => ({
+ ...acc,
+ [key]: {
+ state_type: "VIEW",
+ ref: value,
+ transitions,
+ },
+ }),
+ {} as Record,
+ ),
+ },
+};
diff --git a/devtools/plugins/profiler/content/src/schema/index.tsx b/devtools/plugins/profiler/content/src/schema/index.tsx
new file mode 100644
index 0000000..657475e
--- /dev/null
+++ b/devtools/plugins/profiler/content/src/schema/index.tsx
@@ -0,0 +1,17 @@
+import { dataTypes } from "@player-ui/common-types-plugin";
+import type { Schema } from "@player-ui/types";
+import { makeBindingsForObject } from "@player-tools/dsl";
+
+const RecordType: Schema.DataType> = {
+ type: "RecordType",
+};
+
+export const schema = {
+ profiling: dataTypes.BooleanType,
+ displayFlameGraph: dataTypes.BooleanType,
+ rootNode: RecordType,
+ rootNodes: RecordType,
+ rawNodes: RecordType,
+};
+
+export const bindings = makeBindingsForObject(schema);
diff --git a/devtools/plugins/profiler/content/src/views/ProfileView.tsx b/devtools/plugins/profiler/content/src/views/ProfileView.tsx
new file mode 100644
index 0000000..0a7aefb
--- /dev/null
+++ b/devtools/plugins/profiler/content/src/views/ProfileView.tsx
@@ -0,0 +1,27 @@
+import React from "react";
+import { FlameGraph } from "@devtools-ui/plugin";
+import { expression as e, binding as b } from "@player-tools/dsl";
+import type {
+ BindingTemplateInstance,
+ ExpressionTemplateInstance,
+} from "@player-tools/dsl";
+import { VIEWS_IDS } from "../constants";
+import { Screen, ProfilerFooter } from "../common";
+import { bindings } from "../schema";
+
+// `rootNode.value` is populated at runtime by the transform, so reference it
+// directly rather than through the schema-derived bindings.
+const width = e`${b`rootNode.value`} / 200`;
+
+export const ProfileView = (
+ }
+ />
+ }
+ footer={ProfilerFooter}
+ />
+);
diff --git a/devtools/plugins/profiler/content/src/views/RawView.tsx b/devtools/plugins/profiler/content/src/views/RawView.tsx
new file mode 100644
index 0000000..d9b9d75
--- /dev/null
+++ b/devtools/plugins/profiler/content/src/views/RawView.tsx
@@ -0,0 +1,16 @@
+import React from "react";
+import { ObjectInspector } from "@devtools-ui/plugin";
+import type { BindingTemplateInstance } from "@player-tools/dsl";
+import { VIEWS_IDS } from "../constants";
+import { Screen, ProfilerFooter } from "../common";
+import { bindings } from "../schema";
+
+export const RawView = (
+
+ }
+ footer={ProfilerFooter}
+ />
+);
diff --git a/devtools/plugins/profiler/content/src/views/index.tsx b/devtools/plugins/profiler/content/src/views/index.tsx
new file mode 100644
index 0000000..030a80a
--- /dev/null
+++ b/devtools/plugins/profiler/content/src/views/index.tsx
@@ -0,0 +1,4 @@
+import { ProfileView } from "./ProfileView";
+import { RawView } from "./RawView";
+
+export const views = [ProfileView, RawView];
diff --git a/devtools/plugins/profiler/core/BUILD b/devtools/plugins/profiler/core/BUILD
new file mode 100644
index 0000000..33aa9a1
--- /dev/null
+++ b/devtools/plugins/profiler/core/BUILD
@@ -0,0 +1,29 @@
+load("@npm//:defs.bzl", "npm_link_all_packages")
+load("@rules_player//javascript:defs.bzl", "js_pipeline")
+load("//helpers:defs.bzl", "NATIVE_BUILD_DEPS", "tsup_config", "vitest_config")
+
+npm_link_all_packages(name = "node_modules")
+
+tsup_config(name = "tsup_config")
+
+vitest_config(name = "vitest_config")
+
+js_pipeline(
+ package_name = "@player-devtools/profiler-plugin",
+ build_deps = NATIVE_BUILD_DEPS,
+ native_bundle = "ProfilerDevtoolsPlugin",
+ deps = [
+ ":node_modules/@player-devtools/messenger",
+ ":node_modules/@player-devtools/plugin",
+ ":node_modules/@player-devtools/profiler-plugin-content",
+ ":node_modules/@player-devtools/types",
+ "//:node_modules/@devtools-ui/plugin",
+ "//:node_modules/@player-ui/player",
+ "//:node_modules/@types/uuid",
+ "//:node_modules/dequal",
+ "//:node_modules/dset",
+ "//:node_modules/immer",
+ "//:node_modules/uuid",
+ "//:node_modules/tapable-ts",
+ ],
+)
diff --git a/devtools/plugins/profiler/core/package.json b/devtools/plugins/profiler/core/package.json
new file mode 100644
index 0000000..a189fd9
--- /dev/null
+++ b/devtools/plugins/profiler/core/package.json
@@ -0,0 +1,11 @@
+{
+ "name": "@player-devtools/profiler-plugin",
+ "version": "0.0.0-PLACEHOLDER",
+ "main": "src/index.ts",
+ "dependencies": {
+ "@player-devtools/messenger": "workspace:*",
+ "@player-devtools/plugin": "workspace:*",
+ "@player-devtools/profiler-plugin-content": "workspace:*",
+ "@player-devtools/types": "workspace:*"
+ }
+}
diff --git a/devtools/plugins/profiler/core/src/__tests__/__snapshots__/plugin.test.ts.snap b/devtools/plugins/profiler/core/src/__tests__/__snapshots__/plugin.test.ts.snap
new file mode 100644
index 0000000..a362c0c
--- /dev/null
+++ b/devtools/plugins/profiler/core/src/__tests__/__snapshots__/plugin.test.ts.snap
@@ -0,0 +1,1484 @@
+// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
+
+exports[`Plugin > should profile player hooks when navigating through a flow 1`] = `
+{
+ "displayFlameGraph": true,
+ "profiling": false,
+ "rawNodes": [
+ {
+ "children": [],
+ "endTime": 2490.2999999999997,
+ "name": "state",
+ "startTime": 2490.2,
+ "tooltip": "state, 0.1000 (ms)",
+ "value": 100,
+ },
+ {
+ "children": [],
+ "endTime": 2490.5999999999995,
+ "name": "resolveFlowContent",
+ "startTime": 2490.4999999999995,
+ "tooltip": "resolveFlowContent, 0.1000 (ms)",
+ "value": 100,
+ },
+ {
+ "children": [],
+ "endTime": 2490.899999999999,
+ "name": "onStart",
+ "startTime": 2490.7999999999993,
+ "tooltip": "onStart, 0.1000 (ms)",
+ "value": 100,
+ },
+ {
+ "children": [],
+ "endTime": 2491.199999999999,
+ "name": "flowController",
+ "startTime": 2491.099999999999,
+ "tooltip": "flowController, 0.1000 (ms)",
+ "value": 100,
+ },
+ {
+ "children": [],
+ "endTime": 2491.4999999999986,
+ "name": "bindingParser",
+ "startTime": 2491.3999999999987,
+ "tooltip": "bindingParser, 0.1000 (ms)",
+ "value": 100,
+ },
+ {
+ "children": [],
+ "endTime": 2491.7999999999984,
+ "name": "schema",
+ "startTime": 2491.6999999999985,
+ "tooltip": "schema, 0.1000 (ms)",
+ "value": 100,
+ },
+ {
+ "children": [],
+ "endTime": 2492.099999999998,
+ "name": "validationController",
+ "startTime": 2491.999999999998,
+ "tooltip": "validationController, 0.1000 (ms)",
+ "value": 100,
+ },
+ {
+ "children": [],
+ "endTime": 2492.399999999998,
+ "name": "expressionEvaluator",
+ "startTime": 2492.299999999998,
+ "tooltip": "expressionEvaluator, 0.1000 (ms)",
+ "value": 100,
+ },
+ {
+ "children": [],
+ "endTime": 2492.6999999999975,
+ "name": "dataController",
+ "startTime": 2492.5999999999976,
+ "tooltip": "dataController, 0.1000 (ms)",
+ "value": 100,
+ },
+ {
+ "children": [],
+ "endTime": 2492.9999999999973,
+ "name": "viewController",
+ "startTime": 2492.8999999999974,
+ "tooltip": "viewController, 0.1000 (ms)",
+ "value": 100,
+ },
+ {
+ "children": [],
+ "endTime": 2493.299999999997,
+ "name": "state",
+ "startTime": 2493.199999999997,
+ "tooltip": "state, 0.1000 (ms)",
+ "value": 100,
+ },
+ {
+ "children": [],
+ "endTime": 2493.5999999999967,
+ "name": "flow",
+ "startTime": 2493.499999999997,
+ "tooltip": "flow, 0.1000 (ms)",
+ "value": 100,
+ },
+ {
+ "children": [],
+ "endTime": 2493.8999999999965,
+ "name": "beforeStart",
+ "startTime": 2493.7999999999965,
+ "tooltip": "beforeStart, 0.1000 (ms)",
+ "value": 100,
+ },
+ {
+ "children": [],
+ "endTime": 2494.199999999996,
+ "name": "resolveTransitionNode",
+ "startTime": 2494.0999999999963,
+ "tooltip": "resolveTransitionNode, 0.1000 (ms)",
+ "value": 100,
+ },
+ {
+ "children": [
+ {
+ "children": [],
+ "endTime": 2494.599999999996,
+ "name": "resolveView",
+ "startTime": 2494.499999999996,
+ "tooltip": "resolveView, 0.1000 (ms)",
+ "value": 100,
+ },
+ {
+ "children": [
+ {
+ "children": [],
+ "endTime": 2494.9999999999955,
+ "name": "onTemplatePluginCreated",
+ "startTime": 2494.8999999999955,
+ "tooltip": "onTemplatePluginCreated, 0.1000 (ms)",
+ "value": 100,
+ },
+ ],
+ "endTime": 2495.1999999999953,
+ "name": "view",
+ "startTime": 2494.7999999999956,
+ "tooltip": "view, 0.4000 (ms)",
+ "value": 400,
+ },
+ {
+ "children": [],
+ "endTime": 2495.499999999995,
+ "name": "templatePlugin",
+ "startTime": 2495.399999999995,
+ "tooltip": "templatePlugin, 0.1000 (ms)",
+ "value": 100,
+ },
+ {
+ "children": [],
+ "endTime": 2495.7999999999947,
+ "name": "parser",
+ "startTime": 2495.699999999995,
+ "tooltip": "parser, 0.1000 (ms)",
+ "value": 100,
+ },
+ {
+ "children": [],
+ "endTime": 2496.0999999999945,
+ "name": "parseNode",
+ "startTime": 2495.9999999999945,
+ "tooltip": "parseNode, 0.1000 (ms)",
+ "value": 100,
+ },
+ {
+ "children": [],
+ "endTime": 2496.399999999994,
+ "name": "onParseObject",
+ "startTime": 2496.2999999999943,
+ "tooltip": "onParseObject, 0.1000 (ms)",
+ "value": 100,
+ },
+ {
+ "children": [],
+ "endTime": 2496.699999999994,
+ "name": "parseNode",
+ "startTime": 2496.599999999994,
+ "tooltip": "parseNode, 0.1000 (ms)",
+ "value": 100,
+ },
+ {
+ "children": [],
+ "endTime": 2496.9999999999936,
+ "name": "parseNode",
+ "startTime": 2496.8999999999937,
+ "tooltip": "parseNode, 0.1000 (ms)",
+ "value": 100,
+ },
+ {
+ "children": [],
+ "endTime": 2497.2999999999934,
+ "name": "parseNode",
+ "startTime": 2497.1999999999935,
+ "tooltip": "parseNode, 0.1000 (ms)",
+ "value": 100,
+ },
+ {
+ "children": [],
+ "endTime": 2497.599999999993,
+ "name": "onCreateASTNode",
+ "startTime": 2497.499999999993,
+ "tooltip": "onCreateASTNode, 0.1000 (ms)",
+ "value": 100,
+ },
+ {
+ "children": [],
+ "endTime": 2497.899999999993,
+ "name": "resolver",
+ "startTime": 2497.799999999993,
+ "tooltip": "resolver, 0.1000 (ms)",
+ "value": 100,
+ },
+ {
+ "children": [],
+ "endTime": 2498.1999999999925,
+ "name": "beforeUpdate",
+ "startTime": 2498.0999999999926,
+ "tooltip": "beforeUpdate, 0.1000 (ms)",
+ "value": 100,
+ },
+ {
+ "children": [],
+ "endTime": 2498.4999999999923,
+ "name": "resolveOptions",
+ "startTime": 2498.3999999999924,
+ "tooltip": "resolveOptions, 0.1000 (ms)",
+ "value": 100,
+ },
+ {
+ "children": [],
+ "endTime": 2498.799999999992,
+ "name": "skipResolve",
+ "startTime": 2498.699999999992,
+ "tooltip": "skipResolve, 0.1000 (ms)",
+ "value": 100,
+ },
+ {
+ "children": [],
+ "endTime": 2499.0999999999917,
+ "name": "beforeResolve",
+ "startTime": 2498.999999999992,
+ "tooltip": "beforeResolve, 0.1000 (ms)",
+ "value": 100,
+ },
+ {
+ "children": [],
+ "endTime": 2499.3999999999915,
+ "name": "resolve",
+ "startTime": 2499.2999999999915,
+ "tooltip": "resolve, 0.1000 (ms)",
+ "value": 100,
+ },
+ {
+ "children": [],
+ "endTime": 2499.699999999991,
+ "name": "afterResolve",
+ "startTime": 2499.5999999999913,
+ "tooltip": "afterResolve, 0.1000 (ms)",
+ "value": 100,
+ },
+ {
+ "children": [],
+ "endTime": 2499.999999999991,
+ "name": "afterNodeUpdate",
+ "startTime": 2499.899999999991,
+ "tooltip": "afterNodeUpdate, 0.1000 (ms)",
+ "value": 100,
+ },
+ {
+ "children": [],
+ "endTime": 2500.2999999999906,
+ "name": "afterUpdate",
+ "startTime": 2500.1999999999907,
+ "tooltip": "afterUpdate, 0.1000 (ms)",
+ "value": 100,
+ },
+ {
+ "children": [],
+ "endTime": 2500.5999999999904,
+ "name": "onUpdate",
+ "startTime": 2500.4999999999905,
+ "tooltip": "onUpdate, 0.1000 (ms)",
+ "value": 100,
+ },
+ ],
+ "endTime": 2500.79999999999,
+ "name": "transition",
+ "startTime": 2494.399999999996,
+ "tooltip": "transition, 6.4000 (ms)",
+ "value": 6400,
+ },
+ {
+ "children": [],
+ "endTime": 2501.09999999999,
+ "name": "afterTransition",
+ "startTime": 2500.99999999999,
+ "tooltip": "afterTransition, 0.1000 (ms)",
+ "value": 100,
+ },
+ {
+ "children": [],
+ "endTime": 2501.3999999999896,
+ "name": "skipTransition",
+ "startTime": 2501.2999999999897,
+ "tooltip": "skipTransition, 0.1000 (ms)",
+ "value": 100,
+ },
+ {
+ "children": [],
+ "endTime": 2501.6999999999894,
+ "name": "beforeTransition",
+ "startTime": 2501.5999999999894,
+ "tooltip": "beforeTransition, 0.1000 (ms)",
+ "value": 100,
+ },
+ {
+ "children": [],
+ "endTime": 2501.999999999989,
+ "name": "resolveTransitionNode",
+ "startTime": 2501.899999999989,
+ "tooltip": "resolveTransitionNode, 0.1000 (ms)",
+ "value": 100,
+ },
+ {
+ "children": [],
+ "endTime": 2502.299999999989,
+ "name": "transition",
+ "startTime": 2502.199999999989,
+ "tooltip": "transition, 0.1000 (ms)",
+ "value": 100,
+ },
+ {
+ "children": [
+ {
+ "children": [],
+ "endTime": 2502.6999999999884,
+ "name": "resolveOptions",
+ "startTime": 2502.5999999999885,
+ "tooltip": "resolveOptions, 0.1000 (ms)",
+ "value": 100,
+ },
+ {
+ "children": [],
+ "endTime": 2502.999999999988,
+ "name": "beforeEvaluate",
+ "startTime": 2502.8999999999883,
+ "tooltip": "beforeEvaluate, 0.1000 (ms)",
+ "value": 100,
+ },
+ {
+ "children": [
+ {
+ "children": [],
+ "endTime": 2503.399999999988,
+ "name": "resolve",
+ "startTime": 2503.299999999988,
+ "tooltip": "resolve, 0.1000 (ms)",
+ "value": 100,
+ },
+ {
+ "children": [],
+ "endTime": 2503.6999999999875,
+ "name": "skipOptimization",
+ "startTime": 2503.5999999999876,
+ "tooltip": "skipOptimization, 0.1000 (ms)",
+ "value": 100,
+ },
+ {
+ "children": [],
+ "endTime": 2503.9999999999873,
+ "name": "resolveDataStages",
+ "startTime": 2503.8999999999874,
+ "tooltip": "resolveDataStages, 0.1000 (ms)",
+ "value": 100,
+ },
+ {
+ "children": [
+ {
+ "children": [],
+ "endTime": 2504.399999999987,
+ "name": "resolveTypeForBinding",
+ "startTime": 2504.299999999987,
+ "tooltip": "resolveTypeForBinding, 0.1000 (ms)",
+ "value": 100,
+ },
+ ],
+ "endTime": 2504.5999999999867,
+ "name": "resolveDefaultValue",
+ "startTime": 2504.199999999987,
+ "tooltip": "resolveDefaultValue, 0.4000 (ms)",
+ "value": 400,
+ },
+ {
+ "children": [],
+ "endTime": 2504.8999999999864,
+ "name": "onGet",
+ "startTime": 2504.7999999999865,
+ "tooltip": "onGet, 0.1000 (ms)",
+ "value": 100,
+ },
+ {
+ "children": [],
+ "endTime": 2505.199999999986,
+ "name": "onSet",
+ "startTime": 2505.0999999999863,
+ "tooltip": "onSet, 0.1000 (ms)",
+ "value": 100,
+ },
+ {
+ "children": [],
+ "endTime": 2505.499999999986,
+ "name": "onUpdate",
+ "startTime": 2505.399999999986,
+ "tooltip": "onUpdate, 0.1000 (ms)",
+ "value": 100,
+ },
+ ],
+ "endTime": 2505.6999999999857,
+ "name": "resolve",
+ "startTime": 2503.199999999988,
+ "tooltip": "resolve, 2.5000 (ms)",
+ "value": 2500,
+ },
+ {
+ "children": [],
+ "endTime": 2505.9999999999854,
+ "name": "skipTransition",
+ "startTime": 2505.8999999999855,
+ "tooltip": "skipTransition, 0.1000 (ms)",
+ "value": 100,
+ },
+ {
+ "children": [],
+ "endTime": 2506.299999999985,
+ "name": "beforeTransition",
+ "startTime": 2506.1999999999853,
+ "tooltip": "beforeTransition, 0.1000 (ms)",
+ "value": 100,
+ },
+ {
+ "children": [],
+ "endTime": 2506.599999999985,
+ "name": "resolveTransitionNode",
+ "startTime": 2506.499999999985,
+ "tooltip": "resolveTransitionNode, 0.1000 (ms)",
+ "value": 100,
+ },
+ {
+ "children": [],
+ "endTime": 2506.8999999999846,
+ "name": "transition",
+ "startTime": 2506.7999999999847,
+ "tooltip": "transition, 0.1000 (ms)",
+ "value": 100,
+ },
+ {
+ "children": [],
+ "endTime": 2507.1999999999844,
+ "name": "afterTransition",
+ "startTime": 2507.0999999999844,
+ "tooltip": "afterTransition, 0.1000 (ms)",
+ "value": 100,
+ },
+ ],
+ "endTime": 2507.399999999984,
+ "name": "afterTransition",
+ "startTime": 2502.4999999999886,
+ "tooltip": "afterTransition, 4.9000 (ms)",
+ "value": 4900,
+ },
+ {
+ "children": [],
+ "endTime": 2507.699999999984,
+ "name": "onGet",
+ "startTime": 2507.599999999984,
+ "tooltip": "onGet, 0.1000 (ms)",
+ "value": 100,
+ },
+ {
+ "children": [],
+ "endTime": 2507.9999999999836,
+ "name": "serialize",
+ "startTime": 2507.8999999999837,
+ "tooltip": "serialize, 0.1000 (ms)",
+ "value": 100,
+ },
+ {
+ "children": [],
+ "endTime": 2508.2999999999834,
+ "name": "state",
+ "startTime": 2508.1999999999834,
+ "tooltip": "state, 0.1000 (ms)",
+ "value": 100,
+ },
+ ],
+ "rootNode": {
+ "children": [
+ {
+ "children": [],
+ "endTime": 2490.2999999999997,
+ "name": "state",
+ "startTime": 2490.2,
+ "tooltip": "state, 0.1000 (ms)",
+ "value": 100,
+ },
+ {
+ "backgroundColor": "#000000",
+ "children": [],
+ "color": "#000000",
+ "name": "(work)",
+ "tooltip": "Placeholder time between hooks",
+ "value": 200,
+ },
+ {
+ "children": [],
+ "endTime": 2490.5999999999995,
+ "name": "resolveFlowContent",
+ "startTime": 2490.4999999999995,
+ "tooltip": "resolveFlowContent, 0.1000 (ms)",
+ "value": 100,
+ },
+ {
+ "backgroundColor": "#000000",
+ "children": [],
+ "color": "#000000",
+ "name": "(work)",
+ "tooltip": "Placeholder time between hooks",
+ "value": 200,
+ },
+ {
+ "children": [],
+ "endTime": 2490.899999999999,
+ "name": "onStart",
+ "startTime": 2490.7999999999993,
+ "tooltip": "onStart, 0.1000 (ms)",
+ "value": 100,
+ },
+ {
+ "backgroundColor": "#000000",
+ "children": [],
+ "color": "#000000",
+ "name": "(work)",
+ "tooltip": "Placeholder time between hooks",
+ "value": 200,
+ },
+ {
+ "children": [],
+ "endTime": 2491.199999999999,
+ "name": "flowController",
+ "startTime": 2491.099999999999,
+ "tooltip": "flowController, 0.1000 (ms)",
+ "value": 100,
+ },
+ {
+ "backgroundColor": "#000000",
+ "children": [],
+ "color": "#000000",
+ "name": "(work)",
+ "tooltip": "Placeholder time between hooks",
+ "value": 200,
+ },
+ {
+ "children": [],
+ "endTime": 2491.4999999999986,
+ "name": "bindingParser",
+ "startTime": 2491.3999999999987,
+ "tooltip": "bindingParser, 0.1000 (ms)",
+ "value": 100,
+ },
+ {
+ "backgroundColor": "#000000",
+ "children": [],
+ "color": "#000000",
+ "name": "(work)",
+ "tooltip": "Placeholder time between hooks",
+ "value": 200,
+ },
+ {
+ "children": [],
+ "endTime": 2491.7999999999984,
+ "name": "schema",
+ "startTime": 2491.6999999999985,
+ "tooltip": "schema, 0.1000 (ms)",
+ "value": 100,
+ },
+ {
+ "backgroundColor": "#000000",
+ "children": [],
+ "color": "#000000",
+ "name": "(work)",
+ "tooltip": "Placeholder time between hooks",
+ "value": 200,
+ },
+ {
+ "children": [],
+ "endTime": 2492.099999999998,
+ "name": "validationController",
+ "startTime": 2491.999999999998,
+ "tooltip": "validationController, 0.1000 (ms)",
+ "value": 100,
+ },
+ {
+ "backgroundColor": "#000000",
+ "children": [],
+ "color": "#000000",
+ "name": "(work)",
+ "tooltip": "Placeholder time between hooks",
+ "value": 200,
+ },
+ {
+ "children": [],
+ "endTime": 2492.399999999998,
+ "name": "expressionEvaluator",
+ "startTime": 2492.299999999998,
+ "tooltip": "expressionEvaluator, 0.1000 (ms)",
+ "value": 100,
+ },
+ {
+ "backgroundColor": "#000000",
+ "children": [],
+ "color": "#000000",
+ "name": "(work)",
+ "tooltip": "Placeholder time between hooks",
+ "value": 200,
+ },
+ {
+ "children": [],
+ "endTime": 2492.6999999999975,
+ "name": "dataController",
+ "startTime": 2492.5999999999976,
+ "tooltip": "dataController, 0.1000 (ms)",
+ "value": 100,
+ },
+ {
+ "backgroundColor": "#000000",
+ "children": [],
+ "color": "#000000",
+ "name": "(work)",
+ "tooltip": "Placeholder time between hooks",
+ "value": 200,
+ },
+ {
+ "children": [],
+ "endTime": 2492.9999999999973,
+ "name": "viewController",
+ "startTime": 2492.8999999999974,
+ "tooltip": "viewController, 0.1000 (ms)",
+ "value": 100,
+ },
+ {
+ "backgroundColor": "#000000",
+ "children": [],
+ "color": "#000000",
+ "name": "(work)",
+ "tooltip": "Placeholder time between hooks",
+ "value": 200,
+ },
+ {
+ "children": [],
+ "endTime": 2493.299999999997,
+ "name": "state",
+ "startTime": 2493.199999999997,
+ "tooltip": "state, 0.1000 (ms)",
+ "value": 100,
+ },
+ {
+ "backgroundColor": "#000000",
+ "children": [],
+ "color": "#000000",
+ "name": "(work)",
+ "tooltip": "Placeholder time between hooks",
+ "value": 200,
+ },
+ {
+ "children": [],
+ "endTime": 2493.5999999999967,
+ "name": "flow",
+ "startTime": 2493.499999999997,
+ "tooltip": "flow, 0.1000 (ms)",
+ "value": 100,
+ },
+ {
+ "backgroundColor": "#000000",
+ "children": [],
+ "color": "#000000",
+ "name": "(work)",
+ "tooltip": "Placeholder time between hooks",
+ "value": 200,
+ },
+ {
+ "children": [],
+ "endTime": 2493.8999999999965,
+ "name": "beforeStart",
+ "startTime": 2493.7999999999965,
+ "tooltip": "beforeStart, 0.1000 (ms)",
+ "value": 100,
+ },
+ {
+ "backgroundColor": "#000000",
+ "children": [],
+ "color": "#000000",
+ "name": "(work)",
+ "tooltip": "Placeholder time between hooks",
+ "value": 200,
+ },
+ {
+ "children": [],
+ "endTime": 2494.199999999996,
+ "name": "resolveTransitionNode",
+ "startTime": 2494.0999999999963,
+ "tooltip": "resolveTransitionNode, 0.1000 (ms)",
+ "value": 100,
+ },
+ {
+ "backgroundColor": "#000000",
+ "children": [],
+ "color": "#000000",
+ "name": "(work)",
+ "tooltip": "Placeholder time between hooks",
+ "value": 200,
+ },
+ {
+ "children": [
+ {
+ "backgroundColor": "#000000",
+ "children": [],
+ "color": "#000000",
+ "name": "(work)",
+ "tooltip": "Placeholder time between hooks",
+ "value": 100,
+ },
+ {
+ "children": [],
+ "endTime": 2494.599999999996,
+ "name": "resolveView",
+ "startTime": 2494.499999999996,
+ "tooltip": "resolveView, 0.1000 (ms)",
+ "value": 100,
+ },
+ {
+ "backgroundColor": "#000000",
+ "children": [],
+ "color": "#000000",
+ "name": "(work)",
+ "tooltip": "Placeholder time between hooks",
+ "value": 200,
+ },
+ {
+ "children": [
+ {
+ "backgroundColor": "#000000",
+ "children": [],
+ "color": "#000000",
+ "name": "(work)",
+ "tooltip": "Placeholder time between hooks",
+ "value": 100,
+ },
+ {
+ "children": [],
+ "endTime": 2494.9999999999955,
+ "name": "onTemplatePluginCreated",
+ "startTime": 2494.8999999999955,
+ "tooltip": "onTemplatePluginCreated, 0.1000 (ms)",
+ "value": 100,
+ },
+ ],
+ "endTime": 2495.1999999999953,
+ "name": "view",
+ "startTime": 2494.7999999999956,
+ "tooltip": "view, 0.4000 (ms)",
+ "value": 400,
+ },
+ {
+ "backgroundColor": "#000000",
+ "children": [],
+ "color": "#000000",
+ "name": "(work)",
+ "tooltip": "Placeholder time between hooks",
+ "value": 200,
+ },
+ {
+ "children": [],
+ "endTime": 2495.499999999995,
+ "name": "templatePlugin",
+ "startTime": 2495.399999999995,
+ "tooltip": "templatePlugin, 0.1000 (ms)",
+ "value": 100,
+ },
+ {
+ "backgroundColor": "#000000",
+ "children": [],
+ "color": "#000000",
+ "name": "(work)",
+ "tooltip": "Placeholder time between hooks",
+ "value": 200,
+ },
+ {
+ "children": [],
+ "endTime": 2495.7999999999947,
+ "name": "parser",
+ "startTime": 2495.699999999995,
+ "tooltip": "parser, 0.1000 (ms)",
+ "value": 100,
+ },
+ {
+ "backgroundColor": "#000000",
+ "children": [],
+ "color": "#000000",
+ "name": "(work)",
+ "tooltip": "Placeholder time between hooks",
+ "value": 200,
+ },
+ {
+ "children": [],
+ "endTime": 2496.0999999999945,
+ "name": "parseNode",
+ "startTime": 2495.9999999999945,
+ "tooltip": "parseNode, 0.1000 (ms)",
+ "value": 100,
+ },
+ {
+ "backgroundColor": "#000000",
+ "children": [],
+ "color": "#000000",
+ "name": "(work)",
+ "tooltip": "Placeholder time between hooks",
+ "value": 200,
+ },
+ {
+ "children": [],
+ "endTime": 2496.399999999994,
+ "name": "onParseObject",
+ "startTime": 2496.2999999999943,
+ "tooltip": "onParseObject, 0.1000 (ms)",
+ "value": 100,
+ },
+ {
+ "backgroundColor": "#000000",
+ "children": [],
+ "color": "#000000",
+ "name": "(work)",
+ "tooltip": "Placeholder time between hooks",
+ "value": 200,
+ },
+ {
+ "children": [],
+ "endTime": 2496.699999999994,
+ "name": "parseNode",
+ "startTime": 2496.599999999994,
+ "tooltip": "parseNode, 0.1000 (ms)",
+ "value": 100,
+ },
+ {
+ "backgroundColor": "#000000",
+ "children": [],
+ "color": "#000000",
+ "name": "(work)",
+ "tooltip": "Placeholder time between hooks",
+ "value": 200,
+ },
+ {
+ "children": [],
+ "endTime": 2496.9999999999936,
+ "name": "parseNode",
+ "startTime": 2496.8999999999937,
+ "tooltip": "parseNode, 0.1000 (ms)",
+ "value": 100,
+ },
+ {
+ "backgroundColor": "#000000",
+ "children": [],
+ "color": "#000000",
+ "name": "(work)",
+ "tooltip": "Placeholder time between hooks",
+ "value": 200,
+ },
+ {
+ "children": [],
+ "endTime": 2497.2999999999934,
+ "name": "parseNode",
+ "startTime": 2497.1999999999935,
+ "tooltip": "parseNode, 0.1000 (ms)",
+ "value": 100,
+ },
+ {
+ "backgroundColor": "#000000",
+ "children": [],
+ "color": "#000000",
+ "name": "(work)",
+ "tooltip": "Placeholder time between hooks",
+ "value": 200,
+ },
+ {
+ "children": [],
+ "endTime": 2497.599999999993,
+ "name": "onCreateASTNode",
+ "startTime": 2497.499999999993,
+ "tooltip": "onCreateASTNode, 0.1000 (ms)",
+ "value": 100,
+ },
+ {
+ "backgroundColor": "#000000",
+ "children": [],
+ "color": "#000000",
+ "name": "(work)",
+ "tooltip": "Placeholder time between hooks",
+ "value": 200,
+ },
+ {
+ "children": [],
+ "endTime": 2497.899999999993,
+ "name": "resolver",
+ "startTime": 2497.799999999993,
+ "tooltip": "resolver, 0.1000 (ms)",
+ "value": 100,
+ },
+ {
+ "backgroundColor": "#000000",
+ "children": [],
+ "color": "#000000",
+ "name": "(work)",
+ "tooltip": "Placeholder time between hooks",
+ "value": 200,
+ },
+ {
+ "children": [],
+ "endTime": 2498.1999999999925,
+ "name": "beforeUpdate",
+ "startTime": 2498.0999999999926,
+ "tooltip": "beforeUpdate, 0.1000 (ms)",
+ "value": 100,
+ },
+ {
+ "backgroundColor": "#000000",
+ "children": [],
+ "color": "#000000",
+ "name": "(work)",
+ "tooltip": "Placeholder time between hooks",
+ "value": 200,
+ },
+ {
+ "children": [],
+ "endTime": 2498.4999999999923,
+ "name": "resolveOptions",
+ "startTime": 2498.3999999999924,
+ "tooltip": "resolveOptions, 0.1000 (ms)",
+ "value": 100,
+ },
+ {
+ "backgroundColor": "#000000",
+ "children": [],
+ "color": "#000000",
+ "name": "(work)",
+ "tooltip": "Placeholder time between hooks",
+ "value": 200,
+ },
+ {
+ "children": [],
+ "endTime": 2498.799999999992,
+ "name": "skipResolve",
+ "startTime": 2498.699999999992,
+ "tooltip": "skipResolve, 0.1000 (ms)",
+ "value": 100,
+ },
+ {
+ "backgroundColor": "#000000",
+ "children": [],
+ "color": "#000000",
+ "name": "(work)",
+ "tooltip": "Placeholder time between hooks",
+ "value": 200,
+ },
+ {
+ "children": [],
+ "endTime": 2499.0999999999917,
+ "name": "beforeResolve",
+ "startTime": 2498.999999999992,
+ "tooltip": "beforeResolve, 0.1000 (ms)",
+ "value": 100,
+ },
+ {
+ "backgroundColor": "#000000",
+ "children": [],
+ "color": "#000000",
+ "name": "(work)",
+ "tooltip": "Placeholder time between hooks",
+ "value": 200,
+ },
+ {
+ "children": [],
+ "endTime": 2499.3999999999915,
+ "name": "resolve",
+ "startTime": 2499.2999999999915,
+ "tooltip": "resolve, 0.1000 (ms)",
+ "value": 100,
+ },
+ {
+ "backgroundColor": "#000000",
+ "children": [],
+ "color": "#000000",
+ "name": "(work)",
+ "tooltip": "Placeholder time between hooks",
+ "value": 200,
+ },
+ {
+ "children": [],
+ "endTime": 2499.699999999991,
+ "name": "afterResolve",
+ "startTime": 2499.5999999999913,
+ "tooltip": "afterResolve, 0.1000 (ms)",
+ "value": 100,
+ },
+ {
+ "backgroundColor": "#000000",
+ "children": [],
+ "color": "#000000",
+ "name": "(work)",
+ "tooltip": "Placeholder time between hooks",
+ "value": 200,
+ },
+ {
+ "children": [],
+ "endTime": 2499.999999999991,
+ "name": "afterNodeUpdate",
+ "startTime": 2499.899999999991,
+ "tooltip": "afterNodeUpdate, 0.1000 (ms)",
+ "value": 100,
+ },
+ {
+ "backgroundColor": "#000000",
+ "children": [],
+ "color": "#000000",
+ "name": "(work)",
+ "tooltip": "Placeholder time between hooks",
+ "value": 200,
+ },
+ {
+ "children": [],
+ "endTime": 2500.2999999999906,
+ "name": "afterUpdate",
+ "startTime": 2500.1999999999907,
+ "tooltip": "afterUpdate, 0.1000 (ms)",
+ "value": 100,
+ },
+ {
+ "backgroundColor": "#000000",
+ "children": [],
+ "color": "#000000",
+ "name": "(work)",
+ "tooltip": "Placeholder time between hooks",
+ "value": 200,
+ },
+ {
+ "children": [],
+ "endTime": 2500.5999999999904,
+ "name": "onUpdate",
+ "startTime": 2500.4999999999905,
+ "tooltip": "onUpdate, 0.1000 (ms)",
+ "value": 100,
+ },
+ ],
+ "endTime": 2500.79999999999,
+ "name": "transition",
+ "startTime": 2494.399999999996,
+ "tooltip": "transition, 6.4000 (ms)",
+ "value": 6400,
+ },
+ {
+ "backgroundColor": "#000000",
+ "children": [],
+ "color": "#000000",
+ "name": "(work)",
+ "tooltip": "Placeholder time between hooks",
+ "value": 200,
+ },
+ {
+ "children": [],
+ "endTime": 2501.09999999999,
+ "name": "afterTransition",
+ "startTime": 2500.99999999999,
+ "tooltip": "afterTransition, 0.1000 (ms)",
+ "value": 100,
+ },
+ {
+ "backgroundColor": "#000000",
+ "children": [],
+ "color": "#000000",
+ "name": "(work)",
+ "tooltip": "Placeholder time between hooks",
+ "value": 200,
+ },
+ {
+ "children": [],
+ "endTime": 2501.3999999999896,
+ "name": "skipTransition",
+ "startTime": 2501.2999999999897,
+ "tooltip": "skipTransition, 0.1000 (ms)",
+ "value": 100,
+ },
+ {
+ "backgroundColor": "#000000",
+ "children": [],
+ "color": "#000000",
+ "name": "(work)",
+ "tooltip": "Placeholder time between hooks",
+ "value": 200,
+ },
+ {
+ "children": [],
+ "endTime": 2501.6999999999894,
+ "name": "beforeTransition",
+ "startTime": 2501.5999999999894,
+ "tooltip": "beforeTransition, 0.1000 (ms)",
+ "value": 100,
+ },
+ {
+ "backgroundColor": "#000000",
+ "children": [],
+ "color": "#000000",
+ "name": "(work)",
+ "tooltip": "Placeholder time between hooks",
+ "value": 200,
+ },
+ {
+ "children": [],
+ "endTime": 2501.999999999989,
+ "name": "resolveTransitionNode",
+ "startTime": 2501.899999999989,
+ "tooltip": "resolveTransitionNode, 0.1000 (ms)",
+ "value": 100,
+ },
+ {
+ "backgroundColor": "#000000",
+ "children": [],
+ "color": "#000000",
+ "name": "(work)",
+ "tooltip": "Placeholder time between hooks",
+ "value": 200,
+ },
+ {
+ "children": [],
+ "endTime": 2502.299999999989,
+ "name": "transition",
+ "startTime": 2502.199999999989,
+ "tooltip": "transition, 0.1000 (ms)",
+ "value": 100,
+ },
+ {
+ "backgroundColor": "#000000",
+ "children": [],
+ "color": "#000000",
+ "name": "(work)",
+ "tooltip": "Placeholder time between hooks",
+ "value": 200,
+ },
+ {
+ "children": [
+ {
+ "backgroundColor": "#000000",
+ "children": [],
+ "color": "#000000",
+ "name": "(work)",
+ "tooltip": "Placeholder time between hooks",
+ "value": 100,
+ },
+ {
+ "children": [],
+ "endTime": 2502.6999999999884,
+ "name": "resolveOptions",
+ "startTime": 2502.5999999999885,
+ "tooltip": "resolveOptions, 0.1000 (ms)",
+ "value": 100,
+ },
+ {
+ "backgroundColor": "#000000",
+ "children": [],
+ "color": "#000000",
+ "name": "(work)",
+ "tooltip": "Placeholder time between hooks",
+ "value": 200,
+ },
+ {
+ "children": [],
+ "endTime": 2502.999999999988,
+ "name": "beforeEvaluate",
+ "startTime": 2502.8999999999883,
+ "tooltip": "beforeEvaluate, 0.1000 (ms)",
+ "value": 100,
+ },
+ {
+ "backgroundColor": "#000000",
+ "children": [],
+ "color": "#000000",
+ "name": "(work)",
+ "tooltip": "Placeholder time between hooks",
+ "value": 200,
+ },
+ {
+ "children": [
+ {
+ "backgroundColor": "#000000",
+ "children": [],
+ "color": "#000000",
+ "name": "(work)",
+ "tooltip": "Placeholder time between hooks",
+ "value": 100,
+ },
+ {
+ "children": [],
+ "endTime": 2503.399999999988,
+ "name": "resolve",
+ "startTime": 2503.299999999988,
+ "tooltip": "resolve, 0.1000 (ms)",
+ "value": 100,
+ },
+ {
+ "backgroundColor": "#000000",
+ "children": [],
+ "color": "#000000",
+ "name": "(work)",
+ "tooltip": "Placeholder time between hooks",
+ "value": 200,
+ },
+ {
+ "children": [],
+ "endTime": 2503.6999999999875,
+ "name": "skipOptimization",
+ "startTime": 2503.5999999999876,
+ "tooltip": "skipOptimization, 0.1000 (ms)",
+ "value": 100,
+ },
+ {
+ "backgroundColor": "#000000",
+ "children": [],
+ "color": "#000000",
+ "name": "(work)",
+ "tooltip": "Placeholder time between hooks",
+ "value": 200,
+ },
+ {
+ "children": [],
+ "endTime": 2503.9999999999873,
+ "name": "resolveDataStages",
+ "startTime": 2503.8999999999874,
+ "tooltip": "resolveDataStages, 0.1000 (ms)",
+ "value": 100,
+ },
+ {
+ "backgroundColor": "#000000",
+ "children": [],
+ "color": "#000000",
+ "name": "(work)",
+ "tooltip": "Placeholder time between hooks",
+ "value": 200,
+ },
+ {
+ "children": [
+ {
+ "backgroundColor": "#000000",
+ "children": [],
+ "color": "#000000",
+ "name": "(work)",
+ "tooltip": "Placeholder time between hooks",
+ "value": 100,
+ },
+ {
+ "children": [],
+ "endTime": 2504.399999999987,
+ "name": "resolveTypeForBinding",
+ "startTime": 2504.299999999987,
+ "tooltip": "resolveTypeForBinding, 0.1000 (ms)",
+ "value": 100,
+ },
+ ],
+ "endTime": 2504.5999999999867,
+ "name": "resolveDefaultValue",
+ "startTime": 2504.199999999987,
+ "tooltip": "resolveDefaultValue, 0.4000 (ms)",
+ "value": 400,
+ },
+ {
+ "backgroundColor": "#000000",
+ "children": [],
+ "color": "#000000",
+ "name": "(work)",
+ "tooltip": "Placeholder time between hooks",
+ "value": 200,
+ },
+ {
+ "children": [],
+ "endTime": 2504.8999999999864,
+ "name": "onGet",
+ "startTime": 2504.7999999999865,
+ "tooltip": "onGet, 0.1000 (ms)",
+ "value": 100,
+ },
+ {
+ "backgroundColor": "#000000",
+ "children": [],
+ "color": "#000000",
+ "name": "(work)",
+ "tooltip": "Placeholder time between hooks",
+ "value": 200,
+ },
+ {
+ "children": [],
+ "endTime": 2505.199999999986,
+ "name": "onSet",
+ "startTime": 2505.0999999999863,
+ "tooltip": "onSet, 0.1000 (ms)",
+ "value": 100,
+ },
+ {
+ "backgroundColor": "#000000",
+ "children": [],
+ "color": "#000000",
+ "name": "(work)",
+ "tooltip": "Placeholder time between hooks",
+ "value": 200,
+ },
+ {
+ "children": [],
+ "endTime": 2505.499999999986,
+ "name": "onUpdate",
+ "startTime": 2505.399999999986,
+ "tooltip": "onUpdate, 0.1000 (ms)",
+ "value": 100,
+ },
+ ],
+ "endTime": 2505.6999999999857,
+ "name": "resolve",
+ "startTime": 2503.199999999988,
+ "tooltip": "resolve, 2.5000 (ms)",
+ "value": 2500,
+ },
+ {
+ "backgroundColor": "#000000",
+ "children": [],
+ "color": "#000000",
+ "name": "(work)",
+ "tooltip": "Placeholder time between hooks",
+ "value": 200,
+ },
+ {
+ "children": [],
+ "endTime": 2505.9999999999854,
+ "name": "skipTransition",
+ "startTime": 2505.8999999999855,
+ "tooltip": "skipTransition, 0.1000 (ms)",
+ "value": 100,
+ },
+ {
+ "backgroundColor": "#000000",
+ "children": [],
+ "color": "#000000",
+ "name": "(work)",
+ "tooltip": "Placeholder time between hooks",
+ "value": 200,
+ },
+ {
+ "children": [],
+ "endTime": 2506.299999999985,
+ "name": "beforeTransition",
+ "startTime": 2506.1999999999853,
+ "tooltip": "beforeTransition, 0.1000 (ms)",
+ "value": 100,
+ },
+ {
+ "backgroundColor": "#000000",
+ "children": [],
+ "color": "#000000",
+ "name": "(work)",
+ "tooltip": "Placeholder time between hooks",
+ "value": 200,
+ },
+ {
+ "children": [],
+ "endTime": 2506.599999999985,
+ "name": "resolveTransitionNode",
+ "startTime": 2506.499999999985,
+ "tooltip": "resolveTransitionNode, 0.1000 (ms)",
+ "value": 100,
+ },
+ {
+ "backgroundColor": "#000000",
+ "children": [],
+ "color": "#000000",
+ "name": "(work)",
+ "tooltip": "Placeholder time between hooks",
+ "value": 200,
+ },
+ {
+ "children": [],
+ "endTime": 2506.8999999999846,
+ "name": "transition",
+ "startTime": 2506.7999999999847,
+ "tooltip": "transition, 0.1000 (ms)",
+ "value": 100,
+ },
+ {
+ "backgroundColor": "#000000",
+ "children": [],
+ "color": "#000000",
+ "name": "(work)",
+ "tooltip": "Placeholder time between hooks",
+ "value": 200,
+ },
+ {
+ "children": [],
+ "endTime": 2507.1999999999844,
+ "name": "afterTransition",
+ "startTime": 2507.0999999999844,
+ "tooltip": "afterTransition, 0.1000 (ms)",
+ "value": 100,
+ },
+ ],
+ "endTime": 2507.399999999984,
+ "name": "afterTransition",
+ "startTime": 2502.4999999999886,
+ "tooltip": "afterTransition, 4.9000 (ms)",
+ "value": 4900,
+ },
+ {
+ "backgroundColor": "#000000",
+ "children": [],
+ "color": "#000000",
+ "name": "(work)",
+ "tooltip": "Placeholder time between hooks",
+ "value": 200,
+ },
+ {
+ "children": [],
+ "endTime": 2507.699999999984,
+ "name": "onGet",
+ "startTime": 2507.599999999984,
+ "tooltip": "onGet, 0.1000 (ms)",
+ "value": 100,
+ },
+ {
+ "backgroundColor": "#000000",
+ "children": [],
+ "color": "#000000",
+ "name": "(work)",
+ "tooltip": "Placeholder time between hooks",
+ "value": 200,
+ },
+ {
+ "children": [],
+ "endTime": 2507.9999999999836,
+ "name": "serialize",
+ "startTime": 2507.8999999999837,
+ "tooltip": "serialize, 0.1000 (ms)",
+ "value": 100,
+ },
+ {
+ "backgroundColor": "#000000",
+ "children": [],
+ "color": "#000000",
+ "name": "(work)",
+ "tooltip": "Placeholder time between hooks",
+ "value": 200,
+ },
+ {
+ "children": [],
+ "endTime": 2508.2999999999834,
+ "name": "state",
+ "startTime": 2508.1999999999834,
+ "tooltip": "state, 0.1000 (ms)",
+ "value": 100,
+ },
+ ],
+ "endTime": 2508.2999999999834,
+ "name": "root",
+ "startTime": 2490.2,
+ "value": 18100,
+ },
+}
+`;
diff --git a/devtools/plugins/profiler/core/src/__tests__/addProfilerInterceptorsToHooks.test.ts b/devtools/plugins/profiler/core/src/__tests__/addProfilerInterceptorsToHooks.test.ts
new file mode 100644
index 0000000..bce09ac
--- /dev/null
+++ b/devtools/plugins/profiler/core/src/__tests__/addProfilerInterceptorsToHooks.test.ts
@@ -0,0 +1,60 @@
+import { SyncHook } from "tapable-ts";
+import { describe, expect, test } from "vitest";
+import { addProfilerInterceptorsToHooks } from "../addProfilerInterceptorsToHooks";
+import { Profiler } from "../helpers";
+
+describe("addProfilerInterceptorsToHooks", () => {
+ /**
+ * When a parent hook's call interceptor fires, it recursively calls
+ * addProfilerInterceptorsToHooks on args[0] to discover nested hooks.
+ * If the parent hook fires again with the same child object, the intercepted
+ * WeakSet prevents a second interceptor from being added — so the child hook
+ * always fires exactly once per call regardless of how many times the parent
+ * hook has fired.
+ */
+ test("re-intercepting the same child object on repeated parent calls does not duplicate timers", () => {
+ const profilerInstance = new Profiler();
+ profilerInstance.start();
+
+ // Child object whose hooks get discovered lazily via the parent's call arg
+ const childObj = {
+ hooks: {
+ afterTransition: new SyncHook<[]>(),
+ },
+ };
+
+ // Parent hook that passes childObj as its argument (mirrors Player's "flow" hook
+ // passing a FlowInstance, which carries its own hooks like afterTransition)
+ const parentObj = {
+ hooks: {
+ flow: new SyncHook<[typeof childObj]>(),
+ },
+ };
+
+ addProfilerInterceptorsToHooks(parentObj, profilerInstance);
+
+ // First parent call: wires one interceptor onto childObj.hooks.afterTransition
+ parentObj.hooks.flow.call(childObj);
+ childObj.hooks.afterTransition.call();
+
+ const snapAfterFirst = profilerInstance.getSnapshot();
+ expect(snapAfterFirst.rootNodes).toHaveLength(2);
+ expect(snapAfterFirst.rootNodes[0]!.name).toBe("flow");
+ expect(snapAfterFirst.rootNodes[1]!.name).toBe("afterTransition");
+ expect(snapAfterFirst.rootNodes[1]!.children).toHaveLength(0);
+
+ profilerInstance.start();
+
+ // Second parent call with the same childObj: the WeakSet guard prevents a second
+ // interceptor from being added to afterTransition — it fires exactly once.
+ parentObj.hooks.flow.call(childObj);
+ childObj.hooks.afterTransition.call();
+
+ const snapAfterSecond = profilerInstance.getSnapshot();
+ const afterTransitionNode = snapAfterSecond.rootNodes.find(
+ (n) => n.name === "afterTransition",
+ );
+ expect(afterTransitionNode).toBeDefined();
+ expect(afterTransitionNode!.children).toHaveLength(0);
+ });
+});
diff --git a/devtools/plugins/profiler/core/src/__tests__/plugin.test.ts b/devtools/plugins/profiler/core/src/__tests__/plugin.test.ts
new file mode 100644
index 0000000..e618123
--- /dev/null
+++ b/devtools/plugins/profiler/core/src/__tests__/plugin.test.ts
@@ -0,0 +1,168 @@
+import { Flow, InProgressState, Player } from "@player-ui/player";
+import { describe, expect, test, vi } from "vitest";
+import { ProfilerDevtoolsPlugin } from "../plugin";
+
+let count = 2490.0;
+vi.mock("@player-devtools/plugin", async () => {
+ const actual = await vi.importActual("@player-devtools/plugin");
+ return {
+ ...actual,
+ getNowTime: vi.fn(() => {
+ count += 0.1;
+ return count;
+ }),
+ };
+});
+
+describe("Plugin", () => {
+ // This test is being used to setup a baseline snapshot of perf on a basic player flow.
+ test("should profile player hooks when navigating through a flow", async () => {
+ const profilerPlugin = new ProfilerDevtoolsPlugin({
+ handler: {
+ checkIfDevtoolsIsActive: () => true,
+ processInteraction: () => {},
+ },
+ playerID: "ID",
+ });
+
+ const player = new Player({ plugins: [profilerPlugin] });
+
+ // This flow is used to navigate through common player steps.
+ const flow: Flow = {
+ id: "flow",
+ views: [
+ {
+ id: "view1",
+ type: "foo",
+ value: "bar",
+ },
+ ],
+ navigation: {
+ BEGIN: "FLOW_1",
+ FLOW_1: {
+ startState: "VIEW_1",
+ VIEW_1: {
+ state_type: "VIEW",
+ ref: "view1",
+ transitions: {
+ "*": "ACTION_1",
+ },
+ },
+ ACTION_1: {
+ state_type: "ACTION",
+ exp: "{{a}} = 1",
+ transitions: {
+ "*": "END_DONE",
+ },
+ },
+ END_DONE: {
+ state_type: "END",
+ outcome: "done",
+ },
+ },
+ },
+ };
+ const playerPromise = player.start(flow);
+
+ // Wait for first view update to complete.
+ await vi.waitFor(() => {
+ const playerState = player.getState();
+ expect(playerState.status).toBe("in-progress");
+ expect(
+ (playerState as InProgressState).controllers.view.currentView
+ ?.lastUpdate,
+ ).toBeDefined();
+ });
+
+ // Live update: profiling is active
+ const liveData =
+ profilerPlugin.store.getState().plugins["player-ui-profiler-plugin"]?.flow
+ .data;
+ expect(liveData?.profiling).toBe(true);
+
+ // Transition to action state
+ (player.getState() as InProgressState).controllers.flow.transition("go");
+
+ // Wait for action state to transition to end state and complete
+ await playerPromise;
+ profilerPlugin.processInteraction({
+ payload: {
+ type: "stop-profiling",
+ },
+ type: "PLAYER_DEVTOOLS_PLUGIN_INTERACTION",
+ });
+
+ const storeState = profilerPlugin.store.getState();
+ expect(
+ storeState.plugins["player-ui-profiler-plugin"]?.flow.data,
+ ).toMatchSnapshot();
+ });
+
+ test("stop-profiling marks profiling complete; start-profiling resets state", async () => {
+ const profilerPlugin = new ProfilerDevtoolsPlugin({
+ handler: {
+ checkIfDevtoolsIsActive: () => true,
+ processInteraction: () => {},
+ },
+ playerID: "ID",
+ });
+
+ const player = new Player({ plugins: [profilerPlugin] });
+
+ const flow: Flow = {
+ id: "flow2",
+ views: [{ id: "view1", type: "foo" }],
+ navigation: {
+ BEGIN: "FLOW_1",
+ FLOW_1: {
+ startState: "VIEW_1",
+ VIEW_1: {
+ state_type: "VIEW",
+ ref: "view1",
+ transitions: { "*": "END_DONE" },
+ },
+ END_DONE: { state_type: "END", outcome: "done" },
+ },
+ },
+ };
+
+ const playerPromise = player.start(flow);
+
+ // Wait for the view to render so hooks have fired
+ await vi.waitFor(() => {
+ const playerState = player.getState();
+ expect(playerState.status).toBe("in-progress");
+ expect(
+ (playerState as InProgressState).controllers.view.currentView
+ ?.lastUpdate,
+ ).toBeDefined();
+ });
+
+ profilerPlugin.processInteraction({
+ payload: { type: "stop-profiling" },
+ type: "PLAYER_DEVTOOLS_PLUGIN_INTERACTION",
+ });
+
+ const dataAfterStop =
+ profilerPlugin.store.getState().plugins["player-ui-profiler-plugin"]?.flow
+ .data;
+ expect(dataAfterStop?.profiling).toBe(false);
+ expect(dataAfterStop?.displayFlameGraph).toBe(true);
+
+ // Restart — state should flip back to active profiling
+ profilerPlugin.processInteraction({
+ payload: { type: "start-profiling" },
+ type: "PLAYER_DEVTOOLS_PLUGIN_INTERACTION",
+ });
+
+ const dataAfterRestart =
+ profilerPlugin.store.getState().plugins["player-ui-profiler-plugin"]?.flow
+ .data;
+ expect(dataAfterRestart?.profiling).toBe(true);
+ expect(dataAfterRestart?.displayFlameGraph).toBe(false);
+
+ // Clean up
+ (player.getState() as InProgressState).controllers.flow.transition("go");
+ await playerPromise;
+ });
+});
diff --git a/devtools/plugins/profiler/core/src/addProfilerInterceptorsToHooks.ts b/devtools/plugins/profiler/core/src/addProfilerInterceptorsToHooks.ts
new file mode 100644
index 0000000..7b4618b
--- /dev/null
+++ b/devtools/plugins/profiler/core/src/addProfilerInterceptorsToHooks.ts
@@ -0,0 +1,89 @@
+import {
+ AsyncParallelBailHook,
+ AsyncParallelHook,
+ AsyncSeriesBailHook,
+ AsyncSeriesHook,
+ AsyncSeriesLoopHook,
+ AsyncSeriesWaterfallHook,
+ SyncBailHook,
+ SyncHook,
+ SyncLoopHook,
+ SyncWaterfallHook,
+} from "tapable-ts";
+import { Profiler, hasHooks, isMatchingPaths, isRecordType } from "./helpers";
+
+/* Paths to hooks to ignore.
+ * Currently ignoring "view" hook on player since it acts as a shortcut to the viewController's view hook. Including it would duplicate a lot of profiling work.
+ */
+const IGNORED_PATHS = [["view"]];
+
+// Would love to just check if things are `Hook` but tapable-ts doesn't export the base class ;-;
+type AnyHook =
+ | AsyncParallelBailHook
+ | AsyncParallelHook
+ | AsyncSeriesBailHook
+ | AsyncSeriesHook
+ | AsyncSeriesLoopHook
+ | AsyncSeriesWaterfallHook
+ | SyncBailHook
+ | SyncHook
+ | SyncLoopHook
+ | SyncWaterfallHook;
+
+// Note: cannot use instanceof to check against the hook classes due to how JS is loaded in swift and kotlin
+const isAnyHook = (obj: unknown): obj is AnyHook => {
+ return (
+ isRecordType(obj) &&
+ "intercept" in obj &&
+ typeof obj.intercept === "function"
+ );
+};
+
+/** Recursively add profiler interceptors to each hook in the "hooks" property of obj. */
+export const addProfilerInterceptorsToHooks = (
+ obj: unknown,
+ profiler: Profiler,
+ currentPath: string[] = [],
+ intercepted: WeakSet