File size: 5,439 Bytes
4d70170
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
import { BridgeEvents, SharedData, createThrottleQueue, parse, stringify } from '@vue-devtools/shared-utils'
import type { AppRecord, BackendContext } from '@vue-devtools/app-backend-api'
import { BuiltinBackendFeature } from '@vue-devtools/app-backend-api'
import type { App, ComponentInstance, EditStatePayload } from '@vue/devtools-api'
import { getAppRecord } from './app'

const MAX_$VM = 10
const $vmQueue = []

export async function sendComponentTreeData(appRecord: AppRecord, instanceId: string, filter = '', maxDepth: number = null, recursively = false, ctx: BackendContext) {
  if (!instanceId || appRecord !== ctx.currentAppRecord) {
    return
  }

  // Flush will send all components in the tree
  // So we skip individiual tree updates
  if (
    instanceId !== '_root'
    && ctx.currentAppRecord.backend.options.features.includes(BuiltinBackendFeature.FLUSH)
  ) {
    return
  }

  const instance = getComponentInstance(appRecord, instanceId, ctx)
  if (!instance) {
    ctx.bridge.send(BridgeEvents.TO_FRONT_COMPONENT_TREE, {
      instanceId,
      treeData: null,
      notFound: true,
    })
  }
  else {
    if (filter) {
      filter = filter.toLowerCase()
    }
    if (maxDepth == null) {
      maxDepth = instance === ctx.currentAppRecord.rootInstance ? 2 : 1
    }
    const data = await appRecord.backend.api.walkComponentTree(instance, maxDepth, filter, recursively)
    const payload = {
      instanceId,
      treeData: stringify(data),
    }
    ctx.bridge.send(BridgeEvents.TO_FRONT_COMPONENT_TREE, payload)
  }
}

export async function sendSelectedComponentData(appRecord: AppRecord, instanceId: string, ctx: BackendContext) {
  if (!instanceId || appRecord !== ctx.currentAppRecord) {
    return
  }
  const instance = getComponentInstance(appRecord, instanceId, ctx)
  if (!instance) {
    sendEmptyComponentData(instanceId, ctx)
  }
  else {
    // Expose instance on window
    if (typeof window !== 'undefined') {
      const win = window as any
      win.$vm = instance

      // $vm0, $vm1, $vm2, ...
      if ($vmQueue[0] !== instance) {
        if ($vmQueue.length >= MAX_$VM) {
          $vmQueue.pop()
        }
        for (let i = $vmQueue.length; i > 0; i--) {
          win[`$vm${i}`] = $vmQueue[i] = $vmQueue[i - 1]
        }
        win.$vm0 = $vmQueue[0] = instance
      }
    }
    if (SharedData.debugInfo) {
      // eslint-disable-next-line no-console
      console.log('[DEBUG] inspect', instance)
    }
    const parentInstances = await appRecord.backend.api.walkComponentParents(instance)
    const payload = {
      instanceId,
      data: stringify(await appRecord.backend.api.inspectComponent(instance, ctx.currentAppRecord.options.app)),
      parentIds: parentInstances.map(i => i.__VUE_DEVTOOLS_UID__),
    }
    ctx.bridge.send(BridgeEvents.TO_FRONT_COMPONENT_SELECTED_DATA, payload)
    markSelectedInstance(instanceId, ctx)
  }
}

export function markSelectedInstance(instanceId: string, ctx: BackendContext) {
  ctx.currentInspectedComponentId = instanceId
  ctx.currentAppRecord.lastInspectedComponentId = instanceId
}

export function sendEmptyComponentData(instanceId: string, ctx: BackendContext) {
  ctx.bridge.send(BridgeEvents.TO_FRONT_COMPONENT_SELECTED_DATA, {
    instanceId,
    data: null,
  })
}

export async function editComponentState(instanceId: string, dotPath: string, type: string, state: EditStatePayload, ctx: BackendContext) {
  if (!instanceId) {
    return
  }
  const instance = getComponentInstance(ctx.currentAppRecord, instanceId, ctx)
  if (instance) {
    if ('value' in state && state.value != null) {
      state.value = parse(state.value, true)
    }
    await ctx.currentAppRecord.backend.api.editComponentState(instance, dotPath, type, state, ctx.currentAppRecord.options.app)
    await sendSelectedComponentData(ctx.currentAppRecord, instanceId, ctx)
  }
}

export async function getComponentId(app: App, uid: number, instance: ComponentInstance, ctx: BackendContext) {
  try {
    if (instance.__VUE_DEVTOOLS_UID__) {
      return instance.__VUE_DEVTOOLS_UID__
    }
    const appRecord = await getAppRecord(app, ctx)
    if (!appRecord) {
      return null
    }
    const isRoot = appRecord.rootInstance === instance
    return `${appRecord.id}:${isRoot ? 'root' : uid}`
  }
  catch (e) {
    if (SharedData.debugInfo) {
      console.error(e)
    }
    return null
  }
}

export function getComponentInstance(appRecord: AppRecord, instanceId: string, _ctx: BackendContext) {
  if (instanceId === '_root') {
    instanceId = `${appRecord.id}:root`
  }
  const instance = appRecord.instanceMap.get(instanceId)
  if (!instance) {
    appRecord.missingInstanceQueue.add(instanceId)
    if (SharedData.debugInfo) {
      console.warn(`Instance uid=${instanceId} not found`)
    }
  }
  return instance
}

export async function refreshComponentTreeSearch(ctx: BackendContext) {
  if (!ctx.currentAppRecord.componentFilter) {
    return
  }
  await sendComponentTreeData(ctx.currentAppRecord, '_root', ctx.currentAppRecord.componentFilter, null, false, ctx)
}

const updateTrackingQueue = createThrottleQueue(500)

export function sendComponentUpdateTracking(instanceId: string, time: number, ctx: BackendContext) {
  if (!instanceId) {
    return
  }

  updateTrackingQueue.add(instanceId, () => {
    const payload = {
      instanceId,
      time,
    }
    ctx.bridge.send(BridgeEvents.TO_FRONT_COMPONENT_UPDATED, payload)
  })
}