diff --git a/cc-inspector/.vscode/settings.json b/cc-inspector/.vscode/settings.json index 89b2b75..5986d86 100644 --- a/cc-inspector/.vscode/settings.json +++ b/cc-inspector/.vscode/settings.json @@ -2,10 +2,13 @@ "editor.formatOnSave": true, "editor.formatOnSaveMode": "modifications", "files.autoSave": "onFocusChange", + "editor.codeActionsOnSave": { + "source.organizeImports": "explicit" + }, "[typescript]": { "editor.tabSize": 2, "editor.formatOnSave": true, - "editor.defaultFormatter": "vscode.typescript-language-features", + "editor.defaultFormatter": "esbenp.prettier-vscode", "prettier.printWidth": 1000 }, "[json]": { diff --git a/cc-inspector/cc-plugin.config.ts b/cc-inspector/cc-plugin.config.ts index 44928f0..e76c62e 100644 --- a/cc-inspector/cc-plugin.config.ts +++ b/cc-inspector/cc-plugin.config.ts @@ -50,7 +50,7 @@ const manifest: CocosPluginManifest = { view_options: "src/views/options/index.ts", view_popup: "src/views/popup/index.ts", script_background: "src/scripts/background/index.ts", - script_content: "src/scripts/content.ts", + script_content: "src/scripts/content/index.ts", script_inject: "src/scripts/inject/index.ts", }, }; diff --git a/cc-inspector/src/core/types.ts b/cc-inspector/src/core/types.ts index 391ee96..fb8c778 100644 --- a/cc-inspector/src/core/types.ts +++ b/cc-inspector/src/core/types.ts @@ -11,6 +11,7 @@ export enum Page { } export enum Msg { + None = "None", /** * 具体的节点信息 */ @@ -50,6 +51,15 @@ export enum Msg { } export class PluginEvent { + public static FLAG = "cc-inspector"; + /** + * 增加一个消息的标记位,方便知道是自己插件的消息 + */ + flag: string = PluginEvent.FLAG; + /** + * 消息是否有效 + */ + valid: boolean = false; /** * 消息的类型 */ @@ -84,21 +94,32 @@ export class PluginEvent { if (typeof data === "string") { obj = JSON.stringify(data) } else if (typeof data === "object") { - + obj = data; } else { debugger; } - const cls = data as PluginEvent; - return new PluginEvent(cls.source, cls.target, cls.msg, cls.data); + + const ret = new PluginEvent(Page.None, Page.None, Msg.None, null); + if (obj.flag !== PluginEvent.FLAG) { + ret.valid = false; + } else { + const cls = data as PluginEvent; + ret.source = cls.source; + ret.target = cls.target; + ret.msg = cls.msg; + ret.data = cls.data; + ret.valid = true; + } + return ret; } - static check(event: PluginEvent, source: Page, target: Page) { - return event && source && target && event.source === source && event.target === target; + check(source: Page, target: Page) { + return source && target && this.source === source && this.target === target; } - static reset(event: PluginEvent, source: Page | null, target: Page | null) { - if (event && source && target) { - event.source = source; - event.target = target; + reset(source: Page | null, target: Page | null) { + if (source && target) { + this.source = source; + this.target = target; } } @@ -110,10 +131,11 @@ export class PluginEvent { } toChunk(): Chunk[] { return [ - new Chunk(this.msg).color("white").background("black").margin('0 0 0 5px').padding("0 6px"), - new Chunk(this.source).color('white').background("red").padding('0 4px').margin('0 0 0 0px'), + new Chunk(new Date().toLocaleString()).color("white").background("black").padding('0 4px'), + new Chunk(this.source).color('white').background("red").padding('0 4px').margin('0 0 0 5px'), new Chunk('=>').color('black').background("yellow").bold(), - new Chunk(this.target, true).color("white").background("green").padding('0 4px'), + new Chunk(this.target, false).color("white").background("green").padding('0 4px'), + new Chunk(this.msg, true).color("white").background("black").margin('0 0 0 5px').padding("0 6px"), new Chunk(JSON.stringify(this.data)) ]; } diff --git a/cc-inspector/src/scripts/background/index.ts b/cc-inspector/src/scripts/background/index.ts index f920093..0420a6a 100644 --- a/cc-inspector/src/scripts/background/index.ts +++ b/cc-inspector/src/scripts/background/index.ts @@ -40,12 +40,13 @@ chrome.runtime.onConnect.addListener((port: chrome.runtime.Port) => { } }); chrome.runtime.onMessage.addListener((request: PluginEvent, sender: any, sendResponse: any) => { + const event = PluginEvent.create(request); const tabID = sender.tab.id; const portMan: PortMan | undefined = portMgr.findPort(tabID); if (portMan) { - if (PluginEvent.check(request, Page.Content, Page.Background)) { + if (event.check(Page.Content, Page.Background)) { // 监听来自content.js发来的事件,将消息转发到devtools - PluginEvent.reset(request, Page.Background, Page.Devtools); + event.reset(Page.Background, Page.Devtools); console.log(`%c[Message]url:${sender.url}]\n${JSON.stringify(request)}`, "color:green"); portMgr.sendDevtoolMsg(request); } diff --git a/cc-inspector/src/scripts/background/portDevtools.ts b/cc-inspector/src/scripts/background/portDevtools.ts index 0299f3b..236cf47 100644 --- a/cc-inspector/src/scripts/background/portDevtools.ts +++ b/cc-inspector/src/scripts/background/portDevtools.ts @@ -14,13 +14,13 @@ export class PortDevtools extends PortMan { portMgr.useFrame(data.data); } else { // 从devtools过来的消息统一派发到Content中 - if (PluginEvent.check(data, Page.Devtools, Page.Background)) { + if (data.check(Page.Devtools, Page.Background)) { if (data.msg === Msg.TreeInfo) { if (portMgr.isCurrentFrme(data.data)) { console.log(`frameID[${data.data}]不一致`); } } - PluginEvent.reset(data, Page.Background, Page.Content); + data.reset(Page.Background, Page.Content); const port = portMgr.getCurrentUseContent(); if (!port) { console.warn(`not find andy port`); diff --git a/cc-inspector/src/scripts/background/portMan.ts b/cc-inspector/src/scripts/background/portMan.ts index 68fe092..22b05e0 100644 --- a/cc-inspector/src/scripts/background/portMan.ts +++ b/cc-inspector/src/scripts/background/portMan.ts @@ -31,12 +31,14 @@ export abstract class PortMan { this.title = tab.title; this.terminal = new Terminal(`Port-${this.name}`); port.onMessage.addListener((data: any, port: chrome.runtime.Port) => { - console.log(... this.terminal.message(JSON.stringify(data))); + const event = PluginEvent.create(data); + console.log(... this.terminal.chunkMessage(event.toChunk())); // 如果多个页面都监听 onMessage 事件,对于某一次事件只有第一次调用 sendResponse() 能成功发出回应,所有其他回应将被忽略。 // port.postMessage(data); - const cls = PluginEvent.create(data); - if (this.onMessage) { - this.onMessage(cls); + if (event.valid && this.onMessage) { + this.onMessage(event); + } else { + console.log(... this.terminal.log(JSON.stringify(data))); } }); port.onDisconnect.addListener((port: chrome.runtime.Port) => { diff --git a/cc-inspector/src/scripts/const.ts b/cc-inspector/src/scripts/const.ts new file mode 100644 index 0000000..71e62e2 --- /dev/null +++ b/cc-inspector/src/scripts/const.ts @@ -0,0 +1,10 @@ +export enum DocumentEvent { + /** + * 从inject到content的事件 + */ + Inject2Content = "inject2content", + /** + * 从content到inject的事件 + */ + Content2Inject = "content2inject", +} \ No newline at end of file diff --git a/cc-inspector/src/scripts/content.ts b/cc-inspector/src/scripts/content.ts deleted file mode 100644 index 829bdd9..0000000 --- a/cc-inspector/src/scripts/content.ts +++ /dev/null @@ -1,68 +0,0 @@ -// content.js 和原始界面共享DOM,具有操作dom的能力 -// 但是不共享js,要想访问页面js,只能通过注入的方式 -import { ChromeConst } from "cc-plugin/src/chrome/const"; -import { Msg, Page, PluginEvent } from "../core/types"; -import { Terminal } from "./terminal"; - -export function injectScript(url: string) { - if (chrome && chrome.runtime && chrome.runtime.getURL) { - let content = chrome.runtime.getURL(url) - const script = document.createElement("script") - script.setAttribute("type", "text/javascript") - script.setAttribute("src", content) - script.onload = function () { - document.head.removeChild(script); - } - document.head.appendChild(script) - console.log(...terminal.green(`inject script success: ${content}`)); - } else { - console.log(...terminal.red("inject script failed")); - } -} - -const terminal = new Terminal(Page.Content); -console.log(...terminal.init()); -// 和background.js保持长连接通讯,background和content的交互也要通过这个链接进行通讯 -let connect: chrome.runtime.Port | null = chrome.runtime.connect({ name: Page.Content }) -connect.onDisconnect.addListener(() => { - console.log(...terminal.disconnect("")); - connect = null -}) -connect.onMessage.addListener((data: PluginEvent, sender: chrome.runtime.Port) => { - if (PluginEvent.check(data, Page.Background, Page.Content)) { - console.log(...terminal.message(JSON.stringify(data))); - PluginEvent.reset(data, Page.Content, Page.Inject) - window.postMessage(data, "*"); - } -}) -// 接受来自inject.js的消息数据,然后中转到background.js -window.addEventListener("message", (event) => { - let data: PluginEvent = event.data; - if (PluginEvent.check(data, Page.Inject, Page.Content)) { - console.log(...terminal.message(JSON.stringify(data))); - PluginEvent.reset(data, Page.Content, Page.Devtools) - if (connect) { - connect.postMessage(data) - } else { - console.log(...terminal.log(`connect is null`)); - debugger; - } - } -}, false); - -function checkGame() { - let gameCanvas = document.querySelector("#GameCanvas"); - const sendData = new PluginEvent(Page.Content, Page.Devtools, Msg.Support, { - support: !!gameCanvas, - msg: "未发现GameCanvas,不支持调试游戏!" - }) - if (connect) { - connect.postMessage(sendData) - } else { - console.log(...terminal.log(`connect is null`)); - debugger; - } -} - -injectScript(ChromeConst.script.inject); -checkGame(); \ No newline at end of file diff --git a/cc-inspector/src/scripts/content/index.ts b/cc-inspector/src/scripts/content/index.ts new file mode 100644 index 0000000..3c50bae --- /dev/null +++ b/cc-inspector/src/scripts/content/index.ts @@ -0,0 +1,79 @@ +// content.js 和原始界面共享DOM,具有操作dom的能力 +// 但是不共享js,要想访问页面js,只能通过注入的方式 +import { ChromeConst } from "cc-plugin/src/chrome/const"; +import { Msg, Page, PluginEvent } from "../../core/types"; +import { DocumentEvent } from "../const"; +import { Terminal } from "../terminal"; + +const terminal = new Terminal(Page.Content); +console.log(...terminal.init()); + +// #region 注入脚本 +export function injectScript(url: string) { + if (chrome && chrome.runtime && chrome.runtime.getURL) { + let content = chrome.runtime.getURL(url); + const script = document.createElement("script"); + script.setAttribute("type", "text/javascript"); + script.setAttribute("src", content); + script.onload = function () { + document.head.removeChild(script); + }; + document.head.appendChild(script); + console.log(...terminal.green(`inject script success: ${content}`)); + } else { + console.log(...terminal.red("inject script failed")); + } +} + +// #region 和Inject通讯 +document.addEventListener(DocumentEvent.Inject2Content, (event: CustomEvent) => { + let data: PluginEvent = PluginEvent.create(event.detail); + if (data.valid && data.check(Page.Inject, Page.Content)) { + console.log(...terminal.chunkMessage(data.toChunk())); + data.reset(Page.Content, Page.Devtools); + if (connect) { + // 接受来自inject.js的消息数据,然后中转到background.js + connect.postMessage(data); + } else { + console.log(...terminal.log(`connect is null`)); + throw new Error("connect is null"); + } + } else { + throw new Error(`invalid data: ${event.detail}`); + } +}); +// #region 和background通讯 +let connect: chrome.runtime.Port = chrome.runtime.connect({ name: Page.Content }); +connect.onDisconnect.addListener(() => { + console.log(...terminal.disconnect("")); + connect = null; +}); +connect.onMessage.addListener((data: PluginEvent, sender: chrome.runtime.Port) => { + const event = PluginEvent.create(data); + if (event.valid && event.check(Page.Background, Page.Content)) { + console.log(...terminal.chunkMessage(event.toChunk())); + event.reset(Page.Content, Page.Inject); + const e = new CustomEvent(DocumentEvent.Content2Inject, { detail: event }); + console.log(...terminal.chunkSend(event.toChunk())); + document.dispatchEvent(e); + } else { + throw new Error(`invalid data: ${data}`); + } +}); + +function checkGame() { + let gameCanvas = document.querySelector("#GameCanvas"); + const sendData = new PluginEvent(Page.Content, Page.Devtools, Msg.Support, { + support: !!gameCanvas, + msg: "未发现GameCanvas,不支持调试游戏!", + }); + if (connect) { + connect.postMessage(sendData); + } else { + console.log(...terminal.log(`connect is null`)); + throw new Error("connect is null"); + } +} + +injectScript(ChromeConst.script.inject); +checkGame(); diff --git a/cc-inspector/src/scripts/inject/event.ts b/cc-inspector/src/scripts/inject/event.ts new file mode 100644 index 0000000..2a7465b --- /dev/null +++ b/cc-inspector/src/scripts/inject/event.ts @@ -0,0 +1,21 @@ +import { Msg, Page, PluginEvent } from "../../core/types"; +import { DocumentEvent } from "../const"; +import { Terminal } from "../terminal"; + +export class InjectEvent { + protected terminal = new Terminal("Inject "); + constructor() { + document.addEventListener(DocumentEvent.Content2Inject, (event: CustomEvent) => { + const pluginEvent: PluginEvent = PluginEvent.create(event.detail); + console.log(...this.terminal.chunkMessage(pluginEvent.toChunk())); + this.onMessage(pluginEvent); + }); + } + onMessage(data: PluginEvent) {} + sendMsgToContent(msg: Msg, data: any) { + const detail = new PluginEvent(Page.Inject, Page.Content, msg, data); + console.log(...this.terminal.chunkSend(detail.toChunk())); + const event = new CustomEvent(DocumentEvent.Inject2Content, { detail }); + document.dispatchEvent(event); + } +} diff --git a/cc-inspector/src/scripts/inject/index.ts b/cc-inspector/src/scripts/inject/index.ts index 694d3e9..6492606 100644 --- a/cc-inspector/src/scripts/inject/index.ts +++ b/cc-inspector/src/scripts/inject/index.ts @@ -1,704 +1,4 @@ -// eval 注入脚本的代码,变量尽量使用var,后来发现在import之后,let会自动变为var -import { uniq } from "lodash"; -import { Msg, Page, PluginEvent } from "../../core/types"; -import { ArrayData, BoolData, ColorData, DataType, EngineData, Group, ImageData, Info, InvalidData, NodeInfoData, NumberData, ObjectData, ObjectItemRequestData, Property, StringData, TreeData, Vec2Data, Vec3Data } from "../../views/devtools/data"; -import { getValue, trySetValueWithConfig } from "./setValue"; -import { BuildArrayOptions, BuildImageOptions, BuildObjectOptions, BuildVecOptions } from "./types"; -import { isHasProperty } from "./util"; -import { Terminal } from "../terminal"; - -declare const cc: any; - -class CCInspector { - inspectorGameMemoryStorage: Record = {}; - - private watchIsCocosGame() { - const timer = setInterval(() => { - if (this._isCocosGame()) { - clearInterval(timer); - // @ts-ignore - cc.director.on(cc.Director.EVENT_AFTER_SCENE_LAUNCH, () => { - const isCocosGame = this._isCocosGame(); - this.notifySupportGame(isCocosGame); - }); - } - }, 300); - } - private terminal = new Terminal('Inject '); - init() { - console.log(...this.terminal.init()); - this.watchIsCocosGame(); - window.addEventListener("message", (event) => { - // 接受来自content的事件,有可能也会受到其他插件的 - if (!event || !event.data) { - return; - } - let pluginEvent: PluginEvent = event.data; - if (PluginEvent.check(pluginEvent, Page.Content, Page.Inject)) { - console.log(...this.terminal.message(JSON.stringify(pluginEvent))); - PluginEvent.finish(pluginEvent); - switch (pluginEvent.msg) { - case Msg.Support: { - let isCocosGame = this._isCocosGame(); - this.notifySupportGame(isCocosGame); - break; - } - case Msg.TreeInfo: { - this.updateTreeInfo(); - break; - } - case Msg.NodeInfo: { - let nodeUUID = pluginEvent.data; - this.getNodeInfo(nodeUUID); - break; - } - case Msg.SetProperty: { - const data: Info = pluginEvent.data; - let value = data.data; - if (data.type === DataType.Color) { - // @ts-ignore - value = cc.color().fromHEX(value); - } - - if (this.setValue(data.path, value)) { - this.sendMsgToContent(Msg.UpdateProperty, data); - } else { - console.warn(`设置失败:${data.path}`); - } - break; - } - case Msg.LogData: { - const data: string[] = pluginEvent.data; - const value = getValue(this.inspectorGameMemoryStorage, data); - // 直接写console.log会被tree shaking - const logFunction = console.log; - logFunction(value); - break; - } - case Msg.GetObjectItemData: { - const data: ObjectData = pluginEvent.data; - let val = getValue(this.inspectorGameMemoryStorage, data.path); - if (val) { - let itemData: Property[] = this._buildObjectItemData({ - data: data, - path: data.path, - value: val, - filterKey: false, - }); - let result: ObjectItemRequestData = { - id: data.id, - data: itemData, - }; - this.sendMsgToContent(Msg.GetObjectItemData, result); - } - break; - } - } - } - }); - } - - sendMsgToContent(msg: Msg, data: any) { - // 发送给content.js处理,也会导致发送给了自身,死循环 - window.postMessage(new PluginEvent(Page.Inject, Page.Content, msg, data), "*"); - } - - notifySupportGame(b: boolean) { - this.sendMsgToContent(Msg.Support, b); - } - - updateTreeInfo() { - let isCocosCreatorGame = this._isCocosGame(); - if (isCocosCreatorGame) { - //@ts-ignore - let scene = cc.director.getScene(); - if (scene) { - let treeData = new TreeData(); - this.getNodeChildren(scene, treeData); - this.sendMsgToContent(Msg.TreeInfo, treeData); - } else { - console.warn("can't execute api : cc.director.getScene"); - this.notifySupportGame(false); - } - } else { - this.notifySupportGame(false); - } - } - - // @ts-ignore - draw: cc.Graphics = null; - - _drawRect(node: any) { - let draw = this.draw; - - if (!draw) { - // @ts-ignore - let node = new cc.Node("draw-node"); - // @ts-ignore - cc.director.getScene().addChild(node); - // @ts-ignore - draw = this.draw = node.addComponent(cc.Graphics); - } - draw.clear(); - draw.lineWidth = 10; - // @ts-ignore - draw.strokeColor = new cc.Color().fromHEX("#ff0000"); - const { anchorX, anchorY, width, height, x, y } = node; - let halfWidth = width / 2; - let halfHeight = height / 2; - let leftBottom = node.convertToWorldSpaceAR(cc.v2(-halfWidth, -halfHeight)); - let leftTop = node.convertToWorldSpaceAR(cc.v2(-halfWidth, halfHeight)); - let rightBottom = node.convertToWorldSpaceAR(cc.v2(halfWidth, -halfHeight)); - let rightTop = node.convertToWorldSpaceAR(cc.v2(halfWidth, halfHeight)); - - function line(began: any, end: any) { - draw.moveTo(began.x, began.y); - draw.lineTo(end.x, end.y); - } - - line(leftBottom, rightBottom); - line(rightBottom, rightTop); - line(rightTop, leftTop); - line(leftTop, leftBottom); - this.draw.stroke(); - } - - // 收集节点信息 - getNodeChildren(node: any, data: TreeData) { - data.id = node.uuid; - data.text = node.name; - // @ts-ignore - if (node instanceof cc.Scene) { - // 场景不允许获取active,引擎会报错 - } else { - data.active = !!node.active; - } - this.inspectorGameMemoryStorage[node.uuid] = node; - let nodeChildren = node.children; - for (let i = 0; i < nodeChildren.length; i++) { - let childItem = nodeChildren[i]; - let treeData = new TreeData(); - this.getNodeChildren(childItem, treeData); - data.children.push(treeData); - } - } - - _isCocosGame() { - // @ts-ignore 检测是否包含cc变量 - return typeof cc !== "undefined"; - } - - getAllPropertyDescriptors(obj: Object): string[] { - let keys: string[] = []; - - function circle(root: Object) { - const descriptors = Object.getOwnPropertyDescriptors(root); - for (let descriptorsKey in descriptors) { - if (Object.hasOwnProperty.call(descriptors, descriptorsKey)) { - const value = descriptors[descriptorsKey]; - // 不可枚举的属性,并且允许修改get set的才有效 - if (!value.enumerable && value.configurable) { - keys.push(descriptorsKey); - } - } - } - const proto = Object.getPrototypeOf(root); - if (proto) { - circle(proto); - } - } - - circle(obj); - return keys; - } - - _getNodeKeys(node: any) { - // 3.x变成了getter - let excludeProperty = [ - "children", - "quat", - "node", - "components", - "parent", - // 生命周期函数 - "onFocusInEditor", - "onRestore", - "start", - "lateUpdate", - "update", - "resetInEditor", - "onLostFocusInEditor", - "onEnable", - "onDisable", - "onDestroy", - "onLoad", - ]; - const keyHidden = this.getAllPropertyDescriptors(node); - const keyVisible1 = Object.keys(node); // Object不走原型链 - let keyVisible2: string[] = []; - for (let nodeKey in node) { - // 走原型链 - keyVisible2.push(nodeKey); - } - let allKeys: string[] = uniq( - keyHidden.concat(keyVisible1, keyVisible2) - ).sort(); - allKeys = allKeys.filter((key) => { - return !key.startsWith("_") && !excludeProperty.includes(key); - }); - - allKeys = allKeys.filter((key) => { - try { - return typeof node[key] !== "function"; - } catch (e) { - // console.warn(`属性${key}出现异常:\n`, e); - return false; - } - }); - return allKeys; - } - - _getPairProperty(key: string): null | { key: string; values: string[] } { - let pairProperty: Record = { - rotation: ["rotationX", "rotationY"], - anchor: ["anchorX", "anchorY"], - size: ["width", "height"], - skew: ["skewX", "skewY"], - position: ["x", "y", "z"], // position比较特殊,过来的key就是position也需要能处理 - scale: ["scaleX", "scaleY", "scaleZ"], - }; - for (let pairPropertyKey in pairProperty) { - if (pairProperty.hasOwnProperty(pairPropertyKey)) { - let pair = pairProperty[pairPropertyKey]; - if (pair.includes(key) || key === pairPropertyKey) { - return { key: pairPropertyKey, values: pair }; - } - } - } - return null; - } - - _buildVecData(options: BuildVecOptions) { - const ctor: Function = options.ctor; - const keys: Array = options.keys; - const value: Object = options.value; - const data: Vec3Data | Vec2Data = options.data; - const path: Array = options.path; - - if (ctor && value instanceof ctor) { - let hasUnOwnProperty = keys.find((key) => !value.hasOwnProperty(key)); - if (!hasUnOwnProperty) { - for (let key in keys) { - let propName = keys[key]; - if (value.hasOwnProperty(propName)) { - let propPath = path.concat(propName); - let itemData = this._genInfoData(value, propName, propPath); - if (itemData) { - data.add(new Property(propName, itemData)); - } - } - } - return data; - } - } - return null; - } - - _buildImageData(options: BuildImageOptions) { - const ctor: Function = options.ctor; - const value: Object = options.value; - const data: ImageData = options.data; - const path: Array = options.path; - if (ctor && value instanceof ctor) { - data.path = path; - // 2.4.6 没有了这个属性 - if (value.hasOwnProperty("_textureFilename")) { - //@ts-ignore - data.data = `${window.location.origin}/${value._textureFilename}`; - } else { - data.data = null; - } - return data; - } - return null; - } - - _genInfoData( - node: any, - key: string | number, - path: Array, - filterKey = true - ): Info | null { - let propertyValue = node[key]; - let info = null; - let invalidType = this._isInvalidValue(propertyValue); - if (invalidType) { - info = new InvalidData(invalidType); - } else { - switch (typeof propertyValue) { - case "boolean": - info = new BoolData(propertyValue); - break; - case "number": - info = new NumberData(propertyValue); - break; - case "string": - info = new StringData(propertyValue); - break; - default: - //@ts-ignore - if (propertyValue instanceof cc.Color) { - let hex = propertyValue.toHEX(); - info = new ColorData(`#${hex}`); - } else if (Array.isArray(propertyValue)) { - let keys: number[] = []; - for (let i = 0; i < propertyValue.length; i++) { - keys.push(i); - } - info = this._buildArrayData({ - data: new ArrayData(), - path: path, - value: propertyValue, - keys: keys, - }); - } else { - !info && - (info = this._buildVecData({ - // @ts-ignore - ctor: cc.Vec3, - path: path, - data: new Vec3Data(), - keys: ["x", "y", "z"], - value: propertyValue, - })); - !info && - (info = this._buildVecData({ - // @ts-ignore - ctor: cc.Vec2, - path: path, - data: new Vec2Data(), - keys: ["x", "y"], - value: propertyValue, - })); - !info && - (info = this._buildImageData({ - //@ts-ignore - ctor: cc.SpriteFrame, - data: new ImageData(), - path: path, - value: propertyValue, - })); - if (!info) { - if (typeof propertyValue === "object") { - let ctorName = propertyValue.constructor?.name; - if (ctorName) { - if ( - ctorName.startsWith("cc_") || - // 2.4.0 - ctorName === "CCClass" - ) { - info = new EngineData(); - info.engineType = ctorName; - info.engineName = propertyValue.name; - info.engineUUID = propertyValue.uuid; - } - } - if (!info) { - // 空{} - // MaterialVariant 2.4.0 - info = this._buildObjectData({ - data: new ObjectData(), - path: path, - value: propertyValue, - filterKey: filterKey, - }); - } - } - } - } - break; - } - } - if (info) { - info.readonly = this._isReadonly(node, key); - info.path = path; - } else { - console.error(`暂不支持的属性值`, propertyValue); - } - return info; - } - - _buildArrayData({ value, path, data, keys }: BuildArrayOptions) { - keys = keys.filter((key) => !key.toString().startsWith("_")); - for (let i = 0; i < keys.length; i++) { - let key = keys[i]; - let propPath = path.concat(key.toString()); - let itemData = this._genInfoData(value, key, propPath); - if (itemData) { - data.add(new Property(key.toString(), itemData)); - } - } - return data; - } - - _buildObjectItemData({ - value, - path, - data, - filterKey, - }: BuildObjectOptions): Property[] { - let keys = Object.keys(value); - if (filterKey) { - keys = this.filterKeys(keys); // 不再进行开发者定义的数据 - } - let ret: Property[] = []; - for (let i = 0; i < keys.length; i++) { - let key = keys[i]; - let propPath = path.concat(key.toString()); - let itemData = this._genInfoData(value, key, propPath, filterKey); - if (itemData) { - ret.push(new Property(key, itemData)); - } - } - return ret; - } - - filterKeys(keys: string[]) { - // 剔除_开头的属性 - return keys.filter((key) => !key.toString().startsWith("_")); - } - - _isInvalidValue(value: any) { - // !!Infinity=true - if ( - (value && value !== Infinity) || - value === 0 || - value === false || - value === "" - ) { - return false; - } - - if (value === null) { - return "null"; - } else if (value === Infinity) { - return "Infinity"; - } else if (value === undefined) { - return "undefined"; - } else if (Number.isNaN(value)) { - return "NaN"; - } else { - debugger; - return false; - } - } - - _buildObjectData({ value, path, data, filterKey }: BuildObjectOptions) { - let keys = Object.keys(value); - if (filterKey) { - keys = this.filterKeys(keys); - } - // 只返回一级key,更深层级的key需要的时候,再获取,防止circle object导致的死循环 - let desc: Record = {}; - for (let i = 0; i < keys.length; i++) { - let key = keys[i]; - let propPath = path.concat(key.toString()); - let propValue = (value as any)[key]; - let keyDesc = ""; - if (Array.isArray(propValue)) { - // 只收集一级key - propValue.forEach((item) => { }); - keyDesc = `(${propValue.length}) [...]`; - } else if (this._isInvalidValue(propValue)) { - // 不能改变顺序 - keyDesc = propValue; - } else if (typeof propValue === "object") { - keyDesc = `${propValue.constructor.name} {...}`; - } else { - keyDesc = propValue; - } - desc[key] = keyDesc; - } - data.data = JSON.stringify(desc); - return data; - } - - private getCompName(comp: any): string { - const nameKeys = [ - "__classname__", // 2.4.0 验证通过 - ]; - for (let i = 0; i < nameKeys.length; i++) { - let key = nameKeys[i]; - // 一般来说,这里的name是不会出现假值 - if (comp[key]) { - return comp[key]; - } - } - return comp.constructor.name; - } - - // 校验keys的有效性,3.x有position,没有x,y,z - _checkKeysValid(obj: any, keys: string[]) { - for (let i = 0; i < keys.length; i++) { - const key = keys[i]; - if (!isHasProperty(obj, key)) { - return false; - } - } - return true; - } - - _getGroupData(node: any) { - const name = this.getCompName(node); - let nodeGroup = new Group(name, node.uuid); - let keys = this._getNodeKeys(node); - for (let i = 0; i < keys.length;) { - let key = keys[i]; - let pair = this._getPairProperty(key); - if (pair && this._checkKeysValid(node, pair.values)) { - let bSplice = false; - // 把这个成对的属性剔除掉 - pair.values.forEach((item: string) => { - let index = keys.findIndex((el) => el === item); - if (index !== -1) { - keys.splice(index, 1); - if (pair && item === pair.key) { - // 切掉了自己,才能步进+1 - bSplice = true; - } - } - }); - // 序列化成对的属性 - let info: Vec2Data | Vec3Data | null = null; - let pairValues = pair.values; - if (pairValues.length === 2) { - info = new Vec2Data(); - } else if (pairValues.length === 3) { - info = new Vec3Data(); - } - // todo path - pairValues.forEach((el: string) => { - let propertyPath = [node.uuid, el]; - let vecData = this._genInfoData(node, el, propertyPath); - if (vecData) { - info && info.add(new Property(el, vecData)); - } - }); - if (info) { - let property = new Property(pair.key, info); - nodeGroup.addProperty(property); - } - if (!bSplice) { - i++; - } - } else { - let propertyPath = [node.uuid, key]; - let info = this._genInfoData(node, key, propertyPath); - if (info) { - nodeGroup.addProperty(new Property(key, info)); - } - i++; - } - } - nodeGroup.sort(); - return nodeGroup; - } - - // 获取节点信息,只获取一级key即可,后续 - getNodeInfo(uuid: string) { - let node = this.inspectorGameMemoryStorage[uuid]; - if (node) { - let groupData = []; - // 收集节点信息 - let nodeGroup = this._getGroupData(node); - groupData.push(nodeGroup); - // 收集组件信息 - const nodeComp = node._components; - for (let i = 0; i < nodeComp.length; i++) { - let itemComp = nodeComp[i]; - this.inspectorGameMemoryStorage[itemComp.uuid] = itemComp; - let compGroup = this._getGroupData(itemComp); - groupData.push(compGroup); - } - const data: NodeInfoData = new NodeInfoData(uuid, groupData); - this.sendMsgToContent(Msg.NodeInfo, data); - } else { - // 未获取到节点数据 - console.log("未获取到节点数据"); - } - } - - logValue(uuid: string, key: string) { - let nodeOrComp = this.inspectorGameMemoryStorage[uuid]; - if (nodeOrComp) { - console.log(nodeOrComp[key]); - } - } - - _isReadonly(base: Object, key: string | number): boolean { - let ret = Object.getOwnPropertyDescriptor(base, key); - if (ret) { - return !(ret.set || ret.writable); - } else { - let proto = Object.getPrototypeOf(base); - if (proto) { - return this._isReadonly(proto, key); - } else { - return false; - } - } - } - - setValue(pathArray: Array, value: string): boolean { - let target = this.inspectorGameMemoryStorage; - // 尝试设置creator3.x的数据 - if (trySetValueWithConfig(pathArray, target, value)) { - return true; - } - for (let i = 0; i < pathArray.length; i++) { - let path = pathArray[i]; - if (i === pathArray.length - 1) { - // 到最后的key了 - if (this._isReadonly(target, path)) { - console.warn(`值不允许修改`); - } else { - target[path] = value; - return true; - } - } else { - // creator3.x的enumerable导致无法判断 - if (target.hasOwnProperty(path) || target[path]) { - target = target[path]; - } else { - return false; - } - } - } - return false; - } - - onMemoryInfo() { - const memory = console["memory"]; - this.sendMsgToContent(Msg.MemoryInfo, { - performance: { - // @ts-ignore - jsHeapSizeLimit: window.performance.memory.jsHeapSizeLimit, - // @ts-ignore - totalJSHeapSize: window.performance.memory.totalJSHeapSize, - // @ts-ignore - usedJSHeapSize: window.performance.memory.usedJSHeapSize, - }, - - console: { - jsHeapSizeLimit: memory.jsHeapSizeLimit, - totalJSHeapSize: memory.totalJSHeapSize, - usedJSHeapSize: memory.usedJSHeapSize, - }, - }); - } -} - -const inspector = new CCInspector(); +import { Inspector } from "./inspector"; +const inspector = new Inspector(); inspector.init(); -//@ts-ignore -window.CCInspector = inspector; +window["CCInspector"] = inspector; diff --git a/cc-inspector/src/scripts/inject/inspector.ts b/cc-inspector/src/scripts/inject/inspector.ts new file mode 100644 index 0000000..869d80e --- /dev/null +++ b/cc-inspector/src/scripts/inject/inspector.ts @@ -0,0 +1,666 @@ +// eval 注入脚本的代码,变量尽量使用var,后来发现在import之后,let会自动变为var +import { uniq } from "lodash"; +import { Msg, PluginEvent } from "../../core/types"; +import { ArrayData, BoolData, ColorData, DataType, EngineData, Group, ImageData, Info, InvalidData, NodeInfoData, NumberData, ObjectData, ObjectItemRequestData, Property, StringData, TreeData, Vec2Data, Vec3Data } from "../../views/devtools/data"; +import { InjectEvent } from "./event"; +import { getValue, trySetValueWithConfig } from "./setValue"; +import { BuildArrayOptions, BuildImageOptions, BuildObjectOptions, BuildVecOptions } from "./types"; +import { isHasProperty } from "./util"; +declare const cc: any; + +export class Inspector extends InjectEvent { + inspectorGameMemoryStorage: Record = {}; + + private watchIsCocosGame() { + const timer = setInterval(() => { + if (this._isCocosGame()) { + clearInterval(timer); + // @ts-ignore + cc.director.on(cc.Director.EVENT_AFTER_SCENE_LAUNCH, () => { + const isCocosGame = this._isCocosGame(); + this.notifySupportGame(isCocosGame); + }); + } + }, 300); + } + onMessage(pluginEvent: PluginEvent): void { + switch (pluginEvent.msg) { + case Msg.Support: { + let isCocosGame = this._isCocosGame(); + this.notifySupportGame(isCocosGame); + break; + } + case Msg.TreeInfo: { + this.updateTreeInfo(); + break; + } + case Msg.NodeInfo: { + let nodeUUID = pluginEvent.data; + this.getNodeInfo(nodeUUID); + break; + } + case Msg.SetProperty: { + const data: Info = pluginEvent.data; + let value = data.data; + if (data.type === DataType.Color) { + // @ts-ignore + value = cc.color().fromHEX(value); + } + + if (this.setValue(data.path, value)) { + this.sendMsgToContent(Msg.UpdateProperty, data); + } else { + console.warn(`设置失败:${data.path}`); + } + break; + } + case Msg.LogData: { + const data: string[] = pluginEvent.data; + const value = getValue(this.inspectorGameMemoryStorage, data); + // 直接写console.log会被tree shaking + const logFunction = console.log; + logFunction(value); + break; + } + case Msg.GetObjectItemData: { + const data: ObjectData = pluginEvent.data; + let val = getValue(this.inspectorGameMemoryStorage, data.path); + if (val) { + let itemData: Property[] = this._buildObjectItemData({ + data: data, + path: data.path, + value: val, + filterKey: false, + }); + let result: ObjectItemRequestData = { + id: data.id, + data: itemData, + }; + this.sendMsgToContent(Msg.GetObjectItemData, result); + } + break; + } + } + } + init() { + console.log(...this.terminal.init()); + this.watchIsCocosGame(); + } + + notifySupportGame(b: boolean) { + this.sendMsgToContent(Msg.Support, b); + } + + updateTreeInfo() { + let isCocosCreatorGame = this._isCocosGame(); + if (isCocosCreatorGame) { + //@ts-ignore + let scene = cc.director.getScene(); + if (scene) { + let treeData = new TreeData(); + this.getNodeChildren(scene, treeData); + this.sendMsgToContent(Msg.TreeInfo, treeData); + } else { + console.warn("can't execute api : cc.director.getScene"); + this.notifySupportGame(false); + } + } else { + this.notifySupportGame(false); + } + } + + // @ts-ignore + draw: cc.Graphics = null; + + _drawRect(node: any) { + let draw = this.draw; + + if (!draw) { + // @ts-ignore + let node = new cc.Node("draw-node"); + // @ts-ignore + cc.director.getScene().addChild(node); + // @ts-ignore + draw = this.draw = node.addComponent(cc.Graphics); + } + draw.clear(); + draw.lineWidth = 10; + // @ts-ignore + draw.strokeColor = new cc.Color().fromHEX("#ff0000"); + const { anchorX, anchorY, width, height, x, y } = node; + let halfWidth = width / 2; + let halfHeight = height / 2; + let leftBottom = node.convertToWorldSpaceAR(cc.v2(-halfWidth, -halfHeight)); + let leftTop = node.convertToWorldSpaceAR(cc.v2(-halfWidth, halfHeight)); + let rightBottom = node.convertToWorldSpaceAR(cc.v2(halfWidth, -halfHeight)); + let rightTop = node.convertToWorldSpaceAR(cc.v2(halfWidth, halfHeight)); + + function line(began: any, end: any) { + draw.moveTo(began.x, began.y); + draw.lineTo(end.x, end.y); + } + + line(leftBottom, rightBottom); + line(rightBottom, rightTop); + line(rightTop, leftTop); + line(leftTop, leftBottom); + this.draw.stroke(); + } + + // 收集节点信息 + getNodeChildren(node: any, data: TreeData) { + data.id = node.uuid; + data.text = node.name; + // @ts-ignore + if (node instanceof cc.Scene) { + // 场景不允许获取active,引擎会报错 + } else { + data.active = !!node.active; + } + this.inspectorGameMemoryStorage[node.uuid] = node; + let nodeChildren = node.children; + for (let i = 0; i < nodeChildren.length; i++) { + let childItem = nodeChildren[i]; + let treeData = new TreeData(); + this.getNodeChildren(childItem, treeData); + data.children.push(treeData); + } + } + + _isCocosGame() { + // @ts-ignore 检测是否包含cc变量 + return typeof cc !== "undefined"; + } + + getAllPropertyDescriptors(obj: Object): string[] { + let keys: string[] = []; + + function circle(root: Object) { + const descriptors = Object.getOwnPropertyDescriptors(root); + for (let descriptorsKey in descriptors) { + if (Object.hasOwnProperty.call(descriptors, descriptorsKey)) { + const value = descriptors[descriptorsKey]; + // 不可枚举的属性,并且允许修改get set的才有效 + if (!value.enumerable && value.configurable) { + keys.push(descriptorsKey); + } + } + } + const proto = Object.getPrototypeOf(root); + if (proto) { + circle(proto); + } + } + + circle(obj); + return keys; + } + + _getNodeKeys(node: any) { + // 3.x变成了getter + let excludeProperty = [ + "children", + "quat", + "node", + "components", + "parent", + // 生命周期函数 + "onFocusInEditor", + "onRestore", + "start", + "lateUpdate", + "update", + "resetInEditor", + "onLostFocusInEditor", + "onEnable", + "onDisable", + "onDestroy", + "onLoad", + ]; + const keyHidden = this.getAllPropertyDescriptors(node); + const keyVisible1 = Object.keys(node); // Object不走原型链 + let keyVisible2: string[] = []; + for (let nodeKey in node) { + // 走原型链 + keyVisible2.push(nodeKey); + } + let allKeys: string[] = uniq(keyHidden.concat(keyVisible1, keyVisible2)).sort(); + allKeys = allKeys.filter((key) => { + return !key.startsWith("_") && !excludeProperty.includes(key); + }); + + allKeys = allKeys.filter((key) => { + try { + return typeof node[key] !== "function"; + } catch (e) { + // console.warn(`属性${key}出现异常:\n`, e); + return false; + } + }); + return allKeys; + } + + _getPairProperty(key: string): null | { key: string; values: string[] } { + let pairProperty: Record = { + rotation: ["rotationX", "rotationY"], + anchor: ["anchorX", "anchorY"], + size: ["width", "height"], + skew: ["skewX", "skewY"], + position: ["x", "y", "z"], // position比较特殊,过来的key就是position也需要能处理 + scale: ["scaleX", "scaleY", "scaleZ"], + }; + for (let pairPropertyKey in pairProperty) { + if (pairProperty.hasOwnProperty(pairPropertyKey)) { + let pair = pairProperty[pairPropertyKey]; + if (pair.includes(key) || key === pairPropertyKey) { + return { key: pairPropertyKey, values: pair }; + } + } + } + return null; + } + + _buildVecData(options: BuildVecOptions) { + const ctor: Function = options.ctor; + const keys: Array = options.keys; + const value: Object = options.value; + const data: Vec3Data | Vec2Data = options.data; + const path: Array = options.path; + + if (ctor && value instanceof ctor) { + let hasUnOwnProperty = keys.find((key) => !value.hasOwnProperty(key)); + if (!hasUnOwnProperty) { + for (let key in keys) { + let propName = keys[key]; + if (value.hasOwnProperty(propName)) { + let propPath = path.concat(propName); + let itemData = this._genInfoData(value, propName, propPath); + if (itemData) { + data.add(new Property(propName, itemData)); + } + } + } + return data; + } + } + return null; + } + + _buildImageData(options: BuildImageOptions) { + const ctor: Function = options.ctor; + const value: Object = options.value; + const data: ImageData = options.data; + const path: Array = options.path; + if (ctor && value instanceof ctor) { + data.path = path; + // 2.4.6 没有了这个属性 + if (value.hasOwnProperty("_textureFilename")) { + //@ts-ignore + data.data = `${window.location.origin}/${value._textureFilename}`; + } else { + data.data = null; + } + return data; + } + return null; + } + + _genInfoData(node: any, key: string | number, path: Array, filterKey = true): Info | null { + let propertyValue = node[key]; + let info = null; + let invalidType = this._isInvalidValue(propertyValue); + if (invalidType) { + info = new InvalidData(invalidType); + } else { + switch (typeof propertyValue) { + case "boolean": + info = new BoolData(propertyValue); + break; + case "number": + info = new NumberData(propertyValue); + break; + case "string": + info = new StringData(propertyValue); + break; + default: + //@ts-ignore + if (propertyValue instanceof cc.Color) { + let hex = propertyValue.toHEX(); + info = new ColorData(`#${hex}`); + } else if (Array.isArray(propertyValue)) { + let keys: number[] = []; + for (let i = 0; i < propertyValue.length; i++) { + keys.push(i); + } + info = this._buildArrayData({ + data: new ArrayData(), + path: path, + value: propertyValue, + keys: keys, + }); + } else { + !info && + (info = this._buildVecData({ + // @ts-ignore + ctor: cc.Vec3, + path: path, + data: new Vec3Data(), + keys: ["x", "y", "z"], + value: propertyValue, + })); + !info && + (info = this._buildVecData({ + // @ts-ignore + ctor: cc.Vec2, + path: path, + data: new Vec2Data(), + keys: ["x", "y"], + value: propertyValue, + })); + !info && + (info = this._buildImageData({ + //@ts-ignore + ctor: cc.SpriteFrame, + data: new ImageData(), + path: path, + value: propertyValue, + })); + if (!info) { + if (typeof propertyValue === "object") { + let ctorName = propertyValue.constructor?.name; + if (ctorName) { + if ( + ctorName.startsWith("cc_") || + // 2.4.0 + ctorName === "CCClass" + ) { + info = new EngineData(); + info.engineType = ctorName; + info.engineName = propertyValue.name; + info.engineUUID = propertyValue.uuid; + } + } + if (!info) { + // 空{} + // MaterialVariant 2.4.0 + info = this._buildObjectData({ + data: new ObjectData(), + path: path, + value: propertyValue, + filterKey: filterKey, + }); + } + } + } + } + break; + } + } + if (info) { + info.readonly = this._isReadonly(node, key); + info.path = path; + } else { + console.error(`暂不支持的属性值`, propertyValue); + } + return info; + } + + _buildArrayData({ value, path, data, keys }: BuildArrayOptions) { + keys = keys.filter((key) => !key.toString().startsWith("_")); + for (let i = 0; i < keys.length; i++) { + let key = keys[i]; + let propPath = path.concat(key.toString()); + let itemData = this._genInfoData(value, key, propPath); + if (itemData) { + data.add(new Property(key.toString(), itemData)); + } + } + return data; + } + + _buildObjectItemData({ value, path, data, filterKey }: BuildObjectOptions): Property[] { + let keys = Object.keys(value); + if (filterKey) { + keys = this.filterKeys(keys); // 不再进行开发者定义的数据 + } + let ret: Property[] = []; + for (let i = 0; i < keys.length; i++) { + let key = keys[i]; + let propPath = path.concat(key.toString()); + let itemData = this._genInfoData(value, key, propPath, filterKey); + if (itemData) { + ret.push(new Property(key, itemData)); + } + } + return ret; + } + + filterKeys(keys: string[]) { + // 剔除_开头的属性 + return keys.filter((key) => !key.toString().startsWith("_")); + } + + _isInvalidValue(value: any) { + // !!Infinity=true + if ((value && value !== Infinity) || value === 0 || value === false || value === "") { + return false; + } + + if (value === null) { + return "null"; + } else if (value === Infinity) { + return "Infinity"; + } else if (value === undefined) { + return "undefined"; + } else if (Number.isNaN(value)) { + return "NaN"; + } else { + debugger; + return false; + } + } + + _buildObjectData({ value, path, data, filterKey }: BuildObjectOptions) { + let keys = Object.keys(value); + if (filterKey) { + keys = this.filterKeys(keys); + } + // 只返回一级key,更深层级的key需要的时候,再获取,防止circle object导致的死循环 + let desc: Record = {}; + for (let i = 0; i < keys.length; i++) { + let key = keys[i]; + let propPath = path.concat(key.toString()); + let propValue = (value as any)[key]; + let keyDesc = ""; + if (Array.isArray(propValue)) { + // 只收集一级key + propValue.forEach((item) => {}); + keyDesc = `(${propValue.length}) [...]`; + } else if (this._isInvalidValue(propValue)) { + // 不能改变顺序 + keyDesc = propValue; + } else if (typeof propValue === "object") { + keyDesc = `${propValue.constructor.name} {...}`; + } else { + keyDesc = propValue; + } + desc[key] = keyDesc; + } + data.data = JSON.stringify(desc); + return data; + } + + private getCompName(comp: any): string { + const nameKeys = [ + "__classname__", // 2.4.0 验证通过 + ]; + for (let i = 0; i < nameKeys.length; i++) { + let key = nameKeys[i]; + // 一般来说,这里的name是不会出现假值 + if (comp[key]) { + return comp[key]; + } + } + return comp.constructor.name; + } + + // 校验keys的有效性,3.x有position,没有x,y,z + _checkKeysValid(obj: any, keys: string[]) { + for (let i = 0; i < keys.length; i++) { + const key = keys[i]; + if (!isHasProperty(obj, key)) { + return false; + } + } + return true; + } + + _getGroupData(node: any) { + const name = this.getCompName(node); + let nodeGroup = new Group(name, node.uuid); + let keys = this._getNodeKeys(node); + for (let i = 0; i < keys.length; ) { + let key = keys[i]; + let pair = this._getPairProperty(key); + if (pair && this._checkKeysValid(node, pair.values)) { + let bSplice = false; + // 把这个成对的属性剔除掉 + pair.values.forEach((item: string) => { + let index = keys.findIndex((el) => el === item); + if (index !== -1) { + keys.splice(index, 1); + if (pair && item === pair.key) { + // 切掉了自己,才能步进+1 + bSplice = true; + } + } + }); + // 序列化成对的属性 + let info: Vec2Data | Vec3Data | null = null; + let pairValues = pair.values; + if (pairValues.length === 2) { + info = new Vec2Data(); + } else if (pairValues.length === 3) { + info = new Vec3Data(); + } + // todo path + pairValues.forEach((el: string) => { + let propertyPath = [node.uuid, el]; + let vecData = this._genInfoData(node, el, propertyPath); + if (vecData) { + info && info.add(new Property(el, vecData)); + } + }); + if (info) { + let property = new Property(pair.key, info); + nodeGroup.addProperty(property); + } + if (!bSplice) { + i++; + } + } else { + let propertyPath = [node.uuid, key]; + let info = this._genInfoData(node, key, propertyPath); + if (info) { + nodeGroup.addProperty(new Property(key, info)); + } + i++; + } + } + nodeGroup.sort(); + return nodeGroup; + } + + // 获取节点信息,只获取一级key即可,后续 + getNodeInfo(uuid: string) { + let node = this.inspectorGameMemoryStorage[uuid]; + if (node) { + let groupData = []; + // 收集节点信息 + let nodeGroup = this._getGroupData(node); + groupData.push(nodeGroup); + // 收集组件信息 + const nodeComp = node._components; + for (let i = 0; i < nodeComp.length; i++) { + let itemComp = nodeComp[i]; + this.inspectorGameMemoryStorage[itemComp.uuid] = itemComp; + let compGroup = this._getGroupData(itemComp); + groupData.push(compGroup); + } + const data: NodeInfoData = new NodeInfoData(uuid, groupData); + this.sendMsgToContent(Msg.NodeInfo, data); + } else { + // 未获取到节点数据 + console.log("未获取到节点数据"); + } + } + + logValue(uuid: string, key: string) { + let nodeOrComp = this.inspectorGameMemoryStorage[uuid]; + if (nodeOrComp) { + console.log(nodeOrComp[key]); + } + } + + _isReadonly(base: Object, key: string | number): boolean { + let ret = Object.getOwnPropertyDescriptor(base, key); + if (ret) { + return !(ret.set || ret.writable); + } else { + let proto = Object.getPrototypeOf(base); + if (proto) { + return this._isReadonly(proto, key); + } else { + return false; + } + } + } + + setValue(pathArray: Array, value: string): boolean { + let target = this.inspectorGameMemoryStorage; + // 尝试设置creator3.x的数据 + if (trySetValueWithConfig(pathArray, target, value)) { + return true; + } + for (let i = 0; i < pathArray.length; i++) { + let path = pathArray[i]; + if (i === pathArray.length - 1) { + // 到最后的key了 + if (this._isReadonly(target, path)) { + console.warn(`值不允许修改`); + } else { + target[path] = value; + return true; + } + } else { + // creator3.x的enumerable导致无法判断 + if (target.hasOwnProperty(path) || target[path]) { + target = target[path]; + } else { + return false; + } + } + } + return false; + } + + onMemoryInfo() { + const memory = console["memory"]; + this.sendMsgToContent(Msg.MemoryInfo, { + performance: { + // @ts-ignore + jsHeapSizeLimit: window.performance.memory.jsHeapSizeLimit, + // @ts-ignore + totalJSHeapSize: window.performance.memory.totalJSHeapSize, + // @ts-ignore + usedJSHeapSize: window.performance.memory.usedJSHeapSize, + }, + + console: { + jsHeapSizeLimit: memory.jsHeapSizeLimit, + totalJSHeapSize: memory.totalJSHeapSize, + usedJSHeapSize: memory.usedJSHeapSize, + }, + }); + } +} diff --git a/cc-inspector/src/scripts/terminal.ts b/cc-inspector/src/scripts/terminal.ts index fdfdcab..7e88f68 100644 --- a/cc-inspector/src/scripts/terminal.ts +++ b/cc-inspector/src/scripts/terminal.ts @@ -12,8 +12,8 @@ export class Chunk { */ style: string[] = []; constructor(v: string, newline: boolean = false) { - this.value = v - this.newline = newline + this.value = v; + this.newline = newline; } color(c: string) { this.style.push(`color:${c}`); @@ -47,10 +47,10 @@ export class Chunk { return this; } toValue() { - return `%c${this.value}${this.newline ? '\n' : ''}` + return `%c${this.value}${this.newline ? "\n" : ""}`; } toStyle() { - return this.style.join(';') + return this.style.join(";"); } } @@ -58,7 +58,7 @@ export class Terminal { /** * 标签 */ - tag = 'terminal'; + tag = "terminal"; /** * 子标签 */ @@ -66,46 +66,51 @@ export class Terminal { /** * 标签的颜色 */ - tagColor = 'blue'; + tagColor = "blue"; /** * 标签的背景色 */ - tagBackground = 'yellow'; + tagBackground = "yellow"; /** * 日志文本的颜色 */ - txtColor = 'black'; + txtColor = "black"; private chunks: Chunk[] = []; constructor(tag: string) { this.tag = tag; } init(): string[] { - this.txtColor = 'black'; + this.txtColor = "black"; this.subTag = "init"; return this.log(); } public log(message: string = "", newline: boolean = false): string[] { - const txt = new Chunk(message).color(this.txtColor).background('#e6e6e6').marginLeft("5px") + const txt = new Chunk(message).color(this.txtColor).background("#e6e6e6").marginLeft("5px"); return this.doChunk(newline, [txt]); } - public chunk(chunk: Chunk[]) { + public chunkMessage(chunk: Chunk[]) { this.subTag = "message"; - return this.doChunk(false, chunk) + return this.doChunk(false, chunk); + } + + public chunkSend(chunk: Chunk[]) { + this.subTag = "send "; + return this.doChunk(false, chunk); } private doChunk(newline: boolean = false, chunks: Chunk[]) { this.chunks = []; - const tag = new Chunk(this.tag).color(this.tagColor).background(this.tagBackground).padding("0 4px") + const tag = new Chunk(this.tag).color(this.tagColor).background(this.tagBackground).padding("0 4px"); this.chunks.push(tag); - const subTag = new Chunk(this.subTag, newline).color(this.tagBackground).background(this.tagColor).padding("0 3px") + const subTag = new Chunk(this.subTag, newline).color(this.tagBackground).background(this.tagColor).padding("0 3px"); this.chunks.push(subTag); chunks.forEach((c) => { this.chunks.push(c); - }) + }); - let head = '*'; + let head = "*"; for (let i = 0; i < this.chunks.length; i++) { const chunk = this.chunks[i]; head += chunk.toValue(); @@ -113,7 +118,7 @@ export class Terminal { const ret = [head]; this.chunks.forEach((chunk) => { ret.push(chunk.toStyle()); - }) + }); this.reset(); return ret; } @@ -121,33 +126,38 @@ export class Terminal { this.subTag = ""; } public blue(message: string): string[] { - this.txtColor = 'blue'; + this.txtColor = "blue"; this.subTag = ""; return this.log(message); } public green(message: string): string[] { - this.txtColor = 'green'; + this.txtColor = "green"; this.subTag = ""; return this.log(message); } public red(message: string): string[] { - this.txtColor = 'red'; + this.txtColor = "red"; this.subTag = ""; return this.log(message); } + send(msg: string) { + this.txtColor = "black"; + this.subTag = "send"; + return this.log(`${msg}`); + } message(msg: string): string[] { - this.txtColor = 'black'; - this.subTag = 'message'; + this.txtColor = "black"; + this.subTag = "message"; return this.log(`${msg}`); } connect(msg: string): string[] { - this.txtColor = 'black'; - this.subTag = 'connect'; + this.txtColor = "black"; + this.subTag = "connect"; return this.log(`${msg}`); } disconnect(msg: string): string[] { - this.txtColor = 'black'; - this.subTag = 'disconnect'; + this.txtColor = "black"; + this.subTag = "disconnect"; return this.log(`${msg}`); } } diff --git a/cc-inspector/src/views/devtools/bridge.ts b/cc-inspector/src/views/devtools/bridge.ts index c58c98a..4297ac8 100644 --- a/cc-inspector/src/views/devtools/bridge.ts +++ b/cc-inspector/src/views/devtools/bridge.ts @@ -30,10 +30,12 @@ class Bridge implements TestClient { }) this.connect.onMessage.addListener((event, sender: chrome.runtime.Port) => { - console.log(...this.terminal.message(JSON.stringify(event))); const data = PluginEvent.create(event); - if (this.onMessage) { + console.log(...this.terminal.chunkMessage(data.toChunk())); + if (data.valid && this.onMessage) { this.onMessage(data, sender); + } else { + console.log(JSON.stringify(event)); } }); diff --git a/cc-inspector/src/views/devtools/index.vue b/cc-inspector/src/views/devtools/index.vue index 5e57f1d..1fa676b 100644 --- a/cc-inspector/src/views/devtools/index.vue +++ b/cc-inspector/src/views/devtools/index.vue @@ -395,7 +395,7 @@ export default defineComponent({ bridge.send(Msg.TreeInfo, id); } function onChangeFrame() { - const id = toRaw(frameID.value); + const id = Number(toRaw(frameID.value)); bridge.send(Msg.UseFrame, id); } const elLeft = ref(); diff --git a/cc-inspector/src/views/devtools/test/test.vue b/cc-inspector/src/views/devtools/test/test.vue index 07d4297..b38e247 100644 --- a/cc-inspector/src/views/devtools/test/test.vue +++ b/cc-inspector/src/views/devtools/test/test.vue @@ -84,7 +84,7 @@ export default defineComponent({ console.log(...t.red("red")); console.log(...t.green("green")); console.log(...t.blue("blue")); - console.log(...t.chunk(event.toChunk())); + console.log(...t.chunkMessage(event.toChunk())); }, onTestTree() { const data: TreeData = { diff --git a/cc-inspector/tsconfig.json b/cc-inspector/tsconfig.json index 11b0a0a..09024a3 100644 --- a/cc-inspector/tsconfig.json +++ b/cc-inspector/tsconfig.json @@ -3,7 +3,6 @@ "allowSyntheticDefaultImports": true, "experimentalDecorators": true, "target": "ES6", - "module": "commonjs", - "baseUrl": "./src" + "module": "commonjs" } }