aboutsummaryrefslogtreecommitdiff
path: root/files/zh-cn/mozilla/add-ons/sdk/guides
diff options
context:
space:
mode:
Diffstat (limited to 'files/zh-cn/mozilla/add-ons/sdk/guides')
-rw-r--r--files/zh-cn/mozilla/add-ons/sdk/guides/content_scripts/index.html486
-rw-r--r--files/zh-cn/mozilla/add-ons/sdk/guides/index.html115
-rw-r--r--files/zh-cn/mozilla/add-ons/sdk/guides/multiprocess_firefox_and_the_sdk/index.html212
-rw-r--r--files/zh-cn/mozilla/add-ons/sdk/guides/working_with_events/index.html122
4 files changed, 935 insertions, 0 deletions
diff --git a/files/zh-cn/mozilla/add-ons/sdk/guides/content_scripts/index.html b/files/zh-cn/mozilla/add-ons/sdk/guides/content_scripts/index.html
new file mode 100644
index 0000000000..fa95b15db3
--- /dev/null
+++ b/files/zh-cn/mozilla/add-ons/sdk/guides/content_scripts/index.html
@@ -0,0 +1,486 @@
+---
+title: Content Scripts(内容脚本)
+slug: Mozilla/Add-ons/SDK/Guides/Content_Scripts
+translation_of: Archive/Add-ons/Add-on_SDK/Guides/Content_Scripts
+---
+<article id="wikiArticle">{{AddonSidebar}}
+<p><span class="seoSummary">很多 add-ons 需要访问和修改 web 页面的内容。但是 add-on 的主代码不能直接访问 web 内容。替代方案是, SDK add-ons 需要使用一些分散的脚本代理访问 web 内容,这些脚本被称作<em>内容脚本(content scripts)</em>。本页面描述如何开发和部署内容脚本。 </span></p>
+
+<p>内容脚本是在使用SDK时很令人疑惑的点,但你很有可能不得不使用它们。下面有五个基本原则:</p>
+
+<ul>
+ <li>add-on 的主代码,包括"main.js"和其他"lib"下的模块,可以使用 SDK <a href="/zh-CN/docs/Mozilla/Add-ons/SDK/High-Level_APIs">高层次</a>和<a href="/zh-CN/docs/Mozilla/Add-ons/SDK/Low-Level_APIs">低层次</a> APIs,但不能直接访问 web 内容</li>
+ <li>内容脚本 <a href="/en-US/Add-ons/SDK/Guides/Two_Types_of_Scripts#API_Access_for_Add-on_Code_and_Content_Scripts">不能使用 SDK 的 API</a>(访问不了 globals 的 <code>exports</code>、<code>require</code>),但你可以访问 web 内容</li>
+ <li>SDK API 可以使用,内容脚本,比如 <a href="/en-US/Add-ons/SDK/High-Level_APIs/page-mod">page-mod</a> 和 <a href="/en-US/Add-ons/SDK/High-Level_APIs/tabs">tabs</a>,提供了一些函数,使得 add-on 的主代码可以将内容脚本载入web页面中。</li>
+ <li>内容脚本可以作为字符串加载,但是更常见的是分离存储为 add-on 的"data"目录下文件。 jpm 不会默认创建"data"目录,所以你必须添加该目录并把脚本放进去。</li>
+ <li>一个消息传递 API 允许主代码和内容脚本间相互通信。</li>
+</ul>
+
+<p>这个完整的 add-on 表现出所有的这些原则。它的"main.js"使用 <a href="/en-US/Add-ons/SDK/High-Level_APIs/tabs">tabs</a> 模块附加了一个内容脚本到当前标签页。本例中内容脚本作为字符串传递,内容脚本简单地替换了页面的内容:</p>
+
+<pre class="brush: js">// main.js
+var tabs = require("sdk/tabs");
+var contentScriptString = 'document.body.innerHTML = "&lt;h1&gt;this page has been eaten&lt;/h1&gt;";'
+
+tabs.activeTab.attach({
+ contentScript: contentScriptString
+});</pre>
+
+<p>下面的高层次 SDK 模块能使用内容脚本来修改 web 页面:</p>
+
+<ul>
+ <li><a href="/en-US/Add-ons/SDK/High-Level_APIs/page-mod">page-mod</a>:使你能附加一个内容脚本到匹配上特定 URL 模式的web页面。</li>
+ <li><a href="/en-US/Add-ons/SDK/High-Level_APIs/tabs">tabs</a>:导出一个 <code>Tab</code> 对象来处理浏览器标签页。<code>Tab</code> 对象包括了一个 <a href="/en-US/Add-ons/SDK/High-Level_APIs/tabs#attach(options)"><code>attach()</code></a> 函数来附加内容脚本到标签页。</li>
+ <li><a href="/en-US/Add-ons/SDK/High-Level_APIs/page-worker">page-worker</a>:让你能够恢复一个 web 页面,但不显示它。你可以附加内容脚本到该页面,来访问和操作该页面的 DOM。</li>
+ <li><a href="/en-US/Add-ons/SDK/High-Level_APIs/context-menu">context-menu</a>:使用内容脚本来和按钮所在的页面交互。</li>
+</ul>
+
+<p>另外,还能使用 HTML 定义了一些 SDK 用户接口组件,并且使用分类的脚本来和这些内容交互。从很多方面来讲,这些脚本就像内容脚本一样,但它们并不是本文的关注点。要学习如何和用户接口模块的内容交互,请参看模块定义文档:<a href="/en-US/Add-ons/SDK/High-Level_APIs/panel">panel</a>、<a href="/en-US/Add-ons/SDK/Low-Level_APIs/ui_sidebar">sidebar</a>、<a href="/en-US/Add-ons/SDK/Low-Level_APIs/ui_frame">frame</a>。</p>
+
+<p>这篇指南中列出的几乎所有的示例都是完整并且且最小的,可以在 Github 的 <a href="https://github.com/mdn/addon-sdk-content-scripts">addon-sdk-content-scripts repository</a> 页面上获得。</p>
+
+<h2 id="加载用户脚本">加载用户脚本</h2>
+
+<article id="wikiArticle">
+<p>你可以声明一个字符串或者指定 <code>contentScript</code> 或 <code>contentScriptFile</code> 选项加载一个单独的脚本。<code>contentScript</code> 选项接受一个作为脚本的字符串:</p>
+
+<pre class="brush: js">// main.js
+
+var pageMod = require("sdk/page-mod");
+var contentScriptValue = 'document.body.innerHTML = ' +
+ ' "&lt;h1&gt;Page matches ruleset&lt;/h1&gt;";';
+
+pageMod.PageMod({
+ include: "*.mozilla.org",
+ contentScript: contentScriptValue
+});</pre>
+
+<p><code>contentScriptFile</code> 选项接受一个作为 resource:// URL 的字符串,指向一个存储在你的 add-on 的 <code>data</code> 目录中的脚本文件。jpm不会默认创建"data"目录,所以你必须创建该目录并将你的用户脚本放进去。</p>
+
+<p>本 add-on 提供一个 URL ,指向"content-script.js"文件,存储在 add-on 根目录下的 <code>data</code> 子目录:</p>
+
+<pre class="brush: js">// main.js
+
+var data = require("sdk/self").data;
+var pageMod = require("sdk/page-mod");
+
+pageMod.PageMod({
+ include: "*.mozilla.org",
+ contentScriptFile: data.url("content-script.js")
+});</pre>
+
+<pre class="brush: js">// content-script.js
+
+document.body.innerHTML = "&lt;h1&gt;Page matches ruleset&lt;/h1&gt;";</pre>
+
+<div class="note">
+<p>从 Firefox 34 开始,你可以使用"./content-script.js"替代 self.data.url("content-script.js")。所以你可以像这样重写:</p>
+
+<pre class="brush: js">var pageMod = require("sdk/page-mod");
+
+pageMod.PageMod({
+ include: "*.mozilla.org",
+ contentScriptFile: "./content-script.js"
+});
+</pre>
+</div>
+
+<div class="warning">
+<p>除非你的内容脚本非常简单并且固定是一个静态的字符串,请不要使用 <code>contentScript</code>:否则,你会在从 AMO 获取你的add-on上遇到问题。</p>
+
+<p>相反,把脚本放到一个单独的文件并用 <code>contentScriptFile</code> 加载它。这回事你的代码更易维护、安全、调试和审核。</p>
+</div>
+
+<p>你可以给 <code>contentScript</code> 或 <code>contentScriptFile</code> 传递字符串数组来加载多个脚本:</p>
+
+<pre class="brush: js">// main.js
+
+var tabs = require("sdk/tabs");
+
+tabs.on('ready', function(tab) {
+ tab.attach({
+ contentScript: ['document.body.style.border = "5px solid red";', 'window.alert("hi");']
+ });
+});
+</pre>
+
+<pre class="brush: js">// main.js
+
+var data = require("sdk/self").data;
+var pageMod = require("sdk/page-mod");
+
+pageMod.PageMod({
+ include: "*.mozilla.org",
+ contentScriptFile: [data.url("jquery.min.js"), data.url("my-content-script.js")]
+});</pre>
+
+<p>如果你这么做,这些脚本之间可以直接交互,就像他们被同一个 web 页面加载一样。</p>
+
+<p>你也可以把 <code>contentScript</code> 和 <code>contentScriptFile</code> 一起用。如果你这么做,使用 <code>contentScriptFile</code> 定义的脚本会在使用 <code>contentScript</code> 定义的脚本之前加载。这使你能够用 URL 加载比如 jQuery 这样的 JavaScript 库,然后传递一个简单的能够使用jQuery脚本:</p>
+
+<pre class="brush: js">// main.js
+
+var data = require("sdk/self").data;
+var pageMod = require("sdk/page-mod");
+
+var contentScriptString = '$("body").html("&lt;h1&gt;Page matches ruleset&lt;/h1&gt;");';
+
+pageMod.PageMod({
+ include: "*.mozilla.org",
+ contentScript: contentScriptString,
+ contentScriptFile: data.url("jquery.js")
+});</pre>
+
+<div class="warning">
+<p>除非你的内容脚本非常简单并且固定是一个静态的字符串,请不要使用 <code>contentScript</code>:否则,在从 AMO 获取你的 add-on 上,你会遇到问题。</p>
+
+<p>相反,把脚本放到一个单独的文件并用 <code>contentScriptFile</code> 加载它。这回事你的代码更易维护、安全、调试和审核。</p>
+</div>
+
+<h3 id="控制附加脚本的时间">控制附加脚本的时间</h3>
+
+<p><code>contentScriptWhen</code> 选项指定了什么时候加载内容脚本。从这里选一个:</p>
+
+<ul>
+ <li><code>"start"</code>:页面 document 元素插入 DOM 之后,立即加载脚本。这时 DOM 的内容仍未加载,所以脚本不能与其交互。</li>
+ <li><code>"ready"</code>:页面 DOM 加载完后加载脚本:也就是说,在那个时间点 <a href="https://developer.mozilla.org/en/Gecko-Specific_DOM_Events">DOMContentLoaded</a> 事件触发。这时,内容脚本可以和DOM内容交互,但外部引用的样式表和图片可能还没有完成加载。</li>
+ <li><code>"end"</code>:页面上所有内容(DOM、JS、CSS、images)加载完后,加载脚本,就是在 <a href="https://developer.mozilla.org/en/DOM/window.onload">window.onload 事件</a>触发的时候</li>
+</ul>
+
+<p>默认值为 <code>"end"</code>。</p>
+
+<p>注意 <a href="/en-US/Add-ons/SDK/High-Level_APIs/tabs#attach(options)"><code>tab.attach()</code></a> 不支持 contentScriptWhen,因为它原来就是在页面加载页面的时候被调用的。</p>
+
+<h3 id="传递配置选项">传递配置选项</h3>
+
+<p><code>contentScriptOptions</code> 是一个作为只读对象暴露给内容脚本的JSON对象,在 <code><a href="/en-US/Add-ons/SDK/Guides/Content_Scripts/self">self</a>.options</code> 的属性里:</p>
+
+<pre class="brush: js">// main.js
+
+var tabs = require("sdk/tabs");
+
+tabs.on('ready', function(tab) {
+ tab.attach({
+ contentScript: 'window.alert(self.options.message);',
+ contentScriptOptions: {"message" : "hello world"}
+ });
+});</pre>
+
+<p>这里可以使用任何可以转成json的值(object、array、string等等)。</p>
+
+<h2 id="访问_DOM">访问 DOM</h2>
+
+<p>内容脚本可以访问页面的 DOM,就像任何页面中加载的脚本(页面脚本)一样。但是内容脚本和页面脚本之间是隔离的:</p>
+
+<ul>
+ <li>内容脚本不能看到任何由页面脚本添加到页面的 JavaScript 对象</li>
+ <li>如果页面脚本重定义了某个 DOM 对象的行为,但内容脚本只会看到原来的那个行为。</li>
+</ul>
+
+<p>相反也是如此:页面脚本不能看到内容脚本添加的 JavaScript 对象。</p>
+
+<p>例如,假想一个页面用页面脚本添加变量 <code>foo</code> 到 <code>window</code> 对象:</p>
+
+<pre class="brush: html">&lt;!DOCTYPE html"&gt;
+&lt;html&gt;
+ &lt;head&gt;
+ &lt;script&gt;
+ window.foo = "hello from page script"
+ &lt;/script&gt;
+ &lt;/head&gt;
+&lt;/html&gt;</pre>
+
+<p>在这个脚本后面加载到页面的其他脚本也可以访问 <code>foo</code>。但是内容脚本不能:</p>
+
+<pre class="brush: js">// main.js
+
+var tabs = require("sdk/tabs");
+var mod = require("sdk/page-mod");
+var self = require("sdk/self");
+
+var pageUrl = self.data.url("page.html")
+
+var pageMod = mod.PageMod({
+ include: pageUrl,
+ contentScript: "console.log(window.foo);"
+})
+
+tabs.open(pageUrl);</pre>
+
+<pre>console.log: my-addon: null
+</pre>
+
+<p>这种隔离策略有着很合理的理由。首先,这意味着内容脚本不会泄露对象给 web 页面,这样可能会打开安全漏洞。第二,这意味着,在内容脚本创建对象的时候,可以不用担心是否会和页面脚本添加的对象相冲突。</p>
+
+<p>这种隔离意味着,例如,如果一个 web 页面加载了 jQuery 库,那么内容脚本不能够看到由该库添加的 <code>jQuery</code> 对象——但是可以看到内容脚本添加的自己的 <code>jQuery</code> 对象,并且它不会和页面脚本的 jQuery 版本冲突。</p>
+
+<h3 id="和页面脚本交互">和页面脚本交互</h3>
+
+<p>一般来说,这种内容脚本和页面脚本的隔离正是你所希望的。但是有时候你也许会希望和页面脚本交互:你想在内容脚本和页面脚本之间共享对象来,来在它们之间发送消息。如果你需要这么做,请阅读<a href="/en-US/Add-ons/SDK/Guides/Content_Scripts/Interacting_with_page_scripts">和页面脚本交互</a>。</p>
+
+<h3 id="事件监听器">事件监听器</h3>
+
+<p>你可以监听 DOM 的事件,就像在页面脚本中一样,但是有两个重要的区别:</p>
+
+<p>第一,如果你向 <a href="https://developer.mozilla.org/en/DOM/element.setAttribute"><code>setAttribute()</code></a> 传递字符串,来定义了事件监听器,那么此监听器被当做是在页面上下文中的,所以它不能访问任何内容脚本中的变量。</p>
+
+<p>如下,内容脚本会失败报错"theMessage is not defined":</p>
+
+<pre class="brush: js">var theMessage = "Hello from content script!";
+anElement.setAttribute("onclick", "alert(theMessage);");</pre>
+
+<p>Second, if you define an event listener by direct assignment to a <a href="/en-US/docs/Web/API/GlobalEventHandlers">global event handler</a> like <code>onclick</code>, then the assignment might be overridden by the page. For example, here's an add-on that tries to add a click handler by assignment to <code>window.onclick</code>:</p>
+
+<pre class="brush: js">var myScript = "window.onclick = function() {" +
+ " console.log('unsafewindow.onclick: ' + window.document.title);" +
+ "}";
+
+require("sdk/page-mod").PageMod({
+ include: "*",
+ contentScript: myScript,
+ contentScriptWhen: "start"
+});</pre>
+
+<p>这个示例会在大多数页面上正常工作,但是会在定义 <code>onclick</code> 的页面上失败:</p>
+
+<pre class="brush: html">&lt;html&gt;
+ &lt;head&gt;
+ &lt;/head&gt;
+ &lt;body&gt;
+ &lt;script&gt;
+ window.onclick = function() {
+ window.alert("it's my click now!");
+ }
+ &lt;/script&gt;
+ &lt;/body&gt;
+&lt;/html&gt;</pre>
+
+<p>由于这些原因,最好还是用 <a href="https://developer.mozilla.org/en/DOM/element.addEventListener"><code>addEventListener()</code> 添加一个事件监听器</a>,定义监听器为一个函数:</p>
+
+<pre class="brush: js">var theMessage = "Hello from content script!";
+
+anElement.onclick = function() {
+ alert(theMessage);
+};
+
+anotherElement.addEventListener("click", function() {
+ alert(theMessage);
+});</pre>
+
+<h2 id="和_add-on_通信">和 add-on 通信</h2>
+
+<p>为了使 add-on 脚本和内容脚本相互通信,任何一通信端都要访问 <code>port</code> 对象。</p>
+
+<ul>
+ <li>要从一头发送消息到另一头,使用 <code>port.emit()</code></li>
+ <li>要从另一头接收消息,使用 <code>port.on()</code></li>
+</ul>
+
+<p><img alt="" src="https://mdn.mozillademos.org/files/7873/content-scripting-overview.png" style="display: block; margin-left: auto; margin-right: auto;">消息是异步的:也就是说,发送方不会等待接收方的回应,而仅仅是发送消息完后继续处理别的事情。</p>
+
+<p>这里有一个简单的 add-on 使用 <code>port</code> 发送一个消息到内容脚本:</p>
+
+<pre class="brush: js">// main.js
+
+var tabs = require("sdk/tabs");
+var self = require("sdk/self");
+
+tabs.on("ready", function(tab) {
+ var worker = tab.attach({
+ contentScriptFile: self.data.url("content-script.js")
+ });
+ worker.port.emit("alert", "Message from the add-on");
+});
+
+tabs.open("http://www.mozilla.org");</pre>
+
+<pre class="brush: js">// content-script.js
+
+self.port.on("alert", function(message) {
+ window.alert(message);
+});</pre>
+
+<div class="note">
+<p>context-menu 模块没有使用这里描述的通信模型。了解更多关于使用 context-menu 和内容脚本通信的事情,参看 <a href="/en-US/Add-ons/SDK/High-Level_APIs/context-menu">context-menu documentation</a>。</p>
+</div>
+
+<h3 id="在内容脚本中访问_port"><code>在内容脚本中访问 port</code></h3>
+
+<p>内容脚本中,<code>port</code> 对象是作为global下 <a href="/en-US/Add-ons/SDK/Guides/Content_Scripts/self"><code>self</code></a> 对象的属性。所以要从内容脚本中发送消息的话:</p>
+
+<pre class="brush: js">self.port.emit("myContentScriptMessage", myContentScriptMessagePayload);</pre>
+
+<p>要从 add-on 代码接收消息</p>
+
+<pre class="brush: js">self.port.on("myAddonMessage", function(myAddonMessagePayload) {
+ // Handle the message
+});</pre>
+
+<div class="note">
+<p>注意 global下 <a href="/en-US/Add-ons/SDK/Guides/Content_Scripts/self"><code>self</code></a> 对象和 <a href="https://developer.mozilla.org/en-US/Add-ons/SDK/High-Level_APIs/self"><code>self</code> 模块</a>完全不一样,后者提供一个API给 add-on,用来访问它的数据文件和ID。</p>
+</div>
+
+<h3 id="在内容脚本中访问_port_2">在内容脚本中访问 port</h3>
+
+<p>在 add-on 代码中,联通 add-on 和某一特定内容脚本上下文的通道被封装入 <a href="https://developer.mozilla.org/en-US/Add-ons/SDK/Low-Level_APIs/content_worker"><code>worker</code></a> 对象。所以和内容脚本通信的 <code>port</code> 对象其实是其相对应的 <code>worker</code> 对象的一个属性。</p>
+
+<p>但是,这个 worker 没有暴露给 add-on 代码,以及同样所有的模块。</p>
+
+<h4 id="从_page-worker">从 <code>page-worker</code></h4>
+
+<p><code>page-worker</code> 对象直接整合了 work API。所以要从一个由 <code>page-worker</code> 关联的内容脚本接收消息的话,你可以使用 <code>pageWorker.port.on()</code>:</p>
+
+<pre class="brush: js">// main.js
+
+var self = require("sdk/self");
+
+var pageWorker = require("sdk/page-worker").Page({
+  contentScriptFile: self.data.url("content-script.js"),
+  contentURL: "http://en.wikipedia.org/wiki/Internet"
+});
+
+pageWorker.port.on("first-para", function(firstPara) {
+  console.log(firstPara);
+});</pre>
+
+<p>要从你的 add-on 发送用户定义的消息,你可以只调用 <code>pageWorker.port.emit()</code>:</p>
+
+<pre class="brush: js">// main.js
+
+var self = require("sdk/self");
+
+var pageWorker = require("sdk/page-worker").Page({
+  contentScriptFile: self.data.url("content-script.js"),
+  contentURL: "http://en.wikipedia.org/wiki/Internet"
+});
+
+pageWorker.port.on("first-para", function(firstPara) {
+  console.log(firstPara);
+});
+
+pageWorker.port.emit("get-first-para");</pre>
+
+<pre class="brush: js">// content-script.js
+
+self.port.on("get-first-para", getFirstPara);
+
+function getFirstPara() {
+ var paras = document.getElementsByTagName("p");
+ if (paras.length &gt; 0) {
+ var firstPara = paras[0].textContent;
+ self.port.emit("first-para", firstPara);
+ }
+}</pre>
+
+<h4 id="从_page-mod">从<code> page-mod</code></h4>
+
+<p>单个 <code>page-mod</code> 对象可以附加它的脚本到多个页面,每个页面有它自己的上下文来运行内容脚本,所以每个页面都需要相互隔离的通道(worker)。</p>
+
+<p>所以 <code>page-mod</code> 没有直接整合 worker 的 API。而是在每次内容脚本被附加到页面时,page-mod 发送一个 <code>attach</code> 事件,它的监听器会给对应的上下文传递一个 worker。通过为 <code>attach</code> 提供一个监听器,你可以访问被一个 page-mod 附加到页面上的内容脚本的 <code>port</code> 对象:</p>
+
+<pre class="brush: js">// main.js
+
+var pageMods = require("sdk/page-mod");
+var self = require("sdk/self");
+
+var pageMod = pageMods.PageMod({
+  include: ['*'],
+  contentScriptFile: self.data.url("content-script.js"),
+  onAttach: startListening
+});
+
+function startListening(worker) {
+  worker.port.on('click', function(html) {
+    worker.port.emit('warning', 'Do not click this again');
+  });
+}</pre>
+
+<pre class="brush: js">// content-script.js
+
+window.addEventListener('click', function(event) {
+ self.port.emit('click', event.target.toString());
+ event.stopPropagation();
+ event.preventDefault();
+}, false);
+
+self.port.on('warning', function(message) {
+ window.alert(message);
+});
+</pre>
+
+<p>上面的 add-on 里有两条消息:</p>
+
+<ul>
+ <li>当用户点击页面元素时,<code>click</code> 从 page-mod 被发送到当前 add-on。</li>
+ <li><code>warning</code> 发送一条傻气的字符串回给page-mod</li>
+</ul>
+
+<h4 id="从_Tab.attach()">从 <code>Tab.attach()</code></h4>
+
+<p><code>Tab.attach()</code> 方法返回一个 worker,你可以用来和附加的内容脚本通信。</p>
+
+<p>这个 add-on 添加了一个按钮到Firefox:等用户点击按钮是,这个 add-on 附加一个内容脚本到当前的标签页,发送给内容脚本一条名为 "my-addon-message"的消息,并且监听名为"my-script-response"的响应:</p>
+
+<pre class="brush: js">//main.js
+
+var tabs = require("sdk/tabs");
+var buttons = require("sdk/ui/button/action");
+var self = require("sdk/self");
+
+buttons.ActionButton({
+ id: "attach-script",
+ label: "Attach the script",
+ icon: "./icon-16.png",
+ onClick: attachScript
+});
+
+function attachScript() {
+ var worker = tabs.activeTab.attach({
+ contentScriptFile: self.data.url("content-script.js")
+ });
+ worker.port.on("my-script-response", function(response) {
+ console.log(response);
+ });
+ worker.port.emit("my-addon-message", "Message from the add-on");
+}
+</pre>
+
+<pre class="brush: js">// content-script.js
+
+self.port.on("my-addon-message", handleMessage);
+
+function handleMessage(message) {
+ alert(message);
+ self.port.emit("my-script-response", "Response from content script");
+}</pre>
+
+<h3 id="port的API">port的API</h3>
+
+<p>参看 <a href="/en-US/Add-ons/SDK/Guides/Content_Scripts/port"><code>port</code> 对象的参考文档</a>.</p>
+</article>
+
+<h3 id="postMessage的API">postMessage的API</h3>
+
+<p>在 <code>port</code> 对象加载之前,add-on 代码和内容脚本可以使用另一个 API 通信:</p>
+
+<ul>
+ <li>内容脚本调用 <code>self.postMessage()</code> 来发送,并用 <code>self.on()</code> 来接收</li>
+ <li>内容脚本调用 <code>worker.postMessage()</code> 来发送,并用 <code>worker.on()</code> 来接收</li>
+</ul>
+
+<p>这个API依然可用,并且还有<a href="/en-US/Add-ons/SDK/Guides/Content_Scripts/using_postMessage">文档</a>,但是没有理由替代前文描述的 <code>port</code> API。 例外是 <a href="/en-US/Add-ons/SDK/High-Level_APIs/context-menu">context-menu</a> 模块,它还是使用 postMessage。</p>
+
+<h3 id="内容脚本的内容脚本">内容脚本的内容脚本</h3>
+
+<p>内容脚本可用直接和其他同一个上下文中的内容脚本通信。举个例子,如果一次 <code>Tab.attach()</code> 的调用附加了两个脚本,那么他们可用直接相互查看,就像加载在同一页面内的页面脚本一样。但是如果你调用 <code>Tab.attach()</code> 两次,每次附加一个内容脚本,那么这些内容脚本之间不能通信。你必须使用port API 通过 add-on 的主代码来传递消息。</p>
+
+<h2 id="跨域的内容脚本">跨域的内容脚本</h2>
+
+<p>默认情况下,内容脚本没有跨域的权限。特别是,它们不能访问在不同 <code>iframe</code> 中的在另外的域名上的内容,也不能发起跨域的 XMLHttpRequests。</p>
+
+<p>但是,你可以把需要的域名添加到 <a href="/en-US/Add-ons/SDK/Tools/package_json">package.json</a> 中<code>"permissions"</code>键下的 <code>"cross-domain-content"</code>键下,为这些域名打开这些特性。参阅文章<a href="/en-US/Add-ons/SDK/Guides/Content_Scripts/Cross_Domain_Content_Scripts">跨域内容脚本</a>。</p>
+</article>
+
+<div id="xunlei_com_thunder_helper_plugin_d462f475-c18e-46be-bd10-327458d045bd"> </div>
+
+<div id="xunlei_com_thunder_helper_plugin_d462f475-c18e-46be-bd10-327458d045bd"> </div>
diff --git a/files/zh-cn/mozilla/add-ons/sdk/guides/index.html b/files/zh-cn/mozilla/add-ons/sdk/guides/index.html
new file mode 100644
index 0000000000..51fbdef445
--- /dev/null
+++ b/files/zh-cn/mozilla/add-ons/sdk/guides/index.html
@@ -0,0 +1,115 @@
+---
+title: 教程
+slug: Mozilla/Add-ons/SDK/Guides
+tags:
+ - NeedsTranslation
+ - TopicStub
+translation_of: Archive/Add-ons/Add-on_SDK/Guides
+---
+<p>下面列出了一些文章可以加深你对SDK的理解</p>
+<hr>
+<h3 id="投稿者的教程"><a name="contributors-guide">投稿者的教程</a></h3>
+<div class="column-container">
+ <div class="column-half">
+ <dl>
+ <dt>
+ <a href="Guides/Getting_Started">起步</a></dt>
+ <dd>
+ 学会如何使用SDK : 编写代码, 调试bug, 提交补丁, 审核项目, 获得帮助.</dd>
+ <dt>
+ <a href="Guides/Modules">模块</a></dt>
+ <dd>
+ 通过SDK学会模块的使用 (以CommonJS为规范), 懂得如何使用 sandboxes 和compartments 提高安全性, 并且了解内置的 SDK module loader (被称为Cuddlefish).</dd>
+ <dt>
+ <a href="Guides/Classes_and_Inheritance">类 和 继承</a></dt>
+ <dd>
+ 学会<strong> 类</strong>和<strong>继承</strong>在JavaScript中的运行机制, 使用<strong>构造(constructors)</strong>和<strong>原型(prototypes)</strong>, 并知道如何使用SDK提供的函数帮助器简化它.</dd>
+ </dl>
+ </div>
+ <div class="column-half">
+ <dl>
+ <dt>
+ <a href="Guides/Private_Properties">私有成员</a></dt>
+ <dd>
+ 通过 前缀, 闭包, 和WeakMaps 学会私有成员如何在JavaScript中的实现, 使用 命名空间(通常指WeakMaps) 学会SDK如何支持私有成员.</dd>
+ <dt>
+ <a href="Guides/Content_Processes">脚本运行流程</a></dt>
+ <dd>
+ SDK的设计目的是为了使控制网页内容的扩展脚本可以在不同进程的环境中运行. 这篇文章强调了这一设计的特点.</dd>
+ </dl>
+ </div>
+</div>
+<hr>
+<h3 id="SDK的基础结构"><a name="sdk-infrastructure">SDK的基础结构</a></h3>
+<div class="column-container">
+ <div class="column-half">
+ <dl>
+ <dt>
+ <a href="Guides/Module_structure_of_the_SDK">SDK 模块结构</a></dt>
+ <dd>
+ SDK是可重复使用的 JavaScript 模块. 这里解释了什么是模块, 怎样加载模块, 和SDK模块树的构造.</dd>
+ <dt>
+ <a href="Guides/SDK_API_Lifecycle">SDK API 生存周期</a></dt>
+ <dd>
+ 为SDK的API定义生命周期,  包括API稳定性的排名</dd>
+ </dl>
+ </div>
+ <div class="column-half">
+ <dl>
+ <dt>
+ <a href="Guides/Program_ID">程序 ID</a></dt>
+ <dd>
+ 程序ID 是扩展独一无二的标识符. 教程解释了如何定义你自己的程序 ID.</dd>
+ <dt>
+ <a href="Guides/Firefox_Compatibility">Firefox 兼容</a></dt>
+ <dd>
+ 解决不同版本SDK生成的扩展与不同版本Firefox的兼容问题</dd>
+ </dl>
+ </div>
+</div>
+<hr>
+<h3 id="SDK_常用技巧"><a name="sdk-idioms">SDK 常用技巧</a></h3>
+<div class="column-container">
+ <div class="column-half">
+ <dl>
+ <dt>
+ <a href="Guides/Working_with_Events">善用 事件触发</a></dt>
+ <dd>
+ 通过SDK的事件触发框架 写出以事件驱动为基础的代码</dd>
+ </dl>
+ </div>
+ <div class="column-half">
+ <dl>
+ <dt>
+ <a href="Guides/Two_Types_of_Scripts">脚本的两种类型</a></dt>
+ <dd>
+ 这篇文章可以帮助你理解扩展中的API和普通脚本的区别</dd>
+ </dl>
+ </div>
+</div>
+<p> </p>
+<hr>
+<h3 id="XUL_迁移"><a name="xul-migration">XUL 迁移</a></h3>
+<div class="column-container">
+ <div class="column-half">
+ <dl>
+ <dt>
+ <a href="Guides/XUL_Migration_Guide">XUL 迁移教程</a></dt>
+ <dd>
+ 把XUL扩展迁移到SDK的技巧</dd>
+ <dt>
+ <a href="Guides/XUL_vs_SDK">XUL 与 SDK 不同</a></dt>
+ <dd>
+ 比较 传统的以XUL为基础的扩展 和 SDK 两者优点和缺点</dd>
+ </dl>
+ </div>
+ <div class="column-half">
+ <dl>
+ <dt>
+ <a href="Guides/Porting_the_Library_Detector">移植例子</a></dt>
+ <dd>
+ 一个简单地教你如何让 基于XUL的扩展 迁移到 SDK中的实例</dd>
+ </dl>
+ </div>
+</div>
+<p> </p>
diff --git a/files/zh-cn/mozilla/add-ons/sdk/guides/multiprocess_firefox_and_the_sdk/index.html b/files/zh-cn/mozilla/add-ons/sdk/guides/multiprocess_firefox_and_the_sdk/index.html
new file mode 100644
index 0000000000..c22dd0181e
--- /dev/null
+++ b/files/zh-cn/mozilla/add-ons/sdk/guides/multiprocess_firefox_and_the_sdk/index.html
@@ -0,0 +1,212 @@
+---
+title: 多进程 Firefox 与 SDK
+slug: Mozilla/Add-ons/SDK/Guides/Multiprocess_Firefox_and_the_SDK
+translation_of: Archive/Add-ons/Add-on_SDK/Guides/Multiprocess_Firefox_and_the_SDK
+---
+<p>我们目前正在使 Firefox 变为多进程,它为浏览器界面使用一个操作系统进程,为运行的网页使用另一个进程来执行代码,这个项目被称为 "electrolysis" 或者 "e10s"。更多信息请参考<a href="/en-US/Firefox/Multiprocess_Firefox">多进程 Firefox 相关页面</a>。</p>
+
+<p>本文章介绍了开发者如何测试基于 SDK 的附加组件是否与多进程的 Firefox 兼容,以及如何解决出现的问题。</p>
+
+<h2 id="SDK_的合约">SDK 的合约</h2>
+
+<p>SDK 为附加组件的开发者承诺了:</p>
+
+<ul>
+ <li><a href="/en-US/Add-ons/SDK/High-Level_APIs">顶层 API</a> 对多进程 Firefox 完全工作。如果并没有,请报告为 SDK 中的 bug。</li>
+ <li><a href="/en-US/Add-ons/SDK/Low-Level_APIs">底层 API</a> 也许不工作。如果你在使用底层 API,考虑检查、测试和重构某些代码。</li>
+</ul>
+
+<p>在实践中,大多数底层 API 将正常工作,但可以直接访问网页内容的底层 API 无法正常工作。</p>
+
+<h2 id="兼容性垫片">兼容性垫片</h2>
+
+<p>举例来说,你可能认为这是行不通的:</p>
+
+<pre class="brush: js">var contentDocument = require("sdk/window/utils")
+ .getMostRecentBrowserWindow().content.document;</pre>
+
+<p>但是,Firefox 为附加组件提供了许多 API 的兼容性垫片。这意味着许多 API,包括上述例子的那个,仍然工作。虽然有两条警示需注意:</p>
+
+<ul>
+ <li>这些垫片不完美:某些附加组件在有垫片的情况下仍然不工作。举例来说,许多垫片通过为内容对象返回跨进称对象包装器来工作,并且这<a href="/en-US/Firefox/Multiprocess_Firefox/Cross_Process_Object_Wrappers#Limitations_of_CPOWs">并非始终以同样的方式</a>作为内容对象。</li>
+ <li>这些垫片可能对性能有不良影响,我们希望人们最终去除它们。</li>
+</ul>
+
+<p>We don't yet have a complete list of low-level APIs that are not multiprocess compatible: that is, will not work without the shims. Where we know that a low-level API is not multiprocess compatible, we've indicated that in the documentation page for it.</p>
+
+<h2 id="测试">测试</h2>
+
+<p>To test whether an add-on works without the shims, use the <a href="/en-US/Add-ons/SDK/Tools/package_json#permissions">"multiprocess" permission</a>.</p>
+
+<div class="note">
+<p><strong>Note</strong> that you can only do this if you are using <a href="/en-US/Add-ons/SDK/Tools/jpm">jpm</a>. You can't use the <a href="/en-US/Add-ons/SDK/Tools/package_json#permissions">"multiprocess" permission</a> if you are using <a href="/en-US/Add-ons/SDK/Tools/cfx">cfx</a>.</p>
+</div>
+
+<p>Setting this permission will disable the shims for your add-on, so you can test to see if it's really multiprocess compatible or not.</p>
+
+<p>However, there's a catch: some of the SDK's APIs themselves still depend on the shims. So by setting the <a href="/en-US/Add-ons/SDK/Tools/package_json#permissions">"multiprocess" permission</a>, your add-on might not work, even if you are only using high-level APIs. For example:</p>
+
+<pre class="brush: js">var selection = require("sdk/selection");
+
+function myListener() {
+ console.log(selection.text);
+}
+
+selection.on('select', myListener);
+</pre>
+
+<p>This add-on will not work if you've set the <a href="/en-US/Add-ons/SDK/Tools/package_json#permissions">"multiprocess" permission</a>, because <code><a href="/en-US/Add-ons/SDK/High-Level_APIs/selection">sdk/selection</a></code> depends on the shims. We're working on fixing these problems: see <a href="https://bugzilla.mozilla.org/show_bug.cgi?id=1004745">bug 1004745</a> and its dependencies.</p>
+
+<h2 id="使用框架脚本">使用框架脚本</h2>
+
+<p>If the shims don't enable your add-on to work properly, or you're trying to remove your dependency on the shims, then the solution is the same as it is for all add-ons:</p>
+
+<ul>
+ <li>factor the code that needs access to content objects into frame scripts</li>
+ <li>use the message manager to load the frame scripts into the content process</li>
+ <li>use the message manager to exchange messages with the frame scripts</li>
+</ul>
+
+<p>In the rest of this section we'll outline some SDK-specific details of using the message manager. However, you'll also need to read the main <a href="/en-US/Firefox/Multiprocess_Firefox/The_message_manager">message manager documentation</a> for the details on working with frame scripts.</p>
+
+<div class="note">
+<p>If you're used to working with content scripts in the SDK, then frame scripts might feel similar, but they're actually very different. Frame scripts are more like a part of the main add-on code that just happens to be running in the content process.</p>
+
+<p>Differences between frame scripts and content scripts include:</p>
+
+<ul>
+ <li>the <a href="/en-US/Firefox/Multiprocess_Firefox/Frame_script_environment">environment for frame scripts</a> and the <a href="/en-US/Add-ons/SDK/Guides/Two_Types_of_Scripts#API_Access_for_Add-on_Code_and_Content_Scripts">environment for content scripts</a> are totally different:
+
+ <ul>
+ <li>content scripts have a very similar environment to scripts loaded by web pages, plus the <code><a href="/en-US/Add-ons/SDK/Guides/Content_Scripts/self">self</a></code> global.</li>
+ <li>frame scripts also have access to web content, but have a different set of APIs for communicating with the chrome process, and access to the <a href="/en-US/docs/Components_object">Components object</a>, which enables them to use privileged <a href="/en-US/docs/Mozilla/Tech/XPCOM">XPCOM</a> APIs and load <a href="/en-US/docs/Mozilla/JavaScript_code_modules">JSMs</a>.</li>
+ </ul>
+ </li>
+ <li>content scripts are reloaded when a new document is loaded. Frame scripts aren't: once a frame script is loaded into a tab it remains loaded until the tab is closed. For more details, see the article on <a href="/en-US/Firefox/Multiprocess_Firefox/Message_Manager/Frame_script_loading_and_lifetime">frame script lifetime</a>.</li>
+</ul>
+</div>
+
+<h3 id="访问消息管理器">访问消息管理器</h3>
+
+<h4 id="全局消息管理器">全局消息管理器</h4>
+
+<p>To access the <a href="/en-US/Firefox/Multiprocess_Firefox/Message_Manager/Message_manager_overview#Global_frame_message_manager">global message manager</a>, you load the <a href="/en-US/docs/Mozilla/Tech/XPCOM/Reference/Interface/nsIMessageListenerManager">nsIMessageListenerManager</a> service. Before you can do this in an SDK add-on, you have to <code>require("chrome")</code> to get access to the <code><a href="/en-US/docs/Components_object">Components</a></code> object:</p>
+
+<pre class="brush: js">const {Cc, Ci} = require("chrome");
+
+var globalMM = Cc["@mozilla.org/globalmessagemanager;1"]
+ .getService(Ci.nsIMessageListenerManager);</pre>
+
+<h4 id="窗口消息管理器">窗口消息管理器</h4>
+
+<p>The <a href="/en-US/Firefox/Multiprocess_Firefox/Message_Manager/Message_manager_overview#Window_message_manager">window message manager</a> is available as the <code>messageManager</code> property of a chrome window. To understand what a chrome window is, you need to distinguish three sorts of windows:</p>
+
+<ul>
+ <li>a content window is the <a href="/en-US/docs/Web/API/Window">Window</a> object familiar to Web developers as the global object for JavaScript loaded from an HTML document. This is the same window that content scripts in the SDK access.</li>
+ <li>a chrome window, also called a browser window. This is a Firefox application window, hosting the Firefox UI as well as a collection of tabs, each of which can in turn host a DOM window to display web content. This is the global for traditional overlay-based add-ons. In the SDK, the low-level <a href="/en-US/Add-ons/SDK/Low-Level_APIs/window_utils">window/utils</a> module works with these sorts of windows.</li>
+ <li>an SDK window, as made available by the high level <a href="/en-US/Add-ons/SDK/High-Level_APIs/windows">windows</a> module. Each SDK window maps to a chrome window, but omits most of the chrome window's properties, including <code>messageManager</code>. If you have an SDK window you can convert it to a chrome window using <a href="/en-US/Add-ons/SDK/High-Level_APIs/windows#Converting_to_chrome_windows">viewFor</a>.</li>
+</ul>
+
+<p>So to get a window message manager from the SDK, you have a couple of options:</p>
+
+<p>(1) Use the <a href="/en-US/Add-ons/SDK/Low-Level_APIs/window_utils">window/utils</a> module:</p>
+
+<pre class="brush: js">// Note that although this code uses window/utils,
+// it's safe to run in the chrome process because
+// it only accesses chrome objects.
+
+// get the active chrome window
+var browserWindow = require("sdk/window/utils").getMostRecentBrowserWindow();
+
+var windowMM = browserWindow.messageManager;</pre>
+
+<p>(2) Use <code>viewFor</code> to convert an SDK window to a chrome window:</p>
+
+<pre class="brush: js">// get the active SDK window
+var windows = require("sdk/windows").browserWindows;
+var activeWindow = windows.activeWindow;
+
+// convert it to a chrome window
+var browserWindow = require("sdk/view/core").viewFor(activeWindow);
+
+var windowMM = browserWindow.messageManager;</pre>
+
+<h4 id="浏览器消息管理器">浏览器消息管理器</h4>
+
+<p>The <a href="/en-US/Firefox/Multiprocess_Firefox/Message_Manager/Message_manager_overview#Browser_message_manager">browser message manager</a> is available as the <code>messageManager</code> property of an XUL <code><a href="/en-US/docs/XUL/browser">browser</a></code>.</p>
+
+<p>In the SDK, to get a <code>browser</code> for a given tab, you can use the <a href="/en-US/Add-ons/SDK/Low-Level_APIs/tabs_utils">tabs/utils</a> module's <code><a href="/en-US/Add-ons/SDK/Low-Level_APIs/tabs_utils#getBrowserForTab%28tab%29">getBrowserForTab</a></code> function. <code>getBrowserForTab</code> expects an XUL tab as an argument, so if you have an <a href="/en-US/Add-ons/SDK/High-Level_APIs/tabs">SDK tab</a> object, you'll need to convert it to an XUL tab using <code><a href="/en-US/Add-ons/SDK/High-Level_APIs/tabs#Converting_to_XUL_tabs">viewFor</a></code>:</p>
+
+<pre class="brush: js">// get the active SDK tab
+var tab = require("sdk/tabs").activeTab;
+// convert it to an XUL tab
+var xulTab = require("sdk/view/core").viewFor(tab);
+// get the XUL browser for this tab
+var xulBrowser = require("sdk/tabs/utils").getBrowserForTab(xulTab);
+
+var browserMM = xulBrowser.messageManager;</pre>
+
+<p>Again, although this code uses tabs/utils, it's safe to run in the chrome process because it only accesses chrome objects.</p>
+
+<h3 id="载入框架脚本">载入框架脚本</h3>
+
+<p>You can load frame scripts using the message manager, passing in a chrome:// or resource:// URL pointing to the script. With the SDK, the simplest approach is to keep frame scripts under your add-on's data directory, and pass in a resource:// URL created using <a href="/en-US/Add-ons/SDK/High-Level_APIs/self#data.url(name)">self.data.url</a>:</p>
+
+<pre class="brush: js">const self = require("sdk/self");
+
+messageManager.loadFrameScript(self.data.url("frame-script.js"), false);</pre>
+
+<p>Note that unlike the APIs to load content scripts, you can only load a single frame script here.</p>
+
+<h3 id="例子">例子</h3>
+
+<p>For example, this add-on trivially accesses content directly using a low-level API:</p>
+
+<pre class="brush: js">function logContent() {
+ var contentDocument = require("sdk/window/utils")
+ .getMostRecentBrowserWindow().content.document;
+ console.log(contentDocument.body.innerHTML);
+}
+
+require("sdk/ui/button/action").ActionButton({
+ id: "log-content",
+ label: "Log Content",
+ icon: "./icon-16.png",
+ onClick: logContent
+});</pre>
+
+<p>This add-on will work by default due to the shims, but will break if you set multiprocessCompatible. So you could rewrite the add-on to use frame scripts:</p>
+
+<pre class="brush: js">/*
+frame-script.js is in the "data" directory, and has this content:
+
+sendAsyncMessage("sdk-low-level-apis-e10s@jetpack:got-content",
+ content.document.body.innerHTML);
+
+*/
+
+const self = require("sdk/self");
+
+function logContentAsync() {
+ var tab = require("sdk/tabs").activeTab;
+ var xulTab = require("sdk/view/core").viewFor(tab);
+ var xulBrowser = require("sdk/tabs/utils").getBrowserForTab(xulTab);
+
+ var browserMM = xulBrowser.messageManager;
+ browserMM.loadFrameScript(self.data.url("frame-script.js"), false);
+ browserMM.addMessageListener("sdk-low-level-apis-e10s@jetpack:got-content",
+ logContent);
+}
+
+function logContent(message) {
+ console.log(message.data);
+}
+
+require("sdk/ui/button/action").ActionButton({
+ id: "log-content",
+ label: "Log Content",
+ icon: "./icon-16.png",
+ onClick: logContentAsync
+});</pre>
+
+<p>现在附加组件能正常工作了,即使你设置了 multiprocessCompatible。</p>
diff --git a/files/zh-cn/mozilla/add-ons/sdk/guides/working_with_events/index.html b/files/zh-cn/mozilla/add-ons/sdk/guides/working_with_events/index.html
new file mode 100644
index 0000000000..4c24b84e13
--- /dev/null
+++ b/files/zh-cn/mozilla/add-ons/sdk/guides/working_with_events/index.html
@@ -0,0 +1,122 @@
+---
+title: Working with Events
+slug: Mozilla/Add-ons/SDK/Guides/Working_with_Events
+translation_of: Archive/Add-ons/Add-on_SDK/Guides/Working_with_Events
+---
+<p>The Add-on SDK supports event-driven programming.</p>
+<p>Objects emit events on state changes that might be of interest to add-on code, such as browser windows opening, pages loading, network requests completing, and mouse clicks. By registering a listener function to an event emitter an add-on can receive notifications of these events.</p>
+<p><span>We talk about content scripts in more detail in the <a href="/en-US/Add-ons/SDK/Guides/Content_Scripts">Working with Content Scripts</a> guide.</span></p>
+<p>Additionally, if you're using content scripts to interact with web content, you can define your own events and use them to communicate between the main add-on code and the content scripts. In this case one end of the conversation emits the events, and the other end listens to them.</p>
+<p>So there are two main ways you will interact with the EventEmitter framework:</p>
+<ul>
+ <li>
+ <p><strong>listening to built-in events</strong> emitted by objects in the SDK, such as tabs opening, pages loading, mouse clicks</p>
+ </li>
+ <li>
+ <p><strong>sending and receiving user-defined events</strong> between content scripts and add-on code</p>
+ </li>
+</ul>
+<p>This guide only covers the first of these; the second is explained in the <a href="/en-US/Add-ons/SDK/Guides/Content_Scripts">Working with Content Scripts</a> guide.</p>
+<h2 id="Adding_Listeners">Adding Listeners</h2>
+<p>You can add a listener to an event emitter by calling its <code>on(type, listener)</code> method.</p>
+<p>It takes two parameters:</p>
+<ul>
+ <li>
+ <p><strong><code>type</code></strong>: the type of event we are interested in, identified by a string. Many event emitters may emit more than one type of event: for example, a browser window might emit both <code>open</code> and <code>close</code> events. The list of valid event types is specific to an event emitter and is included with its documentation.</p>
+ </li>
+ <li>
+ <p><strong><code>listener</code></strong>: the listener itself. This is a function which will be called whenever the event occurs. The arguments that will be passed to the listener are specific to an event type and are documented with the event emitter.</p>
+ </li>
+</ul>
+<p>For example, the following add-on registers a listener with the <a href="/en-US/Add-ons/SDK/High-Level_APIs/tabs"><code>tabs</code></a> module to listen for the <code>ready</code> event, and logs a string to the console reporting the event:</p>
+<pre class="brush: js">var tabs = require("sdk/tabs");
+
+tabs.on("ready", function () {
+ console.log("tab loaded");
+});
+</pre>
+<p>It is not possible to enumerate the set of listeners for a given event.</p>
+<p>The value of <code>this</code> in the listener function is the object that emitted the event.</p>
+<h3 id="Listening_to_all_events">Listening to all events</h3>
+<div class="note">
+ <p>This example uses the <a href="https://developer.mozilla.org/en-US/Add-ons/SDK/Low-Level_APIs/ui_button_action">action button</a> API, which is only available from Firefox 29 onwards.</p>
+</div>
+<p>From Firefox 28 onwards, you can pass the wildcard "*" as the <code>type</code> argument. If you do this, the listener will be called for any event emitted by that object, and its argument will be the name of the event:</p>
+<pre class="brush: js">var ui = require('sdk/ui');
+var panels = require("sdk/panel");
+var self = require("sdk/self");
+
+var panel = panels.Panel({
+ contentURL: self.data.url("panel.html")
+});
+
+panel.on("*", function(e) {
+ console.log("event " + e + " was emitted");
+});
+
+var button = ui.ActionButton({
+ id: "my-button",
+ label: "my button",
+ icon: "./icon-16.png",
+ onClick: handleClick
+});
+
+function handleClick(state) {
+ panel.show({
+ position: button
+ });
+}</pre>
+<p>This wildcard feature does not yet work for the <a href="/en-US/Add-ons/SDK/High-Level_APIs/tabs"><code>tabs</code></a> or <a href="/en-US/Add-ons/SDK/High-Level_APIs/windows"><code>windows</code></a> modules.</p>
+<h3 id="Adding_Listeners_in_Constructors">Adding Listeners in Constructors</h3>
+<p>Event emitters may be modules, as is the case for the <code>ready</code> event above, or they may be objects returned by constructors.</p>
+<p>In the latter case the <code>options</code> object passed to the constructor typically defines properties whose names are the names of supported event types prefixed with "on": for example, "onOpen", "onReady" and so on. Then in the constructor you can assign a listener function to this property as an alternative to calling the object's <code>on()</code> method.</p>
+<p>For example: the <a href="/en-US/Add-ons/SDK/Low-Level_APIs/ui_button_action"><code>ActionButton</code></a> object emits an event when the button is clicked.</p>
+<p>The following add-on creates a button and assigns a listener to the <code>onClick</code> property of the <code>options</code> object supplied to the button's constructor. The listener loads https://developer.mozilla.org/:</p>
+<pre class="brush: js">require("sdk/ui/button/action").ActionButton({
+  id: "visit-mozilla",
+  label: "Visit Mozilla",
+  icon: "./icon-16.png",
+  onClick: function() {
+    require("sdk/tabs").open("https://developer.mozilla.org/");
+  }
+});
+</pre>
+<p>This is exactly equivalent to constructing the button and then calling the button's <code>on()</code> method:</p>
+<pre class="brush: js">var button = require("sdk/ui/button/action").ActionButton({
+  id: "visit-mozilla",
+  label: "Visit Mozilla",
+  icon: "./icon-16.png"
+});
+
+button.on("click", function() {
+  require("sdk/tabs").open("https://developer.mozilla.org/");
+});
+</pre>
+<h2 id="Removing_Event_Listeners">Removing Event Listeners</h2>
+<p>Event listeners can be removed by calling <code>removeListener(type, listener)</code>, supplying the type of event and the listener to remove.</p>
+<p>The listener must have been previously been added using one of the methods described above.</p>
+<p>In the following add-on, we add two listeners to the <a href="/en-US/Add-ons/SDK/High-Level_APIs/tabs#ready"><code>tabs</code> module's <code>ready</code> event</a>. One of the handler functions removes the listener again.</p>
+<p>Then we open two tabs.</p>
+<pre class="brush: js">var tabs = require("sdk/tabs");
+
+function listener1() {
+ console.log("Listener 1");
+ tabs.removeListener("ready", listener1);
+}
+
+function listener2() {
+ console.log("Listener 2");
+}
+
+tabs.on("ready", listener1);
+tabs.on("ready", listener2);
+
+tabs.open("https://www.mozilla.org");
+tabs.open("https://www.mozilla.org");
+</pre>
+<p>We should see output like this:</p>
+<pre>info: tabevents: Listener 1
+info: tabevents: Listener 2
+info: tabevents: Listener 2
+</pre>
+<p>Listeners will be removed automatically when the add-on is unloaded.</p>