Skip to content

Commit d44835f

Browse files
authored
Add browser extension scanner (#78)
Provide a Chrome Manifest V3 extension that reuses the existing Rust scanner through a browser-wasm build. This lets users check visible page text for non-standard Traditional Chinese usage from the active tab without maintaining separate JavaScript rules. Expose a wasm_bindgen scan_text entry point, add the extension UI / background / content scripts, and document the wasm-pack build flow. The extension keeps permissions scoped to activeTab, retries transient WASM initialization failures, clears per-tab state when tabs change or close, and bounds persisted session results to avoid storage quota failures. Add an Extension CI workflow that builds the WASM scanner, runs helper tests, assembles a distributable extension package, and uploads both unpacked and zipped run artifacts.
1 parent bd28cd7 commit d44835f

31 files changed

Lines changed: 1517 additions & 20 deletions

.github/workflows/extension-ci.yml

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
name: Extension CI
2+
3+
on:
4+
push:
5+
branches: ["main"]
6+
pull_request:
7+
branches: ["main"]
8+
workflow_dispatch:
9+
10+
permissions:
11+
contents: read
12+
13+
env:
14+
CARGO_TERM_COLOR: always
15+
EXTENSION_PACKAGE_DIR: extension-package/zhtw-mcp-extension
16+
EXTENSION_ZIP: zhtw-mcp-extension.zip
17+
18+
concurrency:
19+
group: "extension-ci-${{ github.ref }}"
20+
cancel-in-progress: true
21+
22+
jobs:
23+
package-extension:
24+
runs-on: ubuntu-24.04
25+
timeout-minutes: 20
26+
27+
steps:
28+
- uses: actions/checkout@v6
29+
30+
- name: Install Rust toolchain
31+
run: |
32+
rustup toolchain install stable --profile minimal --component rustfmt
33+
rustup target add wasm32-unknown-unknown
34+
35+
- name: Rust cache
36+
uses: Swatinem/rust-cache@v2
37+
with:
38+
prefix-key: "v1-extension-rust"
39+
shared-key: "ubuntu-24.04"
40+
41+
- name: Set up Node.js
42+
uses: actions/setup-node@v4
43+
with:
44+
node-version: "22"
45+
46+
- name: Install wasm-pack
47+
run: cargo install wasm-pack --locked
48+
49+
- name: Build scanner WASM
50+
run: sh extension/build-wasm.sh
51+
52+
- name: Test extension helpers
53+
run: npm test --prefix extension
54+
55+
- name: Assemble distributable extension
56+
run: |
57+
rm -rf extension-package "$EXTENSION_ZIP"
58+
mkdir -p "$EXTENSION_PACKAGE_DIR"/{dist,icons}
59+
cp -R \
60+
extension/src \
61+
extension/styles \
62+
extension/manifest.json \
63+
extension/popup.html \
64+
"$EXTENSION_PACKAGE_DIR"/
65+
cp extension/dist/*.js extension/dist/*.wasm "$EXTENSION_PACKAGE_DIR"/dist/
66+
if [ -d extension/dist/snippets ]; then
67+
cp -R extension/dist/snippets "$EXTENSION_PACKAGE_DIR"/dist/
68+
fi
69+
cp extension/icons/icon-{16,32,48,128}.png "$EXTENSION_PACKAGE_DIR"/icons/
70+
(cd extension-package && zip -r "../$EXTENSION_ZIP" zhtw-mcp-extension)
71+
72+
- name: Upload unpacked extension artifact
73+
uses: actions/upload-artifact@v4
74+
with:
75+
name: zhtw-mcp-extension-unpacked
76+
path: ${{ env.EXTENSION_PACKAGE_DIR }}
77+
if-no-files-found: error
78+
79+
- name: Upload zipped extension artifact
80+
uses: actions/upload-artifact@v4
81+
with:
82+
name: zhtw-mcp-extension-zip
83+
path: ${{ env.EXTENSION_ZIP }}
84+
if-no-files-found: error

.gitignore

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,3 +11,8 @@ __pycache__/
1111
src/engine/s2t_data.rs
1212
# Downloaded OpenCC dictionary cache
1313
data/opencc/
14+
extension/dist/*
15+
!extension/dist/README.md
16+
.DS_Store
17+
18+
AGENTS.md

Cargo.lock

Lines changed: 69 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 27 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -13,32 +13,51 @@ readme = "README.md"
1313
include = ["src/**/*", "assets/ruleset.json", "Cargo.toml", "LICENSE", "README.md", "rust-toolchain.toml"]
1414

1515
[features]
16-
default = ["translate"]
17-
async-transport = ["dep:tokio"]
16+
default = ["native", "translate"]
17+
native = [
18+
"dep:dirs",
19+
"dep:env_logger",
20+
"dep:fs2",
21+
"dep:rayon",
22+
"dep:tempfile",
23+
"dep:toml",
24+
]
25+
async-transport = ["native", "dep:tokio"]
26+
browser-wasm = ["dep:console_error_panic_hook", "dep:wasm-bindgen"]
1827
translate = ["dep:ureq", "dep:urlencoding"]
1928

29+
[lib]
30+
crate-type = ["rlib", "cdylib"]
31+
32+
[[bin]]
33+
name = "zhtw-mcp"
34+
path = "src/main.rs"
35+
required-features = ["native"]
36+
2037
[dependencies]
2138
serde = { version = "1", features = ["derive", "rc"] }
2239
serde_json = "1"
2340
aho-corasick = "1"
2441
daachorse = "1"
2542
regex = "1"
2643
blake3 = "1"
27-
rayon = "1"
44+
rayon = { version = "1", optional = true }
2845
anyhow = "1"
2946
unicode-normalization = "0.1"
3047
pulldown-cmark = { version = "0.13", default-features = false }
3148
log = "0.4"
32-
env_logger = { version = "0.11", default-features = false }
33-
dirs = "5.0"
34-
tempfile = "3.0"
35-
toml = "0.8"
49+
env_logger = { version = "0.11", default-features = false, optional = true }
50+
dirs = { version = "5.0", optional = true }
51+
tempfile = { version = "3.0", optional = true }
52+
toml = { version = "0.8", optional = true }
3653
tokio = { version = "1", features = ["rt", "io-util", "time", "sync", "macros", "io-std"], optional = true }
3754
ureq = { version = "3", optional = true }
3855
urlencoding = { version = "2", optional = true }
3956
rustc-hash = "2"
40-
fs2 = "0.4"
57+
fs2 = { version = "0.4", optional = true }
4158
postcard = { version = "1", features = ["alloc"] }
59+
console_error_panic_hook = { version = "0.1", optional = true }
60+
wasm-bindgen = { version = "0.2", optional = true }
4261

4362
[build-dependencies]
4463
serde = { version = "1", features = ["derive"] }

extension/README.md

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
# zhtw-mcp Chrome extension
2+
3+
This Manifest V3 extension checks the visible text in the active tab for non-standard Traditional Chinese usage, highlights findings in the page, and shows the warning/error count in the extension badge.
4+
5+
## Build the scanner WASM
6+
7+
Install the browser Rust target and `wasm-pack`, then build the scanner glue:
8+
9+
```sh
10+
rustup target add wasm32-unknown-unknown
11+
cargo install wasm-pack
12+
sh extension/build-wasm.sh
13+
```
14+
15+
The generated files are written to `extension/dist/`.
16+
17+
## Load in Chrome
18+
19+
1. Open `chrome://extensions`.
20+
2. Enable Developer mode.
21+
3. Choose **Load unpacked** and select the `extension/` directory.
22+
4. Open a page containing text such as `這個軟件使用了遞歸算法來遍歷鏈表`.
23+
5. Click the extension icon, then **檢查目前分頁**.
24+
25+
The extension uses `activeTab`, so it scans only after a user gesture and only for the current active tab. Badge counts include warning and error issues; info-level findings appear in the popup but do not increase the badge.
26+
27+
## Test JavaScript helpers
28+
29+
```sh
30+
npm test --prefix extension
31+
```

extension/build-wasm.sh

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
#!/bin/sh
2+
set -eu
3+
4+
cd "$(dirname "$0")/.."
5+
6+
if ! command -v wasm-pack >/dev/null 2>&1; then
7+
echo "wasm-pack is required. Install it from https://rustwasm.github.io/wasm-pack/installer/" >&2
8+
exit 1
9+
fi
10+
11+
if [ ! -f src/engine/s2t_data.rs ]; then
12+
python3 scripts/gen-s2t-tables.py
13+
rustfmt src/engine/s2t_data.rs
14+
fi
15+
16+
wasm-pack build . \
17+
--target web \
18+
--out-dir extension/dist \
19+
--out-name zhtw_mcp_wasm \
20+
--no-opt \
21+
--no-default-features \
22+
--features browser-wasm

extension/icons/icon-128.png

18.8 KB
Loading

extension/icons/icon-16.png

914 Bytes
Loading

extension/icons/icon-32.png

2.22 KB
Loading

extension/icons/icon-48.png

3.89 KB
Loading

0 commit comments

Comments
 (0)