comate-ui-inspector 0.1.5-alpha.1 → 0.1.5-alpha.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/enhancements/recorder/recorder-main.js +8 -4
- package/extension/popup.html +84 -62
- package/extension/popup.js +26 -10
- package/hooks/inject-ui-inspector.mjs +30 -18
- package/package.json +1 -1
|
@@ -126,10 +126,12 @@
|
|
|
126
126
|
if (rec && sameHost(url)) {
|
|
127
127
|
p.then(
|
|
128
128
|
function (r) {
|
|
129
|
-
|
|
129
|
+
var t1 = Date.now();
|
|
130
|
+
rec.network.push({method: method, url: url, status: r.status, startTs: t0, endTs: t1, ms: t1 - t0});
|
|
130
131
|
},
|
|
131
132
|
function () {
|
|
132
|
-
|
|
133
|
+
var t1 = Date.now();
|
|
134
|
+
rec.network.push({method: method, url: url, status: 0, startTs: t0, endTs: t1, ms: t1 - t0, error: true});
|
|
133
135
|
}
|
|
134
136
|
);
|
|
135
137
|
}
|
|
@@ -148,12 +150,14 @@
|
|
|
148
150
|
if (info) {
|
|
149
151
|
xhr.addEventListener('loadend', function () {
|
|
150
152
|
if (rec && sameHost(info.url)) {
|
|
153
|
+
var t1 = Date.now();
|
|
151
154
|
rec.network.push({
|
|
152
155
|
method: info.method,
|
|
153
156
|
url: info.url,
|
|
154
157
|
status: xhr.status,
|
|
155
|
-
|
|
156
|
-
|
|
158
|
+
startTs: info.t0,
|
|
159
|
+
endTs: t1,
|
|
160
|
+
ms: t1 - info.t0,
|
|
157
161
|
});
|
|
158
162
|
}
|
|
159
163
|
});
|
package/extension/popup.html
CHANGED
|
@@ -119,11 +119,27 @@
|
|
|
119
119
|
<body>
|
|
120
120
|
<h1>ui-inspector 配置</h1>
|
|
121
121
|
|
|
122
|
-
<!--
|
|
122
|
+
<!-- 顶层 tab:模式 / 设置 -->
|
|
123
123
|
<div
|
|
124
|
-
|
|
125
|
-
|
|
124
|
+
class="tabs"
|
|
125
|
+
id="top-tabs"
|
|
126
126
|
>
|
|
127
|
+
<button
|
|
128
|
+
class="tab active"
|
|
129
|
+
data-top="mode"
|
|
130
|
+
>
|
|
131
|
+
模式
|
|
132
|
+
</button>
|
|
133
|
+
<button
|
|
134
|
+
class="tab"
|
|
135
|
+
data-top="settings"
|
|
136
|
+
>
|
|
137
|
+
设置
|
|
138
|
+
</button>
|
|
139
|
+
</div>
|
|
140
|
+
|
|
141
|
+
<!-- 「模式」页:选择 Element + 各增强包模式(popup.js 读 enhancements.json 动态填充子 tab) -->
|
|
142
|
+
<div id="top-mode">
|
|
127
143
|
<div
|
|
128
144
|
class="tabs"
|
|
129
145
|
id="mode-tabs"
|
|
@@ -137,71 +153,77 @@
|
|
|
137
153
|
<div id="mode-panes"></div>
|
|
138
154
|
</div>
|
|
139
155
|
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
<button
|
|
149
|
-
id="add-domain"
|
|
150
|
-
class="primary"
|
|
151
|
-
>
|
|
152
|
-
添加
|
|
153
|
-
</button>
|
|
154
|
-
</div>
|
|
155
|
-
<ul id="domain-list"></ul>
|
|
156
|
-
<div class="hint">添加时浏览器会请求该域名的访问权限,允许后即在该域名注入选取脚本。</div>
|
|
157
|
-
</section>
|
|
158
|
-
|
|
159
|
-
<section>
|
|
160
|
-
<div class="label">传输通路</div>
|
|
161
|
-
<label class="radio">
|
|
162
|
-
<input
|
|
163
|
-
type="radio"
|
|
164
|
-
name="transport"
|
|
165
|
-
value="native"
|
|
166
|
-
/>
|
|
167
|
-
本地(Native Messaging,默认)
|
|
168
|
-
</label>
|
|
169
|
-
<label class="radio">
|
|
170
|
-
<input
|
|
171
|
-
type="radio"
|
|
172
|
-
name="transport"
|
|
173
|
-
value="http"
|
|
174
|
-
/>
|
|
175
|
-
远程(HTTP bridge)
|
|
176
|
-
</label>
|
|
177
|
-
<div id="bridge-config">
|
|
178
|
-
<div
|
|
179
|
-
class="row"
|
|
180
|
-
style="margin-top: 6px"
|
|
181
|
-
>
|
|
156
|
+
<!-- 「设置」页:生效域名 + 传输通路 -->
|
|
157
|
+
<div
|
|
158
|
+
id="top-settings"
|
|
159
|
+
style="display: none"
|
|
160
|
+
>
|
|
161
|
+
<section>
|
|
162
|
+
<div class="label">生效域名</div>
|
|
163
|
+
<div class="row">
|
|
182
164
|
<input
|
|
183
|
-
id="
|
|
165
|
+
id="domain-input"
|
|
184
166
|
type="text"
|
|
185
|
-
placeholder="
|
|
167
|
+
placeholder="如 https://example.com"
|
|
186
168
|
/>
|
|
169
|
+
<button
|
|
170
|
+
id="add-domain"
|
|
171
|
+
class="primary"
|
|
172
|
+
>
|
|
173
|
+
添加
|
|
174
|
+
</button>
|
|
187
175
|
</div>
|
|
188
|
-
<
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
176
|
+
<ul id="domain-list"></ul>
|
|
177
|
+
<div class="hint">添加时浏览器会请求该域名的访问权限,允许后即在该域名注入选取脚本。</div>
|
|
178
|
+
</section>
|
|
179
|
+
|
|
180
|
+
<section>
|
|
181
|
+
<div class="label">传输通路</div>
|
|
182
|
+
<label class="radio">
|
|
183
|
+
<input
|
|
184
|
+
type="radio"
|
|
185
|
+
name="transport"
|
|
186
|
+
value="native"
|
|
187
|
+
/>
|
|
188
|
+
本地(Native Messaging,默认)
|
|
189
|
+
</label>
|
|
190
|
+
<label class="radio">
|
|
191
|
+
<input
|
|
192
|
+
type="radio"
|
|
193
|
+
name="transport"
|
|
194
|
+
value="http"
|
|
195
|
+
/>
|
|
196
|
+
远程(HTTP bridge)
|
|
197
|
+
</label>
|
|
198
|
+
<div id="bridge-config">
|
|
199
|
+
<div
|
|
200
|
+
class="row"
|
|
201
|
+
style="margin-top: 6px"
|
|
202
|
+
>
|
|
203
|
+
<input
|
|
204
|
+
id="bridge-target"
|
|
205
|
+
type="text"
|
|
206
|
+
placeholder="devlocal.oreateai.com:8917"
|
|
207
|
+
/>
|
|
208
|
+
</div>
|
|
209
|
+
<div class="hint">
|
|
210
|
+
填开发机 bridge 的完整地址(如
|
|
211
|
+
<code>devlocal.oreateai.com:8917</code>
|
|
212
|
+
)。开发机上
|
|
213
|
+
<code>ui-inspector install --remote</code>
|
|
214
|
+
起 bridge(默认绑 0.0.0.0 供直连)。
|
|
215
|
+
</div>
|
|
194
216
|
</div>
|
|
195
|
-
</
|
|
196
|
-
</section>
|
|
217
|
+
</section>
|
|
197
218
|
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
219
|
+
<button
|
|
220
|
+
id="save"
|
|
221
|
+
class="primary"
|
|
222
|
+
>
|
|
223
|
+
保存传输设置
|
|
224
|
+
</button>
|
|
225
|
+
<div id="msg"></div>
|
|
226
|
+
</div>
|
|
205
227
|
|
|
206
228
|
<script src="popup.js"></script>
|
|
207
229
|
</body>
|
package/extension/popup.js
CHANGED
|
@@ -139,12 +139,28 @@ $('domain-input').addEventListener('keydown', e => {
|
|
|
139
139
|
renderDomains();
|
|
140
140
|
renderTransport();
|
|
141
141
|
|
|
142
|
-
// ----------
|
|
142
|
+
// ---------- 顶层 tab:模式 / 设置 ----------
|
|
143
|
+
function initTopTabs() {
|
|
144
|
+
const topTabs = [...document.querySelectorAll('#top-tabs .tab')];
|
|
145
|
+
const panes = {mode: $('top-mode'), settings: $('top-settings')};
|
|
146
|
+
for (const b of topTabs) {
|
|
147
|
+
b.onclick = () => {
|
|
148
|
+
for (const x of topTabs) {
|
|
149
|
+
x.classList.toggle('active', x === b);
|
|
150
|
+
}
|
|
151
|
+
for (const [key, el] of Object.entries(panes)) {
|
|
152
|
+
el.style.display = b.dataset.top === key ? 'block' : 'none';
|
|
153
|
+
}
|
|
154
|
+
};
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
initTopTabs();
|
|
158
|
+
|
|
159
|
+
// ---------- 「模式」页:选择 Element + 增强包子 tab ----------
|
|
143
160
|
// 读 extension/enhancements.json(由 `ui-inspector add <名>` 拷贝插件并写入)。
|
|
144
|
-
//
|
|
145
|
-
// 「选择 Element」固定第一项 + 每个插件一项;点 tab 切换对应面板。
|
|
161
|
+
// 子 tab 第一项固定「选择 Element」,其后每个增强包一项;无增强包时隐藏子 tab 条只留默认提示。
|
|
146
162
|
// 面板 HTML 片段从 enhancements/<id>/<pane> 拉取内嵌,随后加载 enhancements/<id>/<paneJs>。
|
|
147
|
-
async function
|
|
163
|
+
async function initModes() {
|
|
148
164
|
let plugins = [];
|
|
149
165
|
try {
|
|
150
166
|
const r = await fetch(chrome.runtime.getURL('enhancements.json'));
|
|
@@ -157,9 +173,6 @@ async function initEnhancements() {
|
|
|
157
173
|
} catch {
|
|
158
174
|
plugins = [];
|
|
159
175
|
}
|
|
160
|
-
if (!plugins.length) {
|
|
161
|
-
return; // 基础版:不显示模式 tab
|
|
162
|
-
}
|
|
163
176
|
|
|
164
177
|
const tabsEl = $('mode-tabs');
|
|
165
178
|
const panesEl = $('mode-panes');
|
|
@@ -169,9 +182,9 @@ async function initEnhancements() {
|
|
|
169
182
|
const b = document.createElement('button');
|
|
170
183
|
b.className = active ? 'tab active' : 'tab';
|
|
171
184
|
b.textContent = label;
|
|
185
|
+
b.dataset.mode = key;
|
|
172
186
|
b.onclick = () => switchMode(key);
|
|
173
187
|
tabsEl.appendChild(b);
|
|
174
|
-
b.dataset.mode = key;
|
|
175
188
|
return b;
|
|
176
189
|
}
|
|
177
190
|
|
|
@@ -208,6 +221,9 @@ async function initEnhancements() {
|
|
|
208
221
|
}
|
|
209
222
|
}
|
|
210
223
|
|
|
211
|
-
|
|
224
|
+
// 只有「选择 Element」一个模式时,子 tab 条没有意义,隐藏它。
|
|
225
|
+
if (!plugins.length) {
|
|
226
|
+
tabsEl.style.display = 'none';
|
|
227
|
+
}
|
|
212
228
|
}
|
|
213
|
-
|
|
229
|
+
initModes();
|
|
@@ -58,25 +58,37 @@ function describe(target, index, total) {
|
|
|
58
58
|
}
|
|
59
59
|
|
|
60
60
|
function describeRecording(r) {
|
|
61
|
-
const
|
|
62
|
-
const
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
61
|
+
const base = r.startedAt || 0;
|
|
62
|
+
const secs = Math.round(((r.endedAt || 0) - base || 0) / 1000);
|
|
63
|
+
const rel = t => (typeof t === 'number' ? `+${((t - base) / 1000).toFixed(2)}s` : '+?');
|
|
64
|
+
|
|
65
|
+
// 把点击 / 控制台 / 网络归并成一条按时间排序的时间线;网络拆成「发起」「完成」两个事件。
|
|
66
|
+
const events = [];
|
|
67
|
+
(r.clicks || []).forEach(c => {
|
|
68
|
+
const files = (c.componentChain || []).map(x => x && x.file).filter(Boolean);
|
|
69
|
+
events.push({
|
|
70
|
+
ts: c.ts,
|
|
71
|
+
text:
|
|
72
|
+
`点击 <${c.tag}> ${JSON.stringify((c.text || '').slice(0, 40))} @ ${c.selector}` +
|
|
73
|
+
(files.length ? ` | 源码 ${files.join(' <- ')}` : ''),
|
|
71
74
|
});
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
75
|
+
});
|
|
76
|
+
(r.console || []).forEach(l => {
|
|
77
|
+
events.push({ts: l.ts, text: `控制台[${l.level}] ${String(l.text).slice(0, 300)}`});
|
|
78
|
+
});
|
|
79
|
+
(r.network || []).forEach(n => {
|
|
80
|
+
events.push({ts: n.startTs, text: `网络→发起 ${n.method} ${n.url}`});
|
|
81
|
+
events.push({ts: n.endTs, text: `网络←完成 ${n.method} ${n.url} → ${n.status} (${n.ms}ms)`});
|
|
82
|
+
});
|
|
83
|
+
// V8 的 sort 稳定:同一时刻保持插入序(发起在完成之前)。
|
|
84
|
+
events.sort((a, b) => (a.ts || 0) - (b.ts || 0));
|
|
85
|
+
|
|
86
|
+
const parts = [
|
|
87
|
+
`【ui-inspector 录制】用户录制了一段操作(页面 ${r.pageUrl || ''},约 ${secs}s,共 ${events.length} 个事件,时间为相对录制起点的偏移):`,
|
|
88
|
+
];
|
|
89
|
+
events.slice(0, 120).forEach(e => parts.push(` ${rel(e.ts)} ${e.text}`));
|
|
90
|
+
if (events.length > 120) {
|
|
91
|
+
parts.push(` …(其余 ${events.length - 120} 个事件省略)`);
|
|
80
92
|
}
|
|
81
93
|
return parts.join('\n');
|
|
82
94
|
}
|
package/package.json
CHANGED