diff --git a/libs/extension/src/background/index.ts b/libs/extension/src/background/index.ts index cc724452..a750c1bb 100644 --- a/libs/extension/src/background/index.ts +++ b/libs/extension/src/background/index.ts @@ -1,5 +1,6 @@ -import { checkData, packagePayload } from '../utils/transferTool'; -import { requestAllVNodeTreeInfos, initDevToolPageConnection } from '../utils/constants'; +import { checkMessage, packagePayload, changeSource } from '../utils/transferTool'; +import { RequestAllVNodeTreeInfos, InitDevToolPageConnection, DevToolBackground } from '../utils/constants'; +import { DevToolPanel, DevToolContentScript } from './../utils/constants'; // 多个页面、tab页共享一个 background,需要建立连接池,给每个tab建立连接 const connections = {}; @@ -7,22 +8,21 @@ const connections = {}; // panel 代码中调用 let backgroundPageConnection = chrome.runtime.connect({...}) 会触发回调函数 chrome.runtime.onConnect.addListener(function (port) { function extensionListener(message) { - const isHorizonMessage = checkData(message); + const isHorizonMessage = checkMessage(message, DevToolPanel); if (isHorizonMessage) { - console.log('received message', message); const { payload } = message; const { type, data } = payload; let passMessage; - if (type === initDevToolPageConnection) { + if (type === InitDevToolPageConnection) { if (!connections[data]) { // 获取 panel 所在 tab 页的tabId connections[data] = port; } - passMessage = packagePayload({ type: requestAllVNodeTreeInfos }); + passMessage = packagePayload({ type: RequestAllVNodeTreeInfos }, DevToolBackground); } else { passMessage = message; + changeSource(passMessage, DevToolBackground); } - console.log('post message:', passMessage); // 查询参数有 active 和 currentWindow, 如果开发者工具与页面分离,会导致currentWindow为false才能找到 // 所以只用 active 参数查找,但不确定这么写是否会引发查询错误的情况 // 或许需要用不同的查询参数查找两次 @@ -53,12 +53,13 @@ chrome.runtime.onConnect.addListener(function (port) { }); // 监听来自 content script 的消息,并将消息发送给对应的 devTools page,也就是 panel -chrome.runtime.onMessage.addListener(function (request, sender, sendResponse) { +chrome.runtime.onMessage.addListener(function (message, sender, sendResponse) { // Messages from content scripts should have sender.tab set if (sender.tab) { const tabId = sender.tab.id; - if (tabId in connections && checkData(request)) { - connections[tabId].postMessage(request); + if (tabId in connections && checkMessage(message, DevToolContentScript)) { + changeSource(message, DevToolBackground); + connections[tabId].postMessage(message); } else { console.log('Tab not found in connection list.'); } diff --git a/libs/extension/src/components/VList.tsx b/libs/extension/src/components/VList.tsx index aeee688a..73036fae 100644 --- a/libs/extension/src/components/VList.tsx +++ b/libs/extension/src/components/VList.tsx @@ -4,7 +4,7 @@ import { useState, useRef, useEffect } from 'horizon'; import styles from './VList.less'; -interface IProps { +interface IProps { data: T[], width: number, // 暂时未用到,当需要支持横向滚动时使用 height: number, // VList 的高度 @@ -20,7 +20,7 @@ export type renderInfoType = { skipItemCountBeforeScrollItem: number, }; -export function VList(props: IProps) { +export function VList(props: IProps) { const { data, height, diff --git a/libs/extension/src/components/VTree.tsx b/libs/extension/src/components/VTree.tsx index 6b990dc1..05c9d4e3 100644 --- a/libs/extension/src/components/VTree.tsx +++ b/libs/extension/src/components/VTree.tsx @@ -6,7 +6,7 @@ import { SizeObserver } from './SizeObserver'; import { renderInfoType, VList } from './VList'; export interface IData { - id: string; + id: number; name: string; indentation: number; userKey: string; diff --git a/libs/extension/src/contentScript/index.ts b/libs/extension/src/contentScript/index.ts index 9357f69f..92c7150f 100644 --- a/libs/extension/src/contentScript/index.ts +++ b/libs/extension/src/contentScript/index.ts @@ -1,5 +1,7 @@ import { injectCode } from '../utils/injectUtils'; -import { checkData } from '../utils/transferTool'; +import { checkMessage } from '../utils/transferTool'; +import { DevToolContentScript, DevToolHook, DevToolBackground } from './../utils/constants'; +import { changeSource } from './../utils/transferTool'; // 页面的window对象不能直接通过 contentScript 代码修改,只能通过添加 js 代码往页面 window 注入hook injectCode(chrome.runtime.getURL('/injector.js')); @@ -12,7 +14,8 @@ window.addEventListener('message', event => { } const data = event.data; - if (checkData(data)) { + if (checkMessage(data, DevToolHook)) { + changeSource(data, DevToolContentScript); // 传递给background chrome.runtime.sendMessage(data); } @@ -22,12 +25,13 @@ window.addEventListener('message', event => { // 监听来自background的消息 chrome.runtime.onMessage.addListener( - function (request, sender) { + function (message, sender) { // 该方法可以监听页面 contentScript 和插件的消息 // 没有 tab 信息说明消息来自插件 - if (!sender.tab && checkData(request)) { + if (!sender.tab && checkMessage(message, DevToolBackground)) { + changeSource(message, DevToolContentScript); // 传递消息给页面 - window.postMessage(request, '*'); + window.postMessage(message, '*'); } } ); diff --git a/libs/extension/src/injector/index.ts b/libs/extension/src/injector/index.ts index d1920678..58432849 100644 --- a/libs/extension/src/injector/index.ts +++ b/libs/extension/src/injector/index.ts @@ -1,6 +1,23 @@ -import parseTreeRoot, { deleteVNode } from '../parser/parseVNode'; -import { packagePayload, checkData } from './../utils/transferTool'; -import { oneVNodeTreeInfos, allVNodeTreesInfos, requestAllVNodeTreeInfos } from './../utils/constants'; +import parseTreeRoot, { deleteVNode, queryVNode } from '../parser/parseVNode'; +import { packagePayload, checkMessage } from './../utils/transferTool'; +import { + RequestAllVNodeTreeInfos, + AllVNodeTreesInfos, + RequestComponentAttrs, + ComponentAttrs, + DevToolHook, + DevToolContentScript +} from './../utils/constants'; +import { VNode } from './../../../horizon/src/renderer/vnode/VNode'; +import { ClassComponent } from '../../../horizon/src/renderer/vnode/VNodeTags'; +import { parseAttr } from '../parser/parseAttr'; + +function postMessage(type: string, data) { + window.postMessage(packagePayload({ + type: type, + data: data, + }, DevToolHook), '*'); +} const roots = []; @@ -11,19 +28,20 @@ function injectHook() { Object.defineProperty(window, '__HORIZON_DEV_HOOK__', { enumerable: false, value: { - addIfNotInclude: function( treeRoot: any) { + addIfNotInclude: function (treeRoot: VNode) { if (!roots.includes(treeRoot)) { roots.push(treeRoot); } }, - send: function (vNode: any) { - const result = parseTreeRoot(vNode); - window.postMessage(packagePayload({ - data: result, - type: oneVNodeTreeInfos - }), '*'); + send: function () { + const result = roots.reduce((pre, current) => { + const info = parseTreeRoot(current); + pre.push(info); + return pre; + }, []); + postMessage(AllVNodeTreesInfos, result); }, - delete: function (vNode: any) { + delete: function (vNode: VNode) { // 开发工具中保存了 vNode 的引用,在清理 VNode 的时候需要一并删除 deleteVNode(vNode); const index = roots.indexOf(vNode); @@ -39,19 +57,28 @@ function injectHook() { return; } const request = event.data; - if (checkData(request)) { + if (checkMessage(request, DevToolContentScript)) { const { payload } = request; const { type, data } = payload; - if (type === requestAllVNodeTreeInfos) { + if (type === RequestAllVNodeTreeInfos) { const result = roots.reduce((pre, current) => { const info = parseTreeRoot(current); pre.push(info); return pre; }, []); - window.postMessage(packagePayload({ - data: result, - type: allVNodeTreesInfos - }), '*'); + postMessage(AllVNodeTreesInfos, result); + } else if (type === RequestComponentAttrs) { + const vNode: VNode = queryVNode(data); + const tag = vNode.tag; + if (tag === ClassComponent) { + const { props, state } = vNode; + const parsedProps = parseAttr(props); + const parsedState = parseAttr(state); + postMessage(ComponentAttrs, { + parsedProps, + parsedState, + }); + } } } }); diff --git a/libs/extension/src/panel/App.tsx b/libs/extension/src/panel/App.tsx index 7ba1151f..d5283843 100644 --- a/libs/extension/src/panel/App.tsx +++ b/libs/extension/src/panel/App.tsx @@ -8,7 +8,13 @@ import { mockParsedVNodeData, parsedMockState } from '../devtools/mock'; import { FilterTree } from '../hooks/FilterTree'; import Close from '../svgs/Close'; import Arrow from './../svgs/Arrow'; -import { initDevToolPageConnection, allVNodeTreesInfos, requestComponentAttrs } from './../utils/constants'; +import { + InitDevToolPageConnection, + AllVNodeTreesInfos, + RequestComponentAttrs, + ComponentAttrs, + DevToolPanel, +} from './../utils/constants'; import { packagePayload } from './../utils/transferTool'; const parseVNodeData = (rawData) => { @@ -18,7 +24,7 @@ const parseVNodeData = (rawData) => { const data: IData[] = []; let i = 0; while (i < rawData.length) { - const id = rawData[i] as string; + const id = rawData[i] as number; i++; const name = rawData[i] as string; i++; @@ -53,11 +59,26 @@ const getParents = (item: IData | null, parsedVNodeData: IData[]) => { return parents; }; +let connection; +if (!isDev) { + // 与 background 的唯一连接 + connection = chrome.runtime.connect({ + name: 'panel' + }); +} + +function postMessage(type: string, data: any) { + connection.postMessage(packagePayload({ + type: type, + data: data, + }, DevToolPanel)); +} + function App() { const [parsedVNodeData, setParsedVNodeData] = useState([]); const [componentAttrs, setComponentAttrs] = useState({}); const [selectComp, setSelectComp] = useState(null); - const treeRootInfos = useRef<{[id: string]: number}>({}); // 记录保存的根节点 id 和长度 + const treeRootInfos = useRef<{id: number, length: number}[]>([]); // 记录保存的根节点 id 和长度, const { filterValue, @@ -81,30 +102,31 @@ function App() { props: parsedMockState, }); } else { - const connection = chrome.runtime.connect({ - name: 'panel' - }); // 页面打开后发送初始化请求 - connection.postMessage(packagePayload({ - type: initDevToolPageConnection, - data: chrome.devtools.inspectedWindow.tabId - })); + postMessage(InitDevToolPageConnection, chrome.devtools.inspectedWindow.tabId); // 监听 background消息 connection.onMessage.addListener(function (message) { const { payload } = message; if (payload) { const { type, data } = payload; - if (type === allVNodeTreesInfos) { + if (type === AllVNodeTreesInfos) { const allTreeData = data.reduce((pre, current) => { const parsedTreeData = parseVNodeData(current); const length = parsedTreeData.length; + treeRootInfos.current.length = 0; if (length) { const treeRoot = parsedTreeData[0]; - treeRootInfos.current[treeRoot.id] = length; + treeRootInfos.current.push({id: treeRoot.id, length: length}); } return pre.concat(parsedTreeData); }, []); setParsedVNodeData(allTreeData); + } else if (type === ComponentAttrs) { + const {parsedProps, parsedState} = data; + setComponentAttrs({ + state: parsedProps, + props: parsedState, + }); } } }); @@ -122,10 +144,7 @@ function App() { props: parsedMockState, }); } else { - connection.postMessage({ - name: requestComponentAttrs, - data: item.id - }); + postMessage(RequestComponentAttrs, item.id); } setSelectComp(item); }; diff --git a/libs/extension/src/parser/parseVNode.ts b/libs/extension/src/parser/parseVNode.ts index 813f9a73..e4ecc276 100644 --- a/libs/extension/src/parser/parseVNode.ts +++ b/libs/extension/src/parser/parseVNode.ts @@ -57,6 +57,10 @@ function parseTreeRoot(treeRoot: VNode) { return result; } +export function queryVNode(id: number) { + return IdToVNodeMap.get(id); +} + export function deleteVNode(vNode: VNode) { if (VNodeToIdMap.has(vNode)) { const id = VNodeToIdMap.get(vNode); diff --git a/libs/extension/src/utils/constants.ts b/libs/extension/src/utils/constants.ts index dd6ce7cc..e779a143 100644 --- a/libs/extension/src/utils/constants.ts +++ b/libs/extension/src/utils/constants.ts @@ -1,12 +1,22 @@ // panel 页面打开后初始化连接标志 -export const initDevToolPageConnection = 'init dev tool page connection'; +export const InitDevToolPageConnection = 'init dev tool page connection'; // background 解析全部 root VNodes 标志 -export const requestAllVNodeTreeInfos = 'request all vNodes tree infos'; +export const RequestAllVNodeTreeInfos = 'request all vNodes tree infos'; // vNodes 全部树解析结果标志 -export const allVNodeTreesInfos = 'vNode trees Infos'; +export const AllVNodeTreesInfos = 'vNode trees Infos'; // 一棵树的解析 -export const oneVNodeTreeInfos = 'one vNode tree'; +export const OneVNodeTreeInfos = 'one vNode tree'; // 获取组件属性 -export const requestComponentAttrs = 'get component attrs'; +export const RequestComponentAttrs = 'get component attrs'; // 返回组件属性 -export const componentAttrs = 'component attrs'; \ No newline at end of file +export const ComponentAttrs = 'component attrs'; + + +// 传递消息来源标志 +export const DevToolPanel = 'dev tool panel'; + +export const DevToolBackground = 'dev tool background'; + +export const DevToolContentScript = 'dev tool content script'; + +export const DevToolHook = 'dev tool hook'; \ No newline at end of file diff --git a/libs/extension/src/utils/transferTool.ts b/libs/extension/src/utils/transferTool.ts index 655be4e7..c25fc4d7 100644 --- a/libs/extension/src/utils/transferTool.ts +++ b/libs/extension/src/utils/transferTool.ts @@ -5,17 +5,28 @@ interface payLoadType { data?: any, } -export function packagePayload(payload: payLoadType) { +interface message { + type: typeof devTools, + payload: payLoadType, + from: string, +} + +export function packagePayload(payload: payLoadType, from: string): message { return { type: devTools, payload, + from, }; } -export function checkData(data: any) { - if (data?.type === devTools) { +export function checkMessage(data: any, from: string) { + if (data?.type === devTools && data?.from === from) { return true; } return false; } +export function changeSource(message: message, from: string) { + message.from = from; +} +