soiz1's picture
Upload folder using huggingface_hub
4d70170 verified
import type {
AppRecord,
BackendContext,
Plugin,
} from '@vue-devtools/app-backend-api'
import {
BuiltinBackendFeature,
createBackendContext,
} from '@vue-devtools/app-backend-api'
import type {
Bridge,
} from '@vue-devtools/shared-utils'
import {
BridgeEvents,
BridgeSubscriptions,
BuiltinTabs,
HookEvents,
SharedData,
createThrottleQueue,
getPluginSettings,
initSharedData,
isBrowser,
parse,
raf,
revive,
target,
} from '@vue-devtools/shared-utils'
import debounce from 'lodash/debounce'
import type { CustomInspectorOptions, PluginDescriptor, SetupFunction, TimelineEventOptions, TimelineLayerOptions } from '@vue/devtools-api'
import { Hooks, now } from '@vue/devtools-api'
import { hook } from './global-hook'
import { isSubscribed, subscribe, unsubscribe } from './util/subscriptions'
import { highlight, unHighlight } from './highlighter'
import { addTimelineEvent, clearTimeline, sendTimelineEventData, sendTimelineLayerEvents, sendTimelineLayers, setupTimeline } from './timeline'
import ComponentPicker from './component-pick'
import {
editComponentState,
getComponentId,
getComponentInstance,
refreshComponentTreeSearch,
sendComponentTreeData,
sendComponentUpdateTracking,
sendEmptyComponentData,
sendSelectedComponentData,
} from './component'
import { addPlugin, addPreviouslyRegisteredPlugins, addQueuedPlugins, sendPluginList } from './plugin'
import { _legacy_getAndRegisterApps, getAppRecord, registerApp, removeApp, selectApp, sendApps, waitForAppsRegistration } from './app'
import { editInspectorState, getInspector, getInspectorWithAppId, selectInspectorNode, sendCustomInspectors, sendInspectorState, sendInspectorTree } from './inspector'
import { showScreenshot } from './timeline-screenshot'
import { performanceMarkEnd, performanceMarkStart } from './perf'
import { initOnPageConfig } from './page-config'
import { addTimelineMarker, sendTimelineMarkers } from './timeline-marker'
import { flashComponent } from './flash.js'
let ctx: BackendContext = target.__vdevtools_ctx ?? null
let connected = target.__vdevtools_connected ?? false
let pageTitleObserver: MutationObserver
export async function initBackend(bridge: Bridge) {
await initSharedData({
bridge,
persist: false,
})
SharedData.isBrowser = isBrowser
initOnPageConfig()
if (!connected) {
// First connect
ctx = target.__vdevtools_ctx = createBackendContext({
bridge,
hook,
})
SharedData.legacyApps = false
if (hook.Vue) {
connect()
_legacy_getAndRegisterApps(ctx, true)
SharedData.legacyApps = true
}
hook.on(HookEvents.INIT, () => {
_legacy_getAndRegisterApps(ctx, true)
SharedData.legacyApps = true
})
hook.on(HookEvents.APP_ADD, async (app) => {
await registerApp(app, ctx)
connect()
})
// Add apps that already sent init
if (hook.apps.length) {
hook.apps.forEach((app) => {
registerApp(app, ctx)
connect()
})
}
}
else {
// Reconnect
ctx.bridge = bridge
connectBridge()
ctx.bridge.send(BridgeEvents.TO_FRONT_RECONNECTED)
}
}
async function connect() {
if (connected) {
return
}
connected = target.__vdevtools_connected = true
await waitForAppsRegistration()
connectBridge()
ctx.currentTab = BuiltinTabs.COMPONENTS
// Apps
hook.on(HookEvents.APP_UNMOUNT, async (app) => {
await removeApp(app, ctx)
})
// Components
const throttleQueue = createThrottleQueue(500)
hook.on(HookEvents.COMPONENT_UPDATED, async (app, uid, parentUid, component) => {
try {
if (!app || (typeof uid !== 'number' && !uid) || !component) {
return
}
const now = Date.now()
let id: string
let appRecord: AppRecord
if (app && uid != null) {
id = await getComponentId(app, uid, component, ctx)
appRecord = await getAppRecord(app, ctx)
}
else {
id = ctx.currentInspectedComponentId
appRecord = ctx.currentAppRecord
}
throttleQueue.add(`update:${id}`, async () => {
try {
if (SharedData.trackUpdates) {
sendComponentUpdateTracking(id, now, ctx)
}
if (SharedData.flashUpdates) {
await flashComponent(component, appRecord.backend)
}
// Update component inspector
if (ctx.currentInspectedComponentId === id) {
await sendSelectedComponentData(appRecord, ctx.currentInspectedComponentId, ctx)
}
// Update tree (tags)
if (isSubscribed(BridgeSubscriptions.COMPONENT_TREE, id)) {
await sendComponentTreeData(appRecord, id, appRecord.componentFilter, 0, false, ctx)
}
}
catch (e) {
if (SharedData.debugInfo) {
console.error(e)
}
}
})
}
catch (e) {
if (SharedData.debugInfo) {
console.error(e)
}
}
})
hook.on(HookEvents.COMPONENT_ADDED, async (app, uid, parentUid, component) => {
try {
if (!app || (typeof uid !== 'number' && !uid) || !component) {
return
}
const now = Date.now()
const id = await getComponentId(app, uid, component, ctx)
throttleQueue.add(`add:${id}`, async () => {
try {
const appRecord = await getAppRecord(app, ctx)
if (component) {
if (component.__VUE_DEVTOOLS_UID__ == null) {
component.__VUE_DEVTOOLS_UID__ = id
}
if (appRecord?.instanceMap) {
if (!appRecord.instanceMap.has(id)) {
appRecord.instanceMap.set(id, component)
}
}
}
if (parentUid != null && appRecord?.instanceMap) {
const parentInstances = await appRecord.backend.api.walkComponentParents(component)
if (parentInstances.length) {
// Check two parents level to update `hasChildren
for (let i = 0; i < parentInstances.length; i++) {
const parentId = await getComponentId(app, parentUid, parentInstances[i], ctx)
if (i < 2 && isSubscribed(BridgeSubscriptions.COMPONENT_TREE, parentId)) {
raf(() => {
sendComponentTreeData(appRecord, parentId, appRecord.componentFilter, null, false, ctx)
})
}
if (SharedData.trackUpdates) {
sendComponentUpdateTracking(parentId, now, ctx)
}
}
}
}
if (ctx.currentInspectedComponentId === id) {
await sendSelectedComponentData(appRecord, id, ctx)
}
if (SharedData.trackUpdates) {
sendComponentUpdateTracking(id, now, ctx)
}
if (SharedData.flashUpdates) {
await flashComponent(component, appRecord.backend)
}
await refreshComponentTreeSearch(ctx)
}
catch (e) {
if (SharedData.debugInfo) {
console.error(e)
}
}
})
}
catch (e) {
if (SharedData.debugInfo) {
console.error(e)
}
}
})
hook.on(HookEvents.COMPONENT_REMOVED, async (app, uid, parentUid, component) => {
try {
if (!app || (typeof uid !== 'number' && !uid) || !component) {
return
}
const id = await getComponentId(app, uid, component, ctx)
throttleQueue.add(`remove:${id}`, async () => {
try {
const appRecord = await getAppRecord(app, ctx)
if (parentUid != null && appRecord) {
const parentInstances = await appRecord.backend.api.walkComponentParents(component)
if (parentInstances.length) {
const parentId = await getComponentId(app, parentUid, parentInstances[0], ctx)
if (isSubscribed(BridgeSubscriptions.COMPONENT_TREE, parentId)) {
raf(async () => {
try {
const appRecord = await getAppRecord(app, ctx)
if (appRecord) {
sendComponentTreeData(appRecord, parentId, appRecord.componentFilter, null, false, ctx)
}
}
catch (e) {
if (SharedData.debugInfo) {
console.error(e)
}
}
})
}
}
}
if (isSubscribed(BridgeSubscriptions.SELECTED_COMPONENT_DATA, id)) {
await sendEmptyComponentData(id, ctx)
}
if (appRecord) {
appRecord.instanceMap.delete(id)
}
await refreshComponentTreeSearch(ctx)
}
catch (e) {
if (SharedData.debugInfo) {
console.error(e)
}
}
})
}
catch (e) {
if (SharedData.debugInfo) {
console.error(e)
}
}
})
hook.on(HookEvents.TRACK_UPDATE, (id, ctx) => {
sendComponentUpdateTracking(id, Date.now(), ctx)
})
hook.on(HookEvents.FLASH_UPDATE, (instance, backend) => {
flashComponent(instance, backend)
})
// Component perf
hook.on(HookEvents.PERFORMANCE_START, (app, uid, vm, type, time) => {
performanceMarkStart(app, uid, vm, type, time, ctx)
})
hook.on(HookEvents.PERFORMANCE_END, (app, uid, vm, type, time) => {
performanceMarkEnd(app, uid, vm, type, time, ctx)
})
// Highlighter
hook.on(HookEvents.COMPONENT_HIGHLIGHT, (instanceId) => {
highlight(ctx.currentAppRecord.instanceMap.get(instanceId), ctx.currentAppRecord.backend, ctx)
})
hook.on(HookEvents.COMPONENT_UNHIGHLIGHT, () => {
unHighlight()
})
// Timeline
setupTimeline(ctx)
hook.on(HookEvents.TIMELINE_LAYER_ADDED, async (options: TimelineLayerOptions, plugin: Plugin) => {
const appRecord = await getAppRecord(plugin.descriptor.app, ctx)
if (appRecord) {
ctx.timelineLayers.push({
...options,
appRecord,
plugin,
events: [],
})
ctx.bridge.send(BridgeEvents.TO_FRONT_TIMELINE_LAYER_ADD, {})
}
})
hook.on(HookEvents.TIMELINE_EVENT_ADDED, async (options: TimelineEventOptions, plugin: Plugin) => {
await addTimelineEvent(options, plugin.descriptor.app, ctx)
})
// Custom inspectors
hook.on(HookEvents.CUSTOM_INSPECTOR_ADD, async (options: CustomInspectorOptions, plugin: Plugin) => {
const appRecord = await getAppRecord(plugin.descriptor.app, ctx)
if (appRecord) {
ctx.customInspectors.push({
...options,
appRecord,
plugin,
treeFilter: '',
selectedNodeId: null,
})
ctx.bridge.send(BridgeEvents.TO_FRONT_CUSTOM_INSPECTOR_ADD, {})
}
})
hook.on(HookEvents.CUSTOM_INSPECTOR_SEND_TREE, async (inspectorId: string, plugin: Plugin) => {
const inspector = getInspector(inspectorId, plugin.descriptor.app, ctx)
if (inspector) {
await sendInspectorTree(inspector, ctx)
}
else if (SharedData.debugInfo) {
console.warn(`Inspector ${inspectorId} not found`)
}
})
hook.on(HookEvents.CUSTOM_INSPECTOR_SEND_STATE, async (inspectorId: string, plugin: Plugin) => {
const inspector = getInspector(inspectorId, plugin.descriptor.app, ctx)
if (inspector) {
await sendInspectorState(inspector, ctx)
}
else if (SharedData.debugInfo) {
console.warn(`Inspector ${inspectorId} not found`)
}
})
hook.on(HookEvents.CUSTOM_INSPECTOR_SELECT_NODE, async (inspectorId: string, nodeId: string, plugin: Plugin) => {
const inspector = getInspector(inspectorId, plugin.descriptor.app, ctx)
if (inspector) {
await selectInspectorNode(inspector, nodeId, ctx)
}
else if (SharedData.debugInfo) {
console.warn(`Inspector ${inspectorId} not found`)
}
})
// Plugins
try {
await addPreviouslyRegisteredPlugins(ctx)
}
catch (e) {
if (SharedData.debugInfo) {
console.error(`Error adding previously registered plugins:`)
console.error(e)
}
}
try {
await addQueuedPlugins(ctx)
}
catch (e) {
if (SharedData.debugInfo) {
console.error(`Error adding queued plugins:`)
console.error(e)
}
}
hook.on(HookEvents.SETUP_DEVTOOLS_PLUGIN, async (pluginDescriptor: PluginDescriptor, setupFn: SetupFunction) => {
await addPlugin({ pluginDescriptor, setupFn }, ctx)
})
target.__VUE_DEVTOOLS_PLUGIN_API_AVAILABLE__ = true
// Legacy flush
const handleFlush = debounce(async () => {
if (ctx.currentAppRecord?.backend.options.features.includes(BuiltinBackendFeature.FLUSH)) {
await sendComponentTreeData(ctx.currentAppRecord, '_root', ctx.currentAppRecord.componentFilter, null, false, ctx)
if (ctx.currentInspectedComponentId) {
await sendSelectedComponentData(ctx.currentAppRecord, ctx.currentInspectedComponentId, ctx)
}
}
}, 500)
hook.off(HookEvents.FLUSH)
hook.on(HookEvents.FLUSH, handleFlush)
// Connect done
try {
await addTimelineMarker({
id: 'vue-devtools-init-backend',
time: now(),
label: 'Vue Devtools connected',
color: 0x41B883,
all: true,
}, ctx)
}
catch (e) {
if (SharedData.debugInfo) {
console.error(`Error while adding devtools connected timeline marker:`)
console.error(e)
}
}
}
function connectBridge() {
// Subscriptions
ctx.bridge.on(BridgeEvents.TO_BACK_SUBSCRIBE, ({ type, key }) => {
subscribe(type, key)
})
ctx.bridge.on(BridgeEvents.TO_BACK_UNSUBSCRIBE, ({ type, key }) => {
unsubscribe(type, key)
})
// Tabs
ctx.bridge.on(BridgeEvents.TO_BACK_TAB_SWITCH, async (tab) => {
ctx.currentTab = tab
await unHighlight()
})
// Apps
ctx.bridge.on(BridgeEvents.TO_BACK_APP_LIST, async () => {
await sendApps(ctx)
})
ctx.bridge.on(BridgeEvents.TO_BACK_APP_SELECT, async (id) => {
if (id == null) {
return
}
const record = ctx.appRecords.find(r => r.id === id)
if (record) {
await selectApp(record, ctx)
}
else if (SharedData.debugInfo) {
console.warn(`App with id ${id} not found`)
}
})
ctx.bridge.on(BridgeEvents.TO_BACK_SCAN_LEGACY_APPS, () => {
if (hook.Vue) {
_legacy_getAndRegisterApps(ctx)
}
})
// Components
ctx.bridge.on(BridgeEvents.TO_BACK_COMPONENT_TREE, async ({ instanceId, filter, recursively }) => {
ctx.currentAppRecord.componentFilter = filter
subscribe(BridgeSubscriptions.COMPONENT_TREE, instanceId)
await sendComponentTreeData(ctx.currentAppRecord, instanceId, filter, null, recursively, ctx)
})
ctx.bridge.on(BridgeEvents.TO_BACK_COMPONENT_SELECTED_DATA, async (instanceId) => {
await sendSelectedComponentData(ctx.currentAppRecord, instanceId, ctx)
})
ctx.bridge.on(BridgeEvents.TO_BACK_COMPONENT_EDIT_STATE, async ({ instanceId, dotPath, type, value, newKey, remove }) => {
await editComponentState(instanceId, dotPath, type, { value, newKey, remove }, ctx)
})
ctx.bridge.on(BridgeEvents.TO_BACK_COMPONENT_INSPECT_DOM, async ({ instanceId }) => {
const instance = getComponentInstance(ctx.currentAppRecord, instanceId, ctx)
if (instance) {
const [el] = await ctx.currentAppRecord.backend.api.getComponentRootElements(instance)
if (el) {
target.__VUE_DEVTOOLS_INSPECT_TARGET__ = el
ctx.bridge.send(BridgeEvents.TO_FRONT_COMPONENT_INSPECT_DOM, null)
}
}
})
ctx.bridge.on(BridgeEvents.TO_BACK_COMPONENT_SCROLL_TO, async ({ instanceId }) => {
if (!isBrowser) {
return
}
const instance = getComponentInstance(ctx.currentAppRecord, instanceId, ctx)
if (instance) {
const [el] = await ctx.currentAppRecord.backend.api.getComponentRootElements(instance)
if (el) {
if (typeof el.scrollIntoView === 'function') {
el.scrollIntoView({
behavior: 'smooth',
block: 'center',
inline: 'center',
})
}
else {
// Handle nodes that don't implement scrollIntoView
const bounds = await ctx.currentAppRecord.backend.api.getComponentBounds(instance)
const scrollTarget = document.createElement('div')
scrollTarget.style.position = 'absolute'
scrollTarget.style.width = `${bounds.width}px`
scrollTarget.style.height = `${bounds.height}px`
scrollTarget.style.top = `${bounds.top}px`
scrollTarget.style.left = `${bounds.left}px`
document.body.appendChild(scrollTarget)
scrollTarget.scrollIntoView({
behavior: 'smooth',
block: 'center',
inline: 'center',
})
setTimeout(() => {
document.body.removeChild(scrollTarget)
}, 2000)
}
highlight(instance, ctx.currentAppRecord.backend, ctx)
setTimeout(() => {
unHighlight()
}, 2000)
}
}
})
ctx.bridge.on(BridgeEvents.TO_BACK_COMPONENT_RENDER_CODE, async ({ instanceId }) => {
if (!isBrowser) {
return
}
const instance = getComponentInstance(ctx.currentAppRecord, instanceId, ctx)
if (instance) {
const { code } = await ctx.currentAppRecord.backend.api.getComponentRenderCode(instance)
ctx.bridge.send(BridgeEvents.TO_FRONT_COMPONENT_RENDER_CODE, {
instanceId,
code,
})
}
})
ctx.bridge.on(BridgeEvents.TO_BACK_CUSTOM_STATE_ACTION, async ({ value, actionIndex }) => {
const rawAction = value._custom.actions[actionIndex]
const action = revive(rawAction?.action)
if (action) {
try {
await action()
}
catch (e) {
if (SharedData.debugInfo) {
console.error(e)
}
}
}
else if (SharedData.debugInfo) {
console.warn(`Couldn't revive action ${actionIndex} from`, value)
}
})
// Highlighter
ctx.bridge.on(BridgeEvents.TO_BACK_COMPONENT_MOUSE_OVER, async (instanceId) => {
await highlight(ctx.currentAppRecord.instanceMap.get(instanceId), ctx.currentAppRecord.backend, ctx)
})
ctx.bridge.on(BridgeEvents.TO_BACK_COMPONENT_MOUSE_OUT, async () => {
await unHighlight()
})
// Component picker
const componentPicker = new ComponentPicker(ctx)
ctx.bridge.on(BridgeEvents.TO_BACK_COMPONENT_PICK, () => {
componentPicker.startSelecting()
})
ctx.bridge.on(BridgeEvents.TO_BACK_COMPONENT_PICK_CANCELED, () => {
componentPicker.stopSelecting()
})
// Timeline
ctx.bridge.on(BridgeEvents.TO_BACK_TIMELINE_LAYER_LIST, async () => {
await sendTimelineLayers(ctx)
})
ctx.bridge.on(BridgeEvents.TO_BACK_TIMELINE_SHOW_SCREENSHOT, async ({ screenshot }) => {
await showScreenshot(screenshot, ctx)
})
ctx.bridge.on(BridgeEvents.TO_BACK_TIMELINE_CLEAR, async () => {
await clearTimeline(ctx)
})
ctx.bridge.on(BridgeEvents.TO_BACK_TIMELINE_EVENT_DATA, async ({ id }) => {
await sendTimelineEventData(id, ctx)
})
ctx.bridge.on(BridgeEvents.TO_BACK_TIMELINE_LAYER_LOAD_EVENTS, async ({ appId, layerId }) => {
await sendTimelineLayerEvents(appId, layerId, ctx)
})
ctx.bridge.on(BridgeEvents.TO_BACK_TIMELINE_LOAD_MARKERS, async () => {
await sendTimelineMarkers(ctx)
})
// Custom inspectors
ctx.bridge.on(BridgeEvents.TO_BACK_CUSTOM_INSPECTOR_LIST, async () => {
await sendCustomInspectors(ctx)
})
ctx.bridge.on(BridgeEvents.TO_BACK_CUSTOM_INSPECTOR_TREE, async ({ inspectorId, appId, treeFilter }) => {
const inspector = await getInspectorWithAppId(inspectorId, appId, ctx)
if (inspector) {
inspector.treeFilter = treeFilter
sendInspectorTree(inspector, ctx)
}
else if (SharedData.debugInfo) {
console.warn(`Inspector ${inspectorId} not found`)
}
})
ctx.bridge.on(BridgeEvents.TO_BACK_CUSTOM_INSPECTOR_STATE, async ({ inspectorId, appId, nodeId }) => {
const inspector = await getInspectorWithAppId(inspectorId, appId, ctx)
if (inspector) {
inspector.selectedNodeId = nodeId
sendInspectorState(inspector, ctx)
}
else if (SharedData.debugInfo) {
console.warn(`Inspector ${inspectorId} not found`)
}
})
ctx.bridge.on(BridgeEvents.TO_BACK_CUSTOM_INSPECTOR_EDIT_STATE, async ({ inspectorId, appId, nodeId, path, type, payload }) => {
const inspector = await getInspectorWithAppId(inspectorId, appId, ctx)
if (inspector) {
await editInspectorState(inspector, nodeId, path, type, payload, ctx)
inspector.selectedNodeId = nodeId
await sendInspectorState(inspector, ctx)
}
else if (SharedData.debugInfo) {
console.warn(`Inspector ${inspectorId} not found`)
}
})
ctx.bridge.on(BridgeEvents.TO_BACK_CUSTOM_INSPECTOR_ACTION, async ({ inspectorId, appId, actionIndex, actionType, args }) => {
const inspector = await getInspectorWithAppId(inspectorId, appId, ctx)
if (inspector) {
const action = inspector[actionType ?? 'actions'][actionIndex]
try {
await action.action(...(args ?? []))
}
catch (e) {
if (SharedData.debugInfo) {
console.error(e)
}
}
}
else if (SharedData.debugInfo) {
console.warn(`Inspector ${inspectorId} not found`)
}
})
// Misc
ctx.bridge.on(BridgeEvents.TO_BACK_LOG, (payload: { level: string, value: any, serialized?: boolean, revive?: boolean }) => {
let value = payload.value
if (payload.serialized) {
value = parse(value, payload.revive)
}
else if (payload.revive) {
value = revive(value)
}
// eslint-disable-next-line no-console
console[payload.level](value)
})
// Plugins
ctx.bridge.on(BridgeEvents.TO_BACK_DEVTOOLS_PLUGIN_LIST, async () => {
await sendPluginList(ctx)
})
ctx.bridge.on(BridgeEvents.TO_BACK_DEVTOOLS_PLUGIN_SETTING_UPDATED, ({ pluginId, key, newValue, oldValue }) => {
const settings = getPluginSettings(pluginId)
ctx.hook.emit(HookEvents.PLUGIN_SETTINGS_SET, pluginId, settings)
ctx.currentAppRecord.backend.api.callHook(Hooks.SET_PLUGIN_SETTINGS, {
app: ctx.currentAppRecord.options.app,
pluginId,
key,
newValue,
oldValue,
settings,
})
})
ctx.bridge.send(BridgeEvents.TO_FRONT_TITLE, { title: document.title })
// Watch page title
const titleEl = document.querySelector('title')
if (titleEl && typeof MutationObserver !== 'undefined') {
if (pageTitleObserver) {
pageTitleObserver.disconnect()
}
pageTitleObserver = new MutationObserver((mutations) => {
const title = mutations[0].target as HTMLTitleElement
ctx.bridge.send(BridgeEvents.TO_FRONT_TITLE, { title: title.textContent })
})
pageTitleObserver.observe(titleEl, { subtree: true, characterData: true, childList: true })
}
}