这两天,写了一个简单的基于有道在线翻译的GreaseMonkey屏幕取词脚本。
点我查看GreaseFork
我想做这件事很久了,从我还不是一个前端开发者的时候,就一直想做这么一个轻量的浏览器脚本,方便自己查看英文的文档和文章。没想到想了这么久,真正没做多久。
作为一个Ubuntu Linux用户,浏览器取词我有几个选择:
尝试安装有道词典Linux版本、openyoudao或者其他stardict或者goldendict这种本地词典。但我并不觉得我需要桌面软件。
有人做了个Google translate tooltip的GreaseMonkey脚本实现这个,非常棒。但谷歌的服务在国内的服务非常不稳定,取词功能经常不能正常使用。
有道提供了网页翻译2.0,通过书签执行一段代码把取词功能注入当前页面。然而,首先随着浏览器安全特性的加强,该书签不能正常使用,其次每次都要先点书签才能取词(也许是快捷键)。
选择是难
很多网站,包括cnblog发现都提供了取词版本。我面临的选择是:
- 在这些已有的浏览器取词脚本基础上学习修改。
- 凭借着自己的感觉从新设计
选择上花了很多时间。
方案一的优点有:
- 成熟美观。
- 能学习到很多东西
方案一问题在于:
- 源码难理解。代码量较大,都是压缩甚至混淆变量过的。
- 有些和当前页面的样式或者脚本搅和在一起。不易分离
- 被浏览器或网站安全设置废掉,未必能使用
终于,由于我的智商被有道在线翻译那个脚本所碾压,我想还是看看功能自己设计下,做个简单版本。
想的很简单
设计是易
想法很简单。
- 鼠标选词
- 向第三方发起请求,比如bing的翻译或者有道的
- 读取返回,弹出tooltip,格式化数据
- 其他辅助功能比如发音、单词本等等
设计是最简单的一环,后面你会看到时间都花到哪里了。
知易行难
通过谷歌,很容易完成第一步,在脚本中得到选中的文字。
第二步就开始面临问题。作为前信息安全专业从业者,很清楚ajax这种东西跨域是受限制的。稍微翻阅scriptish文档发现GM_xmlhttpRequest可以满足我的需求。
除却和 XMLHttpRequest
这种东西并不太一样的api造成的各种细节错误,之后碰到的问题是我整个开发过程最棘手、花费时间最长的问题。
无论onload、onerror还是onreadystate的回调中,GM_log
都没有打印出任何信息。
firebug和火狐内置调试器也没有显示任何通信。这和我在网络上看到的GreaseMonkey相关信息并不太相符。
经检查脚本元数据@grant
,觉得已经授权这个跨域函数也没什么问题。
折腾一阵,确认API调用和细节都无法确认问题后,采取曲线调试方案。
更改请求地址到本地,确认请求确实发出了。那么,它有返回吗?
在本地用netcat模拟返回数据,仍然没有打印任何信息。我开始怀疑难道GM_xmlhttpRequest是会对返回结果做验证?必须报头正确?
第一天就这么过去了。
第二天我决定尝试代理来看来往的通信是否正常。
方便起见,先用nc充当了下代理,检查了下相互通信,未见有什么不对的。
为严谨起见,用burpsuite来设置一个透明本地代理,让浏览器指向那个代理。经过检验,完全没看出通信有什么问题。但onload和其他回调也不会被触发。
谷歌搜索得到一些stackoverflow、github issue和greasewiki上的信息,但问题仍不能确认和解决。
只是昨天晚上baidu时心心念念,发现firefox贴吧里有人吐槽scriptish不稳定的一些地方,今天又看到一些讨论,决定换回GreaseMonkey试试,事实证明这是明智的。
然而,一换发现什么都打印不出来了。后来反复尝试,发现GM_log不能用,我简直震惊了,wiki上写着玩的么,还是有什么变化。反正我发现console.log可以使用,那就继续开发下去了。
最难的部分就这么糊里糊涂过去了。
数据请求顺风顺水
一旦请求完成,解析json数据,按需展示就是水到渠成的事情。
然而,并不是那么简单。
JS异步与回调之难
JS的异步特性带来了这些不符合人类直观思维方式的流程控制风格。
按理说我应该很习惯javascript的异步操作流程控制的种种问题,但还是踩了次坑。
弹出和渲染tooltip的函数没有读到返回数据!
好在对javascript程序员debug这种问题比之前的问题简单太多。一看想起来GM_xmlhttpRequest是异步过程,而不是同步,我这里却要待异步过程返回结果再执行下一个函数。
想想promise应该不用,虽然firefox41肯定原生支持ES6 promise了。但,就这点函数干脆。。。还是回调“地狱”吧。
JS难中有易
说到ES6,ES6提供了很多方便javascript编程的好东西,通过let
和=>
实现更好的this和作用域一致,通过Template
方便字符串操作等等。
很庆幸,GreaseMonkey的话我只考虑firefox用户,反正好早的时候这些ES6特性浏览器都支持了。
JS易中又难
JS让人非常难过的一个地方,是DOM操作和各种webAPI。只能说丧心病狂。你记得清楚如何获得viewport区域大小么?知道如何获得鼠标相对viewport位置么?知道为啥获取区域高度或宽度并没有获得么?看到clientWidth、offsetWidth、availWidth...有没有想砍人?
为了让脚本能正确在屏幕边缘让tooltip出现在viewport内,在各种边界条件数学计算题这里又纠结了好久。
GreaseMonkey相比Scriptish少了一个比较方便的特性: @css
。虽然可以在head标签中通过GM_addStyle()
来注入样式,我总觉得会不合时宜的覆盖不该覆盖的东西,我对Google Translate Tooltip在阮一峰大大的网站上奇葩的样式表现印象深刻。所以,还是选择在DOM中注入的样式。
这是体力活,你说体力活难不难呢?
最难的部分
安全是最难以面对的一个问题。之所以,很多标签、脚本在页面上失效,都是由于近年来浏览器越来越严格的安全策略。我在开发这个脚本时碰到了两点:
- 在https网站页面中无法加载http的资源。在调试工具中可以看到mixed content的字样。
- 如果网站报头中有CSP限制。调试工具中也能看到提示。
问题一,可以通过GM_xmlhttpRequest方法实现混合协议内容,如果外部资源也支持https请求也行。当我开发发音功能时就发现有道的语音api可以用https访问。
问题二,只能通过各种CORS技术实现(参见附录)。我还没开始做。但看到Stackoverflow上有个示例
你确定要通过打开
about:config
禁用firefox对CSP的支持吗?
不!!
通过GM_xmlhttpRequest
完成异步请求,将数据用浏览器播放出来实现跨域资源引用。这样,在一定程度上并不降低浏览器安全性,却能够实现需求,完成功能。
Cheers!
附录
- https://bugzilla.mozilla.org/show_bug.cgi?id=866522
- https://github.com/greasemonkey/greasemonkey/issues/2046
- http://forums.mozillazine.org/viewtopic.php?f=38&t=2958293
- http://stackoverflow.com/questions/28554022/how-can-i-play-sound-with-a-greasemonkey-script-in-firefox-when-theres-a-conte