這兩天,寫了一個簡單的基于有道在線翻譯的GreaseMonkey屏幕取詞腳本。

我想做這件事很久了,從我還不是一個前端開發(fā)者的時候,就一直想做這么一個輕量的瀏覽器腳本,方便自己查看英文的文檔和文章。沒想到想了這么久,真正沒做多久。
作為一個Ubuntu Linux用戶,瀏覽器取詞我有幾個選擇:
嘗試安裝有道詞典Linux版本、openyoudao或者其他stardict或者goldendict這種本地詞典。但我并不覺得我需要桌面軟件。
有人做了個Google translate tooltip的GreaseMonkey腳本實現(xiàn)這個,非常棒。但谷歌的服務(wù)在國內(nèi)的服務(wù)非常不穩(wěn)定,取詞功能經(jīng)常不能正常使用。
有道提供了網(wǎng)頁翻譯2.0,通過書簽執(zhí)行一段代碼把取詞功能注入當前頁面。然而,首先隨著瀏覽器安全特性的加強,該書簽不能正常使用,其次每次都要先點書簽才能取詞(也許是快捷鍵)。
選擇是難
很多網(wǎng)站,包括cnblog發(fā)現(xiàn)都提供了取詞版本。我面臨的選擇是:
- 在這些已有的瀏覽器取詞腳本基礎(chǔ)上學(xué)習(xí)修改。
- 憑借著自己的感覺從新設(shè)計
選擇上花了很多時間。
方案一的優(yōu)點有:
- 成熟美觀。
- 能學(xué)習(xí)到很多東西
方案一問題在于:
- 源碼難理解。代碼量較大,都是壓縮甚至混淆變量過的。
- 有些和當前頁面的樣式或者腳本攪和在一起。不易分離
- 被瀏覽器或網(wǎng)站安全設(shè)置廢掉,未必能使用
終于,由于我的智商被有道在線翻譯那個腳本所碾壓,我想還是看看功能自己設(shè)計下,做個簡單版本。
想的很簡單
設(shè)計是易
想法很簡單。
- 鼠標選詞
- 向第三方發(fā)起請求,比如bing的翻譯或者有道的
- 讀取返回,彈出tooltip,格式化數(shù)據(jù)
- 其他輔助功能比如發(fā)音、單詞本等等
設(shè)計是最簡單的一環(huán),后面你會看到時間都花到哪里了。
知易行難
通過谷歌,很容易完成第一步,在腳本中得到選中的文字。
第二步就開始面臨問題。作為前信息安全專業(yè)從業(yè)者,很清楚ajax這種東西跨域是受限制的。稍微翻閱scriptish文檔發(fā)現(xiàn)GM_xmlhttpRequest可以滿足我的需求。
除卻和 XMLHttpRequest這種東西并不太一樣的api造成的各種細節(jié)錯誤,之后碰到的問題是我整個開發(fā)過程最棘手、花費時間最長的問題。
無論onload、onerror還是onreadystate的回調(diào)中,GM_log都沒有打印出任何信息。
firebug和火狐內(nèi)置調(diào)試器也沒有顯示任何通信。這和我在網(wǎng)絡(luò)上看到的GreaseMonkey相關(guān)信息并不太相符。
經(jīng)檢查腳本元數(shù)據(jù)@grant,覺得已經(jīng)授權(quán)這個跨域函數(shù)也沒什么問題。
折騰一陣,確認API調(diào)用和細節(jié)都無法確認問題后,采取曲線調(diào)試方案。
更改請求地址到本地,確認請求確實發(fā)出了。那么,它有返回嗎?
在本地用netcat模擬返回數(shù)據(jù),仍然沒有打印任何信息。我開始懷疑難道GM_xmlhttpRequest是會對返回結(jié)果做驗證?必須報頭正確?
第一天就這么過去了。
第二天我決定嘗試代理來看來往的通信是否正常。
方便起見,先用nc充當了下代理,檢查了下相互通信,未見有什么不對的。
為嚴謹起見,用burpsuite來設(shè)置一個透明本地代理,讓瀏覽器指向那個代理。經(jīng)過檢驗,完全沒看出通信有什么問題。但onload和其他回調(diào)也不會被觸發(fā)。
谷歌搜索得到一些stackoverflow、github issue和greasewiki上的信息,但問題仍不能確認和解決。
只是昨天晚上baidu時心心念念,發(fā)現(xiàn)firefox貼吧里有人吐槽scriptish不穩(wěn)定的一些地方,今天又看到一些討論,決定換回GreaseMonkey試試,事實證明這是明智的。
然而,一換發(fā)現(xiàn)什么都打印不出來了。后來反復(fù)嘗試,發(fā)現(xiàn)GM_log不能用,我簡直震驚了,wiki上寫著玩的么,還是有什么變化。反正我發(fā)現(xiàn)console.log可以使用,那就繼續(xù)開發(fā)下去了。
最難的部分就這么糊里糊涂過去了。
數(shù)據(jù)請求順風(fēng)順水
一旦請求完成,解析json數(shù)據(jù),按需展示就是水到渠成的事情。
然而,并不是那么簡單。
JS異步與回調(diào)之難
JS的異步特性帶來了這些不符合人類直觀思維方式的流程控制風(fēng)格。
按理說我應(yīng)該很習(xí)慣javascript的異步操作流程控制的種種問題,但還是踩了次坑。
彈出和渲染tooltip的函數(shù)沒有讀到返回數(shù)據(jù)!
好在對javascript程序員debug這種問題比之前的問題簡單太多。一看想起來GM_xmlhttpRequest是異步過程,而不是同步,我這里卻要待異步過程返回結(jié)果再執(zhí)行下一個函數(shù)。
想想promise應(yīng)該不用,雖然firefox41肯定原生支持ES6 promise了。但,就這點函數(shù)干脆。。。還是回調(diào)“地獄”吧。
JS難中有易
說到ES6,ES6提供了很多方便javascript編程的好東西,通過let和=>實現(xiàn)更好的this和作用域一致,通過Template方便字符串操作等等。
很慶幸,GreaseMonkey的話我只考慮firefox用戶,反正好早的時候這些ES6特性瀏覽器都支持了。
JS易中又難
JS讓人非常難過的一個地方,是DOM操作和各種webAPI。只能說喪心病狂。你記得清楚如何獲得viewport區(qū)域大小么?知道如何獲得鼠標相對viewport位置么?知道為啥獲取區(qū)域高度或?qū)挾炔]有獲得么?看到clientWidth、offsetWidth、availWidth...有沒有想砍人?
為了讓腳本能正確在屏幕邊緣讓tooltip出現(xiàn)在viewport內(nèi),在各種邊界條件數(shù)學(xué)計算題這里又糾結(jié)了好久。
GreaseMonkey相比Scriptish少了一個比較方便的特性: @css。雖然可以在head標簽中通過GM_addStyle()來注入樣式,我總覺得會不合時宜的覆蓋不該覆蓋的東西,我對Google Translate Tooltip在阮一峰大大的網(wǎng)站上奇葩的樣式表現(xiàn)印象深刻。所以,還是選擇在DOM中注入的樣式。
這是體力活,你說體力活難不難呢?
最難的部分
安全是最難以面對的一個問題。之所以,很多標簽、腳本在頁面上失效,都是由于近年來瀏覽器越來越嚴格的安全策略。我在開發(fā)這個腳本時碰到了兩點:
- 在https網(wǎng)站頁面中無法加載http的資源。在調(diào)試工具中可以看到mixed content的字樣。
- 如果網(wǎng)站報頭中有CSP限制。調(diào)試工具中也能看到提示。
問題一,可以通過GM_xmlhttpRequest方法實現(xiàn)混合協(xié)議內(nèi)容,如果外部資源也支持https請求也行。當我開發(fā)發(fā)音功能時就發(fā)現(xiàn)有道的語音api可以用https訪問。
問題二,只能通過各種CORS技術(shù)實現(xiàn)(參見附錄)。我還沒開始做。但看到Stackoverflow上有個示例
你確定要通過打開
about:config禁用firefox對CSP的支持嗎?
不!!
通過GM_xmlhttpRequest完成異步請求,將數(shù)據(jù)用瀏覽器播放出來實現(xiàn)跨域資源引用。這樣,在一定程度上并不降低瀏覽器安全性,卻能夠?qū)崿F(xiàn)需求,完成功能。
Cheers!