重新梳理Inject/Content的逻辑

This commit is contained in:
xu_yanfeng 2024-12-27 14:14:38 +08:00
parent b29e39a612
commit ae59ee5a10
17 changed files with 872 additions and 825 deletions

View File

@ -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]": {

View File

@ -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",
},
};

View File

@ -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))
];
}

View File

@ -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);
}

View File

@ -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`);

View File

@ -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) => {

View File

@ -0,0 +1,10 @@
export enum DocumentEvent {
/**
* inject到content的事件
*/
Inject2Content = "inject2content",
/**
* content到inject的事件
*/
Content2Inject = "content2inject",
}

View File

@ -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();

View 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();

View 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);
}
}

View File

@ -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<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();
import { Inspector } from "./inspector";
const inspector = new Inspector();
inspector.init();
//@ts-ignore
window.CCInspector = inspector;
window["CCInspector"] = inspector;

View 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,
},
});
}
}

View File

@ -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}`);
}
}

View File

@ -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));
}
});

View File

@ -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<HTMLDivElement>();

View File

@ -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 = {

View File

@ -3,7 +3,6 @@
"allowSyntheticDefaultImports": true,
"experimentalDecorators": true,
"target": "ES6",
"module": "commonjs",
"baseUrl": "./src"
"module": "commonjs"
}
}