IT博客汇
  • 首页
  • 精华
  • 技术
  • 设计
  • 资讯
  • 扯淡
  • 权利声明
  • 登录 注册

    GreaseMonkey Userscript 开发的难与易

    Liu Yuyang发表于 2015-11-17 14:55:28
    love 0

    这两天,写了一个简单的基于有道在线翻译的GreaseMonkey屏幕取词脚本。

    点我查看GreaseFork

    youdaodict-1.gif

    我想做这件事很久了,从我还不是一个前端开发者的时候,就一直想做这么一个轻量的浏览器脚本,方便自己查看英文的文档和文章。没想到想了这么久,真正没做多久。

    作为一个Ubuntu Linux用户,浏览器取词我有几个选择:

    1. 尝试安装有道词典Linux版本、openyoudao或者其他stardict或者goldendict这种本地词典。但我并不觉得我需要桌面软件。

    2. 有人做了个Google translate tooltip的GreaseMonkey脚本实现这个,非常棒。但谷歌的服务在国内的服务非常不稳定,取词功能经常不能正常使用。

    3. 有道提供了网页翻译2.0,通过书签执行一段代码把取词功能注入当前页面。然而,首先随着浏览器安全特性的加强,该书签不能正常使用,其次每次都要先点书签才能取词(也许是快捷键)。

    选择是难

    很多网站,包括cnblog发现都提供了取词版本。我面临的选择是:

    1. 在这些已有的浏览器取词脚本基础上学习修改。
    2. 凭借着自己的感觉从新设计

    选择上花了很多时间。

    方案一的优点有:

    1. 成熟美观。
    2. 能学习到很多东西

    方案一问题在于:

    1. 源码难理解。代码量较大,都是压缩甚至混淆变量过的。
    2. 有些和当前页面的样式或者脚本搅和在一起。不易分离
    3. 被浏览器或网站安全设置废掉,未必能使用

    终于,由于我的智商被有道在线翻译那个脚本所碾压,我想还是看看功能自己设计下,做个简单版本。

    想的很简单

    设计是易

    想法很简单。

    1. 鼠标选词
    2. 向第三方发起请求,比如bing的翻译或者有道的
    3. 读取返回,弹出tooltip,格式化数据
    4. 其他辅助功能比如发音、单词本等等

    设计是最简单的一环,后面你会看到时间都花到哪里了。

    知易行难

    通过谷歌,很容易完成第一步,在脚本中得到选中的文字。

    第二步就开始面临问题。作为前信息安全专业从业者,很清楚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中注入的样式。

    这是体力活,你说体力活难不难呢?

    最难的部分

    安全是最难以面对的一个问题。之所以,很多标签、脚本在页面上失效,都是由于近年来浏览器越来越严格的安全策略。我在开发这个脚本时碰到了两点:

    1. 在https网站页面中无法加载http的资源。在调试工具中可以看到mixed content的字样。
    2. 如果网站报头中有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


沪ICP备19023445号-2号
友情链接