<template> <div id="devtools"> <el-drawer title="settings" direction="btt" @close="onCloseSettings" :visible.sync="showSettings"> <div> <settings-vue></settings-vue> </div> </el-drawer> <div class="head" v-show="iframes.length>1"> <div class="label">inspect target:</div> <el-select v-model="frameID" placeholder="please select ..." @change="onChangeFrame" style="flex:1;"> <el-option v-for="item in iframes" :key="item.value" :label="item.label" :value="item.value"> </el-option> </el-select> </div> <div v-show="isShowDebug" class="find"> <div v-if="false"> <el-button type="success" @click="onMemoryTest">内存测试</el-button> </div> <div v-if="false"> <span>JS堆栈限制: {{ memory.performance.jsHeapSizeLimit }}</span> <span>JS堆栈大小: {{ memory.performance.totalJSHeapSize }}</span> <span>JS堆栈使用: {{ memory.performance.usedJSHeapSize }}</span> </div> <div class="left"> <div class="tool-btn"> <div style="padding-left: 15px;flex:1;">Node Tree</div> <el-button v-show="isShowRefreshBtn" type="success" class="el-icon-refresh" size="mini" @click="onBtnClickUpdateTree"></el-button> <el-button @click="onClickSettings" class="el-icon-s-tools"></el-button> </div> <el-input placeholder="enter keywords to filter" v-model="filterText"> <template slot="append"> <div class="matchCase "> <div class="iconfont el-icon-third-font-size" @click.stop="onChangeCase" title="match case" :style="{'color':matchCase?'red':''}"> </div> </div> </template> </el-input> <div class="treeList"> <el-tree :data="treeData" ref="tree" style="display: inline-block;" :props="defaultProps" :highlight-current="true" :default-expand-all="false" :default-expanded-keys="expandedKeys" :filter-node-method="filterNode" :expand-on-click-node="false" node-key="uuid" @node-expand="onNodeExpand" @node-collapse="onNodeCollapse" @node-click="handleNodeClick"> <!-- :render-content="renderContent"--> <span slot-scope="{node,data}" class="leaf" :class="data.active?'leaf-show':'leaf-hide'"> <span>{{ node.label }}</span> <!-- <el-button v-if="!!data||true"> 显示</el-button>--> </span> </el-tree> </div> </div> <div class="right"> <properties v-if="treeItemData" :data="treeItemData"></properties> </div> </div> <div v-show="!isShowDebug" class="no-find"> <span>No games created by cocos creator found!</span> <el-button type="success" class="el-icon-refresh" @click="onBtnClickUpdatePage">刷新</el-button> </div> </div> </template> <script lang="ts"> import Vue from "vue"; import {Component, Watch} from "vue-property-decorator"; import properties from "./propertys.vue"; import {Msg, Page, PluginEvent} from "@/core/types" import {connectBackground} from "@/devtools/connectBackground"; import { EngineData, FrameDetails, Group, Info, NodeInfoData, ObjectData, ObjectItemRequestData, TreeData } from "@/devtools/data"; import Bus, {BusMsg} from "@/devtools/bus"; import settingsVue from "./settings.vue" import {RefreshAuto, RefreshManual, settings} from "@/devtools/settings"; @Component({ components: { properties, settingsVue, } }) export default class Index extends Vue { private isShowDebug: boolean = false; treeItemData: NodeInfoData | null = null; treeData: Array<TreeData> = [] expandedKeys: Array<string> = []; selectedUUID: string | null = null; filterText: string | null = null; iframes: Array<{ label: string, value: number }> = [] frameID: number | null = null; @Watch("filterText") updateFilterText(val: any) { (this.$refs?.tree as any)?.filter(val); } private matchCase = false; onChangeCase() { this.matchCase = !this.matchCase; this.updateFilterText(this.filterText); } private showSettings = false; onClickSettings() { this.showSettings = true; } private isShowRefreshBtn = false; onCloseSettings() { if (settings.data) { const {refreshType, refreshTime} = settings.data; switch (refreshType) { case RefreshAuto: { this.isShowRefreshBtn = false; this.onEnableTreeWatch(true, refreshTime) break; } case RefreshManual: { this.isShowRefreshBtn = true; this.onEnableTreeWatch(false) } } } } // el-tree的渲染key defaultProps = { children: "children", label: "name" }; memory = { performance: {}, console: {}, } timerID: number | null = null; private requestList: Array<{ id: string, cb: Function }> = []; created() { if (chrome && chrome.runtime) { this._initChromeRuntimeConnect(); } window.addEventListener("message", function (event) { console.log("on vue:" + JSON.stringify(event)); }, false); Bus.$on(BusMsg.ShowPlace, (data: EngineData) => { console.log(data) this._expand(data.engineUUID); }) Bus.$on(BusMsg.RequestObjectData, (data: ObjectData, cb: Function) => { if (!data.id || this.requestList.find(el => el.id === data.id)) { return } this.requestList.push({id: data.id, cb}); this.sendMsgToContentScript(Msg.GetObjectItemData, data) }); } filterNode(value: any, data: any) { if (!value) { return true; } else { if (this.matchCase) { // 严格匹配大写 return data?.name?.indexOf(value) !== -1; } else { return data?.name?.toLowerCase().indexOf(value.toLowerCase()) !== -1; } } } onChangeFrame() { this.sendMsgToContentScript(Msg.UseFrame, this.frameID) } _expand(uuid: string) { let expandKeys: Array<string> = []; function circle(array: any) { for (let i = 0; i < array.length; i++) { let item = array[i]; expandKeys.push(item.uuid); if (item.uuid === uuid) { return true } else { let find = circle(item.children); if (find) { return true; } else { expandKeys.pop(); } } } } circle(this.treeData) expandKeys.forEach(key => { if (!this.expandedKeys.find(el => el === key)) { this.expandedKeys.push(key) } }) // 高亮uuid } renderContent(h: Function, options: any) { let {node, data, store} = options; return h("span", {class: ""}, data.name) // return(<span>1111</span>) } _onMsgTreeInfo(treeData: Array<TreeData>) { this.isShowDebug = true; if (!Array.isArray(treeData)) { treeData = [treeData] } this.treeData = treeData; if (this._checkSelectedUUID()) { this.$nextTick(() => { //@ts-ignore this.$refs.tree.setCurrentKey(this.selectedUUID); // todo 需要重新获取下node的数据 }) } } _checkSelectedUUID() { if (this.selectedUUID) { const b = this._findUuidInTree(this.treeData, this.selectedUUID) if (b) { return true; } } this.selectedUUID = null; this.treeItemData = null; return false; } _findUuidInTree(data: TreeData[], targetUUID: string) { function circle(tree: TreeData[]) { for (let i = 0; i < tree.length; i++) { let item: TreeData = tree[i]; if (item.uuid === targetUUID) { return true; } if (circle(item.children)) { return true; } } return false; } return circle(data) } _onMsgNodeInfo(eventData: NodeInfoData) { this.isShowDebug = true; this.treeItemData = eventData; } _onMsgMemoryInfo(eventData: any) { this.memory = eventData; } _onMsgSupport(data: boolean) { this.isShowDebug = data; if (data) { // 如果节点树为空,就刷新一次 if (this.treeData.length === 0) { this.onBtnClickUpdateTree(); } } else { this._reset(); } } _reset() { this.treeData = []; this.treeItemData = null; } mounted() { this.onCloseSettings(); } _initChromeRuntimeConnect() { const msgFunctionMap: Record<string, Function> = {}; msgFunctionMap[Msg.TreeInfo] = this._onMsgTreeInfo; msgFunctionMap[Msg.Support] = this._onMsgSupport; msgFunctionMap[Msg.NodeInfo] = this._onMsgNodeInfo; msgFunctionMap[Msg.MemoryInfo] = this._onMsgMemoryInfo; msgFunctionMap[Msg.UpdateProperty] = this._onMsgUpdateProperty; msgFunctionMap[Msg.UpdateFrames] = this._onMsgUpdateFrames; msgFunctionMap[Msg.GetObjectItemData] = this._onMsgGetObjectItemData; // 接收来自background.js的消息数据 connectBackground.onBackgroundMessage((data: PluginEvent, sender: any) => { if (!data) { return; } if (data.target === Page.Devtools) { console.log("[Devtools]", data); PluginEvent.finish(data); const {msg} = data; if (msg) { const func = msgFunctionMap[msg]; if (func) { func(data.data) } else { console.warn(`没有${msg}消息的函数`) } } } }); } _onMsgGetObjectItemData(requestData: ObjectItemRequestData) { if (requestData.id !== null) { let findIndex = this.requestList.findIndex(el => el.id === requestData.id) if (findIndex > -1) { let del = this.requestList.splice(findIndex, 1)[0]; del.cb(requestData.data); } } } _onMsgUpdateFrames(details: FrameDetails[]) { // 先把iframes里面无效的清空了 this.iframes = this.iframes.filter(item => { details.find(el => el.frameID === item.value) }) // 同步配置 details.forEach(item => { let findItem = this.iframes.find(el => el.value === item.frameID); if (findItem) { findItem.label = item.url; } else { this.iframes.push({ label: item.url, value: item.frameID, }) } }) // 第一次获取到frame配置后,自动获取frame数据 if (this.frameID === null && this.iframes.length > 0 && !this.iframes.find(el => el.value === this.frameID)) { this.frameID = this.iframes[0].value; this.onChangeFrame(); } } _onMsgUpdateProperty(data: Info) { const uuid = data.path[0]; const key = data.path[1]; const value = data.data; let treeArray: Array<TreeData> = []; function circle(array: Array<TreeData>) { array.forEach(item => { treeArray.push(item); circle(item.children); }) } // 更新指定uuid节点的tree的name circle(this.treeData) let ret = treeArray.find(el => el.uuid === uuid); if (ret) { if (key === "name") { ret.name = value; } if (key === "active") { ret.active = !!value; } } } handleNodeClick(data: TreeData) { this.selectedUUID = data.uuid; let uuid = data.uuid; if (uuid !== undefined) { this.sendMsgToContentScript(Msg.NodeInfo, uuid); } } onEnableTreeWatch(watch: boolean, time = 300) { if (watch) { this._clearTimer(); this.timerID = setInterval(() => { this.onBtnClickUpdateTree(); }, time); } else { this._clearTimer(); } } private _clearTimer() { if (this.timerID !== null) { clearInterval(this.timerID); this.timerID = null; } } sendMsgToContentScript(msg: Msg, data?: any) { if (!chrome || !chrome.devtools) { console.log("环境异常,无法执行函数"); return; } connectBackground.postMessageToBackground(msg, data); } // 问题:没有上下文的权限,只能操作DOM _executeScript(para: Object) { let tabID = chrome.devtools.inspectedWindow.tabId; //@ts-ignore chrome.tabs.executeScript(tabID, {code: `var CCInspectorPara='${JSON.stringify(para)}';`}, () => { //@ts-ignore chrome.tabs.executeScript(tabID, {file: "js/execute.js"}) }); } _inspectedCode() { let injectCode = ""; chrome.devtools.inspectedWindow.eval(injectCode, (result, isException) => { if (isException) { console.error(isException); } else { console.log(`执行结果:${result}`) } }); } onBtnClickUpdateTree() { this.sendMsgToContentScript(Msg.TreeInfo, this.frameID); } onBtnClickUpdatePage() { this.sendMsgToContentScript(Msg.Support); } onMemoryTest() { this.sendMsgToContentScript(Msg.MemoryInfo); } onNodeExpand(data: TreeData) { if (data.hasOwnProperty("uuid") && data.uuid) { this.expandedKeys.push(data.uuid) } } onNodeCollapse(data: TreeData) { if (data.hasOwnProperty("uuid")) { let index = this.expandedKeys.findIndex(el => el === data.uuid); if (index !== -1) { this.expandedKeys.splice(index, 1) } } } } </script> <style scoped lang="less"> @import "../../index.less"; #devtools { display: flex; flex-direction: column; width: 100%; height: 100%; overflow: hidden; .head { display: flex; flex-direction: row; align-items: center; padding: 1px 0; border-bottom: solid 1px grey; .label { margin: 0 3px; } } .no-find { display: flex; flex: 1; flex-direction: row; align-items: center; justify-content: center; span { margin-right: 20px; } } .find { display: flex; flex: 1; flex-direction: row; overflow: auto; .left { display: flex; flex-direction: column; .tool-btn { display: flex; flex-direction: row; align-items: center; justify-content: center; } .matchCase { width: 30px; height: 26px; display: flex; flex-direction: row; align-items: center; justify-content: center; } .treeList { margin-top: 3px; height: 100%; border-radius: 4px; min-height: 20px; overflow: auto; width: 300px; .leaf { width: 100%; } .leaf-show { color: black; } .leaf-hide { color: #c7bbbb; text-decoration: line-through; } &::-webkit-scrollbar { width: 6px; height: 6px; background: #999; border-radius: 2px; } &::-webkit-scrollbar-thumb { background-color: #333; border-radius: 2px; } } } .right { flex: 1; background: #e5e9f2; overflow-x: hidden; overflow-y: overlay; &::-webkit-scrollbar { width: 6px; background: #999; border-radius: 2px; height: 6px; } &::-webkit-scrollbar-thumb { background-color: #333; border-radius: 2px; } } } } </style>