mirror of
https://github.com/tidys/cc-inspector-chrome
synced 2025-04-19 08:28:41 +00:00
重新梳理Inject/Content的逻辑
This commit is contained in:
parent
b29e39a612
commit
ae59ee5a10
5
cc-inspector/.vscode/settings.json
vendored
5
cc-inspector/.vscode/settings.json
vendored
@ -2,10 +2,13 @@
|
|||||||
"editor.formatOnSave": true,
|
"editor.formatOnSave": true,
|
||||||
"editor.formatOnSaveMode": "modifications",
|
"editor.formatOnSaveMode": "modifications",
|
||||||
"files.autoSave": "onFocusChange",
|
"files.autoSave": "onFocusChange",
|
||||||
|
"editor.codeActionsOnSave": {
|
||||||
|
"source.organizeImports": "explicit"
|
||||||
|
},
|
||||||
"[typescript]": {
|
"[typescript]": {
|
||||||
"editor.tabSize": 2,
|
"editor.tabSize": 2,
|
||||||
"editor.formatOnSave": true,
|
"editor.formatOnSave": true,
|
||||||
"editor.defaultFormatter": "vscode.typescript-language-features",
|
"editor.defaultFormatter": "esbenp.prettier-vscode",
|
||||||
"prettier.printWidth": 1000
|
"prettier.printWidth": 1000
|
||||||
},
|
},
|
||||||
"[json]": {
|
"[json]": {
|
||||||
|
@ -50,7 +50,7 @@ const manifest: CocosPluginManifest = {
|
|||||||
view_options: "src/views/options/index.ts",
|
view_options: "src/views/options/index.ts",
|
||||||
view_popup: "src/views/popup/index.ts",
|
view_popup: "src/views/popup/index.ts",
|
||||||
script_background: "src/scripts/background/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",
|
script_inject: "src/scripts/inject/index.ts",
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
@ -11,6 +11,7 @@ export enum Page {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export enum Msg {
|
export enum Msg {
|
||||||
|
None = "None",
|
||||||
/**
|
/**
|
||||||
* 具体的节点信息
|
* 具体的节点信息
|
||||||
*/
|
*/
|
||||||
@ -50,6 +51,15 @@ export enum Msg {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export class PluginEvent {
|
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") {
|
if (typeof data === "string") {
|
||||||
obj = JSON.stringify(data)
|
obj = JSON.stringify(data)
|
||||||
} else if (typeof data === "object") {
|
} else if (typeof data === "object") {
|
||||||
|
obj = data;
|
||||||
} else {
|
} else {
|
||||||
debugger;
|
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) {
|
check(source: Page, target: Page) {
|
||||||
return event && source && target && event.source === source && event.target === target;
|
return source && target && this.source === source && this.target === target;
|
||||||
}
|
}
|
||||||
|
|
||||||
static reset(event: PluginEvent, source: Page | null, target: Page | null) {
|
reset(source: Page | null, target: Page | null) {
|
||||||
if (event && source && target) {
|
if (source && target) {
|
||||||
event.source = source;
|
this.source = source;
|
||||||
event.target = target;
|
this.target = target;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -110,10 +131,11 @@ export class PluginEvent {
|
|||||||
}
|
}
|
||||||
toChunk(): Chunk[] {
|
toChunk(): Chunk[] {
|
||||||
return [
|
return [
|
||||||
new Chunk(this.msg).color("white").background("black").margin('0 0 0 5px').padding("0 6px"),
|
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 0px'),
|
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('=>').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))
|
new Chunk(JSON.stringify(this.data))
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
@ -40,12 +40,13 @@ chrome.runtime.onConnect.addListener((port: chrome.runtime.Port) => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
chrome.runtime.onMessage.addListener((request: PluginEvent, sender: any, sendResponse: any) => {
|
chrome.runtime.onMessage.addListener((request: PluginEvent, sender: any, sendResponse: any) => {
|
||||||
|
const event = PluginEvent.create(request);
|
||||||
const tabID = sender.tab.id;
|
const tabID = sender.tab.id;
|
||||||
const portMan: PortMan | undefined = portMgr.findPort(tabID);
|
const portMan: PortMan | undefined = portMgr.findPort(tabID);
|
||||||
if (portMan) {
|
if (portMan) {
|
||||||
if (PluginEvent.check(request, Page.Content, Page.Background)) {
|
if (event.check(Page.Content, Page.Background)) {
|
||||||
// 监听来自content.js发来的事件,将消息转发到devtools
|
// 监听来自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");
|
console.log(`%c[Message]url:${sender.url}]\n${JSON.stringify(request)}`, "color:green");
|
||||||
portMgr.sendDevtoolMsg(request);
|
portMgr.sendDevtoolMsg(request);
|
||||||
}
|
}
|
||||||
|
@ -14,13 +14,13 @@ export class PortDevtools extends PortMan {
|
|||||||
portMgr.useFrame(data.data);
|
portMgr.useFrame(data.data);
|
||||||
} else {
|
} else {
|
||||||
// 从devtools过来的消息统一派发到Content中
|
// 从devtools过来的消息统一派发到Content中
|
||||||
if (PluginEvent.check(data, Page.Devtools, Page.Background)) {
|
if (data.check(Page.Devtools, Page.Background)) {
|
||||||
if (data.msg === Msg.TreeInfo) {
|
if (data.msg === Msg.TreeInfo) {
|
||||||
if (portMgr.isCurrentFrme(data.data)) {
|
if (portMgr.isCurrentFrme(data.data)) {
|
||||||
console.log(`frameID[${data.data}]不一致`);
|
console.log(`frameID[${data.data}]不一致`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
PluginEvent.reset(data, Page.Background, Page.Content);
|
data.reset(Page.Background, Page.Content);
|
||||||
const port = portMgr.getCurrentUseContent();
|
const port = portMgr.getCurrentUseContent();
|
||||||
if (!port) {
|
if (!port) {
|
||||||
console.warn(`not find andy port`);
|
console.warn(`not find andy port`);
|
||||||
|
@ -31,12 +31,14 @@ export abstract class PortMan {
|
|||||||
this.title = tab.title;
|
this.title = tab.title;
|
||||||
this.terminal = new Terminal(`Port-${this.name}`);
|
this.terminal = new Terminal(`Port-${this.name}`);
|
||||||
port.onMessage.addListener((data: any, port: chrome.runtime.Port) => {
|
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() 能成功发出回应,所有其他回应将被忽略。
|
// 如果多个页面都监听 onMessage 事件,对于某一次事件只有第一次调用 sendResponse() 能成功发出回应,所有其他回应将被忽略。
|
||||||
// port.postMessage(data);
|
// port.postMessage(data);
|
||||||
const cls = PluginEvent.create(data);
|
if (event.valid && this.onMessage) {
|
||||||
if (this.onMessage) {
|
this.onMessage(event);
|
||||||
this.onMessage(cls);
|
} else {
|
||||||
|
console.log(... this.terminal.log(JSON.stringify(data)));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
port.onDisconnect.addListener((port: chrome.runtime.Port) => {
|
port.onDisconnect.addListener((port: chrome.runtime.Port) => {
|
||||||
|
10
cc-inspector/src/scripts/const.ts
Normal file
10
cc-inspector/src/scripts/const.ts
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
export enum DocumentEvent {
|
||||||
|
/**
|
||||||
|
* 从inject到content的事件
|
||||||
|
*/
|
||||||
|
Inject2Content = "inject2content",
|
||||||
|
/**
|
||||||
|
* 从content到inject的事件
|
||||||
|
*/
|
||||||
|
Content2Inject = "content2inject",
|
||||||
|
}
|
@ -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();
|
|
79
cc-inspector/src/scripts/content/index.ts
Normal file
79
cc-inspector/src/scripts/content/index.ts
Normal file
@ -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();
|
21
cc-inspector/src/scripts/inject/event.ts
Normal file
21
cc-inspector/src/scripts/inject/event.ts
Normal file
@ -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);
|
||||||
|
}
|
||||||
|
}
|
@ -1,704 +1,4 @@
|
|||||||
// eval 注入脚本的代码,变量尽量使用var,后来发现在import之后,let会自动变为var
|
import { Inspector } from "./inspector";
|
||||||
import { uniq } from "lodash";
|
const inspector = new Inspector();
|
||||||
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<string, any> = {};
|
|
||||||
|
|
||||||
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<string, any> = {
|
|
||||||
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<string> = options.keys;
|
|
||||||
const value: Object = options.value;
|
|
||||||
const data: Vec3Data | Vec2Data = options.data;
|
|
||||||
const path: Array<string> = 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<string> = 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<string>,
|
|
||||||
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<string, any> = {};
|
|
||||||
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<string>, 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();
|
|
||||||
inspector.init();
|
inspector.init();
|
||||||
//@ts-ignore
|
window["CCInspector"] = inspector;
|
||||||
window.CCInspector = inspector;
|
|
||||||
|
666
cc-inspector/src/scripts/inject/inspector.ts
Normal file
666
cc-inspector/src/scripts/inject/inspector.ts
Normal file
@ -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<string, any> = {};
|
||||||
|
|
||||||
|
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<string, any> = {
|
||||||
|
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<string> = options.keys;
|
||||||
|
const value: Object = options.value;
|
||||||
|
const data: Vec3Data | Vec2Data = options.data;
|
||||||
|
const path: Array<string> = 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<string> = 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<string>, 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<string, any> = {};
|
||||||
|
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<string>, 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,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
@ -12,8 +12,8 @@ export class Chunk {
|
|||||||
*/
|
*/
|
||||||
style: string[] = [];
|
style: string[] = [];
|
||||||
constructor(v: string, newline: boolean = false) {
|
constructor(v: string, newline: boolean = false) {
|
||||||
this.value = v
|
this.value = v;
|
||||||
this.newline = newline
|
this.newline = newline;
|
||||||
}
|
}
|
||||||
color(c: string) {
|
color(c: string) {
|
||||||
this.style.push(`color:${c}`);
|
this.style.push(`color:${c}`);
|
||||||
@ -47,10 +47,10 @@ export class Chunk {
|
|||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
toValue() {
|
toValue() {
|
||||||
return `%c${this.value}${this.newline ? '\n' : ''}`
|
return `%c${this.value}${this.newline ? "\n" : ""}`;
|
||||||
}
|
}
|
||||||
toStyle() {
|
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[] = [];
|
private chunks: Chunk[] = [];
|
||||||
constructor(tag: string) {
|
constructor(tag: string) {
|
||||||
this.tag = tag;
|
this.tag = tag;
|
||||||
}
|
}
|
||||||
init(): string[] {
|
init(): string[] {
|
||||||
this.txtColor = 'black';
|
this.txtColor = "black";
|
||||||
this.subTag = "init";
|
this.subTag = "init";
|
||||||
return this.log();
|
return this.log();
|
||||||
}
|
}
|
||||||
|
|
||||||
public log(message: string = "", newline: boolean = false): string[] {
|
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]);
|
return this.doChunk(newline, [txt]);
|
||||||
}
|
}
|
||||||
public chunk(chunk: Chunk[]) {
|
public chunkMessage(chunk: Chunk[]) {
|
||||||
this.subTag = "message";
|
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[]) {
|
private doChunk(newline: boolean = false, chunks: Chunk[]) {
|
||||||
this.chunks = [];
|
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);
|
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);
|
this.chunks.push(subTag);
|
||||||
|
|
||||||
chunks.forEach((c) => {
|
chunks.forEach((c) => {
|
||||||
this.chunks.push(c);
|
this.chunks.push(c);
|
||||||
})
|
});
|
||||||
|
|
||||||
let head = '*';
|
let head = "*";
|
||||||
for (let i = 0; i < this.chunks.length; i++) {
|
for (let i = 0; i < this.chunks.length; i++) {
|
||||||
const chunk = this.chunks[i];
|
const chunk = this.chunks[i];
|
||||||
head += chunk.toValue();
|
head += chunk.toValue();
|
||||||
@ -113,7 +118,7 @@ export class Terminal {
|
|||||||
const ret = [head];
|
const ret = [head];
|
||||||
this.chunks.forEach((chunk) => {
|
this.chunks.forEach((chunk) => {
|
||||||
ret.push(chunk.toStyle());
|
ret.push(chunk.toStyle());
|
||||||
})
|
});
|
||||||
this.reset();
|
this.reset();
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
@ -121,33 +126,38 @@ export class Terminal {
|
|||||||
this.subTag = "";
|
this.subTag = "";
|
||||||
}
|
}
|
||||||
public blue(message: string): string[] {
|
public blue(message: string): string[] {
|
||||||
this.txtColor = 'blue';
|
this.txtColor = "blue";
|
||||||
this.subTag = "";
|
this.subTag = "";
|
||||||
return this.log(message);
|
return this.log(message);
|
||||||
}
|
}
|
||||||
public green(message: string): string[] {
|
public green(message: string): string[] {
|
||||||
this.txtColor = 'green';
|
this.txtColor = "green";
|
||||||
this.subTag = "";
|
this.subTag = "";
|
||||||
return this.log(message);
|
return this.log(message);
|
||||||
}
|
}
|
||||||
public red(message: string): string[] {
|
public red(message: string): string[] {
|
||||||
this.txtColor = 'red';
|
this.txtColor = "red";
|
||||||
this.subTag = "";
|
this.subTag = "";
|
||||||
return this.log(message);
|
return this.log(message);
|
||||||
}
|
}
|
||||||
|
send(msg: string) {
|
||||||
|
this.txtColor = "black";
|
||||||
|
this.subTag = "send";
|
||||||
|
return this.log(`${msg}`);
|
||||||
|
}
|
||||||
message(msg: string): string[] {
|
message(msg: string): string[] {
|
||||||
this.txtColor = 'black';
|
this.txtColor = "black";
|
||||||
this.subTag = 'message';
|
this.subTag = "message";
|
||||||
return this.log(`${msg}`);
|
return this.log(`${msg}`);
|
||||||
}
|
}
|
||||||
connect(msg: string): string[] {
|
connect(msg: string): string[] {
|
||||||
this.txtColor = 'black';
|
this.txtColor = "black";
|
||||||
this.subTag = 'connect';
|
this.subTag = "connect";
|
||||||
return this.log(`${msg}`);
|
return this.log(`${msg}`);
|
||||||
}
|
}
|
||||||
disconnect(msg: string): string[] {
|
disconnect(msg: string): string[] {
|
||||||
this.txtColor = 'black';
|
this.txtColor = "black";
|
||||||
this.subTag = 'disconnect';
|
this.subTag = "disconnect";
|
||||||
return this.log(`${msg}`);
|
return this.log(`${msg}`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -30,10 +30,12 @@ class Bridge implements TestClient {
|
|||||||
})
|
})
|
||||||
|
|
||||||
this.connect.onMessage.addListener((event, sender: chrome.runtime.Port) => {
|
this.connect.onMessage.addListener((event, sender: chrome.runtime.Port) => {
|
||||||
console.log(...this.terminal.message(JSON.stringify(event)));
|
|
||||||
const data = PluginEvent.create(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);
|
this.onMessage(data, sender);
|
||||||
|
} else {
|
||||||
|
console.log(JSON.stringify(event));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -395,7 +395,7 @@ export default defineComponent({
|
|||||||
bridge.send(Msg.TreeInfo, id);
|
bridge.send(Msg.TreeInfo, id);
|
||||||
}
|
}
|
||||||
function onChangeFrame() {
|
function onChangeFrame() {
|
||||||
const id = toRaw(frameID.value);
|
const id = Number(toRaw(frameID.value));
|
||||||
bridge.send(Msg.UseFrame, id);
|
bridge.send(Msg.UseFrame, id);
|
||||||
}
|
}
|
||||||
const elLeft = ref<HTMLDivElement>();
|
const elLeft = ref<HTMLDivElement>();
|
||||||
|
@ -84,7 +84,7 @@ export default defineComponent({
|
|||||||
console.log(...t.red("red"));
|
console.log(...t.red("red"));
|
||||||
console.log(...t.green("green"));
|
console.log(...t.green("green"));
|
||||||
console.log(...t.blue("blue"));
|
console.log(...t.blue("blue"));
|
||||||
console.log(...t.chunk(event.toChunk()));
|
console.log(...t.chunkMessage(event.toChunk()));
|
||||||
},
|
},
|
||||||
onTestTree() {
|
onTestTree() {
|
||||||
const data: TreeData = {
|
const data: TreeData = {
|
||||||
|
@ -3,7 +3,6 @@
|
|||||||
"allowSyntheticDefaultImports": true,
|
"allowSyntheticDefaultImports": true,
|
||||||
"experimentalDecorators": true,
|
"experimentalDecorators": true,
|
||||||
"target": "ES6",
|
"target": "ES6",
|
||||||
"module": "commonjs",
|
"module": "commonjs"
|
||||||
"baseUrl": "./src"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user