深入浅出 Greasemonkey

这本书、及其样例代码和视频文件都是自由软件。在“GNU 通用公共许可证(自由软件基金会)(版本2以及更新版本)”许可下,您可以随意的再分发和/或修改它们。我们发行这本书、及其样例代码和视频文件,希望它能对您有所帮助。但是我们并没有提供任何担保!请查阅GNU 通用公共许可证获取更多细节。

目录

第 1 章 开始

1.1. Greasemonkey 是什么?

Greasemonkey 是一个 Firefox 扩展,它具有通过编写脚本来改变被访问网页的功能。使用它,能使您访问的网站更便于阅读或者更便于使用。使用它,您能修复网页渲染的缺陷,而无须烦扰网站管理员。使用它,您能让网页更好地使用残疾人援助技术,清楚响亮地说出网页内容,或者将网页内容变为盲文。使用它,您能自动地获得其它网站的数据,从而使两个网站更好地相互链接起来。

然而 Greasemonkey 本身并没有作这些事。实际上,在您安装它之后,您注意到根本没有任何变动...直到您开始安装一种叫做“用户脚本”的东西。用户脚本(user script)就是一大块 Javascript 代码,还有些附加信息,用来告诉 Greasemonkey 脚本应该在何时何地运行。每个用户脚本能够针对具体页面,具体网站,或者一批网站。用户脚本能做到您在 Javascript 中可做到的任何事情。实际上,它能做得更多,因为 Greasemonkey 提供了专供用户脚本使用的函数。

这里是Greasemonkey 脚本库含了上百个用户脚本,这些都是用户为了满足自己的需要而写的。一旦您写了自己的用户脚本,只要您认为别人也许发现它有用,您可以把它添加到脚本库中。您也可以自己使用,因为从编写过程中获得知识,获得满足感,才是更重要的。

这是Greasemonkey 的邮件列表,您可以在那里提问,发表用户脚本,和讨论新特性的想法。Greasemonkey 开发人员常去这个邮件列表; 他们也许会回答您的问题!

为什么写这本书?

深入浅出 Greasemonkey 是从 Greasemonkey 邮件列表中的用户讨论和作者本人编写用户脚本的经验中发展而来。仅仅一个星期,作者就发现,新用户经常会提出重复的问题,而这些问题是被回答过的。此外,在写了几个用户脚本以后,作者发现,一些常用的模式,以及可以解决某一类问题的成块的可重用代码会反复出现。因此,作者开始整理这些有用的模式,解释编程思路,同时作者也从中写作中得到不少锻炼。

如果没有 Greasemonkey 的开发者 Aaron Boodman 和 Jeremy Dunck 的大力帮助,没有那些对我的初稿提出宝贵的反馈建议的用户,就不会有现在的这本书。谢谢大家。

1.2. 安装 Greasemonkey

要写用户脚本,您首先要安装 Greasemonkey 扩展, 0.3 或以上版本。

过程 1.1. 安装 Greasemonkey 扩展

  1. 访问Greasemonkey的主页

  2. 点击名为“Install Greasemonkey”的链接。

  3. Firefox会显示(可能在浏览器窗口的上方)阻止网站安装软件的信息。点击编辑选项...打开“允许的站点”对话框,点击允许,将 Greasemonkey 的网站添加到允许安装软件的网站列表中。点击确定,关闭“允许的站点”对话框。

  4. 再次点击名为“Install Greasemonkey”的链接。

  5. 将弹出安装对话框,确定您要安装。几秒钟后,安装按钮变亮,点击安装

    [软件安装对话框截图]
  6. 重新启动 Firefox。

当您重新启动了 Firefox 后,选择工具 (T)单。您应该看到四个菜单项:启用 (E)管理用户脚本 (U)...新建用户脚本 (N)...用户脚本命令 (C)。 只要管理用户脚本 (U)...可以使用,那么就装好了。其他的两个要在特殊情况下才能使用。

一般来说,安装好 Greasemonkey,(除了四个菜单项外)并不会给浏览器添加任何功能。但是它能让您添加别的东西,名叫“用户脚本”,用户脚本可以用来定制指定的网页。

1.3. 安装用户脚本

Greasemonkey “用户脚本”是用 Javascript 编写的独立文件,用来定制一个或多个网页。

[提示]

您可以在Greasemonkey脚本库,找到许多用户脚本。尽管没人要求您必须把脚本放到那儿去,但是实际上,您可以把您的脚本共享到任何地方,这样别人就可以安装它了。甚至您根本不需要一台网络服务器,因为你可以从本地文件中安装用户脚本。

[注意]

用户脚本的文件名必须以.user.js结尾。

我写的第一个用户脚本叫做“Butler”。它增强了Google的搜索结果的功能。

