使用 GreaseMonkey 修复网站
这里有一个奇怪的事情:黑客攻击开源不仅仅是在午夜,在空闲的房间里,弯腰在协议分析器、试验板、源代码控制和一些无助的设备上完成的。不,有时它完全在不同的熔炉中完成:一个无耻的姿态和自我推销的公共世界。一个充满标牌、商店、风格和事物的俗丽和霓虹灯居住地,其中充满了既有见识又平庸的对话的喋喋不休。这是一个充满欢乐和巨大焦虑的地方;一个保守主义高耸入云,激进声音的最后堡垒的地方。在其中,一个好的发型或激进的腿部线条可以让你达到符号调试器的程度,甚至可能更远。它们可能是设备,但与硬件设备完全不同。它的居民像咖啡成瘾和有灵感的经纪人的性格演员一样,在视野中高度活跃地进进出出。当然,我指的是万维网。
在本文中,您将学习如何以一种新的方式进行编码,这种方式是关于改变媒体,而不是改变程序。要进入这个夜总会并体验节奏,您需要合适的装备,而合适的装备是 Mozilla Firefox 和 GreaseMonkey。Alfred Bester 和 William Gibson 正在等待,所以准备好您的 Mojo 并准备好进入网络空间。但首先,介绍一点背景知识。
我们往往忘记了 Web 在某种程度上是开源的。Web 的一些基础设施、浏览器和服务器是传统的开源软件,但这个想法也适用于网页内容。代码的挪用是一种日常现象。每天,Web 开发人员和设计师都使用“查看源代码”浏览器功能从其他人的页面中挪用(行业术语:窃取)代码和设计。一直以来都是如此,并且仍然如此。想法和代码被自由且经常地共享;这是一种艺术设计感。
大多数技术人员都尝试过 Web 开发,而尝试是获得糟糕体验的一种简单方法。在 HTML、CSS 和 JavaScript 这三大技术诞生后的许多年里,它们都充满了错误。对于 20 世纪 90 年代首次尝试并厌恶地走开的早期采用者来说,这可能是挥之不去的体验。跨浏览器代码?不了,谢谢。
幸运的是,最近情况已经大大改善,Web 正在作为技术平台复兴。更好的标准支持、更多的标准支持以及对 Netscape Communication 4.x 和 Internet Explorer 5.0 等陈旧疑虑的减少,使 Web 开发人员几乎可以清晰地实现真正的可移植性,而这种可移植性仅仅受到曾经闪亮但现在相当生锈的 Internet Explorer 的阻碍。在 2005 年,热议的是现代 DHTML、无表格布局、语义标记和异步 JavaScript 和 XML (AJAX)。客户端 Web 开发正在回归,而这些是它的构成要素。这一次,Web 的背后是接受过正规 Web 培训的专业人士和拥有十年经验的资深人士。这些人已经齐心协力,现在可以谈论 Web 技术,而这些技术不再被不兼容问题的静态噪音所淹没。
支持和扩展这一趋势的是 Mozilla Firefox Web 浏览器以及一般的 Mozilla 技术。当然,Mozilla 是完全开源的,像宗教运动一样开放,因此有充足的实验空间。Mozilla 和 Firefox 的关键在于其解释性质。在一个大型、糟糕、联网的 C++ 渲染引擎之上,是一层薄薄的 JavaScript 脚本和 XUL(一种 XML 方言)。这使得 Mozilla 成为 Emacs 或 Tcl/Tk 的远亲,因为它通过解释代码提供了整个 Firefox 用户界面。通过编写扩展程序,您可以增强此用户界面并将其放入数千个愿意的人的日常体验中。访问 update.mozilla.org 以查看此系统实现的无限可能性。每个变体硬件设备都需要 Linux 内核驱动程序支持;每种变体人类对用户界面的期望都需要 Firefox 扩展程序。这有很多扩展程序。
GreaseMonkey 是一个 Firefox 扩展程序(请参阅在线资源)。您必须单击链接两次,一次信任 www.mozdev.org,另一次在之后安装扩展程序。GreaseMonkey 与其他扩展程序的不同之处在于,它除了配置对话框外,没有提供任何特定的用户界面增强功能。相反,它创建了一个类似宏的脚本环境,您可以在其中放置 JavaScript 脚本。这些脚本在您指定的网页上运行。当加载这样的页面时,您的脚本会开始处理页面内容,无论谁提供它。您正在拦截内容提供商的内容并在其到达您之前对其进行修改。难怪 GreaseMonkey 被称为“Web 版 TiVo”。我在 使用 Mozilla 进行快速应用程序开发(Prentice Hall,2004 年)中写了关于页面修改策略的文章,但 GreaseMonkey 通过支持传统的 Web 脚本技术并将所有内容打包成易于理解的产品,将这个想法推向了主流。
对于所有 Firefox 扩展程序,您必须完全重启 Firefox 才能完成安装。使用“文件”→“退出”安全地执行此操作。
有大量预先存在的 GreaseMonkey 脚本可用(请参阅资源)。不过,在您过于兴奋之前,请注意,此类脚本与一个 Firefox 安装绑定,并且对任何服务器都没有影响。在 Linux 或 UNIX 盒子上,此类脚本可能会影响大量用户,但它们主要是个人用品。对于那些关注人际关系问题的读者来说,更广泛的含义应该是显而易见的。
为了了解这一切是如何工作的,在本文中,我使用 GreaseMonkey 黑客攻击了 Linux Journal 网站。我尊敬的编辑 Don Marti 甚至要求我这样做。真是一个勇敢的人。[也许下次他们会邀请我参加网站会议。—编者]
给我一座山,我就爬上去。首先是对即将进行手术的网站进行一些审查。如果您正在阅读印刷版,请记住它是 www.linuxjournal.com。这也是有趣的部分;个人品味各不相同,对于用户端驱动程序(实际上就是 GreaseMonkey 脚本),挑剔和主观是完全有效和专业的。在 Mozilla 的世界里,“dogfood”意味着测试您修复的错误的技术正确性,而 “catfood” 意味着针对不可靠和主观的人测试您的发明,这些人可能会朝任何方向发展。这里全是 “catfood”,没有对错之分。在阅读本文后,LJ 长期的网站维护人员可能会恶狠狠地瞪着我,或者在本文付印之前更改网站。您看到了设计理念。对不起,伙计,他们让我做的。硬核工程师应该别过头去;您可能会发现这种分析令人痛苦。接下来是网站。
以下是一些观察结果。
站点图标,如果您使用选项卡式浏览,它会出现在位置栏和当前选项卡上,很小且缺乏灵感。好吧。
到处都是广告。
Linux Journal 应该是在开源技术期刊中的老大哥,不包括学术界和专业机构。哪里表明了这一点?
标题是红色的。红色是怎么回事?我不着急。
从好的方面来说——我作为评论家的生存岌岌可危——该网站具有稳健的三列布局,并且整体上很干净。有人懂行。查看源代码,布局全部使用 CSS 完成,所以这相对来说比较现代;许多行业网站仍然会输出您能想象到的最糟糕的 HTML。过度使用 <DIV> 标签表明 LJ 的现代化进程已过半;仍然有一些语义标记工作要做,其中有意义的标签用作内容描述符,而不是无意义的 <DIV>。据称,该更新可能会提高网站的搜索引擎性能。
现在,在上述个人观察中,有些很容易纠正,并且不需要 GreaseMonkey。如果您不喜欢广告,那么 AdBlock 扩展程序适合您;没有什么,或者至少很少,需要编码。同样,长期以来,所有浏览器都支持用户指定的样式表。如果您安装了 ChromEdit 扩展程序,您就可以访问该样式表,而无需在文件系统中搜索它。通过“工具”→“编辑用户文件”将其调出,单击 userContent.css 选项卡并开始键入。要使标题变为蓝色,您可以添加
h1.title a { color : blue !important; } @-moz-document domain(linuxjournal.com) { h1.title a { color : blue !important; } }
第一条规则适用于所有网站;第二条是 Mozilla 特有的,仅适用于指定的网站。特定于浏览器在这里是可以的,因为我们完全在客户端工作。
您可以在这些样式表中完成很多工作,特别是如果您精通 CSS。您可以通过重新排序、隐藏或浮动列和其他内容来将页面的布局分解成碎片。所有这些选项都可以通过 GreaseMonkey 实现,但 GreaseMonkey 更适合处理更大的事情。换句话说,如果页面更改很容易通过样式表解决,则不要使用 GreaseMonkey;这有点杀鸡用牛刀。
对于本文,我们将做一个简单的更改。我们将通过用从另一个网站提取的精美书法替换一些内容,为网站带来一些庄重感。
CSS 的 :first-letter 伪选择器允许您采用普通的文本段落并将首字母放大,以便几行文本环绕它流动。这是一个自我重要的功能,也是我们正在寻找的。我们可以简单地应用该功能,但大多数计算机都没有安装精美的中世纪字体。而且,一个大的 Times Roman 字母 F 并没有那么令人兴奋。如果我们能像 凯尔经 一样,使用额外的精美书法,让 LinuxJournal.com 网站得到适当的装饰,那就更好了。
以下是一些屏幕截图,显示了在 Windows XP Professional 上拍摄的前后外观。这是一个及时的提醒,用户体验才是这里最重要的。它还强调,当用 Mozilla 术语表达时,开源意味着跨平台。本文中描述的所有内容在 Windows、Macintosh、Linux 和各种晦涩的 Mozilla 平台(如 Solaris)上都能完全相同地工作。
在第二个屏幕截图中,您可以看到每个段落的首字母都已替换为精美的装饰字母。因为我无法访问 LJ 网站的后端,所以这是一项壮举。事实上,这些图像来自澳大利亚国立大学的中世纪研究图像服务器。
我只使用了缩略图。提供其他网站的一些图像有点调皮,而且这些图像不是完美裁剪的、注册的扫描件,但为了说明一种技术,它们就足够了。让我们希望一些吝啬的老家伙在您阅读本文时不会将其删除。
要使这种修饰生效,您需要一个 GreaseMonkey JavaScript 脚本。要制作这样的脚本,请像对待任何其他网页项目一样进行操作。我将 LJ 主页保存到本地磁盘,然后将其添加到 <head> 部分的末尾
<script src="illuminate.js></script>
现在我可以自由地用纯 Mozilla JavaScript 开发该脚本,而没有跨浏览器约束,因为 GreaseMonkey 仅在 Firefox 上工作。让我告诉您,在 JavaScript 中勇往直前,而无需一次又一次地绊倒 document.all 或其他 MSIE 异常,这真是 200% 纯粹的乐趣。不仅如此,我们还可以为代码带来一些现在已建立的严谨性。以下是手头工作的框架,以 JS 对象签名的形式,这是我喜欢传播的语法教条的一部分
var illuminate = { caps : { ... }, load : function () { ... }, image : function (text) { ... }, mask : function () { ... }, insert : function () { ... }, getElements : function (node) { ... }, getElementsRecursive : function (l, n) { ... }, };
JavaScript 中没有静态类型,几乎没有任何前向声明,因此无法声明对象。相反,我们使用对象字面量。这种方法创建了一组打包的功能,这些功能仅将 illuminate 选项公开给页面的命名空间。因此,它既是一种重用策略,也是一种命名空间非污染策略。对象的所有方法都表示为匿名函数,而 caps 是一个子对象,我们在其中放入数据。匿名函数还可以避免您将函数名称强制放入页面的命名空间中。与早期的脚本技术相比,这是一个巨大的进步。
一旦定义,此对象什么也不做。您需要这样一行代码才能使其运行
window.onload = function () { illuminate.load(); }
这会导致 load() 方法在页面加载完成后运行。包装它的匿名函数提供了一个额外的作用域,使 illuminate 成为当前对象。这样做,引用指向 illuminate 对象,这意味着可以从对象的方法内部利用它。这避免了对象必须使用 illuminate 变量名——更多的命名空间非污染。
清单 1 显示了此对象的完整实现,因此让我们来了解一下。您还可以从 Linux Journal FTP 站点获取完整的脚本。
清单 1. 装饰当前对象
var illuminate = { caps : { "a" : "102.PNG", "b" : "103.PNG", "c" : "104.PNG", "d" : "105.PNG", "e" : "106.PNG", "f" : "107.PNG", "g" : "108.PNG", "h" : "109.PNG", "i" : "110.PNG", "j" : null, "k" : "111.PNG", "l" : "112.PNG", "m" : "113.PNG", "n" : "114.PNG", "o" : "115.PNG", "p" : "116.PNG", "q" : "117.PNG", "r" : "118.PNG", "s" : "119.PNG", "t" : "120.PNG", "u" : null, "v" : "121.PNG", "w" : null, "x" : "122.PNG", "y" : null, "z" : "123.PNG" }, load : function () { this.mask(); this.insert(); }, image : function(text) { var a = text.substring(0,1).toLowerCase(); var link = ""; if (a && this.caps[a]) { link = 'http://rubens.anu.edu.au/htdocs/' + 'bytype/prints/ornament/0001/' + this.caps[a]; } return link; }, mask : function () { var head = document.getElementsByTagName( 'head')[0]; var rules = document.createElement('style'); var text = document.createTextNode( 'div.node > div.content > img[ill] : ' + '{ display: inline; float:left; }\n' + 'div.node > div.links : ' + '{ clear : left; }\n' + 'img[ill] : { display : none; }\n' ); rules.appendChild(text); head.appendChild(rules); }, insert : function () { var list = this.getElements(window.document); var img; var text; for (var i=0; i<list.length; i++) { text = list[i].firstChild; if (text.nodeType == 3 ) { img = document.createElement('img'); img.setAttribute('ill','true'); img.setAttribute('width','64px'); img.setAttribute('height','64px'); img.setAttribute('src', this.image(text.nodeValue)); text.nodeValue = text.nodeValue.substring(1); list[i].insertBefore(img, text); } } }, getElements : function (node) { var rv = []; this.getElementsRecursive(rv, node); return rv; }, getElementsRecursive : function (list, node) { for (var i=node.childNodes.length-1;i>=0;i--) { var child = node.childNodes.item(i); var klass = null; if ( child.nodeType == 1) { klass = child.getAttribute("class"); if ( klass && klass == "content") { list.push(child); } this.getElementsRecursive(list, child); } } } }
caps 关联数组指向 ANU 网站上提供的各个字母。由于它们来自中世纪,因此 J、U、W 和 Y 等新式字母无处可见。我们现在只能不用这些字母,并且还要抵制在圣经中将 I 用作 J 的冲动。无论如何,字母表都在不断变化,尽管变化缓慢。我听到的说法是,如果无线电真实地反映了街头俚语,那么 Double-u 将是下一个发生变化的。显然,它正在被一个名为 Dub 的相同字母替换,例如:“转到 Dub Dub Dub 点卖给您一些东西点 com”。随便你怎么想这种可鄙的趋势。但我要离题了。
load() 方法完成所有工作。它调用 mask(),该方法在当前页面的头部插入一个 <style> 标签作为最后一件事。仔细研究精心设计的 LJ 主页,可以创建像额外的拼图碎片一样适合现有布局方案的样式。第一个样式作用于新的装饰字母,允许文本围绕它们流动
div.node > div.content > img[ill] { display : inline; float : left; }
下一个样式停止浮动效果,以便下一个新闻项目也不会围绕它流动
div.node > div.links { clear : left; }
这些都是标准的 CSS2 内容。最后,其余的 JavaScript 代码在其页面黑客攻击中有点过于热情,您将会看到。因此,这里有一种样式可以关闭意外的额外内容
img[ill] : { display : none; }
在所有情况下,ill 位只是添加的一个自定义标签属性,用于标识特定于此脚本的图像,以便可以使用样式规则轻松地将它们挑选出来。
load() 方法做的第二件事是调用 insert() 方法,该方法将 <img> 标签添加到页面的主要内容中。对于那些只涉足客户端脚本编写(也许是一两个 onclick 处理程序)的人来说,这看起来相当强大,但对于高质量的客户端脚本编写来说,这只是相当例行的工作。
insert() 方法使用现在得到强大支持的 DOM API 获取页面中重要节点的列表。然后,它使用循环遍历该列表,添加如下 <img> 标签
<img ill="true" width="64px" height="64px" src="">
这被添加到找到的每个以 Text 节点作为其第一个子节点的节点。这相当于向紧靠 <div class=“node”> 标签内部的任何 <div class=“content”> 标签添加一个新的子节点。这是对页面结构的重大假设。此外,该组合还有许多不需要的示例,例如,在右侧的广告列和文章列表之外的杂项内容中。这就是为什么我不得不使用额外的样式来关闭一些图像——插入了太多图像。尽管如此,使用大笔刷可以保持代码简单。
在开发此功能时,我还注意到,在主页的某个实例中,有人在一个文章的导语中添加了额外的 <p> 标签。导语是吸引读者阅读完整文章内容的引言。这只是一个简单的错别字或某些编辑的随机创新行为。对于该文章,其布局与其余文章略有不同,脚本未能执行任何操作。至少这比生成错误或异常并停止要好。但这确实表明,如果一个人不够谨慎,并且忽略了优雅降级的问题(在这种情况下,如果事情变得糟糕,脚本会消失为 NO-OP),那么 GreaseMonkey 脚本可能会多么脆弱。对页面预期结构做出的任何假设都应尽可能通用和灵活。谨慎行事。
回到代码中,insert() 还使用标准的 JavaScript 字符串操作来删除导语文本的第一个字符。因此,一个纯文本字符消失了,添加了一个字符的图像。在 Web 和 Unicode 之间,如果不小心说出 “character” 这个词,就会立即冲走潜伏在木制品中的所有词汇学书呆子。让他们来吧,我说。
对象的其余部分是 insert() 方法利用的一些例行处理。image() 函数是更简单的实用程序:它只是在 caps 对象上执行字典查找,该对象实际上是一个关联数组。JavaScript 允许将文字字符串用作数组索引和对象成员名称。检索到的文件名被连接成一个完整的 URL 并返回。这是简单的数据驱动编程。
另一个实用程序是剩下的两个方法,getElements() 和 getElementsRecursive()。它们实现了一个标准的 prefix 树遍历算法,该算法作用于页面的整个 DOM,并包装在 getElements() 的更简洁的外观中。它们是页面扫描例程,并且不是非常通用,因为内部有特定于 Linux Journal 内容的逻辑测试。应该有人编写一组类似于 qsort(3) 的导航例程,以便可以简单地插入一两个比较器仿函数。可能已经有人这样做了,但我没有为本文找到这样的东西。
当遍历 DOM 树时,任何 <div class=“content”> 节点都会附加到已发现节点的列表中。没有复制工作;它都是通过引用节点完成的。遍历整个 DOM 树有点雄心勃勃。对于更集中的 GreaseMonkey 黑客攻击,更有效的方法是直接转到有问题的页面元素,也许可以使用 document.getElementById() 调用。但是,当您不确定页面的确切结构时,最好在最少假设的情况下盲目地浏览所有内容。您进行得有多直接仅仅取决于您要寻找什么样的杠杆作用。
现在脚本已经开发完成,需要做的就是将其配置到 GreaseMonkey 扩展程序中。回想一下,到目前为止,它是在静态和本地保存的测试页面上开发的。坦率地说,配置任务有点奇怪,至少在 GreaseMonkey 0.3.3 中是这样。
要使其到位,请确保脚本命名为 illuminate.user.js。接下来,使用安装了 GreaseMonkey 的 Firefox,导航到脚本所在的本地目录。在 Linux 上,它类似于
file:///home/nrm/
在 Windows 上,它可能以开头
file:///C|/Documents%20and%20Settings/nrm/Desktop/
请注意三个正斜杠。文件 URI 方案类似于 NFS 或 SMB,并且在理论上可以检索位于任何位置的文件,例如
file://www.example.com/something.txt
省略域,默认值为 localhost,这通常是您想要的。
一旦出现该目录列表,您应该会看到 illuminate.user.js 文件的链接。右键单击它(在 Mac OS X 上为上下文单击),神奇的选项就会显示出来:“安装用户脚本 ...”。选择它,因为在“工具”菜单上摆弄 GreaseMonkey 选项无法给您带来同样的乐趣。接下来会出现 GreaseMonkey 配置对话框,新脚本位于左侧。单击右侧的“添加”,然后键入 Linux Journal URL,如下所示
https://linuxjournal.cn/*
单击“确定”,脚本即已安装。现在可以通过“工具”菜单访问它以进行后续管理。重新加载 LJ 页面,一切都应该正常工作,装饰首字下沉也已到位。如果不是,则需要打开 JavaScript 控制台并返回到脚本调试,使用 1.0.4 和 GreaseMonkey 0.3.3 进行测试。
装饰首字下沉的故事就这样讲完了,这是一个内容聚合的故事。当然,这只是一个微不足道的例子。您不仅限于修补单个、肮脏的 <img> 标签,也不必对现有页面内容过于乐观。GreaseMonkey 脚本可以将页面内容分解成碎片,并且您可以将任意数量的额外内容从任何来源粘贴到页面中。令人敬畏的 XMLHttpRequest 对象可用于此类脚本,并且可以用于加载世界上任何可通过 HTTP 访问的内容——然后可以将这些内容放入当前页面中。您还可以使用此对象将当前页面的片段发送到其他地方,但那是另一篇文章的内容了。在这里,我尝试在页面上优雅地添加内容。
现在您可能会说,“这是一个愚蠢的练习,除了我之外没有人会看到我的作品。” 然而,这仅仅是一个分发问题,可以通过许多不同的 IT 部署技术来解决,其中最重要的是超文本链接。
这种增强功能也并非那么愚蠢。想象一下您最喜欢的网络设备(可能是路由器)的 Web 界面。如果开源且支持 Web 的 Big Brother LAN 监控应用程序的主机状态指示灯出现在该路由器提供的网页中过滤规则中匹配主机的 IP 地址旁边,那不是很好吗?至少这样您就不会尝试修复根本没有运行的盒子的路由。您会在路由器配置页面中的条目旁边看到一个大的红灯。GreaseMonkey 正是解决此类问题的正确工具,尤其是在没有人可以访问路由器嵌入式 Web 服务器生成的源页面的情况下。
此外,许多网页都是繁忙的地方,充满了导航小部件和数据输入字段。GreaseMonkey 脚本可以将所有这些内容分解成碎片,删除或添加页面元素,从而简化用户的个人浏览行为。不喜欢顶部的菜单栏?隐藏它。不记得如何填写该表格?添加一些浮动在其上方的提醒文本。您明白了。
最后,由于 GreaseMonkey 是基于内容的,因此与其他内容媒体的类比值得考虑。如果存在热门唱片和热门电影,那么毫无疑问,热门 GreaseMonkey 脚本也会在适当的时候出现。它相对于其黑客攻击的网站的政治立场将开创一个非常有趣的先例。它会是一个抗议、解构、涂鸦、支持或谴责相关网站的脚本吗?只有时间会证明。与此同时,GreaseMonkey 是一个用于更改其他难以更改的 Web 内容的便捷工具。
本文资源: /article/8458。
Nigel McFarlane (www.nigelmcfarlane.com) 是 Mozilla 社区的定期和不定期的技术评论员,专注于教育、分析和一些范围狭窄的错误。Nigel 是 Firefox Hacks (O'Reilly Media) 和 使用 Mozilla 进行快速应用程序开发 (Prentice Hall PTR) 的作者。