这本书、及其样例代码和视频文件都是自由软件。在“GNU 通用公共许可证(自由软件基金会)(版本2以及更新版本)”许可下,您可以随意的再分发和/或修改它们。我们发行这本书、及其样例代码和视频文件,希望它能对您有所帮助。但是我们并没有提供任何担保!请查阅GNU 通用公共许可证 获取更多细节。
目录
然而 Greasemonkey 本身并没有作这些事。实际上,在您安装它之后,您注意到根本没有任何变动...直到您开始安装一种叫做“用户脚本 ”的东西。用户脚本(user script)就是一大块 Javascript 代码,还有些附加信息,用来告诉 Greasemonkey 脚本应该在何时何地运行。每个用户脚本能够针对具体页面,具体网站,或者一批网站。用户脚本能做到您在 Javascript 中可做到的任何事情。实际上,它能做得更多,因为 Greasemonkey 提供了专供用户脚本使用的函数。
这里是Greasemonkey 脚本库 含了上百个用户脚本,这些都是用户为了满足自己的需要而写的。一旦您写了自己的用户脚本,只要您认为别人也许发现它有用,您可以把它添加到脚本库中。您也可以自己使用,因为从编写过程中获得知识,获得满足感,才是更重要的。
这是Greasemonkey 的邮件列表 ,您可以在那里提问,发表用户脚本,和讨论新特性的想法。Greasemonkey 开发人员常去这个邮件列表; 他们也许会回答您的问题!
深入浅出 Greasemonkey 是从 Greasemonkey 邮件列表中的用户讨论和作者本人编写用户脚本的经验中发展而来。仅仅一个星期,作者就发现,新用户经常会提出重复的问题,而这些问题是被回答过的。此外, 在写了几个用户脚本以后,作者发现,一些常用的模式,以及可以解决某一类问题的成块的可重用代码会反复出现。因此,作者开始整理这些有用的模式,解释编程 思路,同时作者也从中写作中得到不少锻炼。
如果没有 Greasemonkey 的开发者 Aaron Boodman 和 Jeremy Dunck 的大力帮助,没有那些对我的初稿提出宝贵的反馈建议的用户,就不会有现在的这本书。谢谢大家。
访问Greasemonkey的主页 。
点击名为“Install Greasemonkey ”的链接。
Firefox会显示(可能在浏览器窗口的上方)阻止网站安装软件的信息。点击编辑选项... 打开“允许的站点 ”对话框,点击允许 ,将 Greasemonkey 的网站添加到允许安装软件的网站列表中。点击确定 ,关闭“允许的站点 ”对话框。
再次点击名为“Install Greasemonkey ”的链接。
将弹出安装对话框,确定您要安装。几秒钟后,安装按钮变亮,点击安装 。
重新启动 Firefox。
当您重新启动了 Firefox 后,选择工具 (T ) 单。您应该看到四个菜单项:启用 (E ) 、管理用户脚本 (U )... 、新建用户脚本 (N )... 和用户脚本命令 (C ) 。 只要管理用户脚本 (U )... 可以使用,那么就装好了。其他的两个要在特殊情况下才能使用。
一般来说,安装好 Greasemonkey,(除了四个菜单项外)并不会给浏览器添加任何功能。但是它能让您添加别的东西,名叫“用户脚本 ”,用户脚本可以用来定制指定的网页。
您可以在Greasemonkey脚本库 ,找到许多用户脚本。尽管没人要求您必须把脚本放到那儿去,但是实际上,您可以把您的脚本共享到任何地方,这样别人就可以安装它了。甚至您根本不需要一台网络服务器,因为你可以从本地文件中安装用户脚本。 |
我写的第一个用户脚本叫做“Butler ”。它增强了Google的搜索结果的功能。
访问Butler 的主页 ,可看到有关 Butler 的功能简述。(并不是所有的用户脚本都有主页, Greasemonkey 只要有用户脚本就够了。)
点击“Download version... ”链接 (0.3截至发稿时)。
弹出一个标题为“Greasemonkey 安装过程 ”的对话框,其中显示了将要安装的用户脚本名称,简介以及作用与排除的页面列表。所有这些信息都包含在脚本之中;以后您会在用元数据描述您的用户脚本 学到定义的方法。
点击确定 安装用户脚本。
没有意外的话,Greasemonkey 会在状态栏滑出一个提示,“'Butler' 安装成功 ”。
现在,可以在 Google 中任意搜索一些东西。在搜索结果页面的顶部会有一行文字:“Try your search on: Yahoo, Ask Jeeves, AlltheWeb, ... ”。在其下面会有一个标签:“Enhanced by Butler ”。这些都是由 Butler 用户脚本加进去的。
在菜单中,选择工具 (T ) → G reasemonkey → 管理用户脚本 (U )... ,Greasemonkey 会弹出一个对话框:“管理用户脚本 ”。
在对话框左方一栏中列出了已安装的所有用户脚本 (如果您从本文开始一步步来的话 ,这里只有一个脚本:Butler。)
选中列表中的 Butler,然后取消启用 复选框。左方列表中的“Butler ”就会由黑色转为灰色。(当它还是选中状态的时候,看起来比较费劲,但是当安装了很多脚本的时候,这个特性就非常有用了。)
点击 确定 ,退出“管理用户脚本 ”对话框。
现在 Butler 已经安装,但是未被启用。您在Google上随便搜索下就会发现确实如此。在页面顶端的“Enhanced by Butler ”应该没有了。您可以在“管理用户脚本 ”对话框中重复刚才的步骤,重新选中 Butler,重新启用启用 复选框。
也可以用“管理用户脚本 ”对话框来彻底得卸载脚本。
在菜单中,选择工具 (T ) → G reasemonkey → 管理用户脚本 (U )... 。Greasemonkey 会弹出“管理用户脚本 ”对话框。
在左方列表栏中,选中 Butler 再点击卸载 。不需要确认;用户脚本马上就被卸载掉了。
第三步... 没有第三步!(感谢 Jeff Goldblum。)
先等一下,还没完呢!您也可以修改您之前安装过的用户脚本的配置。记得第一次安装 Butler 时看到的对话框吗 ,上面有两个列表:包含和排除的网站?嗯,您可以在“管理用户脚本 ” 对话框中编辑这两个列表,不管是第一次安装时或者其它什么时候。
好!继续。假如您觉得 Butler 不错,但是它在Froogle 上毫无用处。Google的商品对比网站。那么可以编辑用户脚本的配置来排除这个网站,而让它在别的 Google 网站上仍然生效。
在菜单中,选择工具 (T ) → G reasemonkey → 管理用户脚本 (U )... . Greasemonkey 会弹出“管理用户脚本 ”对话框。
在左方面板中,选择“Butler ”。接着在右方面板中就会显示两个列表,一个是执行的页面(“http://*.google.*/* ”),另一个是豁免的页面(空白)。
接下来在“不包含下列网页 ”的列表中,点击添加... 。
Greasemonkey 会弹出另一个对话框:“添加网页地址 ”,并提示您输入新的URL 。在其中输入http://froogle.google.com/*
然后点击确定 。
回到“管理用户脚本 ”对话框中,豁免页面列表中现在就有了您新添的 URL 和通配符,http://froogle.google.com/*
,表示 Butler 不 会在froogle.google.com
站点的任何页面上执行。星号被当做通配符使用,可以在 URL : 域名,路径或者任意 URL schema(http://
)中使用 。
点击确定 ,退出“管理用户脚本 ”对话框。在 Froogle 中搜索一下,验证下 Butler 会不会执行。但是它仍然会在普通搜索、图片搜索以及 Google 站点的其他页面中执行。
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! ”
// ==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
是可选的。如果存在,它只能被定义一次。如果不存在,将会默认使用下载用户脚本的网站域名。
下一项是描述。
// @description example script to alert "Hello world!" on every page
这是关于用户脚本功能的描述。在您第一次安装脚本时,它将会在安装对话框中显示,之后会在“管理用户脚本 ”对话框中显示。描述不应多于两句。
@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 *
)。
您需要定义非常精确的 |
尽管这段代码仿佛够用了,而且也达到了目的。Greasemonkey 实际上在幕后做了很多的事情来确保用户脚本不会与页面所包含的原有脚本发生严重的冲突。特别是它会自动的把您的用户脚本封装在一个匿名的函数包里。一般情 况下,您可以忽视,但是终究有一天会让您遇到麻烦。所以最好现在就了解一下。
最经常遇到的麻烦之一是在用户脚本里定义的变量和函数不 能被别的脚本访问。事实上,只要用户脚本运行完了,所有的变量和函数就都不能使用了。如果您期望使用 window.setTimeout
函数,或者在链接挂上字符串式的 onclick
属性然后期望 Javascript 稍后调用您的函数,那么您会遇到问题。
例如,下面这个用户脚本中定义了一个函数 helloworld
, 然后尝试设置一个计数器来在一秒后调用这个函数。
function helloworld() {
alert('Hello world!');
}
window.setTimeout("helloworld()", 60);
这段代码没有起任何作用;不会弹出提示窗口。如果您打开错误控制台 ,会看到一个异常:Error: helloworld is not defined.
这是因为当延迟结束,开始调用helloworld()
时,helloworld
函数已经不存在了。
如果您需要引用用户脚本中的变量或者函数,应该显式的把它们定义为window
对象的属性,它是始终存在的。
window.helloworld = function() {
alert('Hello world!');
}
window.setTimeout("helloworld()", 60);
目的达到了!页面完成加载一秒后,一个提示框骄傲的弹了出来,写着:“Hello world! ”
然而,在 window
上设置属性依然不太理想;这有点像用全局变量来做局部变量该做的事。(事实上,就是那么回事,window
是全局的,可以被页面中的所有脚本访问。更实际的讲,它可能会与页面自身的脚本,甚至是其它的用户脚本相互干扰。
最佳的解决方案是定义匿名函数,把它作为第一个参数传递给 window.setTimeout
。
在菜单中,选择工具 (T ) → G reasemonkey → 管理用户脚本 (U )... . Greasemonkey 会弹出“管理用户脚本 ”对话框。
在左方面板中,选择 Hello World 再点击编辑 。Hello World 的已安装版本将会在你喜好的文本编辑器中打开。(否则,请检查.js
文件是否已经关联到您喜好的文本编辑器上。)
修改alert
语句,替换显示内容为“Live editing! ”。
在编辑器中保存所做的更改,然后回到浏览器,随意刷新某个页面来测试所做的更改,你将能立刻看到效果。你不需要重新安装或者“刷新 ”用户脚本,您是在“动态 ”修改。
在 Firefox 菜单中,选择 工具 (T ) → 错误控制台 (C ) 。
控制台中列出了从打开 Firefox 以来在所有页面上发生的所有错误,应该会有很多的(您会惊奇地发现,很多大的网站的脚本是那么的糟糕)。在开始调试您的用户脚本前,请点击清空 (C ) 来清空列表。
现在刷新页面来测试出现错误的用户脚本。如果它的确出错了,一条异常会显示在错误控制台中。
GM_log
有一个参数:日志的字符串。在将信息输出到错误控制台后,用户脚本会一如既往地执行。
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个字符。还有,输出的信息在错误控制台中可以正确断行,可以向下滚动来查看日志消息其余部分。为日志着迷吧!
DOM 查看器包含在 Firefox 的安装程序中,但是由您的平台而定,它可能默认没装。如果您在工具 (T ) 菜单中看不到DOM 查看器(N ) ,那么您需要重新安装 Firefox。重新安装 Firefox 不会影响您现有的书签,设置,扩展以及用户脚本。
运行 Firefox 的安装程序。
接受用户协议后,选择定制 (C ) 安装。
选择安装路径后,安装向导会询问安装的组件。选择开发工具 。
安装结束后,运行 Firefox。您会看到工具 (T ) → DOM 查看器 (N ) 。
下面我们将要访问深入浅出 Greasemonkey的主页,让您了解 DOM 查看器的强大功能。
在菜单中,选择工具 (T ) → DOM 查看器 (N ) 打开 DOM 查看器。
在 DOM 查看器的左侧视图中,会看到 DOM 节点的树状图。如果看不到,打开左上角的下拉菜单,选择DOM Nodes 。
DOM 的树状列表从 document 节点开始,标记为 #document
。展开此节点,可以看到 HTML
节点。
展开 HTML
元素后可以看到三个节点:HEAD
、#text
和 BODY
。注意 BODY
的 id
为 diveintogreasemonkey-org
。如果没看到,调整一下列宽度。
展开 BODY
可以看到五个节点: #text
, DIV id="intro"
, #text
, DIV id="main"
和 #text
。
展开 DIV id="intro"
可以看到两个节点: #text
和 DIV class="sectionInner"
。
展开 DIV class="sectionInner"
可以看到两个节点: #text
和 DIV class="sectionInner2"
。
展开 DIV class="sectionInner2"
可以看到五个节点: #text
, DIV class="s"
, #text
, DIV class="s"
和 #text
。
展开第一个 DIV class="s"
可以看到五个节点: #text
, H1
, #text
, P
和 #text
。
选择 H1
节点。在原始页面上(DOM 查看器后面), H1
元素会闪现红色的边框。在右侧面板中可以看到节点的名称(“H1 ”)、命名空间 URI (空白,因为 HTML 没有命名空间 -- 只有在页面被当作 application/xhtml+xml
时有效,或者显示一些其他名字空间 XML 的页面)、节点类型(1
代表元素)和节点值(空白,因为标题没有值 -- 标题上的文字有自己的节点)。
在右侧面板的顶部,有个下拉菜单,在其中可以看到数个选项:DOM Node , Box Model , XBL Bindings , CSS Style Rules , Computed Style 和 Javascript Object 。它们提供了当前选中的节点的不同信息。有些是可以修改的,变更会立即反映到原始页面上。选择 Javascript Object 可以看到选中的 H1
元素的所有可脚本控制的的属性和方法。
选择 CSS Style Rules 。右侧面板会分为两部分,顶部的列出了所有可以作用于这个元素的样式(包括浏览器已有的默认样式),下面的显示了样式所定义的属性。
在右上方的视图中选中第二条规则,这条样式规则是在 http://www.firefox.net.cn/dig/css/dig.css 样式表中定义的。
在右下面板中双击 font-variant
属性,然后输入新值: normal
。在原始页面中(DOM查看器后面),“深入浅出 Greasemonkey ” 标志文字会立即从 small-caps 变为正常的大小写字母。
在右下面板中任意位置点击右键(Mac 用户 control-click),然后选择 新建属性 。会弹出一个对话框:“新样式规则 ”。输入属性名称: background-color
,然后点击确定 ,接下来属性值:red
,然后点击确定 应用新属性。新属性会显示在右下面板中,并且原始页面会立即变成红色背景。
如果觉得依次展开 DOM 节点树中每层很不方便,那么我强烈推荐您使用 Inspect Element 扩展,它能迅速找到 DOM 查看器中的指定元素。
安装 Inspect Element 扩展前,您必须 先安装 DOM 查看器,否则 Firefox 就不能正常启动。如果已经遇到了这种情况,打开命令行窗口,进入 Firefox 的安装目录,输入 |
访问 Inspect Element 下载页面,然后点击 Install Now 。
重新启动 Firefox。
右击(Mac 用户 control-click) 标志语:深入浅出 Greasemonkey
。
在上下文菜单中,选择 Inspect element 。.DOM 查看器会打开,并且选中了 H1
元素,然后您可以马上开始查看和/或编辑它的属性。
DOM 查看器不会“跟着 ”您一起浏览网页。如果打开 DOM 查看器然后在原始窗口中浏览别的网页,DOM 查看器会变得非常困惑。好的做法是,去您想去的地方,查看您想查看的页面,在做别的事情前先关闭 DOM 查看器。 |
将 Shell bookmarklet 拖放到您的书签工具栏。
访问一个网页(例如,深入浅出 Greasemonkey 主页),然后点击书签工具栏上的 Shell bookmarklet。Javascript Shell 窗口会在后台打开。
Javascript Shell 提供了与 DOM 查看器 一样强大的功能,但是环境更加宽松。就把它当作 DOM 的命令行版本吧。游戏开始。
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
函数。
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 的对象层级分类。先列出的是链接元素的特有方法和属性(比如 blur
和 focus
方法, href
和 hreflang
属性),然后列出了所有节点公有的方法和属性(比如 insertBefore
),等等。
此外,这和 DOM 查看器上的内容一样……只不过是多些打字多些试验,少点些鼠标。
同 DOM 查看器 一样,Javascript Shell 也不会“跟着 ”您浏览网页。如果打开 Javascript Shell 然后在原始窗口中浏览别的网页,Javascript Shell 会变得非常困惑。好的做法是,去您想去的地方,查看您想查看的页面,在做别的事情前先关闭 Javascript Shell。 |
id
和 class
的属性。 var allElements, thisElement;
allElements = document.getElementsByTagName('*');
for (var i = 0; i < allElements.length; i++) {
thisElement = allElements[i];
// 使用 thisElement
}
只有真需要操作所有 元素时才能采用这种方法。如果只要操作特定的一些元素,可使用 XPath 查询可以正确快速地得到这些元素。更多资料,请查看操作所有有特定属性的元素 。 |
var allTextareas, thisTextarea;
allTextareas = document.getElementsByTagName('textarea');
for (var i = 0; i < allTextareas.length; i++) {
thisTextarea = allTextareas[i];
// 使用 thisTextarea
}
不应该用这个方法来操作页面中所有的链接,因为 |
举个例子来说,如果您想获得页面中的全部链接。您也许会想到用 document.getElementsByTagName('a')
,但是您还要检查每个元属是否具有 href
属性,因为 <a>
元属还可以用作有名称的锚。
然而可以用 Firefox 内建的 XPath 功能来查找具有 href
属性的 <a>
元属。
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
属性的元素。
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 元素,在这里完成您的操作
}
}
这是另一个 XPath 查询,它获取了具有特定 class
属性的 <div>
元素。
class
为 sponsoredlink
的 <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
类型页面执行的用户脚本中才会用到。即使对它不了解也没关系,因为那种类型的页面不是很多,您可能一次也遇不到。如果您很想知道它的用法,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
的其他参数, 我很少用除非您已经在这里看到他们了。事实上,您可以定义一个函数来封装它们。
xpath
函数 function xpath(query) {
return document.evaluate(query, document, null,
XPathResult.UNORDERED_NODE_SNAPSHOT_TYPE, null);
}
现在您可以简单的调用 xpath('//a[@href]')
来获得页面上的全部链接,或者用 xpath('//*[@title]')
获得具有 title
属性的元素。您仍然需要用 snapshotItem
方法访问结果中的每个元素;它不是一个规则的 Javascript 数组。
<hr>
假设有个元素的 ID 为 "navbar"
。
var navbar, newElement;
navbar = document.getElementById('navbar');
if (navbar) {
newElement = document.createElement('hr');
navbar.parentNode.insertBefore(newElement, navbar.nextSibling);
}
假设有个元素 ID 为 "ads"
。
var adSidebar = document.getElementById('ads');
if (adSidebar) {
adSidebar.parentNode.removeChild(adSidebar);
}
假设页面上有一个元素 ID 为 "annoyingsmily"
。
var theImage, altText;
theImage = document.getElementById('annoyingsmily');
if (theImage) {
altText = document.createTextNode(theImage.alt);
theImage.parentNode.replaceChild(altText, theImage);
}
如果要用一大段 HTML 替换一个元素,您可以构造 HTML 为一个字符串,然后设置元素的 |
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
(一个 <div>
包含另一个 <div>
,这个 <div>
包含有一个 <p>
)插入到页面的任何位置 — 页面开始,结尾或者我选择的任意元素之前 或其后 。总之,页面上任何位置。
var logo = document.createElement('img');
logo.src = 'data:image/gif;base64,R0lGODlhDQAOAJEAANno6wBmZgAAAAAAACH5BAAAAAAA'+
'LAAAAAANAA4AQAIjjI8Iyw3GhACSQecutsFV3nzgNi7SVEbo06lZa66LRib2UQAAOw%3D%3D';
document.body.insertBefore(logo, document.body.firstChild);
在这个例子中,<img>
元素的 src
是 data:
URL ,它以编码形式的包含了一整幅图像的信息。当这个新元素被插入到页面中时,它会像其他图片一样显示但它却不用存储在服务器上。事实上,可以在您的用户脚本中嵌入图片,它们可以和其它的代码一样发布到网上。
使用 |
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; }');
这个函数只一个字符串参数,包含要插入到页面中的样式规则。它一成不变地把您的样式规则插入到页面 <head>
段的 <style>
元素中。Firefox 自动判别这些变化,解释样式规则,并应用到页面中。您应该把许多不同的样式规则包含到一次函数调用中;只要把连接成一个字符串然后传给函数。
为了演示,我们创建一个简单的测试页面。它首先对页面中所有的 <p>
元素定义了同一个样式,但随后对其中的一个 <p>
元素用它的 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>
style
属性定义的样式 var p1elem, p2elem;
p1elem = document.getElementById('p1');
p2elem = document.getElementById('p2');
alert(p1elem.style.color); // 提示为空字符串
alert(p2elem.style.color); // 提示 "blue"
用这种方法获得的信息并不是很有用,所以您不应该再用 element
.style
来获取单独的样式。 (您可以用它来设置 元素的样式,详见设置元素样式 。)
那么您该用什么方法来取代它呢?getComputedStyle()
。
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)"
假设有个元素 ID 为 "logo"
。
var logo = document.getElementById('logo');
logo.style.marginTop = '2em';
logo.style.backgroundColor = 'white';
logo.style.color = 'red';
并不是每个样式的属性名称都是显然的。通常,它们有相似的模式,例如 |
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 中是完全有效的。一般说来,在“外层 ”函数中定义的“内层 ”函数可以访问外层函数的所有局部变量 -- 甚至在外层函数执行结束以后。这是一个很有用的特性,可以通过它来创建事件句柄和用来在运行时建立的数据构架的其他函数。
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'
做比较。
虽然改写链接很简单 ,您也可以走的更远,使用 addEventListener
函数,俘获页面上任何地方的每次点击。(这也能俘获点击链接。)您可以考虑下怎么做:是否让可点击元素的点击操作“失效 ” ,或者做些完全不同的操作。
document.addEventListener('click', function(event) {
// event.target 是被点击的元素
// 把你的代码放在这里
// 如果您想阻止默认点击动作
// (例如链接转向),使用下面这两条命令:
event.stopPropagation();
event.preventDefault();
}, true);
注释:用 匿名函数 作为传入 document.addEventListener
函数的一个参数。
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
来得到这个表单。
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 字符串,和内容类型。两者都是必需的。
|
这个模式非常强大,如果把它与 GM_xmlhttpRequest
函数结合起来解析远程源的 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),然后在对话框中显示出来。
GMail Secure 强制 GMail 使用安全的链接,重定向 http://gmail.google.com/
到 https://gmail.google.com/
。
Google 提供了 GMail ,他们的 webmail 服务,可以通过非安全连接使用(http://
地址)或者通过安全连接使用(https://
地址)。我试着记着在公用场所(例如网吧)查看邮件时要用安全链接。但是为什么不让电脑帮您记着呢?我写了个用户脚本,当它检测到当我通过非安全链接访问 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:');
Bloglines 是一个基于 web 的同步 feed 聚合网站。它的界面上有两个面板;左边的显示您的订阅列表,右边的显示订阅的内容。这个界面很友好;唯一不满意的是:我想每次访问 Bloglines,只看所有未读条目。
在 Bloglines 中,显示所有的未读条目只需要单击一下左手边的面板中订阅列表的根级别(root level),未读的内容就会出现在右手边的面板中。由于我每次都要这样做,因此我写了一个用户脚本来自动完成这项工作。
// ==UserScript==
// @name Bloglines Autoloader
// @namespace http://diveintogreasemonkey.org/download/
// @description Auto-display all new items in Bloglines
// @include http://bloglines.com/myblogs*
// @include http://www.bloglines.com/myblogs*
// ==/UserScript==
if (doLoadAll) {
doLoadAll();
}
这个用户脚本相当的简单。Bloglines 定义了一个函数 doLoadAll()
,当我点击订阅列表的根级别(root level)时就会执行它。调用这个函数可以显示所有的未读条目。
然而,由于 Bloglines 使用了帧(frame),而用户脚本会在每个帧中执行(因为他们都匹配我在 @include
中定义的模式),所以首先要检查在这个帧中是否有 doLoadAll()
函数:
if (doLoadAll) {
如果函数存在,我就调用它。因为用户脚本就像原页面的内容一样执行,我可以调用原页面里定义的任何脚本。
doLoadAll();
}
Ain't It Cool News 是一家致力于娱乐新闻报道方面的站点。我很喜欢这个站点。但是,我对网站的外观不太满意,每个新闻标题字体都太粗大了,而且在鼠标移过时还会改变颜色。所以,我写了一个用户脚本来改变我不喜欢的样式。
aintitreadable.user.js
// ==UserScript==
// @name Ain't It Readable
// @namespace http://diveintogreasemonkey.org/download/
// @description change style on aint-it-cool-news.com
// @include http://aint-it-cool-news.com/*
// @include http://*.aint-it-cool-news.com/*
// ==/UserScript==
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(
'h1, h2, h3, h4 {' +
' font-size: 12px ! important;' +
' line-height: 14px ! important;' +
' font-weight: normal ! important;' +
'}' +
'h1:hover, h2:hover, h3:hover, h4:hover {' +
' background-color: inherit ! important;' +
' color: inherit ! important;' +
'}');
这个用户脚本十分简单。首先,我定义了一个函数,可以把任意 CSS 添到页面中。有关此函数的信息,请阅读添加 CSS 样式 。
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);
}
接着,我调用这个函数来把 CSS 加到页面中:把标题变小一点,不那么粗,并且取消了当鼠标移过时颜色的变化。在这个案例里,页面对其中每一点都定义了样式规则,所以我用 ! important
关键字,以保证我的样式规则能替换掉页面原有的规则。
注释:这个函数只有一个参数:包含新增的样式规则的字符串。我把字符串排成容易阅读的格式,但是它仍然只是个字符串。
addGlobalStyle(
'h1, h2, h3, h4 {' +
' font-size: 12px ! important;' +
' line-height: 14px ! important;' +
' font-weight: normal ! important;' +
'}' +
'h1:hover, h2:hover, h3:hover, h4:hover {' +
' background-color: inherit ! important;' +
' color: inherit ! important;' +
'}');
offsiteblank.user.js
// ==UserScript==
// @name Offsite Blank
// @namespace http://diveintogreasemonkey.org/download/
// @description force offsite links to open in a new window
// @include http://*
// @include https://*
// ==/UserScript==
var a, thisdomain, links;
thisdomain = window.location.host;
links = document.getElementsByTagName('a');
for (var i = 0; i < links.length; i++) {
a = links[i];
if (a.host && a.host != thisdomain) {
a.target = "_blank";
}
}
首先,我声明这个用户脚本将在所有的网页上执行(但是不是全部,例如,本地机器上储存的 HTML 文档,可以从文件 (F ) → 打开 (O pen 菜单中打开)。
// @include http://*
// @include https://*
这段代码分为四步实现:
获取当前页面的域名很容易。更多的信息请阅读获取当前域名 。
thisdomain = window.location.host;
获取页面中所有链接也一样容易,尽管我应该注意到我忽视了自己的建议 简单地使用 document.getElementsByTagName('a')
而不用 XPath 查询。其实也没什么不同……
links = document.getElementsByTagName('a');
接下来,我遍历了所有的链接(实际上是所有的 <a>
元素,它们中的一些可能是链接)然后检查链接的域名和当前页面的域名是否匹配。由于一些链接可能指向非 HTTP 的 URL (例如,本地文件或者 FTP 服务器),我需要检查 a.host
是否存在,然后再检查它与当前域名是否相等.。
for (var i = 0; i < links.length; i++) {
a = links[i];
if (a.host && a.host != thisdomain) {
...
}
如果我找到一个包含域名的链接,但是它的域名与当前域名不相同,我只需设置它的 target
属性为 "_blank"
就可以强制链接在新窗口打开了。
a.target = "_blank";
当然,我不能修复世界上每个出版系统,但是我可以为自己写个用户脚本来解决,自动转换智能引号和其他有问题的高位字符为等同的7位 ASCII 字符。
dumbquotes.user.js
// ==UserScript==
// @name DumbQuotes
// @namespace http://diveintogreasemonkey.org/download/
// @description straighten curly quotes and apostrophes, simplify fancy dashes, etc.
// @include *
// ==/UserScript==
var replacements, regex, key, textnodes, node, s;
replacements = {
"\xa0": " ",
"\xa9": "(c)",
"\xae": "(r)",
"\xb7": "*",
"\u2018": "'",
"\u2019": "'",
"\u201c": '"',
"\u201d": '"',
"\u2026": "...",
"\u2002": " ",
"\u2003": " ",
"\u2009": " ",
"\u2013": "-",
"\u2014": "--",
"\u2122": "(tm)"};
regex = {};
for (key in replacements) {
regex[key] = new RegExp(key, 'g');
}
textnodes = document.evaluate(
"//text()",
document,
null,
XPathResult.UNORDERED_NODE_SNAPSHOT_TYPE,
null);
for (var i = 0; i < textnodes.snapshotLength; i++) {
node = textnodes.snapshotItem(i);
s = node.data;
for (key in replacements) {
s = s.replace(regex[key], replacements[key]);
}
node.data = s;
}
这段代码分为四个步骤:
第一步其实是两步。Javascript 的字符串替换基于正则表达式。所以要替换8位字符为等价的7位字符,需要创建一个正则表达式集合。
replacements = {
"\xa0": " ",
"\xa9": "(c)",
"\xae": "(r)",
"\xb7": "*",
"\u2018": "'",
"\u2019": "'",
"\u201c": '"',
"\u201d": '"',
"\u2026": "...",
"\u2002": " ",
"\u2003": " ",
"\u2009": " ",
"\u2013": "-",
"\u2014": "--",
"\u2122": "(tm)"};
regex = {};
for (key in replacements) {
regex[key] = new RegExp(key, 'g');
}
我使用花括号语法迅速建立了一个关联数组。等价于(但是键入更少)单独把每个关键字和值对应起来:
replacements["\xa0"] = " ";
replacements["\xa9"] = "(c)";
replacements["\xae"] = "(r)";
// 等等
每个8位的字符都是用十六进制值表示的,是用了逃逸语法(escaping syntax)"\xa0"
或 "\u2018"
。我们完成字符到字符串的关联数组后,我会遍历整个数组,然后建立正则表达式对象列表。每个正则表达式对象会在全局范围搜索8位字符。(第二个参数 'g'
意义为全局搜索;否则每个正则表达式只会搜索和替换第一次出现的特定8位字符,这样我可能会漏掉很多。)
下一步是获取当前文档中的全部文本节点。您可以非常想说,“嗨,我只要用 document.body.innerHTML
就得到全部页面的字符串,然后搜索替换就成了。 ”
var tmp = document.body.innerHTML;
// 在 tmp 上完成一批搜索和替换
document.body.innerHTML = tmp;
但这是个坏习惯,因为 innerHTML
会返回页面中的全部源代码:所有的标签、所有的脚本、所有的属性等等。在这种情况下,有可能不会造成问题(HTML 标签并不包含8位字符),但它在其他情况下不堪设想,很难调试。你要问你自己到底要搜索和替换什么。如果答案是“原始的页面源代码 ”,那么就去使用 innerHTML
。然而在这种情况下,答案是“全部的页面文字 ”,所以正确的方法是使用 XPath 查询获取所有文本节点。
textnodes = document.evaluate(
"//text()",
document,
null,
XPathResult.UNORDERED_NODE_SNAPSHOT_TYPE,
null);
这里使用了 XPath 函数,text()
,用来匹配任意文本节点。您可能对查询元素节点很熟悉了:所有 <a>
元素集,或者所有具有 alt
属性的 <img>
元素集。但是 DOM 也包含有节点中存在的文本内容。(也有其他类型的节点,例如注释和处理指令。)现在它也是我们感兴趣的文本节点。
第3步是遍历所有文本节点。这完全跟遍历像 //a[@href]
XPath 查询返回的节点集一样;唯一的不同就是遍历中的是文本节点,而不是元素节点。
for (var i = 0; i < textnodes.snapshotLength; i++) {
node = textnodes.snapshotItem(i);
s = node.data;
// 做替换操作
node.data = s;
node
是循环中的当前文本节点,而 s
是 node
中存在文本的字符串变量。我打算使用 s
完成替换操作,然后把处理结果复制回原始节点中。
所以现在我有了单个节点的文本,我需要完成替换操作。因为我已经建好了正则表达式列表和替换字符串列表,所以这相对简单。
for (key in replacements) {
s = s.replace(regex[key], replacements[key]);
}
Frownies 是对一个玩笑的回应。Greasemonkey 邮件列表 有人宣称他们开发了一个用户脚本,可以把 ASCII “表情符号 ” 比如 :-)
转换为与它们相同含义的图片。其他人答复道,想知道要完成逆向转换要花多久时间:转换图片表情为文本。
为了创纪录,我花了大约20分钟。大部分时间是花在研究已发布的自动转换图片表情的软件上了,然后编写了详尽的转换表。
这个脚本倚赖于能自动生成表情图片的大多数软件有如下特征:这些软件会把相同含义的文字表情符号放在 <img>
元素的 alt
属性里。所以,这个脚本实际上所做的是:如果 ALT 文本匹配预定义的常量列表中的某一条,就用图片的 ALT 文本来替换图片。
frownies.user.js
// ==UserScript==
// @name Frownies
// @namespace http://diveintogreasemonkey.org/download/
// @description convert graphical smilies to their text equivalents
// @include *
// ==/UserScript==
var smilies, images, img, replacement;
smilies = [":)", ":-)" ":-(", ":(", ";-)", ";)", ":-D", ":D", ":-/",
":/", ":X", ":-X", ":\">", ":P", ":-P", ":O", ":-O", "X-(",
"X(", ":->", ":>", "B-)", "B)", ">:)", ":((", ":(((", ":-((",
":))", ":-))", ":-|", ":|", "O:-)", "O:)", ":-B", ":B", "=;",
"I)", "I-)", "|-)", "|)", ":-&", ":&", ":-$", ":$", "[-(", ":O)",
":@)", "3:-O", ":(|)", "@};-", "**==", "(~~)", "*-:)", "8-X",
"8X", "=:)", "<):)", ";;)", ":*", ":-*", ":S", ":-S", "/:)",
"/:-)", "8-|", "8|", "8-}", "8}", "(:|", "=P~", ":-?", ":?",
"#-O", "#O", "=D>", "~:>", "%%-", "~O)", ":-L", ":L", "[-O<",
"[O<", "@-)", "@)", "$-)", "$)", ">-)", ":-\"", ":^O", "B-(",
"B(", ":)>-", "[-X", "[X", "\\:D/", ">:D<", "(%)", "=((", "#:-S",
"#:S", "=))", "L-)", "L)", "<:-P", "<:P", ":-SS", ":SS", ":-W",
":W", ":-<", ":<", ">:P", ">:-P", ">:/", ";))", ":-@", "^:)^",
":-J", "(*)", ":GRIN:", ":-)", ":SMILE:", ":SAD:", ":EEK:",
":SHOCK:", ":???:", "8)", "8-)", ":COOL:", ":LOL:", ":MAD:",
":RAZZ:", ":OOPS:", ":CRY:", ":EVIL:", ":TWISTED:", ":ROLL:",
":WINK:", ":!:", ":?:", ":IDEA:", ":ARROW:", ":NEUTRAL:",
":MRGREEN:"];
images = document.evaluate(
'//img[@alt]',
document,
null,
XPathResult.UNORDERED_NODE_SNAPSHOT_TYPE,
null);
for (var i = 0; i < images.snapshotLength; i++) {
img = images.snapshotItem(i);
alt = img.alt.toUpperCase();
for (var j in smilies) {
if (alt == smilies[j]) {
replacement = document.createTextNode(alt);
img.parentNode.replaceChild(replacement, img);
}
}
}
这段代码分为四个步骤:
alt
属性的图片。 <img>
元素为包含 ASCII 表情的文本节点。 第一步简单的定义了一个列表,用 Javascript 的 [ ]
语法。
smilies = [":)", ":-)" ":-(", ":(", ";-)", ";)", ":-D", ":D", ":-/",
":/", ":X", ":-X", ":\">", ":P", ":-P", ":O", ":-O", "X-(",
"X(", ":->", ":>", "B-)", "B)", ">:)", ":((", ":(((", ":-((",
":))", ":-))", ":-|", ":|", "O:-)", "O:)", ":-B", ":B", "=;",
"I)", "I-)", "|-)", "|)", ":-&", ":&", ":-$", ":$", "[-(", ":O)",
":@)", "3:-O", ":(|)", "@};-", "**==", "(~~)", "*-:)", "8-X",
"8X", "=:)", "<):)", ";;)", ":*", ":-*", ":S", ":-S", "/:)",
"/:-)", "8-|", "8|", "8-}", "8}", "(:|", "=P~", ":-?", ":?",
"#-O", "#O", "=D>", "~:>", "%%-", "~O)", ":-L", ":L", "[-O<",
"[O<", "@-)", "@)", "$-)", "$)", ">-)", ":-\"", ":^O", "B-(",
"B(", ":)>-", "[-X", "[X", "\\:D/", ">:D<", "(%)", "=((", "#:-S",
"#:S", "=))", "L-)", "L)", "<:-P", "<:P", ":-SS", ":SS", ":-W",
":W", ":-<", ":<", ">:P", ">:-P", ">:/", ";))", ":-@", "^:)^",
":-J", "(*)", ":GRIN:", ":-)", ":SMILE:", ":SAD:", ":EEK:",
":SHOCK:", ":???:", "8)", "8-)", ":COOL:", ":LOL:", ":MAD:",
":RAZZ:", ":OOPS:", ":CRY:", ":EVIL:", ":TWISTED:", ":ROLL:",
":WINK:", ":!:", ":?:", ":IDEA:", ":ARROW:", ":NEUTRAL:",
":MRGREEN:"];
接下来,我在页面中使用 XPath 查询语句搜索所有的含有 alt
属性的 <img>
元素。有关 XPath 查询语句的更多信息,请阅读操作所有有特定属性的元素 。
images = document.evaluate(
'//img[@alt]',
document,
null,
XPathResult.UNORDERED_NODE_SNAPSHOT_TYPE,
null);
第三步,遍历所有的 <img>
元素,检验 alt
属性是否与我们定义的表情符号匹配。由于一些表情符号含有字母,我用toUpperCase()方法在比较前把 alt
属性转换为大写。
for (var i = 0; i < images.snapshotLength; i++) {
img = images.snapshotItem(i);
alt = img.alt.toUpperCase())
for (var j in smilies) {
if (alt == smilies[j]) {
// ...
}
}
}
最后,我创建了一个包含文字表情符号的新文本节点,然后用它替换掉已有的 <img>
元素。更多的信息,请阅读替换元素为新内容 。
replacement = document.createTextNode(alt);
img.parentNode.replaceChild(replacement, img);
zoomtextarea.user.js
// ==UserScript==
// @name Zoom Textarea
// @namespace http://diveintogreasemonkey.org/download/
// @description add controls to zoom textareas
// @include *
// ==/UserScript==
var textareas, textarea;
textareas = document.getElementsByTagName('textarea');
if (!textareas.length) { return; }
function textarea_zoom_in(event) {
var link, textarea, s;
link = event.currentTarget;
textarea = link._target;
s = getComputedStyle(textarea, "");
textarea.style.width = (parseFloat(s.width) * 1.5) + "px";
textarea.style.height = (parseFloat(s.height) * 1.5) + "px";
textarea.style.fontSize = (parseFloat(s.fontSize) + 7.0) + 'px';
event.preventDefault();
}
function textarea_zoom_out(event) {
var link, textarea, s;
link = event.currentTarget;
textarea = link._target;
s = getComputedStyle(textarea, "");
textarea.style.width = (parseFloat(s.width) * 2.0 / 3.0) + "px";
textarea.style.height = (parseFloat(s.height) * 2.0 / 3.0) + "px";
textarea.style.fontSize = (parseFloat(s.fontSize) - 7.0) + "px";
event.preventDefault();
}
function createButton(target, func, title, width, height, src) {
var img, button;
img = document.createElement('img');
img.width = width;
img.height = height;
img.style.borderTop = img.style.borderLeft = "1px solid #ccc";
img.style.borderRight = img.style.borderBottom = "1px solid #888";
img.style.marginRight = "2px";
img.src = src;
button = document.createElement('a');
button._target = target;
button.title = title;
button.href = '#';
button.onclick = func;
button.appendChild(img);
return button;
}
for (var i = 0; i < textareas.length; i++) {
textarea = textareas[i];
textarea.parentNode.insertBefore(
createButton(
textarea,
textarea_zoom_in,
'Increase textarea size',
20,
20,
'data:image/gif;base64,'+
'R0lGODlhFAAUAOYAANPS1tva3uTj52NjY2JiY7KxtPf3%2BLOys6WkpmJiYvDw8fX19vb'+
'296Wlpre3uEZFR%2B%2Fv8aqpq9va3a6tr6Kho%2Bjo6bKytZqZml5eYMLBxNra21JSU3'+
'Jxc3RzdXl4emJhZOvq7KamppGQkr29vba2uGBgYdLR1dLS0lBPUVRTVYB%2Fgvj4%2BYK'+
'Bg6SjptrZ3cPDxb69wG1tbsXFxsrJy29vccDAwfT09VJRU6uqrFlZW6moqo2Mj4yLjLKy'+
's%2Fj4%2BK%2Busu7t783Nz3l4e19fX7u6vaalqNPS1MjHylZVV318ftfW2UhHSG9uccv'+
'KzfHw8qqqrNPS1eXk5tvb3K%2BvsHNydeLi40pKS2JhY2hnalpZWlVVVtDQ0URDRJmZm5'+
'mYm11dXp2cnm9vcFxcXaOjo0pJSsC%2FwuXk6AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA'+
'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAC'+
'H5BAAAAAAALAAAAAAUABQAAAeagGaCg4SFhoeIiYqKTSQUFwgwi4JlB0pOCkEiRQKKRxM'+
'gKwMGDFEqBYpPRj4GAwwLCkQsijwQBAQJCUNSW1mKSUALNiVVJzIvSIo7GRUaGzUOPTpC'+
'igUeMyNTIWMHGC2KAl5hCBENYDlcWC7gOB1LDzRdWlZMAZOEJl83VPb3ggAfUnDo5w%2F'+
'AFRQxJPj7J4aMhYWCoPyASFFRIAA7'),
textarea);
textarea.parentNode.insertBefore(
createButton(
textarea,
textarea_zoom_out,
'Decrease textarea size',
20,
20,
'data:image/gif;base64,'+
'R0lGODlhFAAUAOYAANPS1uTj59va3vDw8bKxtGJiYrOys6Wkpvj4%2BPb29%2FX19mJiY'+
'%2Ff3%2BKqqrLe3uLKytURDRFpZWqmoqllZW9va3aOjo6Kho4KBg729vWJhZK%2BuskZF'+
'R4B%2FgsLBxHNydY2Mj%2Ff396amptLS0l9fX9fW2dDQ0W1tbpmZm8DAwfT09fHw8n18f'+
'uLi49LR1V5eYOjo6VBPUa6tr769wEhHSNra20pJStPS1KuqrNPS1ZmYm%2B7t77Kys8rJ'+
'y%2Fj4%2BaSjpm9uca%2BvsMjHyqalqHRzdVJRU8PDxVRTVcvKzc3Nz0pKS9rZ3evq7MC'+
'%2FwsXFxp2cnnl4e1VVVu%2Fv8ba2uM7Oz29vcbu6vZqZmnJxc9vb3PHx8uXk5mhnamJh'+
'Y1xcXZGQklZVV29vcHl4eoyLjKqpq6Wlpl1dXuXk6AAAAAAAAAAAAAAAAAAAAAAAAAAAA'+
'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA'+
'AAACH5BAAAAAAALAAAAAAUABQAAAeZgGaCg4SFhoeIiYqKR1IWVgcyi4JMBiQqA0heQgG'+
'KQTFLPQgMCVocBIoNNqMgCQoDVReKYlELCwUFI1glEYorOgopWSwiTUVfih8dLzRTKA47'+
'Ek%2BKBGE8GEAhFQYuPooBOWAHY2ROExBbSt83QzMbVCdQST8Ck4QtZUQe9faCABlGrvD'+
'rB4ALDBMU%2BvnrUuOBQkE4NDycqCgQADs%3D'),
textarea);
textarea.parentNode.insertBefore(
document.createElement('br'),
textarea);
}
这段代码看起来很复杂,而且它确实复杂,但是有特殊原因的。看起来很复杂是因为它中间有大段的乱码似的字符串。这些是的 data:
URI s ,它们看起来像地狱却很容易生成。真正复杂的地方还在别处。
首先,我获取了页面上所有的 <textarea>
元素集。此模式的详情,请阅读操作特定 HTML 元素的所有实例 。如果没有,那就没必要继续了,所以 return
(返回)。
textareas = document.getElementsByTagName('textarea');
if (!textareas.length) { return; }
现在,我们跑下题。我们的“工具栏 ”看起来像是一行按钮,但是每个按钮就是看起来可以点的某些图片,外面包上一个能执行我们的 Javascript 函数的链接。
由于我们要创建多个按钮(尽管这个脚本只有两个按钮,但是您可以便利地扩展出更多功能),我创建了一个函数来封装所有的制造按钮的逻辑。
function createButton(target, func, title, width, height, src) {
The createButton
function takes 6 arguments:
target
<textarea>
元素
func
title
width
src
参数提供的图形实际宽度一致。
height
src
参数提供的图形实际高度一致。
src
data:
URI
创建一个按钮分为两个步骤:创建 <img>
元素,然后创建其外的 <a>
元素。
我调用 document.createElement
来创建 <img>
元素,并且设置了一小部分属性,包含样式的属性。关于此模式的更多信息,请阅读设置元素样式 。
img = document.createElement('img');
img._target = target;
img.width = width;
img.height = height;
img.style.borderTop = img.style.borderLeft = "1px solid #ccc";
img.style.borderRight = img.style.borderBottom = "1px solid #888";
img.style.marginRight = "2px";
img.src = src;
这里有个很酷的技巧,您可以使用下面的语法同时为两个属性赋上相同的值:
img.style.borderTop = img.style.borderLeft = "1px solid #ccc";
OK, 继续前进。创建按钮的第二部分是创建链接(<a>
元素),然后把 <img>
元素放在里面。
button = document.createElement('a');
button._target = target;
button.title = title;
button.href = '#';
button.onclick = func;
button.appendChild(img);
我想指出两点。第一,我需要赋给链接伪造的 href
属性,否则 Firefox 会把它当作命名锚并且不会把它加到 tab 索引中(也就是您无法通过 tab 移动它上面,也就无法通过键盘使用)。第二,我设置了 _target
属性来保存目标 <textarea>
的引用。这在 Javascript 中是完全合法的;您只需要给新属性赋值,就可以给对象创建新属性。稍后,在 onclick
的事件句柄中,我会访问这个自定义的 _target
属性。
现在,让我们跳回到 onclick
句柄。每个句柄都是一个函数,有一个参数:event
。
function textarea_zoom_in(event)
event
对象有许多属性,现在我感兴趣的只有一个:currentTarget
。
link = event.currentTarget;
如果您阅读过关于 Event
对象的文档 ,您可以读到很多目标相关的属性,包含一个简单的:target
。您可能很想使用 event.target
去获取已访问的链接,但是它表现(依我看来)不一致。当用户 tab 到按钮上然后敲回车键
,event.target
是这个链接,但是当用户用鼠标点击按钮时,event.target
是链接中的图片!我想这肯定有个合理的解释,但是它超过了我对 DOM 事件模型的理解层次。无论如何,event.currentTarget
在所有情况下都返回链接,所以我使用它。
下一步我取回了目前打算缩放的 <textarea>
的引用,通过自定义的 _target
属性,我在创建按钮时设置了这个属性。
textarea = link._target;
现在好戏刚刚开始。(您认为您已经找到乐趣了吗!)我需要获得 <textarea>
当前的长宽和字体大小,这样我就可以把它们放大。简单的从textarea.style
(textarea.style.width
, textarea.style.height
和 textarea.style.fontSize
)中获得适合的属性并不 管用,因为只有在 <textarea>
的 style
属性中定义它们的时候才能得到值。这不是我想要的;我要得到真实的当前样式。我需要的是 getComputedStyle
. 关于此函数的更多信息,请阅读获取元素样式 。
s = getComputedStyle(textarea, "");
textarea.style.width = (parseFloat(s.width) * 1.5) + "px";
textarea.style.height = (parseFloat(s.height) * 1.5) + "px";
textarea.style.fontSize = (parseFloat(s.fontSize) + 7.0) + 'px';
最后,您还记得按钮链接上为了键盘易用新而加的伪造的 href
值吗?嗯,它已经变成了烦恼,因为 Firefox 执行完 onclick
句柄后,它就会尝试跳转到这个链接。因为它指向了不存在的锚,无论按钮在什么地方,Firefox 都会跳到页面的顶端。这是件恼人的事,要阻止它,我需要在完成 onclick
句柄前调用 event.preventDefault()
。
event.preventDefault();
脚本的其余部分很简单。我遍历了所有的 <textarea>
元素,为他们各自创建缩放按钮(每个都有它自己的 onclick
句柄和按钮图片),然后在 <textarea>
前插入缩放按钮。对于每张图片,我使用 data:
URI 来创建内嵌的图片,所以用户不需要访问远端服务器去获取按钮图片。关于此模式的更多信息,请阅读在元素前插入内容 和在没有服务器的情况下添加图片 。
for (var i = 0; i < textareas.length; i++) {
textarea = textareas[i];
textarea.parentNode.insertBefore(
createButton(
textarea,
textarea_zoom_in,
'Increase textarea size',
20,
20,
'data:image/gif;base64,'+
'R0lGODlhFAAUAOYAANPS1tva3uTj52NjY2JiY7KxtPf3%2BLOys6WkpmJiYvDw8fX19vb'+
'296Wlpre3uEZFR%2B%2Fv8aqpq9va3a6tr6Kho%2Bjo6bKytZqZml5eYMLBxNra21JSU3'+
'Jxc3RzdXl4emJhZOvq7KamppGQkr29vba2uGBgYdLR1dLS0lBPUVRTVYB%2Fgvj4%2BYK'+
'Bg6SjptrZ3cPDxb69wG1tbsXFxsrJy29vccDAwfT09VJRU6uqrFlZW6moqo2Mj4yLjLKy'+
's%2Fj4%2BK%2Busu7t783Nz3l4e19fX7u6vaalqNPS1MjHylZVV318ftfW2UhHSG9uccv'+
'KzfHw8qqqrNPS1eXk5tvb3K%2BvsHNydeLi40pKS2JhY2hnalpZWlVVVtDQ0URDRJmZm5'+
'mYm11dXp2cnm9vcFxcXaOjo0pJSsC%2FwuXk6AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA'+
'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAC'+
'H5BAAAAAAALAAAAAAUABQAAAeagGaCg4SFhoeIiYqKTSQUFwgwi4JlB0pOCkEiRQKKRxM'+
'gKwMGDFEqBYpPRj4GAwwLCkQsijwQBAQJCUNSW1mKSUALNiVVJzIvSIo7GRUaGzUOPTpC'+
'igUeMyNTIWMHGC2KAl5hCBENYDlcWC7gOB1LDzRdWlZMAZOEJl83VPb3ggAfUnDo5w%2F'+
'AFRQxJPj7J4aMhYWCoPyASFFRIAA7'),
textarea);
textarea.parentNode.insertBefore(
createButton(
textarea,
textarea_zoom_out,
'Decrease textarea size',
20,
20,
'data:image/gif;base64,'+
'R0lGODlhFAAUAOYAANPS1uTj59va3vDw8bKxtGJiYrOys6Wkpvj4%2BPb29%2FX19mJiY'+
'%2Ff3%2BKqqrLe3uLKytURDRFpZWqmoqllZW9va3aOjo6Kho4KBg729vWJhZK%2BuskZF'+
'R4B%2FgsLBxHNydY2Mj%2Ff396amptLS0l9fX9fW2dDQ0W1tbpmZm8DAwfT09fHw8n18f'+
'uLi49LR1V5eYOjo6VBPUa6tr769wEhHSNra20pJStPS1KuqrNPS1ZmYm%2B7t77Kys8rJ'+
'y%2Fj4%2BaSjpm9uca%2BvsMjHyqalqHRzdVJRU8PDxVRTVcvKzc3Nz0pKS9rZ3evq7MC'+
'%2FwsXFxp2cnnl4e1VVVu%2Fv8ba2uM7Oz29vcbu6vZqZmnJxc9vb3PHx8uXk5mhnamJh'+
'Y1xcXZGQklZVV29vcHl4eoyLjKqpq6Wlpl1dXuXk6AAAAAAAAAAAAAAAAAAAAAAAAAAAA'+
'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA'+
'AAACH5BAAAAAAALAAAAAAUABQAAAeZgGaCg4SFhoeIiYqKR1IWVgcyi4JMBiQqA0heQgG'+
'KQTFLPQgMCVocBIoNNqMgCQoDVReKYlELCwUFI1glEYorOgopWSwiTUVfih8dLzRTKA47'+
'Ek%2BKBGE8GEAhFQYuPooBOWAHY2ROExBbSt83QzMbVCdQST8Ck4QtZUQe9faCABlGrvD'+
'rB4ALDBMU%2BvnrUuOBQkE4NDycqCgQADs%3D'),
textarea);
textarea.parentNode.insertBefore(
document.createElement('br'),
textarea);
}
Access Bar 可以显示出页面中已定义的快捷键。快捷键是由网页作者定义的,用来增强页面易用性的键盘快捷方式,让您可以通过键盘快速跳转到特定的链接或表单。(了解快捷键 。)
Firefox 支持使用快捷键,但是它不会显示页面中已定义的快捷键。由此,Access Bar 诞生了。
accessbar.user.js
// ==UserScript==
// @name Access Bar
// @namespace http://diveintogreasemonkey.org/download/
// @description show accesskeys defined on page
// @include *
// ==/UserScript==
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);
}
var akeys, descriptions, a, desc, label, div;
akeys = document.evaluate(
"//*[@accesskey]",
document,
null,
XPathResult.UNORDERED_NODE_SNAPSHOT_TYPE,
null);
if (!akeys.snapshotLength) { return; }
descriptions = new Array();
desc = '';
for (var i = 0; i < akeys.snapshotLength; i++) {
a = akeys.snapshotItem(i);
desctext = '';
if (a.nodeName == 'INPUT') {
label = document.evaluate("//label[@for='" + a.name + "']",
document,
null,
XPathResult.FIRST_ORDERED_NODE_TYPE,
null).singleNodeValue;
if (label) {
desctext = label.title;
if (!desctext) { desctext = label.textContent; }
}
}
if (!desctext) { desctext = a.textContent; }
if (!desctext) { desctext = a.title; }
if (!desctext) { desctext = a.name; }
if (!desctext) { desctext = a.id; }
if (!desctext) { desctext = a.href; }
if (!desctext) { desctext = a.value; }
desc = '<strong>[' +
a.getAttribute('accesskey').toUpperCase() + ']</strong> ';
if (a.href) {
desc += '<a href="' + a.href + '">' + desctext + '</a>';
} else {
desc += desctext;
}
descriptions.push(desc);
}
descriptions.sort();
div = document.createElement('div');
div.id = 'accessbar-div-0';
desc = '<div><ul><li class="first">' + descriptions[0] + '</li>';
for (var i = 1; i < descriptions.length; i++) {
desc = desc + '<li>' + descriptions[i] + '</li>';
}
desc = desc + '</ul></div>';
div.innerHTML = desc;
document.body.style.paddingBottom = "4em";
window.addEventListener(
"load",
function() {
document.body.appendChild(div);
},
true);
addGlobalStyle(
'#accessbar-div-0 {'+
' position: fixed;' +
' left: 0;' +
' right: 0;' +
' bottom: 0;' +
' top: auto;' +
' border-top: 1px solid silver;' +
' background: black;' +
' color: white;' +
' margin: 1em 0 0 0;' +
' padding: 5px 0 0.4em 0;' +
' width: 100%;' +
' font-family: Verdana, sans-serif;' +
' font-size: small;' +
' line-height: 160%;' +
'}' +
'#accessbar-div-0 a,' +
'#accessbar-div-0 li,' +
'#accessbar-div-0 span,' +
'#accessbar-div-0 strong {' +
' background-color: transparent;' +
' color: white;' +
'}' +
'#accessbar-div-0 div {' +
' margin: 0 1em 0 1em;' +
'}' +
'#accessbar-div-0 div ul {' +
' margin-left: 0;' +
' margin-bottom: 5px;' +
' padding-left: 0;' +
' display: inline;' +
'}' +
'#accessbar-div-0 div ul li {' +
' margin-left: 0;' +
' padding: 3px 15px;' +
' border-left: 1px solid silver;' +
' list-style: none;' +
' display: inline;' +
'}' +
'#accessbar-div-0 div ul li.first {' +
' border-left: none;' +
' padding-left: 0;' +
'}');
这段代码分为六个步骤:
addGlobalStyle
accesskey
熟悉的页面元素 accesskey
的元素,构建一个有序的链接列表。 ul
和 li
元素 首先,我需要一个辅助函数 addGlobalStyle
,注入我自己的 CSS 样式(在第六步中会用到)。关于此模式的更多信息,请阅读添加 CSS 样式 。
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);
}
第二步,找出页面中有 accesskey
属性的全部元素。在 Firefox 的 XPath 支持下很容易办到。注释:如果 XPath 查询返回空集,我可以保证,那是因为这里没有什么需要显示的。此模式的更多信息,请阅读操作所有有特定属性的元素 。
var akeys, descriptions, a, i, desc, label, div;
akeys = document.evaluate(
"//*[@accesskey]",
document,
null,
XPathResult.UNORDERED_NODE_SNAPSHOT_TYPE,
null);
if (!akeys.snapshotLength) { return; }
第三步,构造每个启用 accesskey
元素的适当描述的清单列表,这是脚本中最复杂的一段。问题是 accesskey
属性可以出现在许多不同的 HTML 元素中。
表单中的 input
元素可以定义 accesskey
。input
元素可有或可无相关联的 label
,它包含与 input
域有关联的文字标签。如果有,label
可能包含 title
属性,提供关于 input
域的更多详细信息。或者 label
属性只包含文本。或者 input
元素可能没有关联的标签,这中情况下,我最好使用 input
元素的 value
属性。
另一方面,label
自己也可以定义 accesskey
,代替标签描述的 input
元素。再者,我会查找 label
元素的 title
属性作为描述,如果 title
不存在,就退而使用标签的文本。
链接同样可以定义 accesskey
属性。如果有,选择链接的文本是理所当然的。但是如果链接不包含文本(例如,它只包含一张图片),那么下个地方就是找链接的 title
属性。如果链接既没有文本,又没有 title
,我就退而使用链接的 name
属性,要是还是不成,就用链接的 id
属性。
Ain't heuristics a bitch? 这是完整的算法。记着,akeys
是一个 XPathResult
对象,所以我要调用 akeys.snapshotItem(i)
来得到每个结果。
descriptions = new Array();
desc = '';
for (var i = 0; i < akeys.snapshotLength; i++) {
a = akeys.snapshotItem(i);
desctext = '';
if (a.nodeName == 'INPUT') {
label = document.evaluate("//label[@for='" + a.name + "']",
document,
null,
XPathResult.FIRST_ORDERED_NODE_TYPE,
null).singleNodeValue;
if (label) {
desctext = label.title;
if (!desctext) { desctext = label.textContent; }
}
}
if (!desctext) { desctext = a.textContent; }
if (!desctext) { desctext = a.title; }
if (!desctext) { desctext = a.name; }
if (!desctext) { desctext = a.id; }
if (!desctext) { desctext = a.href; }
if (!desctext) { desctext = a.value; }
desc = '<strong>[' +
a.getAttribute('accesskey').toUpperCase() + ']</strong> ';
if (a.href) {
desc += '<a href="' + a.href + '">' + desctext + '</a>';
} else {
desc += desctext;
}
descriptions.push(desc);
}
第四步比较简单,因为 Javascript 数组有个 sort
方法,可以给数组在原地排序。
descriptions.sort();
第五步创建了 HTML 代码,用来渲染启用 accesskey
的元素列表。我创建一个外包装 <div>
,为字符串形式的快捷键列表构造 HTML ,设置外包装 <div>
的 innerHTML
属性,最后把它加到页面的末尾。因为当用户脚本执行时,对页面的复杂改动应该在页面加载完成以后,所以我使用 window.addEventListener
加入 onload
事件,来向页面中加入 <div>
。
有关 innerHTML
的用法,请阅读快速插入复杂的 HTML , 有关 window.addEventListener
的用法,请阅读处理已渲染的页面 。
div = document.createElement('div');
div.id = 'accessbar-div-0';
desc = '<div><ul><li class="first">' + descriptions[0] + '</li>';
for (var i = 1; i < descriptions.length; i++) {
desc = desc + '<li>' + descriptions[i] + '</li>';
}
desc = desc + '</ul></div>';
div.innerHTML = desc;
document.body.style.paddingBottom = "4em";
window.addEventListener(
"load",
function() {
document.body.appendChild(div);
},
true);
最后,第六步,为了使插入的 HTML 看起来更协调,我在页面中加入了自己的 CSS 声明。(很明显,它将作为固定的黑色工具栏出现在页面的底部。滚动页面的时候,它仍然在那个位置,这是因为 Firefox 支持 position:fixed
显示类型。)更多的信息,请阅读添加 CSS 样式 。
addGlobalStyle(
'#accessbar-div-0 {'+
' position: fixed;' +
' left: 0;' +
' right: 0;' +
' bottom: 0;' +
' top: auto;' +
' border-top: 1px solid silver;' +
' background: black;' +
' color: white;' +
' margin: 1em 0 0 0;' +
' padding: 5px 0 0.4em 0;' +
' width: 100%;' +
' font-family: Verdana, sans-serif;' +
' font-size: small;' +
' line-height: 160%;' +
'}' +
'#accessbar-div-0 a,' +
'#accessbar-div-0 li,' +
'#accessbar-div-0 span,' +
'#accessbar-div-0 strong {' +
' background-color: transparent;' +
' color: white;' +
'}' +
'#accessbar-div-0 div {' +
' margin: 0 1em 0 1em;' +
'}' +
'#accessbar-div-0 div ul {' +
' margin-left: 0;' +
' margin-bottom: 5px;' +
' padding-left: 0;' +
' display: inline;' +
'}' +
'#accessbar-div-0 div ul li {' +
' margin-left: 0;' +
' padding: 3px 15px;' +
' border-left: 1px solid silver;' +
' list-style: none;' +
' display: inline;' +
'}' +
'#accessbar-div-0 div ul li.first {' +
' border-left: none;' +
' padding-left: 0;' +
'}');
|
GM_setValue
保存脚本专用配置值,GM_getValue
读取它。
function GM_setValue(key, value);
function GM_getValue(key, defaultValue);
key
参数是没有固定格式(no fixed format)的字符串。value
可以是字符串,布尔值或者整数。GM_getValue
的 defaultValue
参数是可选的;如果设置了此参数,当查询的 key
指定的值不存在时返回此默认参数。如果不设置 defaultValue
,当查询的 key
指定的值不存在时,GM_getValue
会返回 undefined
。
这2个函数由 Greasemonkey 0.3 引入。您应该测试它们是否存在 ,否则完全降低(degrade gracefully)。
GM_registerMenuCommand
)来切换脚本是否生效。 function GM_registerMenuCommand(menuText, callbackFunction);
menuText
参数是一个字符串,新增的菜单项的名称。callbackFunction
参数是函数对象。选中此菜单项,callbackFunction
函数就会被调用。
这个函数由 Greasemonkey 0.2.6 引入。您应该测试它是否存在 ,否则完全降低(degrade gracefully)。
更多细节和示例,请阅读GM_xmlhttpRequest 。
这个函数是在 Greasemonkey 0.2.6 中引入的。您应该测试它是否存在 ,否则完全降低(degrade gracefully)。
GM_xmlhttpRequest
,利用 Flickr 自己的 REST API,在 Flickr 上新增功能。 <a href="...">
链接,但是仍然可以跟踪点击。在适当的跟踪 URL 上调用 GM_xmlhttpRequest
。 想让您的用户脚本“长出 ” Greasemonkey 的架构吗?您的用户脚本需要访问 Javascript 特许函数,本地文件,或者只有全资格的扩展才具有的其它 Firefox 特性?可以把您的用户脚本转换为全资格的 XPI
,仅仅点几下就行了。感谢 Adrian Holovaty 惊人的 Greasemonkey 编译器 !
访问 Butler 用户脚本源代码 。在菜单中,选择编辑 (E ) → 全选 (A ) (Ctrl -A )复制用户脚本源代码到剪贴板。
访问Greasemonkey 编译器 。
转到 “Javascript ” field. 在菜单中,选择编辑 (E ) → 粘贴 (P ) (Ctrl -V )粘贴 Butler 用户脚本源代码。
在 “Creator(作者) ” 栏中,填入 Mark Pilgrim
。
在 “Version(版本) ” 栏中,填入 Butler 用户脚本的当前版本(0.3截至发稿时, 否则请检查脚本的元数据段 找到确切的版本)。
打开一个新窗口或者标签,访问 GUID Generator(GUID 产生器) ,产生一个随机的 GUID。把这个 GUID 复制到剪贴板,包含花括号。
切换回 “Greasemonkey 编译器 ” 页面。在 “GUID ” 栏中,粘贴刚才从 GUID 产生器上获得的 GUID。
在 “Homepage(主页) ” 栏中,填入
。 http://diveintomark.org/projects/butler/
点击 Create the Firefox extension(创建 Firefox 扩展) 。Firefox 会弹出一个对话框:“打开 butler.xpi ”。选择保存到磁盘 ,并且选择下载目录
好啦!您先在有 Butler 的浏览器扩展了。在安装 Butler 扩展前,应该先禁用 Butler 用户脚本 然后 Google 一下禁用是否生效。然后安装 Butler 扩展,选择文件 (F ) → 打开... (O ) 然后选中用 Greasemonkey 编辑器创建的 butler.xpi
文件。务必在安装完成后重新启动浏览器。
.xpi
文件其实就是有确定的目录结构的 ZIP 压缩包。可以用任一 ZIP 程序(例如 Windows 下用 7-zip 或者在 Mac OS X 下用 Stuffit Expander )解压缩文档,然后看下浏览器扩展是由哪些文件组成。
butler.xpi
|
+-- install.rdf
|
+-- chrome/
|
+-- butler/
|
+-- content/
|
+-- browser.xul
|
+-- contents.rdf
|
+-- javascript.js
这里有四个有关的文件。两个 RDF 文件是大多数 Firefox 的样板文件。其他两个包含有实现代码。
install.rdf
browser.xul
@include
和 @exclude
参数,然后注入执行脚本。
contents.rdf
javascript.js
基本上,Greasemonkey 编译器创建了真正的 Greasemonkey 缩减版,没有用户脚本,在适当的 URL 上自动加载一个用户脚本。但是如果有所有的源文件,您可以自己创建自定义对话框、修改配置面板,或者注册User Script C ommands 以外的菜单项……发狂了!
GM_getValue — 读取脚本专用的配置值
returntype GM_getValue ( |
key , | |
defaultValue ) ; |
GM_getValue
返回一条脚本专用的配置值。返回值可以是字符串,布尔值或者整数。key
参数是没有固定格式(no fixed format)的字符串。defaultValue
参数是可选的;如果设置了此参数,当查询的 key
指定的值不存在时返回此默认参数。如果不设置 defaultValue
,当查询的 key
指定的值不存在时返回 undefined
。
Greasemonkey 的配置值与浏览器 cookies 很相似,但也有重要的区别。两者都存放在本地电脑中,但是 cookies 是域名专用的而且只能被创建它们的域名访问,Greasemonkey 配置值是脚本专用的而且只能被创建这些参数的脚本访问(与执行用户脚本的地址也无关)。而且与 cookies 不同的是,配置值绝不会发送到远程服务器上去。
GM_setValue — 设置脚本专用的配置值
function GM_setValue (
key , value )
;
GM_registerMenuCommand — 在 用户脚本命令 (C ) 子菜单中添加菜单项
function GM_registerMenuCommand ( |
commandName , | |
commandFunc , | ||
accelKey , | ||
accelModifiers , | ||
accessKey ) ; |
GM_registerMenuCommand
在 工具 (T ) → G reasemonkey → 用户脚本命令 (C ) 子菜单中添加菜单项。commandName
参数是一个字符串,新增的菜单项的名称。commandFunc
参数是函数对象。选中此菜单项,commandFunc
函数就会被调用。accelKey
单字符(例如:'g')或者键盘码,用来触发命令。accelKey
字符串,列出了必须与 accelKey
同时按下的修饰键。如果多于一个功能键,用空格分开。例如,'shift' 或者 'ctrl alt'。可用的功能键有:shift, alt, meta, control 和 accel。accessKey
单字符(例如:'g')当菜单打开时用来跳转到命令。它最好是 commandName
中的一个字母。
function callbackFunction (
e )
;
参数 e
是菜单选择事件。我不知道这有什么用。
GM_xmlhttpRequest — 进行任意的 HTTP 请求
GM_xmlhttpRequest (
details )
;
GM_xmlhttpRequest
建立任意的 HTTP 请求。 详细
参数是一个有七个域的对象。
GET
,但也可使任一个 HTTP 动词,包含 POST
, PUT
和 DELETE
。
headers: {'User-Agent': 'Mozilla/4.0 (compatible) Greasemonkey',
'Accept': 'application/atom+xml,application/xml,text/xml'}
method
== 'POST'
),在 headers
域中必需有 Content-type
,值为 'application/x-www-form-urlencoded'
,而且在 data
域包含有 URL 编码后的表单数据。
onload
onerror
onreadystatechange
onload
回调 onload
的回调函数,有一个变量,responseDetails
。
function onloadCallback ( |
responseDetails ) ; |
responseDetails
是有五个域的对象。
200
意为请求正常完成。
onerror
回调 onerror
的回调函数,有一个参数,responseDetails
。
function onerrorCallback ( |
responseDetails ) ; |
responseDetails
是有五个域的对象。
404
意为页面无法找到。
onreadystatechange
回调 onreadystatechange
的回调函数,当请求正在进行时反复调用。有一个参数,responseDetails
。
function onreadystatechangeCallback ( |
responseDetails ) ; |
responseDetails
是有五个域的对象。responseDetails.readyState
指示了请求当前所在的阶段。
responseDetails.readyState
<
4
时,这个值是0
。
responseDetails.readyState
<
4
时,这个值是空字符串。
responseDetails.readyState
<
4
时,这个值是空字符串。
responseDetails.readyState
<
4
时,这个值是空字符串。
1
2
3
4
下面的代码从http://greaseblog.blogspot.com/ 获取 Atom feed,然后用警告窗口显示结果。
GM_xmlhttpRequest({
method: 'GET',
url: 'http://greaseblog.blogspot.com/atom.xml',
headers: {
'User-agent': 'Mozilla/4.0 (compatible) Greasemonkey',
'Accept': 'application/atom+xml,application/xml,text/xml',
},
onload: function(responseDetails) {
alert('Request for Atom feed returned ' + responseDetails.status +
' ' + responseDetails.statusText + '\n\n' +
'Feed data:\n' + responseDetails.responseText);
}
});
tag:
URI s 2.2. 用元数据描述您的用户脚本 id
和 class
的属性。 3.5. 其他调试工具 Event
文档 5.7. 案例:Zoom Textarea GM_registerMenuCommand
)来切换脚本是否生效。 6.1. 存取持久数据 GM_xmlhttpRequest
,利用 Flickr 自己的 REST API,在 Flickr 上新增功能。 6.3. 整合其他网站的数据 <a href="...">
链接,但是仍然可以跟踪点击。在适当的跟踪 URL 上调用 GM_xmlhttpRequest
。 6.3. 整合其他网站的数据 XMLHttpRequest
在 Mozilla 中的支持 GM_xmlhttpRequest XMLHttpRequest
在 Internet Explorer 中的支持 GM_xmlhttpRequest XMLHttpRequest
在 Safari 中的支持 GM_xmlhttpRequest 您可以在Greasemonkey脚本库 ,找到许多用户脚本。尽管没人要求您必须把脚本放到那儿去,但是实际上,您可以把您的脚本共享到任何地方,这样别人就可以安装它了。甚至您根本不需要一台网络服务器,因为你可以从本地文件中安装用户脚本。 1.3. 安装用户脚本
元数据可以以任意次序排列。笔者推荐使用@name
,@namespace
,@description
,@include
,最后是@exclude
,但是其它的顺序也没关系。 2.2. 用元数据描述您的用户脚本
在“管理用户脚本 ”对话框中,点击编辑 ,您正在“动态 ”修改脚本的副本,它在 Firefox 的个人配置文件夹中。我形成了一个习惯,每“动态 ”修改完毕,又回到编辑器,选择文件(F ) → 另存为(a )... ,把用户脚本保存到另一个文件夹中。尽管这不是必须的(Greasemonkey 只会用您配置文件夹中的那个脚本),但是我喜欢在完成全部修改后把脚本在其它文件夹里保存一个“原本 ”。 2.4. 修改用户脚本
在错误控制台中可以用右键(Mac用户用 control-click)点击任意行选中,然后选择复制(C ) ,将信息复制到剪贴板。 3.2. 用 GM_log
记日志
如果已有一个元素的引用(例如 thisElement
),可以用 thisElement.nodeName
来判断它的 HTML 标签。如果页面被当作 text/html
类型,标签名称就总是大写,不论它在原始页面是如何定义的。如果页面被当作 application/xhtml+xml
类型,那么标签名称就总是小写的。我总是用 thisElement.nodeName.toUpperCase()
这样我就可以不用管这些了。 4.6. 操作所有有特定属性的元素
即使 someExistingElement
是它的父元素的最后一个孩子(在它之后没有下一个元素),仍然可以在 someExistingElement.nextSibling
之前插入新内容。在这种情况下,someExistingElement.nextSibling
将返回一个空值,insertBefore
函数将把新内容追加到最后。(以许这对你来说没有太大意义,但我想让您知道的是,尽管这种方法似乎是不太对,但它却总是有效的。) 4.8. 在元素后插入内容
使用 data:
URI kitchen 来建立您自己的 data:
URL s。 4.12. 在没有服务器的情况下添加图片
可以使用 addGlobalStyle
函数样式化插入到页面中的元素或者部分原页面中已有的元素。然而,如果要样式化已有的元素,应该在您定义的每条规则上使用 ! important
关键字,保证您的样式覆盖原页面中已定义的规则。 4.13. 添加 CSS 样式
删除和替换 document.body.innerHTML
并不会更改页面。原页面在 <head>
中定义的所有内容仍然是有效的,包括页面的标题,CSS 样式,以及脚本。可以分开修改或者移除。 4.16. 处理已渲染的页面
正常情况下,当用户提交一个表单时,例如,点击表单中的 提交 按钮或者按 回车键
),都会触发 submit
事件。但是,当脚本调用 aForm.submit()
提交表单时,却不会 触发 submit
事件。因此,您必须做两件事来捕获表单的提交事件:给 submit
事件增加事件监听,并且 修改 HTMLFormElement
类的原型来重定向 submit()
方法到您的自定义函数上。 4.22. 覆盖内建的 Javascript 方法
访问 about:config
,然后再过滤器中填 greasemonkey.scriptvals
,就可以查看已存储的配置值。 GM_getValue
当您在本书中看到“参考资料 ”链接列表时,您可以点“参考资料 ”的标题就可以跳转到这个所有参考资料链接清单。 “参考资料 ”链接清单
当您在本书中看到技巧或者警告时,您可以点击前面的提示小图标,然后跳转到此页。 技巧清单
当您在本书中看到视频的链接时,点击提示 小图标,可以跳转到全部视频清单。 步骤清单
helloworld.user.js
gmlog.user.js
) GM_xmlhttpRequest
函数无效时警告用户 <textarea>
元属 title
属性的元素 class
为 sponsoredlink
的 <div>
xpath
函数 <hr>
<hr>
style
属性定义的样式 https://
地址 aintitreadable.user.js
offsiteblank.user.js
dumbquotes.user.js
frownies.user.js
zoomtextarea.user.js
accessbar.user.js
修订 2005-05-09 | 2005-05-09 |
|
|
修订 2005-05-06 | 2005-05-06 |
|
|
修订 2005-05-05 | 2005-05-05 |
|
|
修订 2005-05-04 | 2005-05-04 |
|
|
修订 2005-05-03 | 2005-05-03 |
|
|
修订 2005-05-02 | 2005-05-02 |
|
|
修订 2005-05-01 | 2005-05-01 |
|
|
修订 2005-04-30 | 2005-04-30 |
修订 2005-04-25 | 2005-04-25 |
|
我使用 Emacs ,在 DocBook XML 中撰写了此书。SAXON 把它转换为了 HTML (使用了 Norman Walsh 的 Norman Walsh 的 XSL 样式表 的自定义版本)。HTMLDoc 把它转为了 PDF 。 w3m 把它转为了纯文本。Plucker Distiller 把它转换为了 Palm OS™ 数据库所以您能在移动设备上阅读它。由 Ant 脚本自动化了上述过程。
我用 CamStudio 和 TMPGEnc 制作了附带的视频。
Dean Edwards 写了 js-highlight 。它提供了 Javascript 范例的自动语法高亮的功能。
如果您对学习 DocBook 的技术写作感兴趣,您可以下载这本书的 XML 源代码 ,其中包含此书的 DocBook 版本和所有 XSL 样式表以及我用来生成此书的不同版本的安装脚本。而且,您也该阅读下这本权威的书 DocBook: The Definitive Guide 。您也可以订阅DocBook 邮件列表 。
简体中文版主要翻译人员:Fiag(fiag.hit _at_ gmail _dot_ com);Drsu;Baggio。
由于 HTMLDoc 不支持中文,所以暂时无中文PDF文档。
大多数软件许可证决意剥夺你的共享和修改软件的自由。对比之下,GNU通用公共许可证力图保证你的共享和修改自由软件的自由——保证自由软件对所有用户是 自由的。GPL适用于大多数自由软件基金会的软件,以及由使用这些软件而承担义务的作者所开发的软件。(自由软件基金会的其他一些软件受 GNU库通用许可证的保护)。你也可以将它用到你的程序中。
当我们谈到自由软件(free software)时,我们指的是自由而不是价格。我们的 GNU通用公共许可证决意保证你有发布自由软件的自由(如果你愿意,你可以对此项服务收取一定的费用);保证你能收到源程序或者在你需要时能得到它;保证 你能修改软件或将它的一部分用于新的自由软件;而且还保证你知道你能做这些事情。
为了保护你的权利,我们需要作出规定:禁止任何人不承认你的权利,或者要求你放弃这些权利。如果你修改了自由软件或者发布了软件的副本,这些规定就转化为你的责任。
例如,如果你发布这样一个程序的副本,不管是收费的还是免费的,你必须将你具有的一切权利给予你的接受者;你必须保证他们能收到或得到源程序;并且将这些条款给他们看,使他们知道他们有这样的权利。
我们采取两项措施来保护你的权利:
同样,为了保护每个作者和我们自己,我们需要清楚地让每个人明白,自由软件没有担保(no warranty)。如果由于其他某个人修改了软件,并继续加以传播。我们需要它的接受者明白:他们所得到的并不是原来的自由软件。由其他人引人的任何问 题,不应损害原作者的声誉。
最后,任何自由软件不断受到软件专利的威胁。我们希望避免这种风险:自由软件的再发布者以个人名义获得专利许可证,从而将软件变为私有。为防止这一点,我们必须明确:任何专利必须以允许每个人自由使用为前提,否则就不准许有专利。
以下是有关复制,发布和修改的条款和条件。
此许可证适用于任何包含版权所有者声明的程序和其他作品,版权所有者在声明中明确说明程序和作品可以在GPL条款的约束下发布。下面提到的“程序”指的是任何这样的程序或作品。而 “基于程序的作品 ” 指的是程序或者任何受版权法约柬的衍生作品。也就是说包含程序或程序的一部分的作品。可以是原封不动的,或经过修改的和/或翻译成其他语言的(程序)。(在下文中,翻译包含在“修改 ”的条款中。) 每个许可证接受人(licensee)用“你 ”来称呼。
许可证条款不适用于复制,发布和修改以外的活动。这些活动超出这些条款的范围。运行程序的活动不受条款的限止。仅当程序的输出构成基于程序作品的内容时,这一条款才适用(如果只运行程序就无关)。是否普遍适用取决于程序具体用来做什么。
只要你在每一副本上明显和恰当地出版版权声明和不承担担保的声明,保持此许可证的声明和没有担保的声明完整无损,并和程序一起绘每个其他的程序接受者一份许可证的副本,你就可以用任何媒体复制和发布你收到的原始的程序的源代码。
你可以为转让副本的实际行动收取一定费用。你也有权选择提供担保以换取一定费用。
你可以修改程序的一个或几个副本或程序的任何部分,以此形成基于程序的作品。只要你同时满足下面的所有条件,你就可以按前面 第 1 款 的要求复制和发布这一经过修改的程序或作品:
这些要求适用于修改了的作品的整体。如果能够确定作品的一部分并非程序的衍生产品,可以合理地认为这部分是独立的,是不同的作品。当你将它作为独立作品发 布时,它不受此许可证和它的条款的约束。但是当你将这部分作为基于程序的作品的一部分发布时,作为整体它将受到许可证条款约束。准予其他许可证持有人的使 用范围扩大到整个产品。也就是每个部分,不管它是谁写的。
因此,本条款的意图不在于索取权利;或剥夺全部由你写成的作品的权利。而是履行权利来控制基于程序的集体作品或衍生作品的发布。
此外,将与程序无关的作品和该程序或基于程序的作品一起放在存贮体或发布媒体的同一卷上,并不导致将其他作品置于此许可证的约束范围之内。
你可以以目标码或可执行形式复制或发布程序(或符合 第 2 款 的基于程序的作品),只要你遵守前面的第1 , 2 款,并同时满足下列3条中的1条:
作品的源码指的是对作品进行修改最优先择取的形式。对可执行的作品讲,完整的源码包括:所有模块的所有源程序,加上有关的接口的定义,加上控制可执行作品 的安装和编译的 script。作为特殊例外,发布的源码不必包含任何常规发布的供可执行代码在上面运行的操作系统的主要组成部分(如编译程序,内核等)。除非这些组成部 分和可执行作品结合在一起。
如果采用提供对指定地点的访问和复制的方式发布可执行码或目标码,那么,提供对同一地点的访问和复制源码可以算作源码的发布,即使第三方不强求与目标码一起复制源码。
除非你明确按许可证提出的要求去做,否则你不能复制,修改,转发许可证和发布程序。任何试图用其他方式复制,修改,转发许可证和发布程序是无效的。而且将 自动结束许可证赋予你的权利。然而,对那些从你那里按许可证条款得到副本和权利的人们,只要他们继续全面履行条款,许可证赋予他们的权利仍然有效。
你没有在许可证上签字,因而你没有必要一定接受这一许可证。然而,没有任何其他东西赋予你修改和发布程序及其衍生作品的权利。如果你不接受许可证,这些行 为是法律禁止的。因此,如果你修改或发布程序(或任何基于程序的作品),你就表明你接受这一许可证以及它的所有有关复制,发布和修改程序或基于程序的作品 的条款和条件。
每当你重新发布程序(或任何基于程序的作品)时,接受者自动从原始许可证颁发者那里接到受这些条款和条件支配的复制,发布或修改程序的许可证。你不可以对接受者履行这里赋予他们的权利强加其他限制。你也没有强求第三方履行许可证条款的义务。
如果由于法院判决或违反专利的指控或任何其他原因(不限于专利问题)的结果,强加于你的条件(不管是法院判决,协议或其他)和许可证的条件有冲突。他们也 不能用许可证条款为你开脱。在你不能同时满足本许可证规定的义务及其他相关的义务时,作为结果,你可以根本不发布程序。例如,如果某一专利许可证不允许所 有那些直接或间接从你那里接受副本的人们在不付专利费的情况下重新发布程序,唯一能同时满足两方面要求的办法是停止发布程序。
如果本条款的任何部分在特定的环境下无效或无法实施,就使用条款的其余部分。并将条款作为整体用于其他环境。
本条款的目的不在于引诱你侵犯专利或其他财产权的要求,或争论这种要求的有效性。本条款的主要目的在于保护自由软件发布系统的完整性。它是通过通用公共许 可证的应用来实现的。许多人坚持应用这一系统,已经为通过这一系统发布大量自由软件作出慷慨的供献。作者/捐献者有权决定他/她是否通过任何其他系统发布 软件。许可证待有人不能强制这种选择。
本节的目的在于明确说明许可证其余部分可能产生的结果。
如果由于专利或者由于有版权的接口问题使程序在某些国家的发布和使用受到限止,将此程序置于许可证约束下的原始版权拥有者可以增加限止发布地区的条款,将这些国家明确排除在外。并在这些国家以外的地区发布程序。在这种情况下,许可证包含的限止条款和许可证正文一样有效。
自由软件基金会可能随时出版通用公共许可证的修改版或新版。新版和当前的版本在原则上保持一致,但在提到新问题时或有关事项时,在细节上可能出现差别。
每一版本都有不同的版本号。如果程序指定适用于它的许可证版本号以及“任何更新的版本”。你有权选择遵循指定的版本或自由软件基金会以后出版的新版本,如果程序未指定许可证版本,你可选择自由软件基金会已经出版的任何版本。
如果你愿意将程序的一部分结合到其他自由程序中,而它们的发布条件不同。写信给作者,要求准予使用。如果是自由软件基金会加以版权保护的软件,写信给自由 软件基金会。我们有时会作为例外的情况处理。我们的决定受两个主要目标的指导。这两个主要目标是:我们的自由软件的衍生作品继续保持自由状态。以及从整体 上促进软件的共享和重复利用。
由于程序准予免费使用,在适用法准许的范围内,对程序没有担保。除非另有书面说明,版权所有者和/或其他提供程序的人们“一样”不提供任何类型的担保。不 论是明确的,还是隐含的。包括但不限于隐含的适销和适合特定用途的保证。全部的风险,如程序的质量和性能问题都由你来承担。如果程序出现缺陷,你承担所有 必要的服务,修复和改正的费用。
除非适用法或书面协议的要求,在任何情况下,任何版权所有者或任何按许可证条款修改和发布程序的人们都不对你的损失负有任何责任。包括由于使用或不能使用 程序引起的任何一般的,特殊的,偶然发生的或重大的损失(包括但不限于数据的损失,或者数据变得不精确,或者你或第三方的持续的损失,或者程序不能和其他 程序协调运行等)。即使版权所有者和其他人提到这种损失的可能性也不例外。
条款和条件结束
如果你开发了新程序,而且你需要它得到公众最大限度的利用。要做到这一点的最好办法是将它变为自由软件。使得每个人都能在遵守条款的基础上对它进行修改和重新发布。
为了做到这一点,绘程序附上下列声明。最安全的方式是将它放在每个源程序的开头,以便最有效地传递拒绝担保的信息。每个文件至少应有“版权所有”行以及在什么地方能看到声明全文的说明。
<用一行空间给出程序的名称和它用来做什么的简单说明。> 版权所有 (C) <年份> <作者姓名>
这一程序是自由软件,你可以遵照自由软件基金会出版的GNU通用公共许可证条款来修改和重新发布这一程序。或者用许可证的第二版,或者(根据你的选择)用任何更新的版本。
发布这一程序的目的是希望它有用,但没有任何担保。甚至没有适合特定目的的隐含的担保。更详细的情况请参阅GNU通用公共许可证。
你应该已经和程序一起收到一份GNU通用公共许可证的副本。如果还没有,写信给:The Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
当然,请加上通过电子或者纸质邮件联系你的信息。
如果程序以交互方式进行工作,当它开始进人交互方式工作时,使它输出类似下面的简短声明:
Gnomovision第69版,版权所有(C) 年份,作者姓名 Gnomovision绝对没有担保。要知道详细情况,请输人 ‘show w’。 这是自由软件,欢迎你遵守一定的条件重新发布它,要知道详细情况,请输人‘Show c’。
假设的命令‘shovr w’和‘show c’应显示通用公共许可证的相应条款。当然,你使用的命令名称可以不同于‘show w’和‘show c’。根据你的程序的具体情况,也可以用菜单或鼠标选项来显示这些条款。
如果需要,你应该取得你的上司(如果你是程序员)或你的学校签署放弃程序版权的声明。下面只是一个例子,你应该改变相应的名称:
Ynyodyne公司以此方式放弃 James Harker 所写的 Gnomovision 程序的全部版权利益。
<Ty coon签名>,1989年4月1日 Ty Coon, 副总裁
这一许可证不允许你将程序并人专用程序。如果你的程序是一个子程序库。你可能会认为用库的方式和专用应用程序连接更有用。如果这是你想做的事,使用GNU库通用公共许可证代替本许可证。