這兩天统抬,寫了一個(gè)簡單的基于有道在線翻譯的GreaseMonkey屏幕取詞腳本。
我想做這件事很久了危队,從我還不是一個(gè)前端開發(fā)者的時(shí)候聪建,就一直想做這么一個(gè)輕量的瀏覽器腳本,方便自己查看英文的文檔和文章茫陆。沒想到想了這么久金麸,真正沒做多久。
作為一個(gè)Ubuntu Linux用戶簿盅,瀏覽器取詞我有幾個(gè)選擇:
嘗試安裝有道詞典Linux版本挥下、openyoudao或者其他stardict或者goldendict這種本地詞典揍魂。但我并不覺得我需要桌面軟件。
有人做了個(gè)Google translate tooltip的GreaseMonkey腳本實(shí)現(xiàn)這個(gè)棚瘟,非常棒。但谷歌的服務(wù)在國內(nèi)的服務(wù)非常不穩(wěn)定解取,取詞功能經(jīng)常不能正常使用。
有道提供了網(wǎng)頁翻譯2.0返顺,通過書簽執(zhí)行一段代碼把取詞功能注入當(dāng)前頁面。然而慧邮,首先隨著瀏覽器安全特性的加強(qiáng),該書簽不能正常使用倡缠,其次每次都要先點(diǎn)書簽才能取詞(也許是快捷鍵)载荔。
選擇是難
很多網(wǎng)站,包括cnblog發(fā)現(xiàn)都提供了取詞版本定庵。我面臨的選擇是:
- 在這些已有的瀏覽器取詞腳本基礎(chǔ)上學(xué)習(xí)修改蓝仲。
- 憑借著自己的感覺從新設(shè)計(jì)
選擇上花了很多時(shí)間而晒。
方案一的優(yōu)點(diǎn)有:
- 成熟美觀诈胜。
- 能學(xué)習(xí)到很多東西
方案一問題在于:
- 源碼難理解摔笤。代碼量較大够滑,都是壓縮甚至混淆變量過的。
- 有些和當(dāng)前頁面的樣式或者腳本攪和在一起吕世。不易分離
- 被瀏覽器或網(wǎng)站安全設(shè)置廢掉彰触,未必能使用
終于,由于我的智商被有道在線翻譯那個(gè)腳本所碾壓命辖,我想還是看看功能自己設(shè)計(jì)下况毅,做個(gè)簡單版本分蓖。
想的很簡單
設(shè)計(jì)是易
想法很簡單。
- 鼠標(biāo)選詞
- 向第三方發(fā)起請求尔许,比如bing的翻譯或者有道的
- 讀取返回么鹤,彈出tooltip,格式化數(shù)據(jù)
- 其他輔助功能比如發(fā)音味廊、單詞本等等
設(shè)計(jì)是最簡單的一環(huán)蒸甜,后面你會看到時(shí)間都花到哪里了。
知易行難
通過谷歌余佛,很容易完成第一步迅皇,在腳本中得到選中的文字。
第二步就開始面臨問題衙熔。作為前信息安全專業(yè)從業(yè)者,很清楚ajax這種東西跨域是受限制的搅荞。稍微翻閱scriptish文檔發(fā)現(xiàn)GM_xmlhttpRequest可以滿足我的需求红氯。
除卻和 XMLHttpRequest
這種東西并不太一樣的api造成的各種細(xì)節(jié)錯(cuò)誤,之后碰到的問題是我整個(gè)開發(fā)過程最棘手咕痛、花費(fèi)時(shí)間最長的問題痢甘。
無論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)這個(gè)跨域函數(shù)也沒什么問題。
折騰一陣愉粤,確認(rèn)API調(diào)用和細(xì)節(jié)都無法確認(rèn)問題后砾医,采取曲線調(diào)試方案。
更改請求地址到本地衣厘,確認(rèn)請求確實(shí)發(fā)出了如蚜。那么,它有返回嗎影暴?
在本地用netcat模擬返回?cái)?shù)據(jù)错邦,仍然沒有打印任何信息。我開始懷疑難道GM_xmlhttpRequest是會對返回結(jié)果做驗(yàn)證型宙?必須報(bào)頭正確撬呢?
第一天就這么過去了。
第二天我決定嘗試代理來看來往的通信是否正常妆兑。
方便起見倾芝,先用nc充當(dāng)了下代理讨勤,檢查了下相互通信,未見有什么不對的晨另。
為嚴(yán)謹(jǐn)起見潭千,用burpsuite來設(shè)置一個(gè)透明本地代理,讓瀏覽器指向那個(gè)代理借尿。經(jīng)過檢驗(yàn)刨晴,完全沒看出通信有什么問題。但onload和其他回調(diào)也不會被觸發(fā)路翻。
谷歌搜索得到一些stackoverflow狈癞、github issue和greasewiki上的信息,但問題仍不能確認(rèn)和解決茂契。
只是昨天晚上baidu時(shí)心心念念蝶桶,發(fā)現(xiàn)firefox貼吧里有人吐槽scriptish不穩(wěn)定的一些地方,今天又看到一些討論掉冶,決定換回GreaseMonkey試試真竖,事實(shí)證明這是明智的。
然而厌小,一換發(fā)現(xiàn)什么都打印不出來了恢共。后來反復(fù)嘗試,發(fā)現(xiàn)GM_log不能用璧亚,我簡直震驚了讨韭,wiki上寫著玩的么,還是有什么變化癣蟋。反正我發(fā)現(xiàn)console.log可以使用透硝,那就繼續(xù)開發(fā)下去了。
最難的部分就這么糊里糊涂過去了疯搅。
數(shù)據(jù)請求順風(fēng)順?biāo)?/h2>
一旦請求完成蹬铺,解析json數(shù)據(jù),按需展示就是水到渠成的事情秉撇。
然而甜攀,并不是那么簡單。
JS異步與回調(diào)之難
JS的異步特性帶來了這些不符合人類直觀思維方式的流程控制風(fēng)格琐馆。
按理說我應(yīng)該很習(xí)慣javascript的異步操作流程控制的種種問題规阀,但還是踩了次坑。
彈出和渲染tooltip的函數(shù)沒有讀到返回?cái)?shù)據(jù)瘦麸!
好在對javascript程序員debug這種問題比之前的問題簡單太多谁撼。一看想起來GM_xmlhttpRequest是異步過程,而不是同步,我這里卻要待異步過程返回結(jié)果再執(zhí)行下一個(gè)函數(shù)厉碟。
想想promise應(yīng)該不用喊巍,雖然firefox41肯定原生支持ES6 promise了。但箍鼓,就這點(diǎn)函數(shù)干脆崭参。。款咖。還是回調(diào)“地獄”吧何暮。
JS難中有易
說到ES6,ES6提供了很多方便javascript編程的好東西铐殃,通過let
和=>
實(shí)現(xiàn)更好的this和作用域一致海洼,通過Template
方便字符串操作等等。
很慶幸富腊,GreaseMonkey的話我只考慮firefox用戶坏逢,反正好早的時(shí)候這些ES6特性瀏覽器都支持了。
JS易中又難
JS讓人非常難過的一個(gè)地方赘被,是DOM操作和各種webAPI是整。只能說喪心病狂。你記得清楚如何獲得viewport區(qū)域大小么帘腹?知道如何獲得鼠標(biāo)相對viewport位置么?知道為啥獲取區(qū)域高度或?qū)挾炔]有獲得么许饿?看到clientWidth阳欲、offsetWidth、availWidth...有沒有想砍人陋率?
為了讓腳本能正確在屏幕邊緣讓tooltip出現(xiàn)在viewport內(nèi)球化,在各種邊界條件數(shù)學(xué)計(jì)算題這里又糾結(jié)了好久。
GreaseMonkey相比Scriptish少了一個(gè)比較方便的特性: @css
瓦糟。雖然可以在head標(biāo)簽中通過GM_addStyle()
來注入樣式筒愚,我總覺得會不合時(shí)宜的覆蓋不該覆蓋的東西,我對Google Translate Tooltip在阮一峰大大的網(wǎng)站上奇葩的樣式表現(xiàn)印象深刻菩浙。所以巢掺,還是選擇在DOM中注入的樣式。
這是體力活劲蜻,你說體力活難不難呢陆淀?
最難的部分
安全是最難以面對的一個(gè)問題。之所以先嬉,很多標(biāo)簽轧苫、腳本在頁面上失效,都是由于近年來瀏覽器越來越嚴(yán)格的安全策略疫蔓。我在開發(fā)這個(gè)腳本時(shí)碰到了兩點(diǎn):
- 在https網(wǎng)站頁面中無法加載http的資源含懊。在調(diào)試工具中可以看到mixed content的字樣身冬。
- 如果網(wǎng)站報(bào)頭中有CSP限制。調(diào)試工具中也能看到提示岔乔。
問題一酥筝,可以通過GM_xmlhttpRequest方法實(shí)現(xiàn)混合協(xié)議內(nèi)容,如果外部資源也支持https請求也行重罪。當(dāng)我開發(fā)發(fā)音功能時(shí)就發(fā)現(xiàn)有道的語音api可以用https訪問樱哼。
問題二,只能通過各種CORS技術(shù)實(shí)現(xiàn)(參見附錄)剿配。我還沒開始做搅幅。但看到Stackoverflow上有個(gè)示例
你確定要通過打開
about:config
禁用firefox對CSP的支持嗎?
不:襞摺茄唐!
通過GM_xmlhttpRequest
完成異步請求,將數(shù)據(jù)用瀏覽器播放出來實(shí)現(xiàn)跨域資源引用蝇更。這樣沪编,在一定程度上并不降低瀏覽器安全性,卻能夠?qū)崿F(xiàn)需求年扩,完成功能蚁廓。
Cheers!