Tridactyl 初學使用心得

瀏覽器擴充元件中一直有一系列套件, 是可以完全用鍵盤控制一般網頁瀏覽的,像 vimfx vimium tridactyl。 最近用了 tridactyl ,真是一個非常 hack 的套件, 可以客制化的地方超多,但也很多功能幾乎是半成品。 如果是會寫 js 的進階使用者,強烈推薦 tridactyl, 如果用的滿意,也希望可以幫忙貢獻或捐款。

從 vimium 的功能增強說起

一直以來都會用像 vimfx 之類的擴充套件, 就能用鍵盤控制瀏覽器,不用用到滑鼠就能瀏覽網頁。 這類套件大多是模仿 vim 的按鍵操作,用 jk 來上下滾動網頁, 用 f 來點擊連結或按鈕,其它就比較不一定; 像 JK 來切換分頁,HL 上一頁下一頁。

我是用 firefox。 原本一直用功能比 vimfx 稍強的[vimium] firefox 版,[vimium-ff] 後來無意間裝了 vimium-c,發現功能遠勝 vimium。 像多了快捷鍵在文字框輸入時也能生效, 主要用途是像 chromium 原本在輸入時要按 Esc 來離開輸入模式, 就像 vim 那樣。 問題是 Esc 很遠很難按,一般 vim user 會挷到 C-cjk

但後來仔細看了 vimium-c,發現這東西是某個中國人做的, 而且還 在 readme 特別宣告 , 不贊成 chrome 或 edge 商店中, 將台灣與中國並列的作法。

针对适用区域的声明

Vimium C(和 gdh1995 发布的其他扩展)在被发布到“Microsoft Edge 加载项”和“Chrome 网上应用店”等商店上时,均已向 所有地区 的所有人公开。 但这个行为只是为了让这些插件更容易使用,并不代表或者暗示作者 gdh1995 “同意或者不反对”“台湾”一词可以同“中国”并列。 虽然并列显示这一现状的确错误地出现在了这些商店的(开发者)页面中(于 2021 年 6 月 3 日确认)。

根据《中华人民共和国宪法》和国际共识,台湾是中华人民共和国的神圣领土(不可分割)的一部分。

既然對方都那麼強烈反對了,只好另尋它款擴充, 就找到了 tridactyl。

tridactyl 新手適應

一開始用 tridactyl 不太適應,他的鍵綁定和 vimium 系不太一樣, 畢竟 vimium-ff 和 vimium-c 都算 vimium 衍生的。 之前從 vimfx 切到 vimium 時也有調過。 總之我就都重新綁定成我習慣的。

單手操作

有一陣子發現我會幾乎只用左手摸鍵盤,右手可能在摸滑鼠或拿飲料。 所以把一些功能都綁到左側鍵盤。 像把 f 的連結目標編號改成只有左側鍵盤的鍵, 上下捲動功能也綁到左側的按鍵 Dd 上, 切換分頁的 JK 也綁到 bw,gi 綁到 a。 上一頁下一頁則是綁到 eE , 這沒什麼邏輯,就挑一個還空著的按鍵。

tridactylrc

tridactyl 自己定義了一些簡單的可組合的語法, 有點像 tcl,不過比 tcl 還不如,因為沒有 quote 跳脫功能。 嚴格來說不算一種語言, 只是一些指令會把傳入的參數當成多個指令組合執行, 而某一些指令可以執行任意 js。

整體有點像 tmux.conf 的語法, 例如 bind j scrollline 10 。 但語法上又模仿了 unix 用連字符當參數的用法, 例如 bind --mode=insert <C-b> text.backward_char

整體我不太看好長期發展,因為有些功能初看還不錯, 但感覺沒考慮到深處的設計哲學。 例如用來組合指令的 composite, 使用管道符時會把前一個指令的結果傳給下一個, 還有個自動等待 promise 結果的功能。 但某些理論上應該是要回傳 promise 的指令, 如 fillcmdline 卻沒有回傳指令輸入後完成的 promise, 所以就要自己包成 promise。 發展到後面就會變成縫縫補補。

任意執行 js 的能力

我認為 tridactyl 最大的強項是是 :js 這個命令, 可以用來執行任意 js 程式碼, 加上許多內部功能也有做成可呼叫的 js 函數, 所以可以用這個命令做到非常多的事。

比附加元件更方便

事實上,因為 tridactyl 曝露了大量的內部函數, 也直接曝露了 WebExtension API, 所以可以從 js 直接呼叫 webextension api, 等於可以不用獨立做一個附加元件,就執行某些需要附加元件等級的功能。