过程 1.2. 安装 Butler 用户脚本

  1. 访问Butler 的主页,可看到有关 Butler 的功能简述。(并不是所有的用户脚本都有主页, Greasemonkey 只要有用户脚本就够了。)

  2. 点击“Download version...”链接 (0.3截至发稿时)。

  3. 弹出一个标题为“Greasemonkey 安装过程”的对话框,其中显示了将要安装的用户脚本名称,简介以及作用与排除的页面列表。所有这些信息都包含在脚本之中;以后您会在用元数据描述您的用户脚本学到定义的方法。

  4. 点击确定安装用户脚本。

没有意外的话,Greasemonkey 会在状态栏滑出一个提示,“'Butler' 安装成功”。

现在,可以在 Google中任意搜索一些东西。在搜索结果页面的顶部会有一行文字:“Try your search on: Yahoo, Ask Jeeves, AlltheWeb, ...”。在其下面会有一个标签:“Enhanced by Butler”。这些都是由 Butler 用户脚本加进去的。

参考资料

1.4. 管理用户脚本

如果愿意可以安装很多个 Greasemonkey 脚本。 Greasemonkey 带有配置对话框来管理用户脚本:暂时禁用,改变配置或卸载脚本。

过程 1.3. 暂时禁用 Butler

  1. 在菜单中,选择工具 (T)Greasemonkey管理用户脚本 (U)...,Greasemonkey 会弹出一个对话框:“管理用户脚本”。

  2. 在对话框左方一栏中列出了已安装的所有用户脚本 (如果您从本文开始一步步来的话,这里只有一个脚本:Butler。)

  3. 选中列表中的 Butler,然后取消启用复选框。左方列表中的“Butler”就会由黑色转为灰色。(当它还是选中状态的时候,看起来比较费劲,但是当安装了很多脚本的时候,这个特性就非常有用了。)

  4. 点击 确定,退出“管理用户脚本”对话框。

现在 Butler 已经安装,但是未被启用。您在Google上随便搜索下就会发现确实如此。在页面顶端的“Enhanced by Butler”应该没有了。您可以在“管理用户脚本”对话框中重复刚才的步骤,重新选中 Butler,重新启用启用复选框。

[注意]

虽然我用“暂时”来形容禁用用户脚本,但是如果您不重新启用它,它就始终会被禁用。之所以是暂时,只因为您可以方便的启用它,而不需要再到我的网站上来找原始脚本,而且还要重新安装。

也可以用“管理用户脚本”对话框来彻底得卸载脚本。

过程 1.4. 卸载 Butler

  1. 在菜单中,选择工具 (T)Greasemonkey管理用户脚本 (U)...。Greasemonkey 会弹出“管理用户脚本”对话框。

  2. 在左方列表栏中,选中 Butler 再点击卸载。不需要确认;用户脚本马上就被卸载掉了。

  3. 第三步... 没有第三步!(感谢 Jeff Goldblum。)

先等一下,还没完呢!您也可以修改您之前安装过的用户脚本的配置。记得第一次安装 Butler 时看到的对话框吗,上面有两个列表:包含和排除的网站?嗯,您可以在“管理用户脚本” 对话框中编辑这两个列表,不管是第一次安装时或者其它什么时候。

好!继续。假如您觉得 Butler 不错,但是它在Froogle上毫无用处。Google的商品对比网站。那么可以编辑用户脚本的配置来排除这个网站,而让它在别的 Google 网站上仍然生效。

