mooncat-browser 0.1.0
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/README.md +213 -0
- package/browser-op/backend/browserd.cjs +1004 -0
- package/browser-op/backend/rpc-client.cjs +64 -0
- package/browser-op/backend/state.cjs +51 -0
- package/browser-op/cdp/capture-inject.js +426 -0
- package/browser-op/cdp/capture-inject.ts +426 -0
- package/browser-op/cdp/capture-service.cjs +172 -0
- package/browser-op/cdp/chrome-launcher.cjs +370 -0
- package/browser-op/cdp/chrome-path.cjs +57 -0
- package/browser-op/cdp/state.cjs +89 -0
- package/browser-op/extension/extension-detect.cjs +228 -0
- package/browser-op/extension/server.cjs +197 -0
- package/browser-op/extension/service.cjs +228 -0
- package/browser-op/extension/state.cjs +78 -0
- package/browser-op/index.cjs +389 -0
- package/browser-op/package.json +17 -0
- package/browser-op/py/behavior.py +138 -0
- package/browser-op/py/browser.py +340 -0
- package/browser-op/py/captcha.py +115 -0
- package/browser-op/py/crawler.py +125 -0
- package/browser-op/py/examples/01_open_and_probe.py +48 -0
- package/browser-op/py/examples/02_reuse_and_probe.py +66 -0
- package/browser-op/py/examples/03_interact.py +66 -0
- package/browser-op/py/find.py +150 -0
- package/browser-op/py/honeypot.py +73 -0
- package/browser-op/py/humanize.py +392 -0
- package/browser-op/py/image.py +186 -0
- package/browser-op/py/interact.py +193 -0
- package/browser-op/py/markdown.py +38 -0
- package/browser-op/py/pyproject.toml +32 -0
- package/browser-op/py/ready.py +208 -0
- package/browser-op/py/scroll.py +180 -0
- package/browser-op/py/upload.py +103 -0
- package/browser-op/py/visual_target.py +47 -0
- package/browser-op/py/visualize.py +91 -0
- package/browser-op/state.cjs +63 -0
- package/browser-op/web/behavior.js +153 -0
- package/browser-op/web/browser.js +231 -0
- package/browser-op/web/captcha.js +85 -0
- package/browser-op/web/crawler.js +109 -0
- package/browser-op/web/find.js +147 -0
- package/browser-op/web/honeypot.js +68 -0
- package/browser-op/web/humanize.js +522 -0
- package/browser-op/web/image.js +177 -0
- package/browser-op/web/interact.js +169 -0
- package/browser-op/web/markdown.js +80 -0
- package/browser-op/web/ready.js +295 -0
- package/browser-op/web/scroll.js +167 -0
- package/browser-op/web/upload.js +116 -0
- package/browser-op/web/visual-runtime.inject.cjs +6 -0
- package/browser-op/webplater/.env.example +7 -0
- package/browser-op/webplater/ARCHITECTURE.md +102 -0
- package/browser-op/webplater/dist/chrome-mv3/assets/popup-BUZEUmsx.css +1 -0
- package/browser-op/webplater/dist/chrome-mv3/background.js +2 -0
- package/browser-op/webplater/dist/chrome-mv3/capture.js +310 -0
- package/browser-op/webplater/dist/chrome-mv3/chunks/_virtual_wxt-html-plugins-DPbbfBKe.js +1 -0
- package/browser-op/webplater/dist/chrome-mv3/chunks/offscreen-CFXYw9Mo.js +1 -0
- package/browser-op/webplater/dist/chrome-mv3/chunks/popup-C-lpxZZO.js +1 -0
- package/browser-op/webplater/dist/chrome-mv3/content-scripts/content.js +7 -0
- package/browser-op/webplater/dist/chrome-mv3/manifest.json +1 -0
- package/browser-op/webplater/dist/chrome-mv3/offscreen.html +16 -0
- package/browser-op/webplater/dist/chrome-mv3/popup.html +31 -0
- package/browser-op/webplater/entrypoints/background.ts +938 -0
- package/browser-op/webplater/entrypoints/content.ts +1150 -0
- package/browser-op/webplater/entrypoints/offscreen/index.html +15 -0
- package/browser-op/webplater/entrypoints/offscreen/main.ts +161 -0
- package/browser-op/webplater/entrypoints/popup/index.html +29 -0
- package/browser-op/webplater/entrypoints/popup/main.ts +61 -0
- package/browser-op/webplater/entrypoints/popup/style.css +100 -0
- package/browser-op/webplater/lib/snapshot.ts +352 -0
- package/browser-op/webplater/package.json +29 -0
- package/browser-op/webplater/pnpm-lock.yaml +3411 -0
- package/browser-op/webplater/public/capture.js +310 -0
- package/browser-op/webplater/scripts/publish-extension.mjs +176 -0
- package/browser-op/webplater/tsconfig.json +19 -0
- package/browser-op/webplater/wxt.config.ts +34 -0
- package/dist/actions.md +102 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +278 -0
- package/dist/cli.js.map +1 -0
- package/dist/client.d.ts +94 -0
- package/dist/client.d.ts.map +1 -0
- package/dist/client.js +277 -0
- package/dist/client.js.map +1 -0
- package/dist/config.d.ts +61 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +119 -0
- package/dist/config.js.map +1 -0
- package/dist/protocol.d.ts +195 -0
- package/dist/protocol.d.ts.map +1 -0
- package/dist/protocol.js +11 -0
- package/dist/protocol.js.map +1 -0
- package/dist/server.d.ts +66 -0
- package/dist/server.d.ts.map +1 -0
- package/dist/server.js +259 -0
- package/dist/server.js.map +1 -0
- package/package.json +78 -0
- package/schemas/browser.clearCookies.schema.json +13 -0
- package/schemas/browser.close.schema.json +9 -0
- package/schemas/browser.getCookies.schema.json +13 -0
- package/schemas/browser.getDownload.schema.json +15 -0
- package/schemas/browser.health.schema.json +9 -0
- package/schemas/browser.listDownloads.schema.json +16 -0
- package/schemas/browser.listTabs.schema.json +9 -0
- package/schemas/browser.newTab.schema.json +15 -0
- package/schemas/browser.open.schema.json +15 -0
- package/schemas/browser.operate.schema.json +15 -0
- package/schemas/browser.reuseTab.schema.json +15 -0
- package/schemas/browser.setCookies.schema.json +15 -0
- package/schemas/browser.waitFor.schema.json +15 -0
- package/schemas/browser.waitForDownload.schema.json +15 -0
- package/skills/browser/SKILL.md +110 -0
- package/skills/browser/references/collect.md +163 -0
- package/skills/browser/references/high-risk.md +161 -0
- package/skills/browser/references/operate-actions.md +92 -0
- package/skills/browser/references/probing.md +302 -0
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
<!doctype html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8" />
|
|
5
|
+
<title>WebPlater Offscreen</title>
|
|
6
|
+
</head>
|
|
7
|
+
<body>
|
|
8
|
+
<!--
|
|
9
|
+
offscreen document:常驻宿主,持有到 browserd 的 WebSocket 长连接。
|
|
10
|
+
页面本身无 UI,仅承载 main.ts 的 WS 桥接逻辑。
|
|
11
|
+
由 background.ts 的 ensureOffscreen() 创建。
|
|
12
|
+
-->
|
|
13
|
+
<script type="module" src="./main.ts"></script>
|
|
14
|
+
</body>
|
|
15
|
+
</html>
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
// offscreen main — 持有到 browserd 的 WebSocket 长连接,把 host 命令桥接到 background。
|
|
2
|
+
//
|
|
3
|
+
// 动机:MV3 service worker 会被 Chrome 回收(约 30s 空闲,单事件最长 5min),
|
|
4
|
+
// 把 WS 长连接放在 SW 里会随回收断开、长任务中断。offscreen document 常驻到关闭,
|
|
5
|
+
// 是 WS 的可靠宿主。
|
|
6
|
+
//
|
|
7
|
+
// 命令链路:
|
|
8
|
+
// browserd :17321 ⇿ offscreen (WS) ⇿ background (chrome.runtime message)
|
|
9
|
+
//
|
|
10
|
+
// background 负责 ensureOffscreen() 建本 document;本文件只做 WS 持有 + 双向桥接。
|
|
11
|
+
//
|
|
12
|
+
// 注意:本文件作为 offscreen/index.html 的内嵌 module script,必须用顶层副作用执行
|
|
13
|
+
// (与 popup/main.ts 一致)。不要用 `export default defineUnlistedScript`——那会被
|
|
14
|
+
// wxt 识别成独立 unlisted-script 入口并从本 html 抽走,导致脚本不被加载。
|
|
15
|
+
|
|
16
|
+
interface CommandMessage {
|
|
17
|
+
id: number
|
|
18
|
+
type: 'command'
|
|
19
|
+
method: string
|
|
20
|
+
params?: Record<string, unknown>
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
interface ResponseMessage {
|
|
24
|
+
id: number
|
|
25
|
+
type: 'response'
|
|
26
|
+
ok: boolean
|
|
27
|
+
result?: unknown
|
|
28
|
+
error?: string
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
interface BackgroundResponse {
|
|
32
|
+
ok: boolean
|
|
33
|
+
result?: unknown
|
|
34
|
+
error?: string
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const HOST_WS_URL = 'ws://127.0.0.1:17321'
|
|
38
|
+
const RECONNECT_DELAY_MS = 1000
|
|
39
|
+
const PING_INTERVAL_MS = 20000
|
|
40
|
+
|
|
41
|
+
let socket: WebSocket | null = null
|
|
42
|
+
let reconnectTimer: ReturnType<typeof setTimeout> | null = null
|
|
43
|
+
let pingTimer: ReturnType<typeof setInterval> | null = null
|
|
44
|
+
|
|
45
|
+
function connect() {
|
|
46
|
+
if (socket?.readyState === WebSocket.CONNECTING || socket?.readyState === WebSocket.OPEN) {
|
|
47
|
+
return
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
socket = new WebSocket(HOST_WS_URL)
|
|
51
|
+
|
|
52
|
+
socket.addEventListener('open', () => {
|
|
53
|
+
if (reconnectTimer) {
|
|
54
|
+
clearTimeout(reconnectTimer)
|
|
55
|
+
reconnectTimer = null
|
|
56
|
+
}
|
|
57
|
+
console.log('[WebPlater/offscreen] connected to host', HOST_WS_URL)
|
|
58
|
+
// hello 只需 type:host server.cjs 只看消息 type 判活,name/version/extensionId
|
|
59
|
+
// 是装饰字段,且 chrome.runtime.getManifest/id 在 offscreen 不可用,故不取。
|
|
60
|
+
send({ type: 'hello', name: 'bee-web-plugin' })
|
|
61
|
+
startPing()
|
|
62
|
+
})
|
|
63
|
+
|
|
64
|
+
socket.addEventListener('message', async (event) => {
|
|
65
|
+
let msg: CommandMessage | undefined
|
|
66
|
+
try {
|
|
67
|
+
msg = JSON.parse(String(event.data))
|
|
68
|
+
} catch {
|
|
69
|
+
console.error('[WebPlater/offscreen] invalid JSON from host', event.data)
|
|
70
|
+
return
|
|
71
|
+
}
|
|
72
|
+
if (!msg || msg.type !== 'command' || typeof msg.id !== 'number') return
|
|
73
|
+
|
|
74
|
+
try {
|
|
75
|
+
const result = await sendToBackground(msg.id, msg.method, msg.params ?? {})
|
|
76
|
+
respond(msg.id, { ok: true, result })
|
|
77
|
+
} catch (err) {
|
|
78
|
+
respond(msg.id, {
|
|
79
|
+
ok: false,
|
|
80
|
+
error: err instanceof Error ? err.message : String(err),
|
|
81
|
+
})
|
|
82
|
+
}
|
|
83
|
+
})
|
|
84
|
+
|
|
85
|
+
socket.addEventListener('close', () => {
|
|
86
|
+
console.log('[WebPlater/offscreen] disconnected, reconnecting...')
|
|
87
|
+
socket = null
|
|
88
|
+
stopPing()
|
|
89
|
+
scheduleReconnect()
|
|
90
|
+
})
|
|
91
|
+
|
|
92
|
+
socket.addEventListener('error', () => {
|
|
93
|
+
// close 事件会随后触发,由它负责重连
|
|
94
|
+
socket?.close()
|
|
95
|
+
})
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
function send(payload: unknown): void {
|
|
99
|
+
if (socket?.readyState === WebSocket.OPEN) {
|
|
100
|
+
socket.send(JSON.stringify(payload))
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
function respond(id: number, response: { ok: boolean; result?: unknown; error?: string }): void {
|
|
105
|
+
const msg: ResponseMessage = { id, type: 'response', ...response }
|
|
106
|
+
send(msg)
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* 把命令转发给 background 执行并等回包。
|
|
111
|
+
* chrome.runtime.sendMessage 会唤醒休眠的 service worker;SW 在 message handler
|
|
112
|
+
* pending(sendResponse 未调用)期间保持活跃,长任务(如 evaluate 300s)不会因 SW
|
|
113
|
+
* 回收而中断连接本身(连接在本 offscreen 持有)。
|
|
114
|
+
*/
|
|
115
|
+
function sendToBackground(
|
|
116
|
+
id: number,
|
|
117
|
+
method: string,
|
|
118
|
+
params: Record<string, unknown>,
|
|
119
|
+
): Promise<unknown> {
|
|
120
|
+
return new Promise((resolve, reject) => {
|
|
121
|
+
chrome.runtime.sendMessage(
|
|
122
|
+
{ source: 'offscreen-command', id, method, params },
|
|
123
|
+
(response) => {
|
|
124
|
+
if (chrome.runtime.lastError) {
|
|
125
|
+
reject(new Error(chrome.runtime.lastError.message))
|
|
126
|
+
return
|
|
127
|
+
}
|
|
128
|
+
const r = response as BackgroundResponse | undefined
|
|
129
|
+
if (r && typeof r === 'object' && r.ok === false) {
|
|
130
|
+
reject(new Error(r.error ?? 'background error'))
|
|
131
|
+
return
|
|
132
|
+
}
|
|
133
|
+
resolve(r?.result)
|
|
134
|
+
},
|
|
135
|
+
)
|
|
136
|
+
})
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
function startPing(): void {
|
|
140
|
+
stopPing()
|
|
141
|
+
pingTimer = setInterval(() => {
|
|
142
|
+
send({ type: 'ping', time: Date.now() })
|
|
143
|
+
}, PING_INTERVAL_MS)
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
function stopPing(): void {
|
|
147
|
+
if (pingTimer) {
|
|
148
|
+
clearInterval(pingTimer)
|
|
149
|
+
pingTimer = null
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
function scheduleReconnect(): void {
|
|
154
|
+
if (reconnectTimer) return
|
|
155
|
+
reconnectTimer = setTimeout(() => {
|
|
156
|
+
reconnectTimer = null
|
|
157
|
+
connect()
|
|
158
|
+
}, RECONNECT_DELAY_MS)
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
connect()
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
<!doctype html>
|
|
2
|
+
<html lang="zh-CN">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8" />
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
6
|
+
<title>WebPlater</title>
|
|
7
|
+
</head>
|
|
8
|
+
<body>
|
|
9
|
+
<div id="app">
|
|
10
|
+
<header>
|
|
11
|
+
<h1>WebPlater</h1>
|
|
12
|
+
<p id="status" class="status">checking...</p>
|
|
13
|
+
</header>
|
|
14
|
+
|
|
15
|
+
<section>
|
|
16
|
+
<h2>调试</h2>
|
|
17
|
+
<div class="row">
|
|
18
|
+
<button id="btn-tabs">List Tabs</button>
|
|
19
|
+
<button id="btn-status">Page Status</button>
|
|
20
|
+
<button id="btn-reconnect">Reconnect</button>
|
|
21
|
+
</div>
|
|
22
|
+
</section>
|
|
23
|
+
|
|
24
|
+
<pre id="result">Ready</pre>
|
|
25
|
+
</div>
|
|
26
|
+
|
|
27
|
+
<script type="module" src="./main.ts"></script>
|
|
28
|
+
</body>
|
|
29
|
+
</html>
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import './style.css'
|
|
2
|
+
|
|
3
|
+
const result = document.getElementById('result') as HTMLPreElement
|
|
4
|
+
const statusEl = document.getElementById('status') as HTMLParagraphElement
|
|
5
|
+
|
|
6
|
+
function render(data: unknown): void {
|
|
7
|
+
result.textContent = typeof data === 'string' ? data : JSON.stringify(data, null, 2)
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
// popup 无法直接接触 background 的 WebSocket 私有变量,
|
|
11
|
+
// 这里只做命令级调试:通过一条 runtime 消息让 background 回包。
|
|
12
|
+
// status 文本仅反映扩展是否响应,不反映 WebSocket 真实连接态(那只在 background 内)。
|
|
13
|
+
async function pingBackground(): Promise<boolean> {
|
|
14
|
+
return new Promise((resolve) => {
|
|
15
|
+
chrome.runtime.sendMessage({ source: 'bee-plugin-popup', method: 'listTabs' }, (resp) => {
|
|
16
|
+
if (chrome.runtime.lastError) {
|
|
17
|
+
resolve(false)
|
|
18
|
+
return
|
|
19
|
+
}
|
|
20
|
+
resolve(!!resp)
|
|
21
|
+
})
|
|
22
|
+
})
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
document.getElementById('btn-tabs')!.addEventListener('click', async () => {
|
|
26
|
+
result.textContent = 'loading...'
|
|
27
|
+
chrome.runtime.sendMessage({ source: 'bee-plugin-popup', method: 'listTabs' }, (resp) => {
|
|
28
|
+
if (chrome.runtime.lastError) {
|
|
29
|
+
render(chrome.runtime.lastError.message)
|
|
30
|
+
return
|
|
31
|
+
}
|
|
32
|
+
render(resp)
|
|
33
|
+
})
|
|
34
|
+
})
|
|
35
|
+
|
|
36
|
+
document.getElementById('btn-status')!.addEventListener('click', async () => {
|
|
37
|
+
result.textContent = 'loading...'
|
|
38
|
+
chrome.runtime.sendMessage({ source: 'bee-plugin-popup', method: 'status' }, (resp) => {
|
|
39
|
+
if (chrome.runtime.lastError) {
|
|
40
|
+
render(chrome.runtime.lastError.message)
|
|
41
|
+
return
|
|
42
|
+
}
|
|
43
|
+
render(resp)
|
|
44
|
+
})
|
|
45
|
+
})
|
|
46
|
+
|
|
47
|
+
document.getElementById('btn-reconnect')!.addEventListener('click', () => {
|
|
48
|
+
// offscreen 持有 WS 并自重连;这里确保 offscreen document 存在
|
|
49
|
+
chrome.runtime.sendMessage({ source: 'bee-plugin-popup', method: 'reconnect' }, (resp) => {
|
|
50
|
+
if (chrome.runtime.lastError) {
|
|
51
|
+
render(chrome.runtime.lastError.message)
|
|
52
|
+
return
|
|
53
|
+
}
|
|
54
|
+
render(resp?.ok ? 'offscreen ensured; WS (re)connects automatically' : resp)
|
|
55
|
+
})
|
|
56
|
+
})
|
|
57
|
+
|
|
58
|
+
;(async () => {
|
|
59
|
+
const ok = await pingBackground()
|
|
60
|
+
statusEl.textContent = ok ? 'background responsive' : 'background no response'
|
|
61
|
+
})()
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
* {
|
|
2
|
+
box-sizing: border-box;
|
|
3
|
+
}
|
|
4
|
+
|
|
5
|
+
body {
|
|
6
|
+
margin: 0;
|
|
7
|
+
padding: 0;
|
|
8
|
+
font-family: system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
|
9
|
+
font-size: 13px;
|
|
10
|
+
color: #333;
|
|
11
|
+
background: #f5f5f5;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
#app {
|
|
15
|
+
width: 440px;
|
|
16
|
+
min-height: 300px;
|
|
17
|
+
padding: 16px;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
header h1 {
|
|
21
|
+
margin: 0 0 4px;
|
|
22
|
+
font-size: 16px;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
.status {
|
|
26
|
+
margin: 0 0 12px;
|
|
27
|
+
font-size: 12px;
|
|
28
|
+
color: #888;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
section {
|
|
32
|
+
background: #fff;
|
|
33
|
+
border-radius: 8px;
|
|
34
|
+
padding: 12px;
|
|
35
|
+
margin-bottom: 12px;
|
|
36
|
+
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.06);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
section h2 {
|
|
40
|
+
font-size: 13px;
|
|
41
|
+
margin: 0 0 10px;
|
|
42
|
+
color: #666;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
.row {
|
|
46
|
+
display: flex;
|
|
47
|
+
gap: 8px;
|
|
48
|
+
flex-wrap: wrap;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
button {
|
|
52
|
+
padding: 6px 12px;
|
|
53
|
+
border: 1px solid #d9d9d9;
|
|
54
|
+
border-radius: 4px;
|
|
55
|
+
background: #fff;
|
|
56
|
+
cursor: pointer;
|
|
57
|
+
font-size: 12px;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
button:hover {
|
|
61
|
+
border-color: #40a9ff;
|
|
62
|
+
color: #1890ff;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
input[type='text'],
|
|
66
|
+
textarea {
|
|
67
|
+
width: 100%;
|
|
68
|
+
padding: 6px 8px;
|
|
69
|
+
margin-bottom: 8px;
|
|
70
|
+
border: 1px solid #d9d9d9;
|
|
71
|
+
border-radius: 4px;
|
|
72
|
+
font-family: monospace;
|
|
73
|
+
font-size: 12px;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
textarea {
|
|
77
|
+
resize: vertical;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
.inline {
|
|
81
|
+
display: flex;
|
|
82
|
+
align-items: center;
|
|
83
|
+
gap: 6px;
|
|
84
|
+
font-size: 12px;
|
|
85
|
+
color: #666;
|
|
86
|
+
margin-top: 6px;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
#result {
|
|
90
|
+
background: #f6f8fa;
|
|
91
|
+
border: 1px solid #e1e4e8;
|
|
92
|
+
border-radius: 4px;
|
|
93
|
+
padding: 10px;
|
|
94
|
+
max-height: 280px;
|
|
95
|
+
overflow: auto;
|
|
96
|
+
font-size: 11px;
|
|
97
|
+
line-height: 1.5;
|
|
98
|
+
white-space: pre-wrap;
|
|
99
|
+
word-break: break-all;
|
|
100
|
+
}
|