像是可以定義一個命令 history_clear_url , 利用 history api,把目前歷史記錄裡符合目前 url 的網頁都刪除。 或是 tab_list ,用來列出目前所有開啟的分頁的網址, 然後可以存在 tridactyl 的內部儲存空間或存成文字檔, 甚至用 setinterval 每幾小時記錄一次,防止 crash 時分頁記錄消失; 這大概就是某個附加元件 tab session manager 的最基本功能。

雖然做起來會有點綁手綁腳,像要跳脫某些特殊字元, 要找出 tridactyl 有限的事件中, 這些命令應該要掛到什麼事件上觸發。

任意擴充命令

另一點就是,自己用 js 定義的函數和內建的命令差異不大。 像內建有 tabpush tabqueue 命令, 都是用來把分頁移動到另一個視窗。 但我希望能夠移動後,自動讓該分頁取得焦點, 所以自己定義了一個 tabpush_and_focus , 利用 web extension 的 tabs.onActivated 事件, 抓到分頁移動後焦點移到另一分頁的準確時間點, 準確重新聚焦到該分頁上。

// alias tabpush_and_focus jsb ...
void async function() {
    const tab = await activeTab();
    const tabId = tab.id;
    const oldWindowId = tab.windowId;
    fillcmdline("tabpush");
    await new Promise(ok => {
        browser.tabs.onActivated.addListener(function leftCurrentTab(event) {
            browser.tabs.onActivated.removeListener(leftCurrentTab);
            ok();
        });
    });
    const tabNow = await browser.tabs.get(tabId);
    if (tabNow.windowId == oldWindowId) return;
    browser.tabs.update(tabId, {
        active: true
    });
    browser.windows.update(tabNow.windowId, {
        focused: true
    });
}();

自動化的能力

js 的能力直接讓自動化成為可能。 例如我想要讓 gr 二個鍵,可以把目前頁面加入書籤, 並關閉目前頁面,也完全可以做到。 甚至是網頁等級內的自動化, 像在 gitlab 上打開某 repository 內檔案的編輯介面, 然後從某處把新版內容填入,並按下預覽鍵。

// alias mktridactylrc_put tabopen_and_js -s https://gitlab.io/gholk/tridactylrc/-/edit/main/tridactylrc ...
const $ = x => document.querySelector(x);
$("a[href='#editor']").click();
const rc = await tri.controller.acceptExCmd("mktridactylrc_fix");
const node = $("#editor>*");
const getEditor = await tri.excmds.js("getEditor");
const editor = getEditor(node);
await editor.setContent(rc + "\n");
$("a[href='#preview']").click();

但某次更新後 gitlab 的 editor 不再暴露全域的 monaco editor 變數, 所以上述腳本就不能運作了,此是後話。

比 GreaseMonkey 更自由

綜合來看有點像 grease monkey 等的 user.js, 但 grease monkey 提供的程式的 進入點 很有限, 只有頁面載入時執行。 如果要定義某個按鍵時執行對應動作, 就得自己綁定到對應的 key press, 或是在頁面上新增按鈕,然後監聽按鈕按下的事件。

這二點都很麻煩,要避免影響到原本網頁的結構運作。 但 tridactyl 直接幫你處理好這些事, 只要綁到某一按鍵就就好了, 或是甚至不用綁,寫成 ex command 就好了。

例如在 youtube 上,可以定義讓 gu 是按下 like 鍵,讓 gd 是 dislike。 在動畫瘋上,可以定義讓 I 是顯示輸入彈幕的文字框。

其他有的沒的

其他也有很多可以獨立在其他附加元件裡做, 但因為方便我直接做在 tridactyl 裡了。 像 adblock 用來擋廣告的 webRequest api, 我用來擋了巴哈的通知,防止某些重導向連結如 google 搜尋結果自追蹤用連結, 改某些網頁的 user agent。

以下是擋掉 google 追蹤用跳轉連結的 tridactyl 命令:

autocmd BeforeRequest https://www.google.com/url?* req => { const u = new URL(req.url); return {redirectUrl: u.searchParams.get('q')} }

糟糕的心得

最後我想說,這其實是一篇糟糕的心得。 我從三月開始用 tridactyl, 七月開始修 bug,九月開始開發新功能。 我已經沒辦法想像沒有 tridactyl 的生活, 但我卻沒辦法好好描述 tridactyl 到底好用在哪。 總之我不想再等了,所以就這樣發布吧。

也許我該做的,是用文學編程的方式, 把自己的 tridactylrc 註解一次,說明每個指令做了什麼。 但我的 rc 又會不斷更新,更新後舊的文件就要重新對應, 感覺就很麻煩。