过程 1.5. 重新配置 Butler 排除 Froogle

  1. 在菜单中,选择工具 (T)Greasemonkey管理用户脚本 (U).... Greasemonkey 会弹出“管理用户脚本”对话框。

  2. 在左方面板中,选择“Butler”。接着在右方面板中就会显示两个列表,一个是执行的页面(“http://*.google.*/*”),另一个是豁免的页面(空白)。

  3. 接下来在“不包含下列网页”的列表中,点击添加...

  4. Greasemonkey 会弹出另一个对话框:“添加网页地址”,并提示您输入新的URL。在其中输入http://froogle.google.com/*然后点击确定

  5. 回到“管理用户脚本”对话框中,豁免页面列表中现在就有了您新添的 URL 和通配符,http://froogle.google.com/*,表示 Butler 会在froogle.google.com站点的任何页面上执行。星号被当做通配符使用,可以在 URL: 域名,路径或者任意 URL schema(http://)中使用 。

  6. 点击确定,退出“管理用户脚本”对话框。在 Froogle 中搜索一下,验证下 Butler 会不会执行。但是它仍然会在普通搜索、图片搜索以及 Google 站点的其他页面中执行。

第 2 章 您的第一个用户脚本

2.1. Hello World

我们步入 Greasemonkey 美妙世界的万里长征将从第一步开始,所有读过技术手册的读者都会很熟悉这一步:让您的电脑打出“Hello world”。

例 2.1. helloworld.user.js

// Hello World! example user script
// version 0.1 BETA!
// 2005-04-22
// Copyright (c) 2005, Mark Pilgrim
// Released under the GPL license
// http://www.gnu.org/copyleft/gpl.html
//
// --------------------------------------------------------------------
//
// This is a Greasemonkey user script.
//
// To install, you need Greasemonkey: http://greasemonkey.mozdev.org/
// Then restart Firefox and revisit this script.
// Under Tools, there will be a new menu item to "Install User Script".
// Accept the default configuration and install.
//
// To uninstall, go to Tools/Manage User Scripts,
// select "Hello World", and click Uninstall.
//
// --------------------------------------------------------------------
//
// ==UserScript==
// @name          Hello World
// @namespace     http://diveintogreasemonkey.org/download/
// @description   example script to alert "Hello world!" on every page
// @include       *
// @exclude       http://diveintogreasemonkey.org/*
// @exclude       http://www.diveintogreasemonkey.org/*
// ==/UserScript==

alert('Hello world!');

正如您所见到的,这个Hello World脚本的大部分都是注释。有些注释,比如如何安装,没什么特殊含义;那只是对初学者的一些指导。但是,有一节注释确实有特殊含义,下一节会有详细的解释。

要看到脚本的效果,您首先要安装,然后访问一个不在diveintogreasemonkey.org域名下的网站(例如,Google)。这个页面将会像平时一样显示出来,还会弹出一个对话框:“Hello world!

2.2. 用元数据描述您的用户脚本

每个用户脚本都含有一段元数据,用来向 Greasemonkey 描述这个脚本自身的信息:发行者,执行规则等等。

例 2.2. Hello World 元数据

// ==UserScript==
// @name          Hello World
// @namespace     http://diveintogreasemonkey.org/download/
// @description   example script to alert "Hello world!" on every page
// @include       *
// @exclude       http://diveintogreasemonkey.org/*
// @exclude       http://www.diveintogreasemonkey.org/*
// ==/UserScript==

这里有六条独立的元数据信息,作为一个整体包含在注释中。现在让我们按顺序逐条解释。首先讲最外面的这层包装。

// ==UserScript==
//
// ==/UserScript==

上述标记很重要,必须完全吻合。Greasemonkey 用它们来标记用户脚本的元数据段。这段注释可以放在用户脚本的任何部位,但经常会放在靠近顶部的地方。

在元数据段内,第一项是名字。

// @name          Hello World

这是您的用户脚本的名字。它将会在您第一次安装脚本时在安装对话框(install dialog)中显示出来。之后会显示在“管理用户脚本”对话框中。这个名字应该言简意赅。

@name可选的。如果存在,它只能被定义一次。如果不存在,将会默认显示用户脚本的去掉扩展名.user.js的文件名。

下一个是命名空间(namespace)。

// @namespace     http://diveintogreasemonkey.org/download/

这是一个 URL,Greasemonkey 用它来区分名称相同但是作者不同的用户脚本。如果您有一个域名,您可以使用它作命名空间。另外您也可以用 tag: URI

@namespace是可选的。如果存在,它只能被定义一次。如果不存在,将会默认使用下载用户脚本的网站域名。

[提示]

元数据可以以任意次序排列。笔者推荐使用@name@namespace@description@include,最后是@exclude,但是其它的顺序也没关系。

下一项是描述。

// @description   example script to alert "Hello world!" on every page

这是关于用户脚本功能的描述。在您第一次安装脚本时,它将会在安装对话框中显示,之后会在“管理用户脚本”对话框中显示。描述不应多于两句。

@description是可选的。如果使用它,那么它只能被定义一次。如果不使用,默认会显示为空白。

[重要]

不要忘记写@description。即使您所写的用户脚本是给自己用的。你最后很可能会拥有很多脚本,如果没有描述的话,在“管理用户脚本”对话框中管理脚本将会成为一件令人头疼的事。

下面三行是最重要的 (从 Greasemonkey 的角度来看):@include@exclude URL

// @include       *
// @exclude       http://diveintogreasemonkey.org/*
// @exclude       http://www.diveintogreasemonkey.org/*

这几行让 Greasemonkey 知道在那些网站上执行您的用户脚本。您可以明确的指定一个 URL,或者用通配符*来代替域名或路径中的部分字符。在这个例子中,我们告诉 Greasemonkey 在除了http://diveintogreasemonkey.org/http://www.diveintogreasemonkey.org/的所有网站上执行。排除(Excludes)优先于包含(includes),所以即使http://diveintogreasemonkey.org/download/匹配* (所有网站),它还是会被排除掉,因为它还匹配http://diveintogreasemonkey.org/*

@include@exclude 是可选的,可以自定义执行和豁免的 URL,但必须每条规则各占一行。如果您没有任何定义, Greasemonkey 将会对所有的网站执行您的用户脚本。(等同于@include *)。

[注意]

您需要定义非常精确的@include@exclude元数据。Greasemonkey 不会对域名作任何的假设,如果一个网站符合http://example.com/http://www.example.com/,您需要把这两个网址都标示出来。

参考资料

2.3. 编写用户脚本代码

我们的第一个用户脚本是在执行时简单地显示一条提示信息:“Hello world!”。

例 2.3. 显示“Hello world!”提示信息

alert('Hello world!');

尽管这段代码仿佛够用了,而且也达到了目的。Greasemonkey 实际上在幕后做了很多的事情来确保用户脚本不会与页面所包含的原有脚本发生严重的冲突。特别是它会自动的把您的用户脚本封装在一个匿名的函数包里。一般情况下,您可以忽视,但是终究有一天会让您遇到麻烦。所以最好现在就了解一下。

最经常遇到的麻烦之一是在用户脚本里定义的变量和函数能被别的脚本访问。事实上,只要用户脚本运行完了,所有的变量和函数就都不能使用了。如果您期望使用 window.setTimeout 函数,或者在链接挂上字符串式的 onclick 属性然后期望 Javascript 稍后调用您的函数,那么您会遇到问题。

例如,下面这个用户脚本中定义了一个函数 helloworld, 然后尝试设置一个计数器来在一秒后调用这个函数。

例 2.4. 延迟调用函数的错误方法

function helloworld() {
alert('Hello world!');
}

window.setTimeout("helloworld()", 60);

这段代码没有起任何作用;不会弹出提示窗口。如果您打开错误控制台,会看到一个异常:Error: helloworld is not defined.这是因为当延迟结束,开始调用helloworld()时,helloworld函数已经不存在了。

如果您需要引用用户脚本中的变量或者函数,应该显式的把它们定义为window对象的属性,它是始终存在的。

例 2.5. 延迟调用函数的更好方法

window.helloworld = function() {
	alert('Hello world!');
	}

window.setTimeout("helloworld()", 60);

目的达到了!页面完成加载一秒后,一个提示框骄傲的弹了出来,写着:“Hello world!

然而,在 window上设置属性依然不太理想;这有点像用全局变量来做局部变量该做的事。(事实上,就是那么回事,window是全局的,可以被页面中的所有脚本访问。更实际的讲,它可能会与页面自身的脚本,甚至是其它的用户脚本相互干扰。

最佳的解决方案是定义匿名函数,把它作为第一个参数传递给 window.setTimeout

例 2.6. 延迟调用函数的最好方法

window.setTimeout(function() { alert('Hello world!') }, 60);

我在这里所做的是建立一个没有名字的函数(一个“匿名函数”),然后直接把它传递给 window.setTimeout。这样可以完成与上个例子相同的事,而不会留下痕迹。例如不会被其它的脚本检测到。

我发现我在写用户脚本时经常使用匿名函数。它们很适合创建“一次性”函数,然后当作参数传递给类似window.setTimeoutdocument.addEventListener 或者赋值给事件句柄像 clicksubmit

2.4. 修改用户脚本

对于脚本的作者来讲,“管理用户脚本”对话框有个很实用的功能:编辑按钮可以“动态的(live)”修改已安装的脚本。

过程 2.1. 编辑 Hello World 的源代码然后观察运行结果

  1. 在菜单中,选择工具 (T)Greasemonkey管理用户脚本 (U).... Greasemonkey 会弹出“管理用户脚本”对话框。

  2. 在左方面板中,选择 Hello World 再点击编辑。Hello World 的已安装版本将会在你喜好的文本编辑器中打开。(否则,请检查.js文件是否已经关联到您喜好的文本编辑器上。)

  3. 修改alert语句,替换显示内容为“Live editing!”。

  4. 在编辑器中保存所做的更改,然后回到浏览器,随意刷新某个页面来测试所做的更改,你将能立刻看到效果。你不需要重新安装或者“刷新”用户脚本,您是在“动态”修改。

[提示]

在“管理用户脚本”对话框中,点击编辑,您正在“动态”修改脚本的副本,它在 Firefox 的个人配置文件夹中。我形成了一个习惯,每“动态”修改完毕,又回到编辑器,选择文件(F)另存为(a)...,把用户脚本保存到另一个文件夹中。尽管这不是必须的(Greasemonkey 只会用您配置文件夹中的那个脚本),但是我喜欢在完成全部修改后把脚本在其它文件夹里保存一个“原本”。

第 3 章 调试用户脚本

3.1. 用错误控制台追踪错误

如果用户脚本好似没有正常执行,第一个要检查的地方是错误控制台,那里列出了所有与脚本有关的错误,包括用户脚本在内。

过程 3.1. 打开错误控制台

  1. 在 Firefox 菜单中,选择 工具 (T)错误控制台 (C)

  2. 控制台中列出了从打开 Firefox 以来在所有页面上发生的所有错误,应该会有很多的(您会惊奇地发现,很多大的网站的脚本是那么的糟糕)。在开始调试您的用户脚本前,请点击清空 (C)来清空列表。

现在刷新页面来测试出现错误的用户脚本。如果它的确出错了,一条异常会显示在错误控制台中。

[注意]

如果您的用户脚本出错了,错误控制台会显示一条异常(exception)和一个行号。由于 Greasemonkey 将用户脚本插入到页面中,所以行号没有实际的用处,应该忽略这个行号。这并不是您的用户脚本中发生异常的行号。

3.2. 用 GM_log 记日志

Greasemonkey 提供了一个记录用的函数,GM_log,这个函数可以将消息写入错误控制台。这些消息在发布前应当剔出掉,不过在调试时却非常有用。此外,看调试信息比在调试用的警告窗口中一次次点确定好得多。

GM_log有一个参数:日志的字符串。在将信息输出到错误控制台后,用户脚本会一如既往地执行。

例 3.1. 记录到错误控制台然后继续( gmlog.user.js )

if (/^http:\/\/diveintogreasemonkey\.org\//.test(window.location.href)) {
	GM_log('running on Dive Into Greasemonkey site w/o www prefix');
} else {
	GM_log('running elsewhere');
}
GM_log('this line is always printed');

安装这个用户脚本后打开 http://diveintogreasemonkey.org/,这两行就会出现在错误控制台中:

Greasemonkey: http://diveintomark.org/projects/greasemonkey//Test Log:
running on Dive Into Greasemonkey site w/o www prefix
Greasemonkey: http://diveintomark.org/projects/greasemonkey//Test Log:
this line is always printed

如您所见,Greasemonkey 从用户脚本元数据段中取得命名空间和脚本名称,再把作为传给GM_log的参数日志消息算进来,做为显示在错误控制台中显示的信息。

如果您访问的不是http://diveintogreasemonkey.org/,那么下面这两条信息会显示在错误控制台中。

Greasemonkey: http://diveintomark.org/projects/greasemonkey//Test Log:
running elsewhere
Greasemonkey: http://diveintomark.org/projects/greasemonkey//Test Log:
this line is always printed

我已经厌倦去挖掘日志信息的最大长度。它超过了255个字符。还有,输出的信息在错误控制台中可以正确断行,可以向下滚动来查看日志消息其余部分。为日志着迷吧!

[提示]

在错误控制台中可以用右键(Mac用户用 control-click)点击任意行选中,然后选择复制(C),将信息复制到剪贴板。

参见

3.3. 用 DOM 查看器查看元素

DOM 查看器能够查看任何页面的已解析的文档对象模型(DOM)。可以获得每个 HTML 元素、属性和文本节点的详细信息;也可以看到每个页面样式表中的所有 CSS 规则;也可以看到每个对象的所有脚本属性。它的功能非常强大。

DOM 查看器包含在 Firefox 的安装程序中,但是由您的平台而定,它可能默认没装。如果您在工具 (T)菜单中看不到DOM 查看器(N),那么您需要重新安装 Firefox。重新安装 Firefox 不会影响您现有的书签,设置,扩展以及用户脚本。

过程 3.2. 安装 DOM 查看器

  1. 运行 Firefox 的安装程序。

  2. 接受用户协议后,选择定制 (C)安装。

  3. 选择安装路径后,安装向导会询问安装的组件。选择开发工具

  4. 安装结束后,运行 Firefox。您会看到工具 (T)DOM 查看器 (N)

下面我们将要访问深入浅出 Greasemonkey的主页,让您了解 DOM 查看器的强大功能。

过程 3.3. 查看和编辑深入浅出 Greasemonkey的主页

  1. 访问http://www.firefox.net.cn/dig/

  2. 在菜单中,选择工具 (T)DOM 查看器 (N)打开 DOM 查看器。

  3. 在 DOM 查看器的左侧视图中,会看到 DOM 节点的树状图。如果看不到,打开左上角的下拉菜单,选择DOM Nodes

  4. DOM 的树状列表从 document 节点开始,标记为 #document。展开此节点,可以看到 HTML 节点。

  5. 展开 HTML 元素后可以看到三个节点:HEAD#textBODY。注意 BODYiddiveintogreasemonkey-org。如果没看到,调整一下列宽度。

  6. 展开 BODY 可以看到五个节点: #text, DIV id="intro", #text, DIV id="main"#text

  7. 展开 DIV id="intro" 可以看到两个节点: #textDIV class="sectionInner"

  8. 展开 DIV class="sectionInner" 可以看到两个节点: #textDIV class="sectionInner2"

  9. 展开 DIV class="sectionInner2" 可以看到五个节点: #text, DIV class="s", #text, DIV class="s"#text

  10. 展开第一个 DIV class="s" 可以看到五个节点: #text, H1, #text, P#text

  11. 选择 H1 节点。在原始页面上(DOM 查看器后面), H1 元素会闪现红色的边框。在右侧面板中可以看到节点的名称(“H1”)、命名空间 URI (空白,因为 HTML 没有命名空间 -- 只有在页面被当作 application/xhtml+xml 时有效,或者显示一些其他名字空间 XML 的页面)、节点类型(1代表元素)和节点值(空白,因为标题没有值 -- 标题上的文字有自己的节点)。

  12. 在右侧面板的顶部,有个下拉菜单,在其中可以看到数个选项:DOM Node, Box Model, XBL Bindings, CSS Style Rules, Computed StyleJavascript Object。它们提供了当前选中的节点的不同信息。有些是可以修改的,变更会立即反映到原始页面上。选择 Javascript Object 可以看到选中的 H1 元素的所有可脚本控制的的属性和方法。

  13. 选择 CSS Style Rules。右侧面板会分为两部分,顶部的列出了所有可以作用于这个元素的样式(包括浏览器已有的默认样式),下面的显示了样式所定义的属性。

  14. 在右上方的视图中选中第二条规则,这条样式规则是在 http://www.firefox.net.cn/dig/css/dig.css 样式表中定义的。

  15. 在右下面板中双击 font-variant 属性,然后输入新值: normal。在原始页面中(DOM查看器后面),“深入浅出 Greasemonkey” 标志文字会立即从 small-caps 变为正常的大小写字母。

  16. 在右下面板中任意位置点击右键(Mac 用户 control-click),然后选择 新建属性。会弹出一个对话框:“新样式规则”。输入属性名称: background-color,然后点击确定,接下来属性值:red,然后点击确定应用新属性。新属性会显示在右下面板中,并且原始页面会立即变成红色背景。

如果觉得依次展开 DOM 节点树中每层很不方便,那么我强烈推荐您使用 Inspect Element 扩展,它能迅速找到 DOM 查看器中的指定元素。

[警告]

安装 Inspect Element 扩展前,您必须先安装 DOM 查看器,否则 Firefox 就不能正常启动。如果已经遇到了这种情况,打开命令行窗口,进入 Firefox 的安装目录,输入 firefox -safe-mode 。Firefox 会以安全模式启动,不加载任何扩展,然后选择工具 (T)附加软件 (A)接着卸载 Inspect Element。

过程 3.4. 用 Inspect Element 直接查看元素

  1. 访问 Inspect Element 下载页面,然后点击 Install Now

  2. 重新启动 Firefox。

  3. 再访问http://www.firefox.net.cn/dig/

  4. 右击(Mac 用户 control-click) 标志语:深入浅出 Greasemonkey

  5. 在上下文菜单中,选择 Inspect element。.DOM 查看器会打开,并且选中了 H1 元素,然后您可以马上开始查看和/或编辑它的属性。

[警告]

DOM 查看器不会“跟着”您一起浏览网页。如果打开 DOM 查看器然后在原始窗口中浏览别的网页,DOM 查看器会变得非常困惑。好的做法是,去您想去的地方,查看您想查看的页面,在做别的事情前先关闭 DOM 查看器。

参考资料

3.4. 用 Javascript Shell 计算表达式

Javascript Shell 是一个 bookmarklet,可以在当前页面的内容中计算任意 Javascript 表达式。

过程 3.5. 安装 Javascript Shell

  1. 访问 Jesse's Web Development Bookmarklets

  2. Shell bookmarklet 拖放到您的书签工具栏。

  3. 访问一个网页(例如,深入浅出 Greasemonkey 主页),然后点击书签工具栏上的 Shell bookmarklet。Javascript Shell 窗口会在后台打开。

Javascript Shell 提供了与 DOM 查看器一样强大的功能,但是环境更加宽松。就把它当作 DOM 的命令行版本吧。游戏开始。

例 3.2. Javascript Shell 介绍

document.title
深入浅出 Greasemonkey
document.title = 'Hello World'
Hello World
var paragraphs = document.getElementsByTagName('p')
paragraphs
[object HTMLCollection]
paragraphs.length
5
paragraphs[0]
[object HTMLParagraphElement]
paragraphs[0].innerHTML
教老网玩新把戏
paragraphs[0].innerHTML = 'Live editing, baby!'
Live editing, baby!

当输入完上面的内容后,按回车键,变更就会反映到原始网页上。

我想提一下 Javascript Shell 的 props 函数。

例 3.3. 获取元素属性

var link = document.getElementsByTagName('a')[0]
props(link)
Methods of prototype: blur, focus
Fields of prototype: id, title, lang, dir, className, accessKey,
charset, coords, href, hreflang, name, rel, rev, shape, tabIndex,
target, type, protocol, host, hostname, pathname, search, port,
hash, text, offsetTop, offsetLeft, offsetWidth, offsetHeight,
offsetParent, innerHTML, scrollTop, scrollLeft, scrollHeight,
scrollWidth, clientHeight, clientWidth, style
Methods of prototype of prototype of prototype: insertBefore,
replaceChild, removeChild, appendChild, hasChildNodes, cloneNode,
normalize, isSupported, hasAttributes, getAttribute, setAttribute,
removeAttribute, getAttributeNode, setAttributeNode,
removeAttributeNode, getElementsByTagName, getAttributeNS,
setAttributeNS, removeAttributeNS, getAttributeNodeNS,
setAttributeNodeNS, getElementsByTagNameNS, hasAttribute,
hasAttributeNS, addEventListener, removeEventListener, dispatchEvent,
compareDocumentPosition, isSameNode, lookupPrefix, isDefaultNamespace,
lookupNamespaceURI, isEqualNode, getFeature, setUserData, getUserData
Fields of prototype of prototype of prototype: tagName, nodeName,
nodeValue, nodeType, parentNode, childNodes, firstChild, lastChild,
previousSibling, nextSibling, attributes, ownerDocument, namespaceURI,
prefix, localName, ELEMENT_NODE, ATTRIBUTE_NODE, TEXT_NODE,
CDATA_SECTION_NODE, ENTITY_REFERENCE_NODE, ENTITY_NODE,
PROCESSING_INSTRUCTION_NODE, COMMENT_NODE, DOCUMENT_NODE,
DOCUMENT_TYPE_NODE, DOCUMENT_FRAGMENT_NODE, NOTATION_NODE,
baseURI, textContent, DOCUMENT_POSITION_DISCONNECTED,
DOCUMENT_POSITION_PRECEDING, DOCUMENT_POSITION_FOLLOWING,
DOCUMENT_POSITION_CONTAINS, DOCUMENT_POSITION_CONTAINED_BY,
DOCUMENT_POSITION_IMPLEMENTATION_SPECIFIC
Methods of prototype of prototype of prototype of prototype of prototype: toString

哇哦!”这些是什么?它列出了元素 <a> 在 Javascript 中所有有效的属性和方法,并且按照 DOM 的对象层级分类。先列出的是链接元素的特有方法和属性(比如 blurfocus 方法, hrefhreflang 属性),然后列出了所有节点公有的方法和属性(比如 insertBefore),等等。

此外,这和 DOM 查看器上的内容一样……只不过是多些打字多些试验,少点些鼠标。

[警告]

DOM 查看器一样,Javascript Shell 也不会“跟着”您浏览网页。如果打开 Javascript Shell 然后在原始窗口中浏览别的网页,Javascript Shell 会变得非常困惑。好的做法是,去您想去的地方,查看您想查看的页面,在做别的事情前先关闭 Javascript Shell。

3.5. 其他调试工具

下面是我觉得有用的其他调试工具,由于时间有限,我没有全部列出。

参考资料

第 4 章 公共模式

4.1. 在域名以及它所有子域名上执行用户脚本

对于许多网站,无论是否有 www. 前缀,访问网站都是等效的。如果要为这样的站点写用户脚本,需要能匹配这两种地址。

例 4.1. 匹配域名和它所有子域名的元数据标签

// ==UserScript==
// @include http://example.com/*
// @include http://*.example.com/*
// ==/UserScript==

4.2. 测试 Greasemonkey 函数是否有效

Greasemonkey 的新版本给用户脚本提供了新函数。如果准备发布用户脚本,那么应该测试下所用的 Greasemonkey 函数是否存在。

例 4.2. GM_xmlhttpRequest 函数无效时警告用户

if (!GM_xmlhttpRequest) {
	alert('请升级到最新版本的 Greasemonkey.');
	return;
}
// 使用 GM_xmlhttpRequest 的其他代码

4.3. 测试页面中是否有 HTML 元素

可以用 getElementsByTagName 函数来测试页面中是否有 HTML 元素。

例 4.3. 检查页面中是否有 <textarea> 元属

var textareas = document.getElementsByTagName('textarea');
if (textareas.length) {
// 页面中有至少一个 textarea
} else {
// 页面中没有 textarea
}

4.4. 操作所有 HTML 元素

有时需要操作页面中的所有 HTML 元素。Firefox支持 getElementsByTagName('*'),它返回一个可遍历操作的元素集合。

例 4.4. 遍历所有元素

var allElements, thisElement;
allElements = document.getElementsByTagName('*');
for (var i = 0; i < allElements.length; i++) {
	thisElement = allElements[i];
	// 使用 thisElement
}
[注意]

只有真需要操作所有元素时才能采用这种方法。如果只要操作特定的一些元素,可使用 XPath 查询可以正确快速地得到这些元素。更多资料,请查看操作所有有特定属性的元素

实例

4.5. 操作特定 HTML 元素的所有实例

有时您需要操作页面中特定 HTML 元素的所有实例。例如改变所有 <textarea> 的字体大小。最简单的办法就是调用 getElementsByTagName('tagname'),它返回一个可遍历操作的元素集合。

例 4.5. 获取页面上所有的 textarea

var allTextareas, thisTextarea;
allTextareas = document.getElementsByTagName('textarea');
for (var i = 0; i < allTextareas.length; i++) {
	thisTextarea = allTextareas[i];
	// 使用 thisTextarea
}
[注意]

不应该用这个方法来操作页面中所有的链接,因为 <a> 元素也可以用作页面中的锚。要知道如何查找页面中所有的链接,请查看操作所有有特定属性的元素

实例

4.6. 操作所有有特定属性的元素

在 Greasemonkey 的兵器库中最强悍的一个就是 evaluate 方法。利用 XPath 查询语言,它可以用来获取页面中的元素,属性以及文本。

举个例子来说,如果您想获得页面中的全部链接。您也许会想到用 document.getElementsByTagName('a'),但是您还要检查每个元属是否具有 href 属性,因为 <a> 元属还可以用作有名称的锚。

然而可以用 Firefox 内建的 XPath 功能来查找具有 href 属性的 <a> 元属。

例 4.6. 获取页面中的所有链接

var allLinks, thisLink;
allLinks = document.evaluate(
	'//a[@href]',
	document,
	null,
	XPathResult.UNORDERED_NODE_SNAPSHOT_TYPE,
	null);
for (var i = 0; i < allLinks.snapshotLength; i++) {
	thisLink = allLinks.snapshotItem(i);
	// 使用 thisLink
}

document.evaluate 方法是关键。它有一个代表 XPath 查询语句的字符串参数以及一些其它参数,接下来解释一下。这条 XPath 查询语句找到了具有 href 属性的 <a> 元属,用随机的次序排列后返回。(也就是说,集合中的第一个元素并一定是页面中的第一个元素。)然后您可以用 allLinks.snapshotItem(i) 方法访问找到的元素。

XPath 表达式所能做到的甚至会使您惊讶。请看下面这个例子,它获取了全部具有 title 属性的元素。

例 4.7. 获取所有具有 title 属性的元素

var allElements, thisElement;
allElements = document.evaluate(
	'//*[@title]',
	document,
	null,
	XPathResult.UNORDERED_NODE_SNAPSHOT_TYPE,
	null);
for (var i = 0; i < allElements.snapshotLength; i++) {
	thisElement = allElements.snapshotItem(i);
	switch (thisElement.nodeName.toUpperCase()) {
	case 'A':
		// 这是链接,在这里完成您的操作
		break;
	case 'IMG':
		// 这是图片,在这里完成您的操作
		break;
	default:
		// 其他类型的 HTML 元素,在这里完成您的操作
}
}
[提示]

如果已有一个元素的引用(例如 thisElement),可以用 thisElement.nodeName 来判断它的 HTML 标签。如果页面被当作 text/html 类型,标签名称就总是大写,不论它在原始页面是如何定义的。如果页面被当作 application/xhtml+xml类型,那么标签名称就总是小写的。我总是用 thisElement.nodeName.toUpperCase() 这样我就可以不用管这些了。

这是另一个 XPath 查询,它获取了具有特定 class 属性的 <div> 元素。

例 4.8. 获取所有 classsponsoredlink<div>

var allDivs, thisDiv;
allDivs = document.evaluate(
	"//div[@class='sponsoredlink']",
	document,
	null,
	XPathResult.UNORDERED_NODE_SNAPSHOT_TYPE,
	null);
for (var i = 0; i < allDivs.snapshotLength; i++) {
	thisDiv = allDivs.snapshotItem(i);
	// 使用 thisDiv
}

附注:在 XPath 查询语句外使用双引号,这样在语句内就可以使用单引号了。

document.evaluate 方法中有很多参数。第二个参数(在前两个例子中都是 document)可以是任意元素。XPath 查询只返回这个元素的子元素结点。如果已有一个元素的引用(比如,从 document.getElementById 或者 document.getElementsByTagName 数组的一项中得到的引用),您就可以限制查询只返回这个元素的子元素。

第三个参数是对名称空间解析函数的引用,只有在 application/xhtml+xml 类型页面执行的用户脚本中才会用到。即使对它不了解也没关系,因为%