站点正在导入数据,暂时处于只读模式:这段时间里无法登录,也不能发帖或回复。

深入浅出 Greasemonkey

inferior北大未名

深入浅出 Greasemonkey

版权 © 2005 Mark Pilgrimi sebug paper

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

目录

版权 © 2005 Mark Pilgrimi sebug paper

这本书、及其样例代码和视频文件都是自由软件。在“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.setTimeout,document.addEventListener 或者赋值给事件句柄像 click 或 submit。

参考资料

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、#text 和 BODY。注意 BODY 的 id 为 diveintogreasemonkey-org。如果没看到,调整一下列宽度。
  6. 展开 BODY 可以看到五个节点: #text, DIV id="intro", #text, DIV id="main" 和 #text。
  7. 展开 DIV id="intro" 可以看到两个节点: #text 和 DIV class="sectionInner"。
  8. 展开 DIV class="sectionInner" 可以看到两个节点: #text 和 DIV 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 Style 和 Javascript 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

“哇哦!”这些是什么?它列出了元素 `` 在 Javascript 中所有有效的属性和方法,并且按照 DOM 的对象层级分类。先列出的是链接元素的特有方法和属性(比如 blur 和 focus 方法, href 和 hreflang 属性),然后列出了所有节点公有的方法和属性(比如 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. 检查页面中是否有 `` 元属

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 元素的所有实例。例如改变所有 `` 的字体大小。最简单的办法就是调用 getElementsByTagName('tagname'),它返回一个可遍历操作的元素集合。

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

var allTextareas, thisTextarea;
allTextareas = document.getElementsByTagName('textarea');
for (var i = 0; i < allTextareas.length; i++) {
	thisTextarea = allTextareas[i];
	// 使用 thisTextarea
}
[注意]不应该用这个方法来操作页面中所有的链接,因为 `` 元素也可以用作页面中的锚。要知道如何查找页面中所有的链接,请查看操作所有有特定属性的元素。

实例

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

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

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

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

1
  1. inferior北大未名
    inferior

    例 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 属性的 `` 元属,用随机的次序排列后返回。(也就是说,集合中的第一个元素并一定是页面中的第一个元素。)然后您可以用 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 属性的 `` 元素。

    例 4.8. 获取所有 class 为 sponsoredlink 的 ``

    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 类型页面执行的用户脚本中才会用到。即使对它不了解也没关系,因为那种类型的页面不是很多,您可能一次也遇不到。如果您很想知道它的用法,Mozilla XPath 文档解释了它的用法。

    第四个参数是结果的返回方式。在前面的两个例子中都使用了 XPathResult.UNORDERED_NODE_SNAPSHOT_TYPE,它返回的元素是随机次序的。我99%都是使用这种方式,但是,可能出于某种原因,想以在页面上出现的顺序返回结果,您可以使用 XPathResult.ORDERED_NODE_SNAPSHOT_TYPE。Mozilla XPath 文档还提供了一些其他返回方式的例子。

    第五个参数用来合并两次 XPath 查询的结果。传入以前调用 document.evaluate 结果,它将返回两次查询的合并结果。在前面的两个例子中,这个参数都用了 null,这意味着我们只想获得本次 XPath 查询的结果。

    现在明白了吗?XPath 既可简单,也可难,这就看您怎么用了。我强烈推荐您阅读这个优秀的 XPath 教程,可以学到更多的 XPath 语法。关于 document.evaluate 的其他参数, 我很少用除非您已经在这里看到他们了。事实上,您可以定义一个函数来封装它们。

    例 4.9. xpath 函数

    function xpath(query) {
    	return document.evaluate(query, document, null,
    		XPathResult.UNORDERED_NODE_SNAPSHOT_TYPE, null);
    }
    

    现在您可以简单的调用 xpath('//a[@href]') 来获得页面上的全部链接,或者用 xpath('//*[@title]') 获得具有 title 属性的元素。您仍然需要用 snapshotItem 方法访问结果中的每个元素;它不是一个规则的 Javascript 数组。

    实例

    参考资料

    4.7. 在元素前插入内容

    如果已找到某个元素(不论用何种方法),您想在它前面插入额外的内容。您可以使用 insertBefore 函数。

    例 4.10. 在主内容前插入 ``

    假设有个元素的 ID 为 "main"。

    var main, newElement;
    main = document.getElementById('main');
    if (main) {
    	newElement = document.createElement('hr');
    	main.parentNode.insertBefore(newElement, main);
    }
    

    实例

    4.8. 在元素后插入内容

    如果已找到某个元素,您想在它后面插入额外的内容。您也可以使用 insertBefore 函数,要与 nextSibling 属性联合使用。

    例 4.11. 在导航条后面插入 ``

    假设有个元素的 ID 为 "navbar"。

    var navbar, newElement;
    navbar = document.getElementById('navbar');
    if (navbar) {
    	newElement = document.createElement('hr');
    	navbar.parentNode.insertBefore(newElement, navbar.nextSibling);
    }
    
    [提示]即使 someExistingElement 是它的父元素的最后一个孩子(在它之后没有下一个元素),仍然可以在 someExistingElement.nextSibling 之前插入新内容。在这种情况下,someExistingElement.nextSibling 将返回一个空值,insertBefore 函数将把新内容追加到最后。(以许这对你来说没有太大意义,但我想让您知道的是,尽管这种方法似乎是不太对,但它却总是有效的。)

    实例

    4.9. 删除元素

    使用 removeChild 函数,您可以使 Greasemonkey 一下子删除页面中完整的一块。

    例 4.12. 删除广告侧边栏

    假设有个元素 ID 为 "ads"。

    var adSidebar = document.getElementById('ads');
    if (adSidebar) {
    	adSidebar.parentNode.removeChild(adSidebar);
    }
    
    [注意]用removeChild 移除一个元素也会删除它里面的所有内容。例如您移除一个 `` 元素,此时也会将表格单元格(` 元素)一起移除。
    [提示]如果要移除广告,比起写用户脚本来,更容易的办法是安装 AdBlock 然后导入 最新的过滤列表。

    实例

    4.10. 替换元素为新内容

    如果已经获取了一个元素,可以使用 replaceChild 函数来替换它的全部内容。

    例 4.13. 替换图片为替代(alt)文本

    假设页面上有一个元素 ID 为 "annoyingsmily"。

    var theImage, altText;
    theImage = document.getElementById('annoyingsmily');
    if (theImage) {
    	altText = document.createTextNode(theImage.alt);
    	theImage.parentNode.replaceChild(altText, theImage);
    }
    
    [注意]如果要用一大段 HTML 替换一个元素,您可以构造 HTML 为一个字符串,然后设置元素的 innerHTML 属性。

    实例

    4.11. 快速插入复杂的 HTML

    The innerHTML 属性让您可以将 HTML 以字符串的形式表示,然后将它们直接插入到页面中。这样就不需要为每一个 HTML 元素创建单独的 DOM 对象,然后再一个一个的设置它们的属性。

    例 4.14. 在页面顶部加上标语

    var logo = document.createElement("div");
    logo.innerHTML = '<div style="margin: 0 auto 0 auto; ' +
    	'border-bottom: 1px solid #000000; margin-bottom: 5px; ' +
    	'font-size: small; background-color: #000000; ' +
    	'color: #ffffff;"><p style="margin: 2px 0 1px 0;"> ' +
    	'YOUR TEXT HERE ' +
    	'</p></div>';
    document.body.insertBefore(logo, document.body.firstChild);
    

    关键的地方是第二行,在这行我将 logo.innerHTML 设置为字符串。Firefox 将这个字符串解析成 HTML 然后创建所有必需的对象,就像它重新加载一个页面一样。这样我就可以将我的新 logo (一个 包含另一个,这个 包含有一个)插入到页面的任何位置 — 页面开始,结尾或者我选择的任意元素之前或其后。总之,页面上任何位置。

    实例

    4.12. 在没有服务器的情况下添加图片

    Firefox 支持 data: URLs, 它可以把一段数据嵌入到 URL 中,而不需要单独从服务器上来获取。您也许从来都没有听说过 data: URLs ,因为 Internet Explorer 不支持,所以几乎没人使用它们。但是在用户脚本中它们却非常有用。

    例 4.15. 在页面顶部添加图形 logo

    var logo = document.createElement('img');
    logo.src = 'data:image/gif;base64,R0lGODlhDQAOAJEAANno6wBmZgAAAAAAACH5BAAAAAAA'+
    'LAAAAAANAA4AQAIjjI8Iyw3GhACSQecutsFV3nzgNi7SVEbo06lZa66LRib2UQAAOw%3D%3D';
    document.body.insertBefore(logo, document.body.firstChild);
    

    在这个例子中,`` 元素的 src 是 data: URL,它以编码形式的包含了一整幅图像的信息。当这个新元素被插入到页面中时,它会像其他图片一样显示但它却不用存储在服务器上。事实上,可以在您的用户脚本中嵌入图片,它们可以和其它的代码一样发布到网上。

    [提示]使用 data: URI kitchen 来建立您自己的 data: URLs。

    实例

    参见

    4.13. 添加 CSS 样式

    我常常发现需要在页面中添加我自己的 CSS 样式。您可以添加新样式,覆盖掉已有的样式或者样式化用户脚本插入的新元素。

    例 4.16. 放大段落文字

    function addGlobalStyle(css) {
    	var head, style;
    	head = document.getElementsByTagName('head')[0];
    	if (!head) { return; }
    	style = document.createElement('style');
    	style.type = 'text/css';
    	style.innerHTML = css;
    	head.appendChild(style);
    }
    
    addGlobalStyle('p { font-size: large ! important; }');
    

    这个函数只一个字符串参数,包含要插入到页面中的样式规则。它一成不变地把您的样式规则插入到页面 段的 元素中。Firefox 自动判别这些变化,解释样式规则,并应用到页面中。您应该把许多不同的样式规则包含到一次函数调用中;只要把连接成一个字符串然后传给函数。

    [提示]可以使用 addGlobalStyle 函数样式化插入到页面中的元素或者部分原页面中已有的元素。然而,如果要样式化已有的元素,应该在您定义的每条规则上使用 ! important 关键字,保证您的样式覆盖原页面中已定义的规则。

    实例

    参见

    4.14. 获取元素样式

    当多个 CSS 规则同时应用到某个元素上时,获取这个元素的实际样式有时候是有帮助的。您可能会简单的认为,通过元素的 style 属性就可以得到,这样的话您就错了。它只会返回 style 属性的内容。要获得元素的最终样式(包括在外部定义的样式),您需要使用 getComputedStyle 函数。

    为了演示,我们创建一个简单的测试页面。它首先对页面中所有的 元素定义了同一个样式,但随后对其中的一个 元素用它的 style 属性重新定义了样式。

    <html>
    <head>
    <title>样式测试页</title>
    <style type="text/css">
    p { background-color: white; color: red; }
    </style>
    </head>
    <body>
    <p id="p1">这行是红色的。</p>
    <p id="p2" style="color: blue">这行是蓝色的。</p>
    </body>
    </html>
    

    例 4.17. 获取由元素的 style 属性定义的样式

    var p1elem, p2elem;
    p1elem = document.getElementById('p1');
    p2elem = document.getElementById('p2');
    alert(p1elem.style.color); // 提示为空字符串
    alert(p2elem.style.color); // 提示 "blue"
    

    用这种方法获得的信息并不是很有用,所以您不应该再用 element.style 来获取单独的样式。 (您可以用它来设置元素的样式,详见设置元素样式。)

    那么您该用什么方法来取代它呢?getComputedStyle()。

    例 4.18. 获取元素的真实样式

    var p1elem, p2elem, p1style, p2style;
    p1elem = document.getElementById('p1');
    p2elem = document.getElementById('p2');
    p1style = getComputedStyle(p1elem, '');
    p2style = getComputedStyle(p2elem, '');
    alert(p1style.color); // 提示 "rgb(255, 0, 0)"
    alert(p2style.color); // 提示 "rgb(0, 0, 255)"
    

    实例

    4.15. 设置元素样式

    如果只需要设置单个元素的少数样式,可以“手动地”在这个元素的 style 属性中设置多个子属性。

    例 4.19. 设置单个元素的样式

    假设有个元素 ID 为 "logo"。

    var logo = document.getElementById('logo');
    logo.style.marginTop = '2em';
    logo.style.backgroundColor = 'white';
    logo.style.color = 'red';
    
    [警告]并不是每个样式的属性名称都是显然的。通常,它们有相似的模式,例如 margin-top 对应 someElement.style.marginTop。但是也有例外:float 属性对应到 someElement.style.cssFloat,因为 float 是 Javascript 的保留字。

    实例

    参考资料

    4.16. 处理已渲染的页面

    由于 Firefox 保存已渲染页面的内在方式,应该在页面加载完成以后,才能对页面上的 DOM 做出大规模的改动,那么这个操作应该发生在页面完成载入之后,这是由已执行页面在 Firefox 内的存储方式决定的。您可以用 addEventListener 函数来延迟函数的执行。

    例 4.20. 替换全部页面为自定义的内容

    var newBody = 
    '<html>' +
    '<head>' +
    '<title>My New Page</title>' +
    '</head>' +
    '<body>' +
    '<p>This page is a complete replacement of the original.</p>' +
    '</body>' +
    '</html>';
    window.addEventListener(
    	'load', 
    	function() { document.body.innerHTML = newBody; },
    	true);
    

    仔细看这段代码,您可能自然地想知道它是怎么执行的。我声明了一个匿名函数来做为 window.addEventListener 的第二个参数传入,它访问了在这个函数外定义的变量 newBody。这种方法称为 “闭包(closure)”, 在 Javascript 中是完全有效的。一般说来,在“外层”函数中定义的“内层”函数可以访问外层函数的所有局部变量 -- 甚至在外层函数执行结束以后。这是一个很有用的特性,可以通过它来创建事件句柄和用来在运行时建立的数据构架的其他函数。

    [提示]删除和替换 document.body.innerHTML 并不会更改页面。原页面在 `` 中定义的所有内容仍然是有效的,包括页面的标题,CSS 样式,以及脚本。可以分开修改或者移除。

    实例

    4.17. 匹配大小写无关的属性值

    在 HTML 中,许多属性的值是大小写无关的,有的还允许首尾空格。如果想获取到所有这样的值,需要在 XPath 查询中使用一点技巧。

    例 4.21. 获取 method 为 "POST" 或 "post" 的表单(form)

    var postforms = document.evaluate(
    	"//form[translate(@method, 'POST ', 'post')='post']",
    	document,
    	null,
    	XPathResult.UNORDERED_NODE_SNAPSHOT_TYPE,
    	null);
    

    本次 XPath 查询可以获取所有以 POST 方式提交的表单。首先,我们需要用 translate 函数将 method 属性的值中的字母大写变成小写。(XPath 2.0 有 lowercase 函数,但我还从来没用成功过。)其次,我们需要将首尾的空格去掉。我们可以将其整合到调用 translate 函数中,只要在第一个参数中加个额外的空格。因为在第二个参数中没有对应的字母,这样一来所有的空格就被去掉了。最后,我们就能将获得的属性值同 'post' 做比较。

    实例

    4.18. 获取当前域名

    用户脚本操作多个域名(或者所有域名)时,常常需要检测当前页面的域名。可以用 window.location.href 获取当前页面的完整 URL,或者用 window.location.host 只获取域名。

    例 4.22. 获取当前域名

    var href = window.location.host;
    

    实例

    4.19. 改写链接

    如果获取到页面上的链接,可以通过设置它的 href 属性来改写链接。

    例 4.23. 在链接末尾添加查询参数

    假设有个链接的 id 为 "article"。

    var a = document.getElementById('article');
    if (a.href.match(/\?/i)) {
    	// 链接已经包含其它参数,所以添加 "&amp;printer=1"
    	a.href += '&amp;printer=1';
    } else {
    	// 连接没有包含任何参数,所以添加 "?printer=1"
    	a.href += '?printer=1';
    }
    

    实例

    4.20. 重定向页面

    通过设置 window.location.href 属性,您可以用 Greasemonkey 来自动重定向到指定的页面。

    例 4.24. 重定向一个站点到它的安全站点

    window.location.href = window.location.href.replace(/^http:/, 'https:');
    

    实例

    4.21. 俘获用户点击

    虽然改写链接很简单,您也可以走的更远,使用 addEventListener 函数,俘获页面上任何地方的每次点击。(这也能俘获点击链接。)您可以考虑下怎么做:是否让可点击元素的点击操作“失效” ,或者做些完全不同的操作。

    例 4.25. 当用户点击页面上任意地方时做点事

    document.addEventListener('click', function(event) {
    // event.target 是被点击的元素
    
    // 把你的代码放在这里
    
    // 如果您想阻止默认点击动作
    // (例如链接转向),使用下面这两条命令:
    event.stopPropagation();
    event.preventDefault();
    }, true);
    

    注释:用 匿名函数 作为传入 document.addEventListener 函数的一个参数。

    4.22. 覆盖内建的 Javascript 方法

    您可以用 prototype 属性 覆盖原有的对象方法。

    例 4.26. 当表单提交时做点事

    function newsubmit(event) {
    var target = event ? event.target : this;
    
    // 在这里定义想做的操作
    alert('Submitting form to ' + target.action);
    
    // 调用真正的提交函数
    this._submit();
    }
    
    // 捕获所有表单的 onsubmit 事件
    window.addEventListener('submit', newsubmit, true);
    
    // 如果脚本调用 someForm.submit(),onsubmit 事件不会发生,
    // 所以我们需要重新定义 HTMLFormElement 类的 submit 方法。
    HTMLFormElement.prototype._submit = HTMLFormElement.prototype.submit;
    HTMLFormElement.prototype.submit = newsubmit;
    

    在这里做了两件事。首先,我加入了一个用来捕获 submit 事件的监听函数。当用户点击表单的提交按钮时,触发 submit 事件。然而,当别的脚本调用表单的 submit() 方法时,并不会触发 submit 事件。 所以,我做的第二件事是覆盖 HTMLFormElement 类的 submit 方法。

    但是等等,还有几点需要说明。事件监听函数和重新定义的方法都指向同一个函数,newsubmit。如果 newsubmit 被一个 submit 事件调用,event 参数将会被包含在事件对象中,这个事件对象包含事件的信息(例如,event.target 是被提交的表单)。然而,如果脚本手动调用了 submit 方法,event 事件会被忽略掉,但是全局变量 this 将会指向这个表单。因此,在我的 newsubmit 函数中,我先判断 event 是否为空;如果为空,就用 this 来得到这个表单。

    [提示]正常情况下,当用户提交一个表单时,例如,点击表单中的 提交 按钮或者按 回车键),都会触发 submit 事件。但是,当脚本调用 aForm.submit() 提交表单时,却不会触发 submit 事件。因此,您必须做两件事来捕获表单的提交事件:给 submit 事件增加事件监听,并且修改 HTMLFormElement 类的原型来重定向 submit() 方法到您的自定义函数上。

    参考资料

    4.23. 解析 XML

    Firefox 自动将当前页面解析为 DOM,但是您也可以手动创建 DOM 而不使用任何 XML 字符串,可以是您自己建立的,也可以是您从远端主机得到的 XML。

    例 4.27. 将任意字符串解析为 XML

    var xmlString = '<passwd>' + 
    	'  <user id="101">' +
    	'    <login>mark</login>' + 
    	'    <group id="100"/>' +
    	'    <displayname>Mark Pilgrim</displayname>' + 
    	'    <homedir>/home/mark/</homedir>' +
    	'    <shell>/bin/bash</shell>' +
    	'  </user>' +
    	'</passwd>'
    var parser = new DOMParser();
    var xmlDoc = parser.parseFromString(xmlString, "application/xml");
    

    这里关键的地方是 DOMParser 对象,有个 parseFromString 方法。(它也有其他的方法,但在这里我们用不到。)parseFromString 方法有两个参数:解析用的 XML 字符串,和内容类型。两者都是必需的。

    [注意]DOMParser 的 parseFromString 方法将内容的类型作为它的第二个参数。这个方法可以接受 application/xml, application/xhtml+xml 和 text/xml。由于深究起来,理由会很荒唐,您应该始终使用 application/xml。

    这个模式非常强大,如果把它与 GM_xmlhttpRequest 函数结合起来解析远程源的 XML。

    例 4.28. 解析远程源的 XML

    GM_xmlhttpRequest({
    	method: 'GET',
    	url: 'http://greaseblog.blogspot.com/atom.xml',
    	headers: {
    	'User-agent': 'Mozilla/4.0 (compatible) Greasemonkey/0.3',
    	'Accept': 'application/atom+xml,application/xml,text/xml',
    	},
    	onload: function(responseDetails) {
    		var parser = new DOMParser();
    		var dom = parser.parseFromString(responseDetails.responseText,
    		"application/xml");
    		var entries = dom.getElementsByTagName('entry');
    		var title;
    		for (var i = 0; i < entries.length; i++) {
    		title = entries[i].getElementsByTagName('title')[0].textContent;
    		alert(title);
    		}
    	}
    });
    

    这段代码会载入http://greaseblog.blogspot.com/atom.xml的 Atom feed,把它解析为 DOM,然后查询 DOM 得到条目列表。再对每个条目做 DOM 查询,获得条目的标题(title),然后在对话框中显示出来。

    参见

    第 5 章 实例教学

    5.1. 案例:GMail Secure

    强制网站使用安全链接

    GMail Secure 强制 GMail 使用安全的链接,重定向 http://gmail.google.com/ 到 https://gmail.google.com/。

    Google 提供了 GMail,他们的 webmail 服务,可以通过非安全连接使用(http:// 地址)或者通过安全连接使用(https:// 地址)。我试着记着在公用场所(例如网吧)查看邮件时要用安全链接。但是为什么不让电脑帮您记着呢?我写了个用户脚本,当它检测到当我通过非安全链接访问 GMail 时,它就自动帮我重定向到安全链接。

    例 5.1. 重定向 GMail 到等同的 https:// 地址

    // ==UserScript==
    // @name          GMailSecure
    // @namespace     http://diveintogreasemonkey.org/download/
    // @description   force GMail to use secure connection
    // @include       http://gmail.google.com/*
    // ==/UserScript==
    
    window.location.href = window.location.href.replace(/^http:/, 'https:');
    

    这个用户脚本很简单,大部分的“工作”由 @include 这行完成:

    // @include       http://gmail.google.com/*
    

    只有匹配 @include 的时候,这个用户脚本才会执行。所以当脚本执行的时候,我就知道我写错了地址(通过非安全连接访问 GMail)。完成这一切,只要一行代码, 重定向当前页面到相同的 URL,只是用 https:// 前缀替换掉 http://。

    window.location.href = window.location.href.replace(/^http:/, 'https:');
    

    参见

    1
  2. linda
    lindarico y libre 选择大于改变 机会大于努力 认知决定财富

    基本概念是对的,但是文章太老,需要更新

  3. Jessychan杰士陈
    Jessychan杰士陈回复linda

    成天挖坟,这是怀念起这站还有人时的日子了?

  4. positron8964
    positron8964回复linda

    学下前端就行了。油猴子脚本其实就是自己写一段代码,然后注入到别人的前端代码(也就是网页)里。