This commit is contained in:
Richard Newman 2014-01-10 22:20:22 -08:00
Родитель 993e55b82e a87352cc59
Коммит 46d49d4caa
1182 изменённых файлов: 30381 добавлений и 52132 удалений

Просмотреть файл

@ -22,4 +22,4 @@
# changes to stick? As of bug 928195, this shouldn't be necessary! Please
# don't change CLOBBER for WebIDL changes any more.
Bug 956240 requires clobber due to bug 956723
Bug 944533 requires clobber to force a Proguard refresh

Просмотреть файл

@ -27,6 +27,7 @@
#include "nsIPersistentProperties2.h"
#include "nsIScrollableFrame.h"
#include "nsIServiceManager.h"
#include "nsITextControlElement.h"
#include "nsTextFragment.h"
#include "mozilla/Selection.h"
#include "mozilla/MathAlgorithms.h"
@ -102,8 +103,19 @@ HyperTextAccessible::NativeState()
{
uint64_t states = AccessibleWrap::NativeState();
nsCOMPtr<nsIEditor> editor = GetEditor();
if (editor) {
nsCOMPtr<nsITextControlElement> textControl = do_QueryInterface(mContent);
bool editable = !!textControl;
Accessible* hyperText = this;
while (!editable && hyperText) {
if (hyperText->IsHyperText())
editable = hyperText->GetNode()->IsEditable();
if (hyperText->IsDoc())
break;
hyperText = hyperText->Parent();
}
if (editable) {
states |= states::EDITABLE;
} else if (mContent->Tag() == nsGkAtoms::article) {

Просмотреть файл

@ -733,17 +733,29 @@
states: STATE_PROTECTED,
extraStates: EXT_STATE_EDITABLE,
actions: "activate",
children: [ ]
children: [
{
role: ROLE_TEXT_LEAF
}
]
};
testElm("input_password", obj);
ok(getAccessible("input_password").firstChild.name != "44",
"text leaf for password shouldn't have its real value as its name!");
obj = {
role: ROLE_PASSWORD_TEXT,
states: STATE_PROTECTED | STATE_READONLY,
actions: "activate",
children: [ ]
children: [
{
role: ROLE_TEXT_LEAF
}
]
};
testElm("input_password_readonly", obj);
ok(getAccessible("input_password_readonly").firstChild.name != "44",
"text leaf for password shouldn't have its real value as its name!");
//////////////////////////////////////////////////////////////////////////
// HTML:input@type="radio"

Просмотреть файл

@ -108,6 +108,12 @@
BOUNDARY_LINE_START,
[ [ 0, 3, "foo\n", 0, 4 ], [ 4, 4, "", 4, 4 ] ]);
//////////////////////////////////////////////////////////////////////////
// 'Hello world ' (\n is rendered as space)
testTextAtOffset([ "ht_4" ], BOUNDARY_LINE_START,
[ [ 0, 12, "Hello world ", 0, 12 ] ]);
//////////////////////////////////////////////////////////////////////////
// list items
@ -138,7 +144,12 @@
href="https://bugzilla.mozilla.org/show_bug.cgi?id=882292">
Bug 882292
</a>
<a target="_blank"
title="getTextAtOffset broken for last object when closing tag is preceded by newline char"
href="https://bugzilla.mozilla.org/show_bug.cgi?id=947170">
Bug 947170
</a>
<p id="display"></p>
<div id="content" style="display: none"></div>
<pre id="test">
@ -173,6 +184,9 @@ two words
<iframe id="ht_2" src="data:text/html,<div contentEditable='true'>foo<br/></div>"></iframe>
<iframe id="ht_3" src="data:text/html,<div contentEditable='true'>foo<br/><br/></div>"></iframe>
<p id="ht_4">Hello world
</p>
<ul>
<li id="li1">Item</li>
</ul>

Просмотреть файл

@ -1,932 +0,0 @@
<!-- This Source Code Form is subject to the terms of the Mozilla Public
- License, v. 2.0. If a copy of the MPL was not distributed with this
- file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
# cfx #
The `cfx` command-line tool gives you access to the SDK documentation and
development servers as well as testing, running, and building add-ons.
`cfx` usage is:
<pre>
cfx [options] command [command-specific options]
</pre>
"Options" are global options applicable to the tool itself or to all
commands (for example `--help`). `cfx` supports the following global options:
<pre>
-h, --help - show a help message and exit
-v, --verbose - enable lots of output
</pre>
"Command-specific options" are documented alongside the commands.
There are four supported cfx commands:
<table>
<colgroup>
<col width="10%">
<col width="90%">
</colgroup>
<tr>
<td>
<a href="dev-guide/cfx-tool.html#cfx-init"><code>cfx init</code></a>
</td>
<td>
Create a skeleton add-on as a starting point for your own add-on.
</td>
</tr>
<tr>
<td>
<a href="dev-guide/cfx-tool.html#cfx-run"><code>cfx run</code></a>
</td>
<td>
Launch an instance of Firefox with your add-on installed.
</td>
</tr>
<tr>
<td>
<a href="dev-guide/cfx-tool.html#cfx-test"><code>cfx test</code></a>
</td>
<td>
Runs your add-on's unit tests.
</td>
</tr>
<tr>
<td>
<a href="dev-guide/cfx-tool.html#cfx-xpi"><code>cfx xpi</code></a>
</td>
<td>
Package your add-on as an <a href="https://developer.mozilla.org/en/XPI">XPI</a>
file, which is the install file format for Firefox add-ons.
</td>
</tr>
</table>
There are also a number of
[internal commands](dev-guide/cfx-tool.html#internal-commands),
which are more likely to be useful to SDK developers than to add-on developers.
## <a name="cfx-init">cfx init</a> ##
Create a new directory called "my-addon", change into it, and run `cfx init`.
This command will create an skeleton add-on, as a starting point for your
own add-on development, with the following file structure:
<ul class="tree">
<li>my-addon
<ul>
<li>data</li>
<li>docs
<ul><li>main.md</li></ul>
</li>
<li>lib
<ul><li>main.js</li></ul>
</li>
<li>package.json</li>
<li>README.md</li>
<li>tests
<ul><li>test-main.js</li></ul>
</li>
</ul>
</li>
</ul>
<div style="clear:both"></div>
## <a name="cfx-run">cfx run</a> ##
This command is used to run the add-on. Called with no options it looks for a
file called `package.json` in the current directory, loads the corresponding
add-on, and runs it under the version of Firefox it finds in the platform's
default install path.
### Supported Options ####
You can point `cfx run` at a different `package.json` file using the
`--pkgdir` option, and pass arguments to your add-on using the
`--static-args` option.
You can specify a different version of the
<a href="dev-guide/glossary.html#host-application">host application</a>
using the `--binary` option, passing in the path to the application binary to
run. The path may be specified as a full path or may be relative to the current
directory. But note that the version must be 4.0b7 or later.
`cfx run` runs the host application with a new
[profile](http://support.mozilla.com/en-US/kb/profiles). You can specify an
existing profile using the `--profiledir` option, and this gives you access to
that profile's history, bookmarks, and other add-ons. This enables you to run
your add-on alongside debuggers like [Firebug](http://getfirebug.com/).
See <a href="dev-guide/cfx-tool.html#profiledir">
"Using --profiledir"</a> for more information.
<table>
<colgroup>
<col width="30%">
<col width="70%">
</colgroup>
<tr>
<td>
<code>-b BINARY, --binary=BINARY</code>
</td>
<td>
Use the host application binary specified in BINARY. BINARY may be specified as
a full path or as a path relative to the current directory.
</td>
</tr>
<tr>
<td>
<code>--binary-args=CMDARGS</code>
</td>
<td>
<p>Pass <a href="http://kb.mozillazine.org/Command_line_arguments">extra
arguments</a> to the binary being executed (for example, Firefox).</p>
<p>For example, to pass the
<code>-jsconsole</code> argument to Firefox, which will launch the
<a href="https://developer.mozilla.org/en/Error_Console">JavaScript
Error Console</a>, try the following:</p>
<pre>cfx run --binary-args -jsconsole</pre>
<p>To pass multiple arguments, or arguments containing spaces, quote them:</p>
<pre>cfx run --binary-args '-url "www.mozilla.org" -jsconsole'</pre>
</td>
</tr>
<tr>
<td>
<code>--extra-packages=EXTRA_PACKAGES</code>
</td>
<td>
Extra packages to include, specified as a comma-separated list of package
names.
</td>
</tr>
<tr>
<td>
<code>-g CONFIG, --use-config=CONFIG</code>
</td>
<td>
Pass a set of options by
<a href="dev-guide/cfx-tool.html#configurations">referencing a named configuration</a>.
</td>
</tr>
<tr>
<td>
<code>-p PROFILEDIR, --profiledir=PROFILEDIR</code>
</td>
<td>
<p>Use an existing
<a href="http://support.mozilla.com/en-US/kb/profiles">profile</a>
located in PROFILEDIR. PROFILEDIR may be specified as
a full path or as a path relative to the current directory.</p>
<p>See <a href="dev-guide/cfx-tool.html#profiledir">
"Using --profiledir"</a> for more information.
</td>
</tr>
<tr>
<td>
<code>--pkgdir=PKGDIR</code>
</td>
<td>
Use an add-on located in PKGDIR. PKGDIR may be specified as
a full path or as a path relative to the current directory.
</td>
</tr>
<tr>
<td>
<code>--static-args=STATIC_ARGS</code>
</td>
<td>
<a href="dev-guide/cfx-tool.html#arguments">Pass arguments to your add-on</a>,
in JSON format.
</td>
</tr>
</table>
### Experimental Options ###
<table>
<colgroup>
<col width="30%">
<col width="70%">
</colgroup>
<tr>
<td>
<code>-a APP, --app=APP</code>
</td>
<td>
By default, <code>cfx run</code> uses Firefox as the
<a href="dev-guide/glossary.html#host-application">host application</a>.
This option enables you to select a different host. You can specify
"firefox", "xulrunner", "fennec", or "thunderbird". But note that at
present only Firefox is supported.
</td>
</tr>
<tr>
<td>
<code>--no-run</code>
</td>
<td>
<p>With this option <code>cfx</code> will not execute the command, but
will print out the command that it would have used to execute the
command.</p>
<p>For example, if you type:</p>
<pre>
cfx run ---no-run</pre>
<p>you will see something like:</p>
<pre>
To launch the application, enter the following command:
/path/to/firefox/firefox-bin -profile
/path/to/profile/tmpJDNlP6.mozrunner -foreground -no-remote</pre>
<p>This enables you to run the add-on without going through
<code>cfx</code>, which might be useful if you want to run it
inside a debugger like GDB.</p>
</td>
</tr>
<tr>
<td>
<code>-o, --overload-modules</code>
</td>
<td>
<p>In early versions of the SDK, the SDK modules used by an add-on
were themselves included in the add-on. The SDK modules now ship as
part of Firefox. From Firefox 21 onwards, SDK add-ons built with
SDK 1.14 or higher will use the SDK modules that are built into Firefox,
even if the add-on includes its own copies of the SDK modules.</p>
<p>Use this flag to reverse that behavior: if this flag is set and
the add-on includes its own copies of the SDK modules, then the add-on
will use the SDK modules in the add-on, not the ones built into Firefox.</p>
<p>This flag is particularly useful for SDK developers or people working with
the development version of the SDK, who may want to run an add-on using newer
versions of the modules than than those shipping in Firefox.</p>
</td>
</tr>
<tr>
<td>
<code>--templatedir=TEMPLATEDIR</code>
</td>
<td>
The <code>cfx run</code> command constructs the add-on using a extension
template which you can find under the SDK root, in
<code>app-extension</code>.
Use the <code>--templatedir</code> option to specify a different template.
TEMPLATEDIR may be specified as a full path or as a path relative to the
current directory.
</td>
</tr>
</table>
### Internal Options ###
<table>
<colgroup>
<col width="30%">
<col width="70%">
</colgroup>
<tr>
<td>
<code>--addons=ADDONS</code>
</td>
<td>
Paths of add-ons to install, comma-separated. ADDONS may be specified as
a full path or as a path relative to the current directory.
</td>
</tr>
<tr>
<td>
<code>--e10s</code>
</td>
<td>
If this option is set then the add-on runs in a separate process.
This option is currently not implemented.
</td>
</tr>
<tr>
<td>
<code>--keydir=KEYDIR</code>
</td>
<td>
Supply a different location for
<a href="dev-guide/guides/program-id.html">signing keys</a>.
KEYDIR may be specified as a full path or as a path relative to the
current directory.
</td>
</tr>
</table>
## <a name="cfx-test">cfx test</a> ##
Run available tests for the specified package.
<span class="aside">Note the hyphen after "test" in the module name.
`cfx test` will include a module called "test-myCode.js", but will exclude
modules called "test_myCode.js" or "testMyCode.js".</span>
Called with no options this command will look for a file called `package.json`
in the current directory. If `package.json` exists, `cfx` will load the
corresponding add-on, load from the `tests` directory
any modules that start with the word `test-` and run the unit tests
they contain.
See the
[tutorial on unit testing](dev-guide/tutorials/unit-testing.html) and the
[reference documentation for the `assert` module](modules/sdk/test/assert.html)
for details.
### Supported Options ###
As with `cfx run` you can use options to control which host application binary
version to use, and to select a profile.
You can also control which tests are run: you
can test dependent packages, filter the tests by name and run tests multiple
times.
<table>
<colgroup>
<col width="30%">
<col width="70%">
</colgroup>
<tr>
<td>
<code>-b BINARY, --binary=BINARY</code>
</td>
<td>
Use the host application binary specified in BINARY. BINARY may be specified as
a full path or as a path relative to the current directory.
</td>
</tr>
<tr>
<td>
<code>--binary-args=CMDARGS</code>
</td>
<td>
<p>Pass <a href="http://kb.mozillazine.org/Command_line_arguments">extra
arguments</a> to the binary being executed (for example, Firefox).</p>
<p>For example, to pass the
<code>-jsconsole</code> argument to Firefox, which will launch the
<a href="https://developer.mozilla.org/en/Error_Console">JavaScript
Error Console</a>, try the following:</p>
<pre>cfx run --binary-args -jsconsole</pre>
<p>To pass multiple arguments, or arguments containing spaces, quote them:</p>
<pre>cfx run --binary-args '-url "www.mozilla.org" -jsconsole'</pre>
</td>
</tr>
<tr>
<td>
<code>--dependencies</code>
</td>
<td>
Load and run any tests that are included with modules that your package
depends on.
<br>
For example: if your add-on depends on modules from the SDK, then
<code>cfx</code> will run the unit tests for the SDK's modules as well
as yours.
</td>
</tr>
<tr>
<td>
<code>-f FILENAME[:TESTNAME], --filter=FILENAME[:TESTNAME]</code>
</td>
<td>
Only run tests whose filenames match FILENAME and
optionally match TESTNAME, both regexps (test, testall, testex, testpkgs)
<br>
For example: if you specify <code>--filter data</code>, then
<code>cfx</code> will only run tests in those modules whose name contain
the string "data".
</td>
</tr>
<tr>
<td>
<code>-g CONFIG, --use-config=CONFIG</code>
</td>
<td>
Pass a set of options by
<a href="dev-guide/cfx-tool.html#configurations">referencing a named configuration</a>.
</td>
</tr>
<tr>
<td>
<code>-p PROFILEDIR, --profiledir=PROFILEDIR</code>
</td>
<td>
<p>Use an existing
<a href="http://support.mozilla.com/en-US/kb/profiles">profile</a>
located in PROFILEDIR. PROFILEDIR may be specified as
a full path or as a path relative to the current directory.</p>
<p>See <a href="dev-guide/cfx-tool.html#profiledir">
"Using --profiledir"</a> for more information.
</td>
</tr>
<tr>
<td>
<code>--times=ITERATIONS</code>
</td>
<td>
Execute tests ITERATIONS number of times.
</td>
</tr>
</table>
### Experimental Options ###
<table>
<colgroup>
<col width="30%">
<col width="70%">
</colgroup>
<tr>
<td>
<code>-a APP, --app=APP</code>
</td>
<td>
By default, <code>cfx test</code> uses Firefox as the
<a href="dev-guide/glossary.html#host-application">host application</a>.
This option enables you to select a different host. You can specify
"firefox", "xulrunner", "fennec", or "thunderbird". But note that at
present only Firefox is supported.
</td>
</tr>
<tr>
<td>
<code>--no-run</code>
</td>
<td>
<p>With this option <code>cfx</code> will not execute the command, but
will print out the command that it would have used to execute the
command.</p>
<p>For example, if you type:</p>
<pre>
cfx run ---no-run</pre>
<p>you will see something like:</p>
<pre>
To launch the application, enter the following command:
/path/to/firefox/firefox-bin -profile
/path/to/profile/tmpJDNlP6.mozrunner -foreground -no-remote</pre>
<p>This enables you to run the add-on without going through
<code>cfx</code>, which might be useful if you want to run it
inside a debugger like GDB.</p>
</td>
</tr>
<tr>
<td>
<code>-o, --overload-modules</code>
</td>
<td>
<p>In early versions of the SDK, the SDK modules used by an add-on
were themselves included in the add-on. The SDK modules now ship as
part of Firefox. From Firefox 21 onwards, SDK add-ons built with
SDK 1.14 or higher will use the SDK modules that are built into Firefox,
even if the add-on includes its own copies of the SDK modules.</p>
<p>Use this flag to reverse that behavior: if this flag is set and
the add-on includes its own copies of the SDK modules, then the add-on
will use the SDK modules in the add-on, not the ones built into Firefox.</p>
<p>This flag is particularly useful for SDK developers or people working with
the development version of the SDK, who may want to run an add-on using newer
versions of the modules than than those shipping in Firefox.</p>
</td>
</tr>
</table>
### Internal Options ###
<table>
<colgroup>
<col width="30%">
<col width="70%">
</colgroup>
<tr>
<td>
<code>--addons=ADDONS</code>
</td>
<td>
Paths of add-ons to install, comma-separated.
ADDONS may be specified as full paths or relative to the
current directory.
</td>
</tr>
<tr>
<td>
<code>--e10s</code>
</td>
<td>
If this option is set then the add-on runs in a separate process.
This option is currently not implemented.
</td>
</tr>
<tr>
<td>
<code>--keydir=KEYDIR</code>
</td>
<td>
Supply a different location for
<a href="dev-guide/guides/program-id.html">signing keys</a>.
KEYDIR may be specified as a full path or as a path relative to the
current directory.
</td>
</tr>
<tr>
<td>
<code>--logfile=LOGFILE</code>
</td>
<td>
Log console output to the file specified by LOGFILE.
LOGFILE may be specified as a full path or as a path relative to the
current directory.
</td>
</tr>
<tr>
<td>
<code>--profile-memory=PROFILEMEMORY</code>
</td>
<td>
If this option is given and PROFILEMEMORY is any non-zero integer, then
<code>cfx</code> dumps detailed memory usage information to the console
when the tests finish.
</td>
</tr>
<tr>
<td>
<code>--test-runner-pkg=TEST_RUNNER_PKG</code>
</td>
<td>
Name of package containing test runner program. Defaults to
<code>test-harness</code>.
</td>
</tr>
</table>
## <a name="cfx-xpi">cfx xpi</a> ##
This tool is used to package your add-on as an
[XPI](https://developer.mozilla.org/en/XPI) file, which is the install file
format for Mozilla add-ons.
Called with no options, this command looks for a file called `package.json` in
the current directory and creates the corresponding XPI file.
Once you have built an XPI file you can distribute your add-on by submitting
it to [addons.mozilla.org](http://addons.mozilla.org).
### updateURL and updateLink ###
If you choose to host the XPI yourself you should enable the host application
to find new versions of your add-on.
To do this, include a URL in the XPI called the
[updateURL](https://developer.mozilla.org/en/install_manifests#updateURL): the
host application will go here to get information about updates. At the
`updateURL` you host a file in the
[update RDF](https://developer.mozilla.org/en/extension_versioning,_update_and_compatibility#Update_RDF_Format)
format: among other things, this includes another URL called `updateLink` which
points to the updated XPI itself.
The `--update-link` and `--update-url` options simplify this process.
Both options take a URL as an argument.
The `--update-link` option builds an update RDF alongside the XPI, and embeds
the supplied URL in the update RDF as the value of `updateLink`.
The `--update-url` option embeds the supplied URL in the XPI file, as the value
of `updateURL`.
Note that as the [add-on documentation](https://developer.mozilla.org/en/extension_versioning,_update_and_compatibility#Securing_Updates)
explains, you should make sure the update procedure for your add-on is secure,
and this usually involves using HTTPS for the links.
So if we run the following command:
<pre>
cfx xpi --update-link https://example.com/addon/latest/pluginName.xpi --update-url https://example.com/addon/update_rdf/pluginName.update.rdf
</pre>
`cfx` will create two files:
* an XPI file which embeds
`https://example.com/addon/update_rdf/pluginName.update.rdf` as the value of `updateURL`
* an RDF file which embeds `https://example.com/addon/latest/pluginName.xpi` as the value of
`updateLink`.
### Supported Options ###
As with `cfx run` you can point `cfx` at a different `package.json` file using
the `--pkgdir` option. You can also embed arguments in the XPI using the
`--static-args` option: if you do this the arguments will be passed to your
add-on whenever it is run.
<table>
<colgroup>
<col width="50%">
<col width="50%">
</colgroup>
<tr>
<td>
<code>--extra-packages=EXTRA_PACKAGES</code>
</td>
<td>
Extra packages to include, specified as a comma-separated list of package
names.
</td>
</tr>
<tr>
<td>
<code>-g CONFIG, --use-config=CONFIG</code>
</td>
<td>
Pass a set of options by
<a href="dev-guide/cfx-tool.html#configurations">referencing a named configuration</a>.
</td>
</tr>
<tr>
<td>
<code>--pkgdir=PKGDIR</code>
</td>
<td>
Use an add-on located in PKGDIR.
PKGDIR may be specified as a full path or as a path relative to the
current directory.
</td>
</tr>
<tr>
<td>
<code>--static-args=STATIC_ARGS</code>
</td>
<td>
<a href="dev-guide/cfx-tool.html#arguments">Pass arguments to your add-on</a>,
in JSON format.
</td>
</tr>
<tr>
<td>
<code>--update-link=UPDATE_LINK</code>
</td>
<td>
Build an
<a href="https://developer.mozilla.org/en/extension_versioning,_update_and_compatibility#Update_RDF_Format">update RDF</a>
alongside the XPI file, and embed the URL supplied in UPDATE_LINK in it as
the value of <code>updateLink</code>.
</td>
</tr>
<tr>
<td>
<code>--update-link=UPDATE_URL</code>
</td>
<td>
Embed the URL supplied in UPDATE_URL in the XPI file, as the value
of <code>updateURL</code>.
</td>
</tr>
</table>
### Experimental Options ###
<table>
<colgroup>
<col width="50%">
<col width="50%">
</colgroup>
<tr>
<td>
<code>--templatedir=TEMPLATEDIR</code>
</td>
<td>
The <code>cfx xpi</code> command constructs the add-on using a extension
template which you can find under the SDK root, in
<code>app-extension</code>.
Use the <code>--templatedir</code> option to specify a different template.
TEMPLATEDIR may be specified as a full path or as a path relative to the
current directory.
</td>
</tr>
</table>
### Internal Options ###
<table>
<colgroup>
<col width="50%">
<col width="50%">
</colgroup>
<tr>
<td>
<code>--keydir=KEYDIR</code>
</td>
<td>
Supply a different location for
<a href="dev-guide/guides/program-id.html">signing keys</a>.
KEYDIR may be specified as a full path or as a path relative to the
current directory.
</td>
</tr>
</table>
## <a name="internal-commands">Internal Commands</a> ##
### cfx sdocs ###
Executing this command builds a static HTML version of the SDK documentation
that can be hosted on a web server.
#### Options ####
<table>
<colgroup>
<col width="50%">
<col width="50%">
</colgroup>
<tr>
<td>
<code>--baseurl=BASEURL</code>
</td>
<td>
The root of the static docs tree, for example:
<code>http://example.com/sdk-docs/</code>.
</td>
</tr>
</table>
### cfx testcfx ###
This will run a number of tests on the cfx tool, including tests against the
documentation. Use `cfx testcfx -v` for the specific list of tests.
This accepts the same options as `cfx test`.
### cfx testaddons ###
This will run a number of test add-ons that are packaged with the SDK.
This accepts the same options as `cfx test`.
### cfx testpkgs ###
This will test all of the available CommonJS packages. Note that the number
of tests run and their success depends on what application they are run
with, and which binary is used.
This accepts the same options as `cfx test`.
### cfx testex ###
This will test all available example code. Note that the number
of tests run and their success depends on what application they are run
with, and which binary is used.
This accepts the same options as `cfx test`.
### cfx testall ###
This will test *everything*: the cfx tool, all available CommonJS packages,
and all examples.
This accepts the same options as `cfx test`.
## <a name="profiledir">Using --profiledir</a> ##
By default, `cfx run` and `cfx test` use a new profile each time they
are executed. This means that any profile-specific data entered from
one run of `cfx` will not, by default, be available in the next run.
This includes, for example, any extra add-ons you installed, or your
history, or any data stored using the
[simple-storage](modules/sdk/simple-storage.html) API.
To make `cfx` use a specific profile, pass the `--profiledir` option,
specifying the path to the profile you wish to use.
If you give `--profiledir` a path to a nonexistent profile, `cfx`
will create a profile there for you. So you just have to make up
a path and name the first time, and keep using it:
<pre>
cfx run --profiledir="~/addon-dev/profiles/boogaloo"
</pre>
The path must contain at least one "/" (although you may specify
just "./dir").
## <a name="configurations">Using Configurations</a> ##
The `--use-config` option enables you to specify a set of options as a named
configuration in a file, then pass them to `cfx` by referencing the named set.
You define configurations in a file called `local.json` which should live
in the root directory of your SDK. Configurations are listed under a key called
"configs".
Suppose your the following `local.json` is as follows:
<pre>
{
"configs": {
"ff40": ["-b", "/usr/bin/firefox-4.0"]
}
}
</pre>
You can run:
<pre>
cfx test --use-config=ff40
</pre>
And it would be equivalent to:
<pre>
cfx test -a firefox -b /usr/bin/firefox-4.0
</pre>
This method of defining configuration options can be used for all of the `run`,
build, and test tools. If "default" is defined in the `local.json` cfx will use
that configuration unless otherwise specified.
## <a name="arguments">Passing Static Arguments</a> ##
You can use the cfx `--static-args` option to pass arbitrary data to your
program. This may be especially useful if you run cfx from a script.
The value of `--static-args` must be a JSON string. The object encoded by the
JSON becomes the `staticArgs` property of the
[`system` module](modules/sdk/system.html).
The default value of
`--static-args` is `"{}"` (an empty object), so you don't have to worry about
checking whether `staticArgs` exists in `system`.
For example, if your add-on looks like this:
var system = require("sdk/system");
console.log(system.staticArgs.foo);
And you run cfx like this:
<pre>
cfx run --static-args="{ \"foo\": \"Hello from the command line\" }"
</pre>
Then your console should contain this:
<pre>
info: my-addon: Hello from the command line
</pre>
The `--static-args` option is recognized by two of the package-specific
commands: `run` and `xpi`. When used with the `xpi` command, the JSON is
packaged with the XPI's harness options and will therefore be used whenever the
program in the XPI is run.

Просмотреть файл

@ -1,207 +0,0 @@
<!-- This Source Code Form is subject to the terms of the Mozilla Public
- License, v. 2.0. If a copy of the MPL was not distributed with this
- file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
# console #
The `console` object enables your add-on to log messages. If you have started
Firefox for your add-on from the command line with `cfx run` or `cfx test`
then these messages appear in the command shell you used. If the add-on has
been installed in Firefox, then the messages appear in the host application's
[Error Console](https://developer.mozilla.org/en/Error_Console).
If you're developing your add-on using the
[Add-on Builder](https://builder.addons.mozilla.org/) or are using
the [Extension Auto-installer](https://addons.mozilla.org/en-US/firefox/addon/autoinstaller/),
then the add-on is installed in Firefox, meaning that messages will appear in
the Error Console. But see the discussion of
[logging levels](dev-guide/console.html#Logging Levels): by default, messages
logged using `log()`, `info()`, `trace()`, or `warn()` won't be logged in
these situations.
## Console Methods ##
All console methods except `exception()` and `trace()` accept one or
more JavaScript objects as arguments and log them to the console.
Depending on the console's underlying implementation and user interface,
you may be able to examine the properties of non-primitive objects
that are logged.
### <code>console.log(*object*[, *object*, ...])</code> ###
Logs the arguments to the console, preceded by "info:" and the name of your
add-on:
console.log("This is an informational message");
<pre>
info: my-addon: This is an informational message
</pre>
### <code>console.info(*object*[, *object*, ...])</code> ###
A synonym for `console.log()`.
### <code>console.warn(*object*[, *object*, ...])</code> ###
Logs the arguments to the console, preceded by "warn:" and the name of your
add-on:
console.warn("This is a warning message");
<pre>
warn: my-addon: This is a warning message
</pre>
### <code>console.error(*object*[, *object*, ...])</code> ###
Logs the arguments to the console, preceded by "error:" and the name of your
add-on:
console.error("This is an error message");
<pre>
error: my-addon: This is an error message
</pre>
### <code>console.debug(*object*[, *object*, ...])</code> ###
Logs the arguments to the console, preceded by "debug:" and the name of your
add-on:
console.error("This is a debug message");
<pre>
debug: my-addon: This is a debug message
</pre>
### <code>console.exception(*exception*)</code> ###
Logs the given exception instance as an error, outputting information
about the exception's stack traceback if one is available.
try {
doThing();
} catch (e) {
console.exception(e);
}
function UserException(message) {
this.message = message;
this.name = "UserException";
}
function doThing() {
throw new UserException("Thing could not be done!");
}
<pre>
error: my-addon: An exception occurred.
UserException: Thing could not be done!
</pre>
### <code>console.trace()</code> ###
Logs a stack trace at the point the function is called.
<h2 id="Logging Levels">Logging Levels</h2>
Logging's useful, of course, especially during development. But the more
logging there is, the more noise you see in the console output.
Especially when debug logging shows up in a production environment, the
noise can make it harder, not easier, to debug issues.
This is the problem that logging levels are designed to fix. The console
defines a number of logging levels, from "more verbose" to "less verbose",
and a number of different logging functions that correspond to these levels,
which are arranged in order of "severity" from informational
messages, through warnings, to errors.
At a given logging level, only calls to the corresponding functions and
functions with a higher severity will have any effect.
For example, if the logging level is set to "info", then calls to `info()`,
`log()`, `warn()`, and `error()` will all result in output being written.
But if the logging level is "warn" then only calls to `warn()` and `error()`
have any effect, and calls to `info()` and `log()` are simply discarded.
This means that the same code can be more verbose in a development
environment than in a production environment - you just need to arrange for
the appropriate logging level to be set.
The complete set of logging levels is given in the table below, along
with the set of functions that will result in output at each level:
<table>
<colgroup>
<col width="10%">
<col width="90%">
</colgroup>
<tr>
<th>Level</th>
<th>Will log calls to:</th>
</tr>
<tr>
<td>all</td>
<td>Any console method</td>
</tr>
<tr>
<td>debug</td>
<td><code>debug()</code>, <code>log()</code>, <code>info()</code>, <code>trace()</code>, <code>warn()</code>, <code>exception()</code>, <code>error()</code></td>
</tr>
<tr>
<td>info</td>
<td><code>log()</code>, <code>info()</code>, <code>trace()</code>, <code>warn()</code>, <code>exception()</code>, <code>error()</code></td>
</tr>
<tr>
<td>warn</td>
<td><code>warn()</code>, <code>exception()</code>, <code>error()</code></td>
</tr>
<tr>
<td>error</td>
<td><code>exception()</code>, <code>error()</code></td>
</tr>
<tr>
<td>off</td>
<td>Nothing</td>
</tr>
</table>
### Setting the Logging Level ###
The logging level defaults to "error".
There are two system preferences that can be used to override this default:
* **extensions.sdk.console.logLevel**: if set, this determines the logging
level for all installed SDK-based add-ons.
* **extensions.[extension-id].sdk.console.logLevel**: if set, this determines
the logging level for the specified add-on. This overrides the global
preference if both are set.
Both these preferences can be set programmatically using the
[`preferences/service`](modules/sdk/preferences/service.html) API, or manually
using [about:config](http://kb.mozillazine.org/About:config). The value for each
preference is the desired logging level, given as a string.
When you run your add-on using `cfx run` or `cfx test`, the global
**extensions.sdk.console.logLevel** preference is automatically set to "info".
This means that calls to `console.log()` will appear in the console output.
When you install an add-on into Firefox, the logging level will be "error"
by default (that is, unless you have set one of the two preferences). This
means that messages written using `debug()`, `log()`, `info()`, `trace()`,
and `warn()` will not appear in the console.
This includes add-ons being developed using the
[Add-on Builder](https://builder.addons.mozilla.org/) or the
[Extension Auto-installer](https://addons.mozilla.org/en-US/firefox/addon/autoinstaller/).

Просмотреть файл

@ -1,167 +0,0 @@
<!-- This Source Code Form is subject to the terms of the Mozilla Public
- License, v. 2.0. If a copy of the MPL was not distributed with this
- file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
# Credits #
We'd like to thank our many Jetpack project contributors! They include:
### A ###
* Adamantium
* Ehsan Akhgari
* arky
* [Heather Arthur](https://github.com/harthur)
* Dietrich Ayala
### B ###
* [Romain B](https://github.com/Niamor)
* [Louis-Rémi Babé](https://github.com/louisremi)
* Will Bamberg
* Thomas Bassetto
* Tomaz Bevec
* Zbigniew Braniecki
* Daniel Buchner
* James Burke
### C ###
* [Shane Caraveo](https://github.com/mixedpuppy)
* [Matěj Cepl](https://github.com/mcepl)
* Marc Chevrier
* [Timothy Guan-tin Chien](https://github.com/timdream)
* Hernán Rodriguez Colmeiro
* [David Creswick](https://github.com/dcrewi)
### D ###
* dexter
* Christopher Dorn
* Connor Dunn
* dynamis
### F ###
* [Corey Farwell](http://github.com/frewsxcv)
* [Matteo Ferretti](https://github.com/ZER0)
* fuzzykiller
### G ###
* [Marcio Galli](https://github.com/taboca)
* [Ben Gillbanks](http://www.iconfinder.com/browse/iconset/circular_icons/)
* Felipe Gomes
* Irakli Gozalishvili
* Luca Greco
* Jeff Griffiths
* [David Guo](https://github.com/dglol)
### H ###
* Mark Hammond
* Mark A. Hershberger
* Lloyd Hilaiel
* Bobby Holley
### I ###
* Shun Ikejima
### J ###
* Tomislav Jovanovic
* Eric H. Jung
### K ###
* Hrishikesh Kale
* Wes Kocher
* Lajos Koszti
* Kusanagi Kouichi
* [Vladimir Kukushkin](https://github.com/kukushechkin)
### L ###
* Edward Lee
* Gregg Lind
### M ###
* [Nils Maier](https://github.com/nmaier)
* Gervase Markham
* Dave Mason
* Myk Melez
* Zandr Milewski
* Noelle Murata
### N ###
* Siavash Askari Nasr
* Joe R. Nassimian ([placidrage](https://github.com/placidrage))
* Dương H. Nguyễn
* Nick Nguyen
* nodeless
### O ###
* [ongaeshi](https://github.com/ongaeshi)
* Paul OShannessy
* Les Orchard
### P ###
* Robert Pankowecki
* [Jamie Phelps](https://github.com/jxpx777)
* [Alexandre Poirot](https://github.com/ochameau)
* Nickolay Ponomarev
### R ###
* Aza Raskin
### S ###
* [Jordan Santell](https://github.com/jsantell)
* Till Schneidereit
* Justin Scott
* Ayan Shah
* [skratchdot](https://github.com/skratchdot)
* Henrik Skupin
* slash
* Markus Stange
* Dan Stevens
* [J. Ryan Stinnett](https://github.com/jryans)
* [Mihai Sucan](https://github.com/mihaisucan)
* Sunny ([darkowlzz](https://github.com/darkowlzz))
### T ###
* taku0
* Clint Talbert
* Tim Taubert
* Shane Tomlinson
* Dave Townsend
* [Fraser Tweedale](https://github.com/frasertweedale)
* [Matthias Tylkowski](https://github.com/tylkomat)
### V ###
* Peter Van der Beken
* Sander van Veen
* Atul Varma
* [Erik Vold](https://github.com/erikvold)
* Vladimir Vukicevic
### W ###
* Brian Warner
* [Henri Wiechers](https://github.com/hwiechers)
* Drew Willcoxon
* Blake Winton
* Michal Wojciechowski
### Z ###
* Piotr Zalewa
* Brett Zamir

Просмотреть файл

@ -1,73 +0,0 @@
<!-- This Source Code Form is subject to the terms of the Mozilla Public
- License, v. 2.0. If a copy of the MPL was not distributed with this
- file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
# Glossary #
This glossary contains a list of terms used in the Add-on SDK.
__Add-on__: A software package that adds functionality to a Mozilla application,
which can be built with either Mozilla's traditional add-on platform or the SDK.
__Add-on SDK__: A toolchain and associated applications for developing add-ons.
__API Utils__: A small, self-contained set of low-level modules that forms
the base functionality for the SDK. The library can be "bootstrapped" into
any Mozilla application or add-on.
__CFX__: A command-line build, testing, and packaging tool for SDK-based code.
__CommonJS__: A specification for a cross-platform JavaScript module
system and standard library. [Web site](http://commonjs.org/).
__Extension__: Synonym for Add-on.
__Globals__: The set of global variables and objects provided
to all modules, such as `console` and `memory`. Includes
CommonJS globals like `require` and standard JavaScript globals such
as `Array` and `Math`.
<span><a name="host-application">__Host Application__:</a> Add-ons are executed in
the context of a host application, which is the application they are extending.
Firefox and Thunderbird are the most obvious hosts for Mozilla add-ons, but
at present only Firefox is supported as a host for add-ons developed using the
Add-on SDK.</span>
__Jetpack Prototype__: A Mozilla Labs experiment that predated and inspired
the SDK. The SDK incorporates many ideas and some code from the prototype.
__Loader__: An object capable of finding, evaluating, and
exposing CommonJS modules to each other in a given security context,
while providing each module with necessary globals and
enforcing security boundaries between the modules as necessary. It's
entirely possible for Loaders to create new Loaders.
__Low-Level Module__: A module with the following properties:
* Has "chrome" access to the Mozilla platform (e.g. `Components.classes`)
and all globals.
* Is reloadable without leaking memory.
* Logs full exception tracebacks originating from client-provided
callbacks (i.e., does not allow the exceptions to propagate into
Mozilla platform code).
* Can exist side-by-side with multiple instances and versions of
itself.
* Contains documentation on security concerns and threat modeling.
__Module__: A CommonJS module that is either a Low-Level Module
or an Unprivileged Module.
__Package__: A directory structure containing modules,
documentation, tests, and related metadata. If a package contains
a program and includes proper metadata, it can be built into
a Mozilla application or add-on.
__Program__: A module named `main` that optionally exports
a `main()` function. This module is intended either to start an application for
an end-user or add features to an existing application.
__Unprivileged Module__: A CommonJS module that may be run
without unrestricted access to the Mozilla platform, and which may use
all applicable globals that don't require chrome privileges.
[Low-Level Module Best Practices]: dev-guide/module-development/best-practices.html

Просмотреть файл

@ -1,164 +0,0 @@
<!-- This Source Code Form is subject to the terms of the Mozilla Public
- License, v. 2.0. If a copy of the MPL was not distributed with this
- file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
# Accessing the DOM #
This page talks about the access content scripts have to DOM objects
in the pages they are attached to.
## XRayWrapper ##
Content scripts need to be able to access DOM objects in arbitrary web
pages, but this could cause two potential security problems:
1. JavaScript values from the content script could be accessed by the page,
enabling a malicious page to steal data or call privileged methods.
2. a malicious page could redefine standard functions and properties of DOM
objects so they don't do what the add-on expects.
To deal with this, content scripts access DOM objects using
`XRayWrapper`, (also known as
[`XPCNativeWrapper`](https://developer.mozilla.org/en/XPCNativeWrapper)).
These wrappers give the user access to the native values of DOM functions
and properties, even if they have been redefined by a script.
For example: the page below redefines `window.confirm()` to return
`true` without showing a confirmation dialog:
<script type="syntaxhighlighter" class="brush: html"><![CDATA[
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html lang='en' xml:lang='en' xmlns="http://www.w3.org/1999/xhtml">
<head>
<script>
window.confirm = function(message) {
return true;
}
&lt;/script>
</head>
</html>
</script>
But thanks to the wrapper, a content script which calls
`window.confirm()` will get the native implementation:
var widgets = require("sdk/widget");
var tabs = require("sdk/tabs");
var data = require("sdk/self").data;
var widget = widgets.Widget({
id: "transfer",
label: "Transfer",
content: "Transfer",
width: 100,
onClick: function() {
tabs.activeTab.attach({
// native implementation of window.confirm will be used
contentScript: "console.log(window.confirm('Transfer all my money?'));"
});
}
});
tabs.open(data.url("xray.html"));
The wrapper is transparent to content scripts: as far as the content script
is concerned, it is accessing the DOM directly. But because it's not, some
things that you might expect to work, won't. For example, if the page includes
a library like [jQuery](http://www.jquery.com), or any other page script
adds other objects to any DOM nodes, they won't be visible to the content
script. So to use jQuery you'll typically have to add it as a content script,
as in [this example](dev-guide/guides/content-scripts/reddit-example.html).
### XRayWrapper Limitations ###
There are some limitations with accessing objects through XRayWrapper.
First, XRayWrappers don't inherit from JavaScript's
[`Object`](https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Object),
so methods like `valueOf`, `toSource`, and `watch` are not available.
This issue is being tracked as
[bug 787013](https://bugzilla.mozilla.org/show_bug.cgi?id=787013).
Second, you can't access the prototype of an object through an XRayWrapper.
Consider a script like this:
window.HTMLElement.prototype.foo = 'bar';
window.alert(window.document.body.foo);
Run as a normal page script, this will work fine. But if you execute it as
a content script you'll see an error like:
<pre>
TypeError: window.HTMLElement.prototype is undefined
</pre>
This issue is being tracked as
[bug 787070](https://bugzilla.mozilla.org/show_bug.cgi?id=787070).
The main effect of this is that certain features of the
[Prototype JavaScript framework](http://www.prototypejs.org/) don't work
if it is loaded as a content script. As a workaround you can
disable these features by setting
`Prototype.BrowserFeatures.SpecificElementExtensions` to `false`
in `prototype.js`:
<pre>
if (Prototype.Browser.MobileSafari)
Prototype.BrowserFeatures.SpecificElementExtensions = false;
+// Disable element extension in addon-sdk content scripts
+Prototype.BrowserFeatures.SpecificElementExtensions = false;
</pre>
## Adding Event Listeners ##
You can listen for DOM events in a content script just as you can in a normal
page script, but there's one important difference: if you define an event
listener by passing it as a string into
[`setAttribute()`](https://developer.mozilla.org/en/DOM/element.setAttribute),
then the listener is evaluated in the page's context, so it will not have
access to any variables defined in the content script.
For example, this content script will fail with the error "theMessage is not
defined":
var theMessage = "Hello from content script!";
anElement.setAttribute("onclick", "alert(theMessage);");
So using `setAttribute()` is not recommended. Instead, add a listener by
assignment to
[`onclick`](https://developer.mozilla.org/en/DOM/element.onclick) or by using
[`addEventListener()`](https://developer.mozilla.org/en/DOM/element.addEventListener),
in either case defining the listener as a function:
var theMessage = "Hello from content script!";
anElement.onclick = function() {
alert(theMessage);
};
anotherElement.addEventListener("click", function() {
alert(theMessage);
});
Note that with both `onclick` assignment and `addEventListener()`, you must
define the listener as a function. It cannot be defined as a string, whether
in a content script or in a page script.
## unsafeWindow ##
If you really need direct access to the underlying DOM, you can use the
global `unsafeWindow` object.
To see the difference, try editing the example above
so the content script uses `unsafeWindow.confirm()` instead of
`window.confirm()`.
Avoid using `unsafeWindow` if possible: it is the same concept as
Greasemonkey's unsafeWindow, and the
[warnings for that](http://wiki.greasespot.net/UnsafeWindow) apply equally
here. Also, `unsafeWindow` isn't a supported API, so it could be removed or
changed in a future version of the SDK.

Просмотреть файл

@ -1,246 +0,0 @@
<!-- This Source Code Form is subject to the terms of the Mozilla Public
- License, v. 2.0. If a copy of the MPL was not distributed with this
- file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
# Communicating With Other Scripts #
This section of the guide explains how content scripts can
communicate with:
* [your `main.js` file](dev-guide/guides/content-scripts/communicating-with-other-scripts.html#main.js),
or any other modules in your add-on
* [other content scripts loaded by your add-on](dev-guide/guides/content-scripts/communicating-with-other-scripts.html#Content Scripts)
* [page scripts](dev-guide/guides/content-scripts/communicating-with-other-scripts.html#Page Scripts) (that is, scripts embedded in the web page or
included using `<script>` tags)
## main.js ##
Your content scripts can communicate with your add-on's "main.js"
(or any other modules you're written for your add-on) by sending it messages,
using either the `port.emit()` API or the `postMessage()` API. See the
articles on
[using `postMessage()`](dev-guide/guides/content-scripts/using-postmessage.html)
and
[using `port`](dev-guide/guides/content-scripts/using-port.html) for details.
## Content Scripts ##
Content scripts loaded into the same document can interact
with each other directly as well as with the web content itself. However,
content scripts which have been loaded into different documents
cannot interact with each other.
For example:
* if an add-on creates a single `panel` object and loads several content
scripts into the panel, then they can interact with each other
* if an add-on creates two `panel` objects and loads a script into each
one, they can't interact with each other.
* if an add-on creates a single `page-mod` object and loads several content
scripts into the page mod, then only content scripts associated with the
same page can interact with each other: if two different matching pages are
loaded, content scripts attached to page A cannot interact with those attached
to page B.
The web content has no access to objects created by the content script, unless
the content script explicitly makes them available.
## Page Scripts ##
If a page includes its own scripts using `<script>` tags,
either embedded in the page or linked to it using the `src` attribute, there
are a couple of ways a content script can communicate with it:
* using the [DOM `postMessage()` API](dev-guide/guides/content-scripts/communicating-with-other-scripts.html#Using the DOM postMessage API)
* using [custom DOM events](dev-guide/guides/content-scripts/communicating-with-other-scripts.html#Using Custom DOM Events)
### Using the DOM postMessage API ###
You can communicate between the content script and page scripts using
[`window.postMessage()`](https://developer.mozilla.org/en/DOM/window.postMessage),
but there's a twist: in early versions of the SDK, the global `postMessage()`
function in content scripts was used for communicating between the content
script and the main add-on code. Although this has been
[deprecated in favor of `self.postMessage`](https://wiki.mozilla.org/Labs/Jetpack/Release_Notes/1.0b5#Major_Changes),
the old globals are still supported, so you can't currently use
`window.postMessage()`. You must use `document.defaultView.postMessage()`
instead.
#### Messaging From Content Script To Page Script ####
Suppose we have a page called "listen.html" hosted at "my-domain.org", and we want to send messages
from the add-on to a script embedded in that page.
In the main add-on code, we have a
[`page-mod`](modules/sdk/page-mod.html) that attaches the content script
"talk.js" to the right page:
var data = require("sdk/self").data;
var pageMod = require("sdk/page-mod");
pageMod.PageMod({
include: "http://my-domain.org/listen.html",
contentScriptFile: data.url("talk.js")
});
The "talk.js" content script uses `document.defaultView.postMessage()` to send
the message to the page:
document.defaultView.postMessage("Message from content script", "http://my-domain.org/");
The second argument may be '*' which will allow communication with any domain.
Finally, "listen.html" uses `window.addEventListener()` to listen for
messages from the content script:
<script type="syntaxhighlighter" class="brush: html"><![CDATA[
&lt;!DOCTYPE html&gt;
&lt;html&gt;
&lt;head&gt;&lt;/head&gt;
&lt;body&gt;
&lt;script&gt;
window.addEventListener('message', function(event) {
window.alert(event.data);
}, false);
&lt;/script&gt;
&lt;/body&gt;
&lt;/html&gt;
]]>
</script>
#### Messaging From Page Script To Content Script ####
Sending messages from the page script to the content script is just
the same, but in reverse.
Here "main.js" creates a [`page-mod`](modules/sdk/page-mod.html)
that attaches "listen.js" to the web page:
var data = require("sdk/self").data;
var pageMod = require("sdk/page-mod");
pageMod.PageMod({
include: "http://my-domain.org/talk.html",
contentScriptFile: data.url("listen.js")
});
The web page "talk.html" embeds a script that uses `window.postMessage()`
to send the content script a message when the user clicks a button:
<script type="syntaxhighlighter" class="brush: html"><![CDATA[
&lt;!DOCTYPE html&gt;
&lt;html&gt;
&lt;head&gt;&lt;/head&gt;
&lt;body&gt;
&lt;script&gt;
function sendMessage() {
window.postMessage("Message from page script", "http://my-domain.org/");
}
&lt;/script&gt;
&lt;button onclick="sendMessage()"&gt;Send Message&lt;/button&gt;
&lt;/body&gt;
&lt;/html&gt;
</script>
Finally, the content script "listen.js" uses
`document.defaultView.addEventListener()` to listen for messages from the page
script:
document.defaultView.addEventListener('message', function(event) {
console.log(event.data);
console.log(event.origin);
}, false);
### Using Custom DOM Events ###
As an alternative to using `postMessage()` you can use
[custom DOM events](https://developer.mozilla.org/en/DOM/CustomEvent)
to communicate between page scripts and content scripts.
#### Messaging From Content Script To Page Script ####
Here's an example showing how to use custom DOM events to send a message
from a content script to a page script.
First, "main.js" will create a [`page-mod`](modules/sdk/page-mod.html)
that will attach "talk.js" to the target web page:
var data = require("sdk/self").data;
var pageMod = require("sdk/page-mod");
pageMod.PageMod({
include: "http://my-domain.org/listen.html",
contentScriptFile: data.url("talk.js")
});
Next, "talk.js" creates and dispatches a custom event, passing the payload
in the `detail` parameter to `initCustomEvent()`:
<!-- This comment is used to terminate the Markdown list above -->
var event = document.createEvent('CustomEvent');
event.initCustomEvent("addon-message", true, true, { hello: 'world' });
document.documentElement.dispatchEvent(event);
Finally "listen.html" listens for the new event and examines its
`detail` attribute to retrieve the payload:
<script type="syntaxhighlighter" class="brush: html"><![CDATA[
&lt;!DOCTYPE html&gt;
&lt;html&gt;
&lt;head&gt;&lt;/head&gt;
&lt;body&gt;
&lt;script&gt;
document.documentElement.addEventListener("addon-message", function(event) {
window.alert(JSON.stringify(event.detail))
}, false);
&lt;/script&gt;
&lt;/body&gt;
&lt;/html&gt;
</script>
#### Messaging From Page Script to Content Script ####
Sending messages using custom DOM events from the page script
to the content script is just the same, but in reverse.
Again, "main.js" creates a [`page-mod`](modules/sdk/page-mod.html)
to target the page we are interested in:
var data = require("sdk/self").data;
var pageMod = require("sdk/page-mod");
pageMod.PageMod({
include: "http://my-domain.org/talk.html",
contentScriptFile: data.url("listen.js")
});
The web page "talk.html" creates and dispatches a custom DOM event,
using `initCustomEvent()`'s `detail` parameter to supply the payload:
<script type="syntaxhighlighter" class="brush: html"><![CDATA[
&lt;!DOCTYPE html&gt;
&lt;html&gt;
&lt;head&gt;&lt;/head&gt;
&lt;body&gt;
&lt;script&gt;
function sendMessage() {
var event = document.createEvent('CustomEvent');
event.initCustomEvent("addon-message", true, true, { hello: 'world' });
document.documentElement.dispatchEvent(event);
}
&lt;/script&gt;
&lt;button onclick="sendMessage()"&gt;Send Message&lt;/button&gt;
&lt;/body&gt;
&lt;/html&gt;
</script>
Finally, the content script "listen.js" listens for the new event
and retrieves the payload from its `detail` attribute:
document.documentElement.addEventListener("addon-message", function(event) {
console.log(JSON.stringify(event.detail));
}, false);

Просмотреть файл

@ -1,177 +0,0 @@
<!-- This Source Code Form is subject to the terms of the Mozilla Public
- License, v. 2.0. If a copy of the MPL was not distributed with this
- file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
# Cross-domain Content Scripts #
By default, content scripts don't have any cross-domain privileges.
In particular, they can't:
* [access content hosted in an `iframe`, if that content is served from a different domain](dev-guide/guides/content-scripts/cross-domain.html#Cross-domain iframes)
* [make cross-domain XMLHttpRequests](dev-guide/guides/content-scripts/cross-domain.html#Cross-domain XMLHttpRequest)
However, you can enable these features for specific domains
by adding them to your add-on's [package.json](dev-guide/package-spec.html)
under the `"cross-domain-content"` key, which itself lives under the
`"permissions"` key:
<pre>
"permissions": {
"cross-domain-content": ["http://example.org/", "http://example.com/"]
}
</pre>
* The domains listed must include the scheme and fully qualified domain name,
and these must exactly match the domains serving the content - so in the
example above, the content script will not be allowed to access content
served from `https://example.com/`.
* Wildcards are not allowed.
* This feature is currently only available for content scripts, not for page
scripts included in HTML files shipped with your add-on.
## Cross-domain iframes ##
The following "main.js" creates a page-worker which loads a local HTML file
called "page.html", attaches a content script called "page.js" to the
page, waits for messages from the script, and logs the payload.
//main.js
var data = require("sdk/self").data;
var pageWorker = require("sdk/page-worker").Page({
contentURL: data.url("page.html"),
contentScriptFile: data.url("page-script.js")
});
pageWorker.on("message", function(message) {
console.log(message);
});
The "page.html" file embeds an iframe whose content is
served from "http://en.m.wikipedia.org/":
<pre class="brush: html">
&lt;!doctype html&gt;
&lt;!-- page.html --&gt;
&lt;html&gt;
&lt;head>&lt;/head&gt;
&lt;body&gt;
&lt;iframe id="wikipedia" src="http://en.m.wikipedia.org/"&gt;&lt;/iframe&gt;
&lt;/body&gt;
&lt;/html&gt;
</pre>
The "page-script.js" file locates "Today's Featured Article" and sends its
content to "main.js":
// page-script.js
var iframe = window.document.getElementById("wikipedia");
var todaysFeaturedArticle = iframe.contentWindow.document.getElementById("mp-tfa");
self.postMessage(todaysFeaturedArticle.textContent);
For this to work, we need to add the `"cross-domain-content"` key to
"package.json":
<pre>
"permissions": {
"cross-domain-content": ["http://en.m.wikipedia.org/"]
}
</pre>
The add-on should successfully retrieve the iframe's content.
## Cross-domain XMLHttpRequest ##
The following add-on creates a panel whose content is the summary weather
forecast for [Shetland](https://en.wikipedia.org/wiki/Shetland).
If you want to try it out, you'll need to
[register](http://www.metoffice.gov.uk/datapoint/support/API)
and get an API key.
The "main.js":
* creates a panel whose content is supplied by "panel.html" and
adds a content script "panel-script.js" to it
* sends the panel a "show" message when it is shown
* attaches the panel to a widget
<!-- terminate Markdown list -->
// main.js
var data = require("sdk/self").data;
var forecast_panel = require("sdk/panel").Panel({
height: 50,
contentURL: data.url("panel.html"),
contentScriptFile: data.url("panel-script.js")
});
forecast_panel.on("show", function(){
forecast_panel.port.emit("show");
});
require("sdk/widget").Widget({
id: "forecast",
label: "Weather Forecast",
contentURL: "http://www.metoffice.gov.uk/favicon.ico",
panel: forecast_panel
});
The "panel.html" just includes a `<div>` block for the forecast:
<pre class="brush: html">
&lt;!doctype HTML&gt;
&lt;!-- panel.html --&gt;
&lt;html&gt;
&lt;head&gt;&lt;/head&gt;
&lt;body&gt;
&lt;div id="forecast_summary">&lt;/div&gt;
&lt;/body&gt;
&lt;/html&gt;
</pre>
The "panel-script.js" uses [XMLHttpRequest](https://developer.mozilla.org/en-US/docs/DOM/XMLHttpRequest)
to fetch the latest forecast:
// panel-script.js
var url = "http://datapoint.metoffice.gov.uk/public/data/txt/wxfcs/regionalforecast/json/500?key=YOUR-API-KEY";
self.port.on("show", function () {
var request = new XMLHttpRequest();
request.open("GET", url, true);
request.onload = function () {
var jsonResponse = JSON.parse(request.responseText);
var summary = getSummary(jsonResponse);
var element = document.getElementById("forecast_summary");
element.textContent = summary;
};
request.send();
});
function getSummary(forecast) {
return forecast.RegionalFcst.FcstPeriods.Period[0].Paragraph[0].$;
}
Finally, we need to add the `"cross-domain-content"` key to "package.json":
<pre>
"permissions": {
"cross-domain-content": ["http://datapoint.metoffice.gov.uk"]
}
</pre>
## Content Permissions and unsafeWindow ##
If you use `"cross-domain-content"`, then JavaScript values in content
scripts will not be available from pages. Suppose your content script includes
a line like:
// content-script.js:
unsafeWindow.myCustomAPI = function () {};
If you have included the `"cross-domain-content"` key, when the page script
tries to access `myCustomAPI` this will result in a "permission denied"
exception.

Просмотреть файл

@ -1,98 +0,0 @@
<!-- This Source Code Form is subject to the terms of the Mozilla Public
- License, v. 2.0. If a copy of the MPL was not distributed with this
- file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
# Content Scripts #
Almost all interesting add-ons will need to interact with web content or the
browser's user interface. For example, they may need to access and modify the
content of web pages or be notified when the user clicks a link.
The SDK provides several core modules to support this:
**[context-menu](modules/sdk/context-menu.html)**<br>
Add items to the browser's context menu.
**[panel](modules/sdk/panel.html)**<br>
Create a dialog that can host web content.
**[page-worker](modules/sdk/page-worker.html)**<br>
Retrieve a page and access its content, without displaying it to the user.
**[page-mod](modules/sdk/page-mod.html)**<br>
Execute scripts in the context of selected web pages.
**[tabs](modules/sdk/tabs.html)**<br>
Manipulate the browser's tabs, including the web content displayed in the tab.
**[widget](modules/sdk/widget.html)**<br>
Host an add-on's user interface, including web content.
Firefox is moving towards a model in which it uses separate
processes to display the UI, handle web content, and execute add-ons. The main
add-on code will run in the add-on process and will not have direct access to
any web content.
This means that an add-on which needs to interact with web content needs to be
structured in two parts:
* the main script runs in the add-on process
* any code that needs to interact with web content is loaded into the web
content process as a separate script. These separate scripts are called
_content scripts_.
A single add-on may use multiple content scripts, and content scripts loaded
into the same context can interact directly with each other as well as with
the web content itself. See the chapter on
<a href="dev-guide/guides/content-scripts/communicating-with-other-scripts.html">
communicating with other scripts</a>.
The add-on script and content script can't directly access each other's state.
Instead, you can define your own events which each side can emit, and the
other side can register listeners to handle them. The interfaces are similar
to the event-handling interfaces described in the
[Working with Events](dev-guide/guides/events.html) guide.
The diagram below shows an overview of the main components and their
relationships. The gray fill represents code written by the add-on developer.
<img class="image-center" src="static-files/media/content-scripting-overview.png"
alt="Content script events">
This might sound complicated but it doesn't need to be. The following add-on
uses the [page-mod](modules/sdk/page-mod.html) module to replace the
content of any web page in the `.co.uk` domain by executing a content script
in the context of that page:
var pageMod = require("sdk/page-mod");
pageMod.PageMod({
include: ["*.co.uk"],
contentScript: 'document.body.innerHTML = ' +
'"<h1>this page has been eaten</h1>";'
});
In this example the content script is supplied directly to the page mod via
the `contentScript` option in its constructor, and does not need to be
maintained as a separate file at all.
The next few chapters explain content scripts in detail:
* [Loading Content Scripts](dev-guide/guides/content-scripts/loading.html):
how to attach content scripts to web pages, and how to control the point at
which they are executed
* [Accessing the DOM](dev-guide/guides/content-scripts/accessing-the-dom.html):
detail about the access content scripts get to the DOM
* [Communicating With Other Scripts](dev-guide/guides/content-scripts/communicating-with-other-scripts.html):
detail about how content scripts can communicate with "main.js", with other
content scripts, and with scripts loaded by the web page itself
* [Communicating Using <code>port</code>](dev-guide/guides/content-scripts/using-port.html):
how to communicate between your add-on and its content scripts using the
<code>port</code> object
* [Communicating using <code>postMessage()</code>](dev-guide/guides/content-scripts/using-postmessage.html):
how to communicate between your add-on and its content scripts using the
<code>postMessage()</code> API
* [Cross-domain Content Scripts](dev-guide/guides/content-scripts/cross-domain.html):
how to enable a content script to interact with content served from other domains.
* [Example](dev-guide/guides/content-scripts/reddit-example.html):
a simple example add-on using content scripts

Просмотреть файл

@ -1,79 +0,0 @@
<!-- This Source Code Form is subject to the terms of the Mozilla Public
- License, v. 2.0. If a copy of the MPL was not distributed with this
- file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
# Loading Content Scripts #
The constructors for content-script-using objects such as panel and page-mod
define a group of options for loading content scripts:
<pre>
contentScript string, array
contentScriptFile string, array
contentScriptWhen string
contentScriptOptions object
</pre>
We have already seen the `contentScript` option, which enables you to pass
in the text of the script itself as a string literal. This version of the API
avoids the need to maintain a separate file for the content script.
The `contentScriptFile` option enables you to pass in the local file URL from
which the content script will be loaded. To supply the file
"my-content-script.js", located in the /data subdirectory under your add-on's
root directory, use a line like:
// "data" is supplied by the "self" module
var data = require("sdk/self").data;
...
contentScriptFile: data.url("my-content-script.js")
Both `contentScript` and `contentScriptFile` accept an array of strings, so you
can load multiple scripts, which can also interact directly with each other in
the content process:
// "data" is supplied by the "self" module
var data = require("sdk/self").data;
...
contentScriptFile:
[data.url("jquery-1.4.2.min.js"), data.url("my-content-script.js")]
Scripts specified using contentScriptFile are loaded before those specified
using contentScript. This enables you to load a JavaScript library like jQuery
by URL, then pass in a simple script inline that can use jQuery.
<div class="warning">
<p>Unless your content script is extremely simple and consists only of a
static string, don't use <code>contentScript</code>: if you do, you may
have problems getting your add-on approved on AMO.</p>
<p>Instead, keep the script in a separate file and load it using
<code>contentScriptFile</code>. This makes your code easier to maintain,
secure, debug and review.</p>
</div>
The `contentScriptWhen` option specifies when the content script(s) should be
loaded. It takes one of three possible values:
* "start" loads the scripts immediately after the document element for the
page is inserted into the DOM. At this point the DOM content hasn't been
loaded yet, so the script won't be able to interact with it.
* "ready" loads the scripts after the DOM for the page has been loaded: that
is, at the point the
[DOMContentLoaded](https://developer.mozilla.org/en/Gecko-Specific_DOM_Events)
event fires. At this point, content scripts are able to interact with the DOM
content, but externally-referenced stylesheets and images may not have finished
loading.
* "end" loads the scripts after all content (DOM, JS, CSS, images) for the page
has been loaded, at the time the
[window.onload event](https://developer.mozilla.org/en/DOM/window.onload)
fires.
The default value is "end".
The `contentScriptOptions` is a json that is exposed to content scripts as a read
only value under `self.options` property.
Any kind of jsonable value (object, array, string, etc.) can be used here.

Просмотреть файл

@ -1,71 +0,0 @@
<!-- This Source Code Form is subject to the terms of the Mozilla Public
- License, v. 2.0. If a copy of the MPL was not distributed with this
- file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
# Reddit Example #
This example add-on creates a panel containing the mobile version of Reddit.
When the user clicks on the title of a story in the panel, the add-on opens
the linked story in a new tab in the main browser window.
To accomplish this the add-on needs to run a content script in the context of
the Reddit page which intercepts mouse clicks on each title link and fetches the
link's target URL. The content script then needs to send the URL to the add-on
script.
This is the complete add-on script:
var data = require("sdk/self").data;
var reddit_panel = require("sdk/panel").Panel({
width: 240,
height: 320,
contentURL: "http://www.reddit.com/.mobile?keep_extension=True",
contentScriptFile: [data.url("jquery-1.4.4.min.js"),
data.url("panel.js")]
});
reddit_panel.port.on("click", function(url) {
require("sdk/tabs").open(url);
});
require("sdk/widget").Widget({
id: "open-reddit-btn",
label: "Reddit",
contentURL: "http://www.reddit.com/static/favicon.ico",
panel: reddit_panel
});
This code supplies two content scripts to the panel's constructor in the
`contentScriptFile` option: the jQuery library and the script that intercepts
link clicks.
Finally, it registers a listener to the user-defined `click` event which in
turn passes the URL into the `open` function of the
[tabs](modules/sdk/tabs.html) module.
This is the `panel.js` content script that intercepts link clicks:
$(window).click(function (event) {
var t = event.target;
// Don't intercept the click if it isn't on a link.
if (t.nodeName != "A")
return;
// Don't intercept the click if it was on one of the links in the header
// or next/previous footer, since those links should load in the panel itself.
if ($(t).parents('#header').length || $(t).parents('.nextprev').length)
return;
// Intercept the click, passing it to the addon, which will load it in a tab.
event.stopPropagation();
event.preventDefault();
self.port.emit('click', t.toString());
});
This script uses jQuery to interact with the DOM of the page and the
`self.port.emit` function to pass URLs back to the add-on script.
See the `examples/reddit-panel` directory for the complete example (including
the content script containing jQuery).

Просмотреть файл

@ -1,292 +0,0 @@
<!-- This Source Code Form is subject to the terms of the Mozilla Public
- License, v. 2.0. If a copy of the MPL was not distributed with this
- file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
# Communicating using "port" #
To enable add-on scripts and content scripts to communicate with each other,
each end of the conversation has access to a `port` object.
* to send messages from one side to the other, use `port.emit()`
* to receive messages sent from the other side, use `port.on()`
<img class="image-center" src="static-files/media/content-scripting-events.png"
alt="Content script events">
Messages are asynchronous: that is, the sender does not wait for a reply from
the recipient but just emits the message and continues processing.
Here's a simple add-on that sends a message to a content script using `port`:
var tabs = require("sdk/tabs");
var alertContentScript = "self.port.on('alert', function(message) {" +
" window.alert(message);" +
"})";
tabs.on("ready", function(tab) {
worker = tab.attach({
contentScript: alertContentScript
});
worker.port.emit("alert", "Message from the add-on");
});
tabs.open("http://www.mozilla.org");
In total, the `port` object defines four functions:
* [`emit()`](dev-guide/guides/content-scripts/using-port.html#port.emit()):
emit a message.
* [`on()`](dev-guide/guides/content-scripts/using-port.html#port.on()):
listen to a message.
* [`removeListener()`](dev-guide/guides/content-scripts/using-port.html#port.removeListener()):
stop listening to a message.
* [`once()`](dev-guide/guides/content-scripts/using-port.html#port.once()):
listen to only the first occurrence of a message.
## Accessing `port` ##
### Accessing `port` in the Content Script ###
<span class="aside">Note that the global `self` object is completely
different from the [`self` module](modules/sdk/self.html), which
provides an API for an add-on to access its data files and ID.</span>
In the content script the `port` object is available as a property of the
global `self` object. Thus, to emit a message from a content script:
self.port.emit("myContentScriptMessage", myContentScriptMessagePayload);
To receive a message from the add-on code:
self.port.on("myAddonMessage", function(myAddonMessagePayload) {
// Handle the message
});
Compare this to the technique used to receive _built-in_ messages in the
content script. For example, to receive the `context` message in a content script
associated with a [context menu](modules/sdk/context-menu.html)
object, you would call the `on` function attached to the global `self` object:
self.on("context", function() {
// Handle the message
});
So the `port` property is essentially used here as a namespace for
user-defined messages.
### Accessing `port` in the Add-on Script ###
In the add-on code, the channel of communication between the add-on and a
particular content script context is encapsulated by the
[`worker`](modules/sdk/content/worker.html#Worker) object. Thus
the `port` object for communicating with a content script is a property of the
corresponding `worker` object.
However, the worker is not exposed to add-on code in quite the same way
in all modules. The `panel` and `page-worker` objects integrate the
worker API directly. So to receive messages from a content script associated
with a panel you use `panel.port.on()`:
var panel = require("sdk/panel").Panel({
contentScript: "self.port.emit('showing', 'panel is showing');"
});
panel.port.on("showing", function(text) {
console.log(text);
});
panel.show();
Conversely, to emit user-defined messages from your add-on you can just call
`panel.port.emit()`:
var panel = require("sdk/panel").Panel({
contentScript: "self.port.on('alert', function(text) {" +
" console.log(text);" +
"});"
});
panel.show();
panel.port.emit("alert", "panel is showing");
The `panel` and `page-worker` objects only host a single page at a time,
so each distinct page object only needs a single channel of communication
to its content scripts. But some modules, such as `page-mod`, might need to
handle multiple pages, each with its own context in which the content scripts
are executing, so it needs a separate channel (worker) for each page.
So `page-mod` does not integrate the worker API directly: instead, each time a
content script is attached to a page, the
[worker](modules/sdk/content/worker.html#Worker) associated with the page is
supplied to the page-mod in its `onAttach` function. By supplying a target for
this function in the page-mod's constructor you can register to receive
messages from the content script, and take a reference to the worker so as to
emit messages to the content script.
var pageModScript = "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);" +
"});"
var pageMod = require('sdk/page-mod').PageMod({
include: ['*'],
contentScript: pageModScript,
onAttach: function(worker) {
worker.port.on('click', function(html) {
worker.port.emit('warning', 'Do not click this again');
});
}
});
In the add-on above there are two user-defined messages:
* `click` is sent from the page-mod to the add-on, when the user clicks an
element in the page
* `warning` sends a silly string back to the page-mod
## port.emit() ##
The `port.emit()` function sends a message from the "main.js", or another
add-on module, to a content script, or vice versa.
It may be called with any number of parameters, but is most likely to be
called with a name for the message and an optional payload.
The payload can be any value that is
<a href = "dev-guide/guides/content-scripts/using-port.html#json_serializable">serializable to JSON</a>.
From the content script to the main add-on code:
var myMessagePayload = "some data";
self.port.emit("myMessage", myMessagePayload);
From the main add-on code (in this case a panel instance)
to the content script:
var myMessagePayload = "some data";
panel.port.emit("myMessage", myMessagePayload);
## port.on() ##
The `port.on()` function registers a function as a listener for a specific
named message sent from the other side using `port.emit()`.
It takes two parameters: the name of the message and a function to handle it.
In a content script, to listen for "myMessage" sent from the main
add-on code:
self.port.on("myMessage", function handleMyMessage(myMessagePayload) {
// Handle the message
});
In the main add-on code (in this case a panel instance), to listen for
"myMessage" sent from a a content script:
panel.port.on("myMessage", function handleMyMessage(myMessagePayload) {
// Handle the message
});
## port.removeListener() ##
You can uses `port.on()` to listen for messages. To stop listening for a
particular message, use `port.removeListener()`. This takes the
same two parameters as `port.on()`: the name of the message, and the name
of the listener function.
For example, here's an add-on that creates a page-worker and a widget.
The page-worker loads
[http://en.wikipedia.org/wiki/Chalk](http://en.wikipedia.org/wiki/Chalk)
alongside a content script "listener.js". The widget sends the content script
a message called "get-first-para" when it is clicked:
pageWorker = require("sdk/page-worker").Page({
contentScriptFile: require("sdk/self").data.url("listener.js"),
contentURL: "http://en.wikipedia.org/wiki/Chalk"
});
require("sdk/widget").Widget({
id: "mozilla-icon",
label: "My Mozilla Widget",
contentURL: "http://www.mozilla.org/favicon.ico",
onClick: function() {
console.log("sending 'get-first-para'");
pageWorker.port.emit("get-first-para");
}
});
The content script "listener.js" listens for "get-first-para". When it
receives this message, the script logs the first paragraph of the document
and then calls `removeListener()` to stop listening.
function getFirstParagraph() {
var paras = document.getElementsByTagName('p');
console.log(paras[0].textContent);
self.port.removeListener("get-first-para", getFirstParagraph);
}
self.port.on("get-first-para", getFirstParagraph);
The result is that the paragraph is only logged the first time the widget
is clicked.
Due to [bug 816272](https://bugzilla.mozilla.org/show_bug.cgi?id=816272)
the [`page-mod`](modules/sdk/page-mod.html)'s `removeListener()` function
does not prevent the listener from receiving messages that are already queued.
This means that if "main.js" sends the message twice in successive lines, and
the listener stops listening as soon as it receives the first message, then
the listener will still receive the second message.
## port.once() ##
Often you'll want to receive a message just once, then stop listening. The
`port` object offers a shortcut to do this: the `once()` method.
This example rewrites the "listener.js" content script in the
[`port.removeListener()` example](dev-guide/guides/content-scripts/using-port.html#port.removeListener())
so that it uses `once()`:
function getFirstParagraph() {
var paras = document.getElementsByTagName('p');
console.log(paras[0].textContent);
}
self.port.once("get-first-para", getFirstParagraph);
## <a name="json_serializable">JSON-Serializable Values</a> ##
The payload for an message can be any JSON-serializable value. When messages
are sent their payloads are automatically serialized, and when messages are
received their payloads are automatically deserialized, so you don't need to
worry about serialization.
However, you _do_ have to ensure that the payload can be serialized to JSON.
This means that it needs to be a string, number, boolean, null, array of
JSON-serializable values, or an object whose property values are themselves
JSON-serializable. This means you can't send functions, and if the object
contains methods they won't be encoded.
For example, to include an array of strings in the payload:
var pageModScript = "self.port.emit('loaded'," +
" [" +
" document.location.toString()," +
" document.title" +
" ]" +
");"
var pageMod = require('page-mod').PageMod({
include: ['*'],
contentScript: pageModScript,
onAttach: function(worker) {
worker.port.on('loaded', function(pageInfo) {
console.log(pageInfo[0]);
console.log(pageInfo[1]);
});
}
});

Просмотреть файл

@ -1,140 +0,0 @@
<!-- This Source Code Form is subject to the terms of the Mozilla Public
- License, v. 2.0. If a copy of the MPL was not distributed with this
- file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
# Communicating using "postMessage()" #
As an alternative to user-defined events content modules support the built-in
`message` event. In most cases user-defined events are preferable to message
events. However, the `context-menu` module does not support user-defined
events, so to send messages from a content script to the add-on via a context
menu object, you must use message events.
## Handling Message Events in the Content Script ##
To send a message from a content script, you use the `postMessage` function of
the global `self` object:
self.postMessage(contentScriptMessage);
This takes a single parameter, the message payload, which may be any
<a href = "dev-guide/guides/content-scripts/using-port.html#json_serializable">JSON-serializable value</a>.
To receive a message from the add-on script, use `self`'s `on` function:
self.on("message", function(addonMessage) {
// Handle the message
});
Like all event-registration functions, this takes two parameters: the name
of the event, and the handler function. The handler function is passed the
message payload.
## Handling Message Events in the Add-on Script ##
To send a message to a content script, use the worker's `postMessage`
function. Again, `panel` and `page` integrate `worker` directly:
// Post a message to the panel's content scripts
panel.postMessage(addonMessage);
However, for `page-mod` objects you need to listen to the `onAttach` event
and use the [worker](modules/sdk/content/worker.html#Worker) supplied to that:
var pageMod = require('sdk/page-mod').PageMod({
include: ['*'],
contentScript: pageModScript,
onAttach: function(worker) {
worker.postMessage(addonMessage);
}
});
To receive messages from a content script, use the worker's `on` function.
To simplify this most content modules provide an `onMessage` property as an
argument to the constructor:
panel = require("sdk/panel").Panel({
onMessage: function(contentScriptMessage) {
// Handle message from the content script
}
});
## Message Events Versus User-Defined Events ##
You can use message events as an alternative to user-defined events:
var pageModScript = "window.addEventListener('mouseover', function(event) {" +
" self.postMessage(event.target.toString());" +
"}, false);";
var pageMod = require('sdk/page-mod').PageMod({
include: ['*'],
contentScript: pageModScript,
onAttach: function(worker) {
worker.on('message', function(message) {
console.log('mouseover: ' + message);
});
}
});
The reason to prefer user-defined events is that as soon as you need to send
more than one type of message, then both sending and receiving messages gets
more complex.
Suppose the content script wants to send `mouseout` events as well as
`mouseover`. Now we have to embed the event type in the message payload, and
implement a switch function in the receiver to dispatch the message:
var pageModScript = "window.addEventListener('mouseover', function(event) {" +
" self.postMessage({" +
" kind: 'mouseover'," +
" element: event.target.toString()" +
" });" +
"}, false);" +
"window.addEventListener('mouseout', function(event) {" +
" self.postMessage({" +
" kind: 'mouseout'," +
" element: event.target.toString()" +
" });" +
" }, false);"
var pageMod = require('sdk/page-mod').PageMod({
include: ['*'],
contentScript: pageModScript,
onAttach: function(worker) {
worker.on('message', function(message) {
switch(message.kind) {
case 'mouseover':
console.log('mouseover: ' + message.element);
break;
case 'mouseout':
console.log('mouseout: ' + message.element);
break;
}
});
}
});
Implementing the same add-on with user-defined events is shorter and more
readable:
var pageModScript = "window.addEventListener('mouseover', function(event) {" +
" self.port.emit('mouseover', event.target.toString());" +
"}, false);" +
"window.addEventListener('mouseout', function(event) {" +
" self.port.emit('mouseout', event.target.toString());" +
"}, false);";
var pageMod = require('sdk/page-mod').PageMod({
include: ['*'],
contentScript: pageModScript,
onAttach: function(worker) {
worker.port.on('mouseover', function(message) {
console.log('mouseover :' + message);
});
worker.port.on('mouseout', function(message) {
console.log('mouseout :' + message);
});
}
});

Просмотреть файл

@ -1,272 +0,0 @@
<!-- This Source Code Form is subject to the terms of the Mozilla Public
- License, v. 2.0. If a copy of the MPL was not distributed with this
- file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
#Classes and Inheritance
A class is a blueprint from which individual objects are created. These
individual objects are the instances of the class. Each class defines one or
more members, which are initialized to a given value when the class is
instantiated. Data members are properties that allow each instance to have
their own state, whereas member functions are properties that allow instances to
have behavior. Inheritance allows classes to inherit state and behavior from an
existing classes, known as the base class. Unlike languages like C++ and Java,
JavaScript does not have native support for classical inheritance. Instead, it
uses something called prototypal inheritance. As it turns out, it is possible to
emulate classical inheritance using prototypal inheritance, but not without
writing a significant amount of boilerplate code.
Classes in JavaScript are defined using constructor functions. Each constructor
function has an associated object, known as its prototype, which is shared
between all instances of that class. We will show how to define classes using
constructors, and how to use prototypes to efficiently define member functions
on each instance. Classical inheritance can be implemented in JavaScript using
constructors and prototypes. We will show how to make inheritance work correctly
with respect to constructors, prototypes, and the instanceof operator, and how
to override methods in subclasses. The SDK uses a special constructor internally,
known as `Class`, to create constructors that behave properly with respect to
inheritance. The last section shows how to work with the `Class` constructor. It
is possible to read this section on its own. However, to fully appreciate how
`Class` works, and the problem it is supposed to solve, it is recommended that
you read the entire article.
##Constructors
In JavaScript, a class is defined by defining a constructor function for that
class. To illustrate this, let's define a simple constructor for a class
`Shape`:
function Shape(x, y) {
this.x = x;
this.y = y;
}
We can now use this constructor to create instances of `Shape`:
let shape = new Shape(2, 3);
shape instanceof Shape; // => true
shape.x; // => 2
shape.y; // => 3
The keyword new tells JavaScript that we are performing a constructor call.
Constructor calls differ from ordinary function calls in that JavaScript
automatically creates a new object and binds it to the keyword this for the
duration of the call. Moreover, if the constructor does not return a value, the
result of the call defaults to the value of this. Constructors are just ordinary
functions, however, so it is perfectly legal to perform ordinary function calls
on them. In fact, some people (including the Add-on SDK team) prefer to use
constructors this way. However, since the value of this is undefined for
ordinary function calls, we need to add some boilerplate code to convert them to
constructor calls:
function Shape(x, y) {
if (!this)
return new Shape(x, y);
this.x = x;
this.y = y;
}
##Prototypes
Every object has an implicit property, known as its prototype. When JavaScript
looks for a property, it first looks for it in the object itself. If it cannot
find the property there, it looks for it in the object's prototype. If the
property is found on the prototype, the lookup succeeds, and JavaScript pretends
that it found the property on the original object. Every function has an
explicit property, known as `prototype`. When a function is used in a
constructor call, JavaScript makes the value of this property the prototype of
the newly created object:
let shape = Shape(2, 3);
Object.getPrototypeOf(shape) == Shape.prototype; // => true
All instances of a class have the same prototype. This makes the prototype the
perfect place to define properties that are shared between instances of the
class. To illustrate this, let's add a member function to the class `Shape`:
Shape.prototype.draw = function () {
throw Error("not yet implemented");
}
let shape = Shape(2, 3);
Shape.draw(); // => Error: not yet implemented
##Inheritance and Constructors
Suppose we want to create a new class, `Circle`, and inherit it from `Shape`.
Since every `Circle` is also a `Shape`, the constructor for `Circle` must be
called every time we call the constructor for `Shape`. Since JavaScript does
not have native support for inheritance, it doesn't do this automatically.
Instead, we need to call the constructor for `Shape` explicitly. The resulting
constructor looks as follows:
function Circle(x, y, radius) {
if (!this)
return new Circle(x, y, radius);
Shape.call(this, x, y);
this.radius = radius;
}
Note that the constructor for `Shape` is called as an ordinary function, and
reuses the object created for the constructor call to `Circle`. Had we used a
constructor call instead, the constructor for `Shape` would have been applied to
a different object than the constructor for `Circle`. We can now use the above
constructor to create instances of the class `Circle`:
let circle = Circle(2, 3, 5);
circle instanceof Circle; // => true
circle.x; // => 2
circle.y; // => 3
circle.radius; // => 5
##Inheritance and Prototypes
There is a problem with the definition of `Circle` in the previous section that
we have glossed over thus far. Consider the following:
let circle = Circle(2, 3, 5);
circle.draw(); // => TypeError: circle.draw is not a function
This is not quite right. The method `draw` is defined on instances of `Shape`,
so we definitely want it to be defined on instances of `Circle`. The problem is
that `draw` is defined on the prototype of `Shape`, but not on the prototype of
`Circle`. We could of course copy every property from the prototype of `Shape`
over to the prototype of `Circle`, but this is needlessly inefficient. Instead,
we use a clever trick, based on the observation that prototypes are ordinary
objects. Since prototypes are objects, they have a prototype as well. We can
thus override the prototype of `Circle` with an object which prototype is the
prototype of `Shape`.
Circle.prototype = Object.create(Shape.prototype);
Now when JavaScript looks for the method draw on an instance of Circle, it first
looks for it on the object itself. When it cannot find the property there, it
looks for it on the prototype of `Circle`. When it cannot find the property
there either, it looks for it on `Shape`, at which point the lookup succeeds.
The resulting behavior is what we were aiming for.
##Inheritance and Instanceof
The single line of code we added in the previous section solved the problem with
prototypes, but introduced a new problem with the **instanceof** operator.
Consider the following:
let circle = Circle(2, 3, 5);
circle instanceof Shape; // => false
Since instances of `Circle` inherit from `Shape`, we definitely want the result
of this expression to be true. To understand why it is not, we need to
understand how **instanceof** works. Every prototype has a `constructor`
property, which is a reference to the constructor for objects with this
prototype. In other words:
Circle.prototype.constructor == Circle // => true
The **instanceof** operator compares the `constructor` property of the prototype
of the left hand side with that of the right hand side, and returns true if they
are equal. Otherwise, it repeats the comparison for the prototype of the right
hand side, and so on, until either it returns **true**, or the prototype becomes
**null**, in which case it returns **false**. The problem is that when we
overrode the prototype of `Circle` with an object whose prototype is the
prototype of `Shape`, we didn't correctly set its `constructor` property. This
property is set automatically for the `prototype` property of a constructor, but
not for objects created with `Object.create`. The `constructor` property is
supposed to be non-configurable, non-enumberable, and non-writable, so the
correct way to define it is as follows:
Circle.prototype = Object.create(Shape.prototype, {
constructor: {
value: Circle
}
});
##Overriding Methods
As a final example, we show how to override the stub implementation of the
method `draw` in `Shape` with a more specialized one in `Circle`. Recall that
JavaScript returns the first property it finds when walking the prototype chain
of an object from the bottom up. Consequently, overriding a method is as simple
as providing a new definition on the prototype of the subclass:
Circle.prototype.draw = function (ctx) {
ctx.beginPath();
ctx.arc(this.x, this.y, this.radius,
0, 2 * Math.PI, false);
ctx.fill();
};
With this definition in place, we get:
let shape = Shape(2, 3);
shape.draw(); // Error: not yet implemented
let circle = Circle(2, 3, 5);
circle.draw(); // TypeError: ctx is not defined
which is the behavior we were aiming for.
##Classes in the Add-on SDK
We have shown how to emulate classical inheritance in JavaScript using
constructors and prototypes. However, as we have seen, this takes a significant
amount of boilerplate code. The Add-on SDK team consists of highly trained
professionals, but they are also lazy: that is why the SDK contains a helper
function that handles this boilerplate code for us. It is defined in the module
“core/heritage”:
const { Class } = require('sdk/core/heritage');
The function `Class` is a meta-constructor: it creates constructors that behave
properly with respect to inheritance. It takes a single argument, which is an
object which properties will be defined on the prototype of the resulting
constructor. The semantics of `Class` are based on what we've learned earlier.
For instance, to define a constructor for a class `Shape` in terms of `Class`,
we can write:
let Shape = Class({
initialize: function (x, y) {
this.x = x;
this.y = y;
},
draw: function () {
throw new Error("not yet implemented");
}
});
The property `initialize` is special. When it is present, the call to the
constructor is forwarded to it, as are any arguments passed to it (including the
this object). In effect, initialize specifies the body of the constructor. Note
that the constructors created with `Class` automatically check whether they are
called as constructors, so an explicit check is no longer necessary.
Another special property is `extends`. It specifies the base class from which
this class inherits, if any. `Class` uses this information to automatically set
up the prototype chain of the constructor. If the extends property is omitted,
`Class` itself is used as the base class:
var shape = new Shape(2, 3);
shape instanceof Shape; // => true
shape instanceof Class; // => true
To illustrate the use of the `extends` property, let's redefine the constructor
for the class `Circle` in terms of `Class`:
var Circle = Class({
extends: Shape,
initialize: function(x, y, radius) {
Shape.prototype.initialize.call(this, x, y);
this.radius = radius;
},
draw: function () {
context.beginPath();
context.arc(this.x, this.y, this.radius,
0, 2 * Math.PI, false);
context.fill();
}
});
Unlike the definition of `Circle` in the previous section, we no longer have to
override its prototype, or set its `constructor` property. This is all handled
automatically. On the other hand, the call to the constructor for `Shape` still
has to be made explicitly. This is done by forwarding to the initialize method
of the prototype of the base class. Note that this is always safe, even if there
is no `initialize` method defined on the base class: in that case the call is
forwarded to a stub implementation defined on `Class` itself.
The last special property we will look into is `implements`. It specifies a list
of objects, which properties are to be copied to the prototype of the
constructor. Note that only properties defined on the object itself are copied:
properties defined on one of its prototypes are not. This allows objects to
inherit from more than one class. It is not true multiple inheritance, however:
no constructors are called for objects inherited via `implements`, and
**instanceof** only works correctly for classes inherited via `extends`.

Просмотреть файл

@ -1,149 +0,0 @@
<!-- This Source Code Form is subject to the terms of the Mozilla Public
- License, v. 2.0. If a copy of the MPL was not distributed with this
- file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
#Content Processes
A content process was supposed to run all the code associated with a single tab.
Conversely, an add-on process was supposed to run all the code associated with a
single add-on. Neither content or add-on proceses were ever actually
implemented, but by the time they were cancelled, the SDK was already designed
with them in mind. To understand this article, it's probably best to read it as
if content and add-on processes actually exist.
To communicate between add-on and content processes, the SDK uses something
called content scripts. These are explained in the first section. Content
scripts communicate with add-on code using something called event emitters.
These are explained in the next section. Content workers combine these ideas,
allowing you to inject a content script into a content process, and
automatically set up a communication channel between them. These are explained
in the third section.
In the next section, we will look at how content scripts interact with the DOM
in a content process. There are several caveats here, all of them related to
security, that might cause things to not behave in the way you might expect.
The final section explains why the SDK still uses the notion of content scripts
and message passing, even though the multiprocess model for which they were
designed never materialized. This, too, is primarily related to security.
##Content Scripts
When the SDK was first designed, Firefox was being refactored towards a
multiprocess model. In this model, the UI would be rendered in one process
(called the chrome process), whereas each tab and each add-on would run in their
own dedicated process (called content and add-on processes, respectively). The
project behind this refactor was known as Electrolysis, or E10s. Although E10s
has now been suspended, the SDK was designed with this multiprocess model in
mind. Afterwards, it was decided to keep the design the way it is: even though
its no longer necessary, it turns out that from a security point of view there
are several important advantages to thinking about content and add-on code as
living in different processes.
Many add-ons have to interact with content. The problem with the multiprocess
model is that add-ons and content are now in different processes, and scripts in
one process cannot interact directly with scripts in another. We can, however,
pass JSON messages between scripts in different processes. The solution we've
come up with is to introduce the notion of content scripts. A content script is
a script that is injected into a content process by the main script running in
the add-on process. Content scripts differ from scripts that are loaded by the
page itself in that they are provided with a messaging API that can be used to
send messages back to the add-on script.
##Event Emitters
The messaging API we use to send JSON messages between scripts in different
processes is based on the use of event emitters. An event emitter maintains a
list of callbacks (or listeners) for one or more named events. Each event
emitter has several methods: the method on is used to add a listener for an
event. Conversely, the method removeListener is used to remove a listener for an
event. The method once is a helper function which adds a listener for an event,
and automatically removes it the first time it is called.
Each event emitter has two associated emit functions. One emit function is
associated with the event emitter itself. When this function is called with a
given event name, it calls all the listeners currently associated with that
event. The other emit function is associated with another event emitter: it was
passed as an argument to the constructor of this event emitter, and made into a
method. Calling this method causes an event to be emitted on the other event
emitter.
Suppose we have two event emitters in different processes, and we want them to
be able to emit events to each other. In this case, we would replace the emit
function passed to the constructor of each emitter with a function that sends a
message to the other process. We can then hook up a listener to be called when
this message arrives at the other process, which in turn calls the emit function
on the other event emitter. The combination of this function and the
corresponding listener is referred to as a pipe.
##Content Workers
A content worker is an object that is used to inject content scripts into a
content process, and to provide a pipe between each content script and the main
add-on script. The idea is to use a single content worker for each content
process. The constructor for the content worker takes an object containing one
or more named options. Among other things, this allows us to specify one or more
content scripts to be loaded.
When a content script is first loaded, the content worker automatically imports
a messaging API that allows the it to emit messages over a pipe. On the add-on
side, this pipe is exposed via the the port property on the worker. In addition
to the port property, workers also support the web worker API, which allows
scripts to send messages to each other using the postMessage function. This
function uses the same pipe internally, and causes a 'message' event to be
emitted on the other side.
As explained earlier, Firefox doesn't yet use separate processes for tabs or
add-ons, so instead, each content script is loaded in a sandbox. Sandboxes were
explained [this article]("dev-guide/guides/contributors-guide/modules.html").
##Accessing the DOM
The global for the content sandbox has the window object as its prototype. This
allows the content script to access any property on the window object, even
though that object lives outside the sandbox. Recall that the window object
inside the sandbox is actually a wrapper to the real object. A potential
problem with the content script having access to the window object is that a
malicious page could override methods on the window object that it knows are
being used by the add-on, in order to trick the add-on into doing something it
does not expect. Similarly, if the content script defines any values on the
window object, a malicious page could potentially steal that information.
To avoid problems like this, content scripts should always see the built-in
properties of the window object, even when they are overridden by another
script. Conversely, other scripts should not see any properties added to the
window object by the content script. This is where xray wrappers come in. Xray
wrappers automatically wrap native objects like the window object, and only
exposes their native properties, even if they have been overridden on the
wrapped object. Conversely, any properties defined on the wrapper are not
visible from the wrapped object. This avoids both problems we mentioned earlier.
The fact that you can't override the properties of the window object via a
content script is sometimes inconvenient, so it is possible to circumvent this:
by defining the property on window.wrappedObject, the property is defined on the
underlying object, rather than the wrapper itself. This feature should only be
used when you really need it, however.
##A few Notes on Security
As we stated earlier, the SDK was designed with multiprocess support in mind,
despite the fact that work on implementing this in Firefox has currently been
suspended. Since both add-on modules and content scripts are currently loaded in
sandboxes rather than separate processes, and sandboxes can communicate with
each other directly (using imports/exports), you might be wondering why we have
to go through all the trouble of passing messages between add-on and content
scripts. The reason for this extra complexity is that the code for add-on
modules and content scripts has different privileges. Every add-on module can
get chrome privileges simply by asking for them, whereas content scripts have
the same privileges as the page it is running on.
When two sandboxes have the same privileges, a wrapper in one sandbox provides
transparent access to an object in the other sandbox. When the two sandboxes
have different privileges, things become more complicated, however. Code with
content privileges should not be able to acces code with chrome privileges, so
we use specialized wrappers, called security wrappers, to limit access to the
object in the other sandbox. The xray wrappers we saw earlier are an example of
such a security wrapper. Security wrappers are created automatically, by the
underlying host application.
A full discussion of the different kinds of security wrappers and how they work
is out of scope for this document, but the main point is this: security wrappers
are very complex, and very error-prone. They are subject to change, in order to
fix some security leak that recently popped up. As a result, code that worked
just fine last week suddenly does not work the way you expect. By only passing
messages between add-on modules and content scripts, these problems can be
avoided, making your add-on both easier to debug and to maintain.

Просмотреть файл

@ -1,318 +0,0 @@
<!-- This Source Code Form is subject to the terms of the Mozilla Public
- License, v. 2.0. If a copy of the MPL was not distributed with this
- file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
#Getting Started
The contribution process consists of a number of steps. First, you need to get
a copy of the code. Next, you need to open a bug for the bug or feature you want
to work on, and assign it to yourself. Alternatively, you can take an existing
bug to work on. Once you've taken a bug, you can start writing a patch. Once
your patch is complete, you've made sure it doesn't break any tests, and you've
gotten a positive review for it, the last step is to request for your patch to
be merged with the main codebase.
Although these individual steps are all obvious, there are quite some details
involved. The rest of this article will cover each individual step of the
contribution process in more detail.
##Getting the Code
The Add-on SDK code is hosted on GitHub. GitHub is a web-based hosting service
for software projects that is based on Git, a distributed version control
system. Both GitHub and Git are an integral part of our workflow. If you haven't
familiarized yourself with Git before, I strongly suggest you do so now. You're
free to ignore that suggestion if you want, but it's going to hurt you later on
(don't come crying to me if you end up accidentally detaching your head, for
instance). A full explanation of how to use Git is out of scope for this
document, but a very good one
[can be found online here](http://git-scm.com/book). Reading at least sections
1-3 from that book should be enough to get you started.
If you're already familiar with Git, or if you decided to ignore my advice and
jump right in, the following steps will get you a local copy of the Add-on SDK
code on your machine:
1. Fork the SDK repository to your GitHub account
2. Clone the forked repository to your machine
A fork is similar to a clone in that it creates a complete copy of a repository,
including the history of every file. The difference is that a fork copies the
repository to your GitHub account, whereas a clone copies it to your machine. To
create a fork of the SDK repository, you need a GitHub account. If you don't
already have one, you can [create one here](https://github.com/) (don't worry:
it's free!). Once you got yourself an account, go to
[the Add-on SDK repository](https://github.com/mozilla/addon-sdk), and click the
fork button in the upper-right corner. This will start the forking process.
This could take anywhere between a couple of seconds and a couple of minutes.
Once the forking process is complete, the forked repository will be available at
https://github.com/\<your-username\>/addon-sdk. To create a clone of the this
repository, you need to have Git installed on your machine. If you dont have it
already, you can [download it here](http://git-scm.com/). Once you have Git
installed (make sure you also configured your name and e-mail
address), open your terminal, and enter the following command from the directory
where you want to have the clone stored:
> `git clone ssh://github.com/<your-username>/addon-sdk`
This will start the cloning process. Like the forking process, this could take
anywhere between a couple of seconds and a couple of minutes, depending on the
speed of your connection.
If you did everything correctly so far, once the cloning process is complete,
the cloned repository will have been stored inside the directory from which you
ran the clone command, in a new directory called addon-sdk. Now we can start
working with it. Yay!
As a final note: it is possible to skip step 1, and clone the SDK repository
directly to your machine. This is useful if you only want to study the SDK code.
However, if your goal is to actually contribute to the SDK, skipping step 1 is a
bad idea, because you wont be able to make pull requests in that case.
##Opening a Bug
In any large software project, keeping track of bugs is crucially important.
Without it, developers wouldn't be able to answer questions such as: what do I
need to work on, who is working on what, etc. Mozilla uses its own web-based,
general-purpose bugtracker, called Bugzilla, to keep track of bugs. Like GitHub
and Git, Bugzilla is an integral part of our workflow. When you discover a new
bug, or want to implement a new feature, you start by creating an entry for it
in Bugzilla. By doing so, you give the SDK team a chance to confirm whether your
bug isn't actually a feature, or your feature isn't actually a bug
(that is, a feature we feel doesn't belong into the SDK).
Within Bugzilla, the term _bug_ is often used interchangably to refer to both
bugs and features. Similarly, the Bugzilla entry for a bug is also named bug,
and the process of creating it is known as _opening a bug_. It is important that
you understand this terminology, as other people will regularly refer to it.
I really urge you to open a bug first and wait for it to get confirmed before
you start working on something. Nothing sucks more than your patch getting
rejected because we felt it shouldn't go into the SDK. Having this discussion
first saves you from doing useless work. If you have questions about a bug, but
don't know who to ask (or the person you need to ask isn't online), Bugzilla is
the communication channel of choice. When you open a bug, the relevant people
are automatically put on the cc-list, so they will get an e-mail every time you
write a comment in the bug.
To open a bug, you need a Bugzilla account. If you don't already have one, you
can [create it here](https://bugzilla.mozilla.org/). Once you got yourself an
account, click the "new" link in the upper-left corner. This will take you to a
page where you need to select the product that is affected by your bug. It isn't
immediately obvious what you should pick here (and with not immediately obvious
I mean completely non-obvious), so I'll just point it out to you: as you might
expect, the Add-on SDK is listed under "Other products", at the bottom of the
page.
After selecting the Add-on SDK, you will be taken to another page, where you
need to fill out the details for the bug. The important fields are the component
affected by this bug, the summary, and a short description of the bug (don't
worry about coming up with the perfect description for your bug. If something is
not clear, someone from the SDK team will simply write a comment asking for
clarification). The other fields are optional, and you can leave them as is, if
you so desire.
Note that when you fill out the summary field, Bugzilla automatically looks for
bugs that are possible duplicates of the one you're creating. If you spot such a
duplicate, there's no need to create another bug. In fact, doing so is
pointless, as duplicate bugs are almost always immediately closed. Don't worry
about accidentally opening a duplicate bug though. Doing so is not considered a
major offense (unless you do it on purpose, of course).
After filling out the details for the bug, the final step is to click the
"Submit Bug" button at the bottom of the page. Once you click this button, the
bug will be stored in Bugzillas database, and the creation process is
completed. The initial status of your bug will be `UNCONFIRMED`. All you need to
do now is wait for someone from the SDK team to change the status to either
`NEW` or `WONTFIX`.
##Taking a Bug
Since this is a contributor's guide, I've assumed until now that if you opened a
bug, you did so with the intention of fixing it. Simply because you're the one
that opened it doesn't mean you have to fix a bug, however. Conversely, simply
because you're _not_ the one that opened it doesn't mean you can't fix a bug. In
fact, you can work on any bug you like, provided nobody else is already working
on it. To check if somebody is already working on a bug, go to the entry for
that bug and check the "Assigned To" field. If it says "Nobody; OK to take it
and work on it", you're good to go: you can assign the bug to yourself by
clicking on "(take)" right next to it.
Keep in mind that taking a bug to creates the expectation that you will work on
it. It's perfectly ok to take your time, but if this is the first bug you're
working on, you might want to make sure that this isn't something that has very
high priority for the SDK team. You can do so by checking the importance field
on the bug page (P1 is the highest priority). If you've assigned a bug to
yourself that looked easy at the time, but turns out to be too hard for you to
fix, don't feel bad! It happens to all of us. Just remove yourself as the
assignee for the bug, and write a comment explaining why you're no longer able
to work on it, so somebody else can take a shot at it.
A word of warning: taking a bug that is already assigned to someone else is
considered extremely rude. Just imagine yourself working hard on a series of
patches, when suddenly this jerk comes out of nowhere and submits his own
patches for the bug. Not only is doing so an inefficient use of time, it also
shows a lack of respect for other the hard work of other contributors. The other
side of the coin is that contributors do get busy every now and then, so if you
stumble upon a bug that is already assigned to someone else but hasn't shown any
activity lately, chances are the person to which the bug is assigned will gladly
let you take it off his/her hands. The general rule is to always ask the person
assigned to the bug if it is ok for you to take it.
As a final note, if you're not sure what bug to work on, or having a hard time
finding a bug you think you can handle, a useful tip is to search for the term
"good first bug". Bugs that are particularly easy, or are particularly well
suited to familiarize yourself with the SDK, are often given this label by the
SDK team when they're opened.
##Writing a Patch
Once you've taken a bug, you're ready to start doing what you really want to do:
writing some code. The changes introduced by your code are known as a patch.
Your goal, of course, is to get this patch landed in the main SDK repository. In
case you aren't familiar with git, the following command will cause it to
generate a diff:
> `git diff`
A diff describes all the changes introduced by your patch. These changes are not
yet final, since they are not yet stored in the repository. Once your patch is
complete, you can _commit_ it to the repository by writing:
> `git commit`
After pressing enter, you will be prompted for a commit message. What goes in
the commit message is more or less up to you, but you should at least include
the bug number and a short summary (usually a single line) of what the patch
does. This makes it easier to find your commit later on.
It is considered good manners to write your code in the same style as the rest
of a file. It doesn't really matter what coding style you use, as long as it's
consistent. The SDK might not always use the exact same coding style for each
file, but it strives to be as consistent as possible. Having said that: if
you're not completely sure what coding style to use, just pick something and
don't worry about it. If the rest of the file doesn't make it clear what you
should do, it most likely doesn't matter.
##Making a Pull Request
To submit a patch for review, you need to make a pull request. Basically, a pull
request is a way of saying: "Hey, I've created this awesome patch on top of my
fork of the SDK repository, could you please merge it with the global
repository?". GitHub has built-in support for pull requests. However, you can
only make pull requests from repositories on your GitHub account, not from
repositories on your local machine. This is why I told you to fork the SDK
repository to your GitHub account first (you did listen to me, didn't you?).
In the previous section, you commited your patch to your local repository, so
here, the next step is to synchronize your local repository with the remote one,
by writing:
> `git push`
This pushes the changes from your local repository into the remote repository.
As you might have guessed, a push is the opposite of a pull, where somebody else
pulls changes from a remote repository into their own repository (hence the term
'pull request'). After pressing enter, GitHub will prompt you for your username
and password before actually allowing the push.
If you did everything correctly up until this point, your patch should now show
up in your remote repository (take a look at your repository on GitHub to make
sure). We're now ready to make a pull request. To do so, go to your repository
on GitHub and click the "Pull Request" button at the top of the page. This will
take you to a new page, where you need to fill out the title of your pull
request, as well as a short description of what the patch does. As we said
before, it is common practice to at least include the bug number and a short
summary in the title. After you've filled in both fields, click the "Send Pull
Request" button.
That's it, we're done! Or are we? This is software development after all, so
we'd expect there to be at least one redundant step. Luckily, there is such a
step, because we also have to submit our patch for review on Bugzilla. I imagine
you might be wondering to yourself right now: "WHY???". Let me try to explain.
The reason we have this extra step is that most Mozilla projects use Mercurial
and Bugzilla as their version control and project management tool, respectively.
To stay consistent with the rest of Mozilla, we provide a Mercurial mirror of
our Git repository, and submit our patches for review in both GitHub and
Bugzilla.
If that doesn't make any sense to you, that's ok: it doesn't to me, either. The
good news, however, is that you don't have to redo all the work you just did.
Normally, when you want to submit a patch for review on Bugzilla, you have to
create a diff for the patch and add it as an attachment to the bug (if you still
haven't opened one, this would be the time to do it). However, these changes are
also described by the commit of your patch, so its sufficient to attach a file
that links to the pull request. To find the link to your pull request, go to
your GitHub account and click the "Pull Requests" button at the top. This will
take you to a list of your active pull requests. You can use the template here
below as your attachment. Simply copy the link to your pull request, and use it
to replace all instances of \<YOUR_LINK_HERE\>:
<!DOCTYPE html>
<meta charset="utf-8">
<meta http-equiv="refresh" content="<YOUR_LINK_HERE>">
<title>Bugzilla Code Review</title>
<p>You can review this patch at <a href="<YOUR_LINK_HERE >"><YOUR_LINK_HERE></a>,
or wait 5 seconds to be redirected there automatically.</p>
Finally, to add the attachment to the bug, go to the bug in Bugzilla, and click
on "Add an attachment" right above the comments. Make sure you fill out a
description for the attachment, and to set the review flag to '?' (you can find
a list of reviewers on
[this page](https://github.com/mozilla/addon-sdk/wiki/contribute)). The '?' here
means that you're making a request. If your patch gets a positive review, the
reviewer will set this flag to '+'. Otherwise, he/she will set it to '-', with
some feedback on why your patch got rejected. Of course, since we also use
GitHub for our review process, you're most likely to get your feedback there,
instead of Bugzilla. If your patch didn't get a positive review right away,
don't sweat it. If you waited for your bug to get confirmed before submitting
your patch, you'll usually only have to change a few small things to get a
positive review for your next attempt. Once your patch gets a positive review,
you don't need to do anything else. Since you did a pull request, it will
automatically be merged into the remote repository, usually by the person that
reviewed your patch.
##Getting Additional Help
If something in this article wasn't clear to you, or if you need additional
help, the best place to go is irc. Mozilla relies heavily on irc for direct
communication between contributors. The SDK team hangs out on the #jetpack
channel on the irc.mozilla.org server (Jetpack was the original name of the
SDK, in case you're wondering).
Unless you are know what you are doing, it can be hard to get the information
you need from irc, uso here are a few useful tips:
* Mozilla is a global organization, with contributors all over the world, so the
people you are trying to reach are likely not in the same timezone as you.
Most contributors to the SDK are currently based in the US, so if you're in
Europe, and asking a question on irc in the early afternoon, you're not likely
to get many replies.
* Most members of the SDK team are also Mozilla employees, which means they're
often busy doing other stuff. That doesn't mean they don't want to help you.
On the contrary: Mozilla encourages employees to help out contributors
whenever they can. But it does mean that we're sometimes busy doing other
things than checking irc, so your question may go unnoticed. If that happens,
the best course of action is often to just ask again.
* If you direct your question to a specific person, rather than the entire
channel, your chances of getting an answer are a lot better. If you prefix
your message with that person's irc name, he/she will get a notification in
his irc client. Try to make sure that the person you're asking is actually the
one you need, though. Don't just ask random questions to random persons in the
hopes you'll get more response that way.
* If you're not familiar with irc, a common idiom is to send someone a message
saying "ping" to ask if that person is there. When that person actually shows
up and sees the ping, he will send you a message back saying "pong". Cute,
isn't it? But hey, it works.
* Even if someone does end up answering your questions, it can happen that that
person gets distracted by some other task and forget he/she was talking to
you. Please don't take that as a sign we don't care about your questions. We
do, but we too get busy sometimes: we're only human. If you were talking to
somebody and haven't gotten any reply to your last message for some time, feel
free to just ask again.
* If you've decided to pick up a good first bug, you can (in theory at least)
get someone from the SDK team to mentor you. A mentor is someone who is
already familiar with the code who can walk you through it, and who is your go
to guy in case you have any questions about it. The idea of mentoring was
introduced a while ago to make it easier for new contributors to familiarize
themselves with the code. Unfortunately, it hasn't really caught on yet, but
we're trying to change that. So by all means: ask!

Просмотреть файл

@ -1,316 +0,0 @@
<!-- This Source Code Form is subject to the terms of the Mozilla Public
- License, v. 2.0. If a copy of the MPL was not distributed with this
- file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
#Modules
A module is a self-contained unit of code, which is usually stored in a file,
and has a well defined interface. The use of modules greatly improves the
maintainability of code, by splitting it up into independent components, and
enforcing logical boundaries between them. Unfortunately, JavaScript does not
yet have native support for modules: it has to rely on the host application to
provide it with functionality such as loading subscripts, and exporting/
importing names. We will show how to do each of these things using the built-in
Components object provided by Xulrunner application such as Firefox and
Thunderbird.
To improve encapsulation, each module should be defined in the scope of its own
global object. This is made possible by the use of sandboxes. Each sandbox lives
in its own compartment. A compartment is a separate memory space. Each
compartment has a set of privileges that determines what scripts running in that
compartment can and cannot do. We will show how sandboxes and compartments can
be used to improve security in our module system.
The module system used by the SDK is based on the CommonJS specification: it is
implemented using a loader object, which handles all the bookkeeping related to
module loading, such as resolving and caching URLs. We show how to create your
own custom loaders, using the `Loader` constructor provided by the SDK. The SDK
uses its own internal loader, known as Cuddlefish. All modules within the SDK
are loaded using Cuddlefish by default. Like any other custom loader, Cuddlefish
is created using the `Loader` constructor. In the final section, we will take a
look at some of the options passed by the SDK to the `Loader` constructor to
create the Cuddlefish loader.
##Loading Subscripts
When a JavaScript project reaches a certain size, it becomes necessary to split
it up into multiple files. Unfortunately, JavaScript does not provide any means
to load scripts from other locations: we have to rely on the host application to
provide us with this functionality. Applications such as Firefox and Thunderbird
are based on Xulrunner. Xulrunner adds a built-in object, known as `Components`,
to the global scope. This object forms the central access point for all
functionality provided by the host application. A complete explanation of how to
use `Components` is out of scope for this document. However, the following
example shows how it can be used to load scripts from other locations:
const {
classes: Cc
interfaces: Ci
} = Components;
var instance = Cc["@mozilla.org/moz/jssubscript-loader;1"];
var loader = instance.getService(Ci.mozIJSSubScriptLoader);
function loadScript(url) {
loader.loadSubScript(url);
}
When a script is loaded, it is evaluated in the scope of the global object of
the script that loaded it. Any property defined on the global object will be
accessible from both scripts:
index.js:
loadScript("www.foo.com/a.js");
foo; // => 3
a.js:
foo = 3;
##Exporting Names
The script loader we obtained from the `Components` object allows us load
scripts from other locations, but its API is rather limited. For instance, it
does not know how to handle relative URLs, which is cumbersome if you want to
organize your project hierarchically. A more serious problem with the
`loadScript` function, however, is that it evaluates all scripts in the scope of
the same global object. This becomes a problem when two scripts try to define
the same property:
index.js:
loadScript("www.foo.com/a.js");
loadScript("www.foo.com/b.js");
foo; // => 5
a.js:
foo = 3;
b.js:
foo = 5;
In the above example, the value of `foo` depends on the order in which the
subscripts are loaded: there is no way to access the property foo defined by
"a.js", since it is overwritten by "b.js". To prevent scripts from interfering
with each other, `loadScript` should evaluate each script to be loaded in the
scope of their own global object, and then return the global object as its
result. In effect, any properties defined by the script being loaded on its
global object are exported to the loading script. The script loader we obtained
from `Components` allows us to do just that:
function loadScript(url) {
let global = {};
loader.loadSubScript(url, global);
return global;
}
If present, the `loadSubScript` function evaluates the script to be loaded in
the scope of the second argument. Using this new version of `loadScript`, we can
now rewrite our earlier example as follows
index.js:
let a = loadScript("www.foo.com/a.js");
let b = loadScript("www.foo.com/b.js");
a.foo // => 3
b.foo; // => 5
a.js:
foo = 3;
b.js:
foo = 5;:
##Importing Names
In addition to exporting properties from the script being loaded to the loading
script, we can also import properties from the loading script to the script
being loaded:
function loadScript(url, imports) {
let global = {
imports: imports,
exports: {}
};
loader.loadSubScript(url, global);
return global.exports;
}
Among other things, this allows us to import `loadScript` to scripts being
loaded, allowing them to load further scripts:
index.js:
loadScript("www.foo.com/a.js", {
loadScript: loadScript
}).foo; => 5
a.js:
exports.foo = imports.loadScript("www.foo.com/b.js").bar;
b.js:
exports.bar = 5;
##Sandboxes and Compartments
The `loadScript` function as defined int the previous section still has some
serious shortcomings. The object it passed to the `loadSubScript` function is an
ordinary object, which has the global object of the loading script as its
prototype. This breaks encapsulation, as it allows the script being loaded to
access the built-in constructors of the loading script, which are defined on its
global object. The problem with breaking encapsulation like this is that
malicious scripts can use it to get the loading script to execute arbitrary
code, by overriding one of the methods on the built-in constructors. If the
loading script has chrome privileges, then so will any methods called by the
loading script, even if that method was installed by a malicious script.
To avoid problems like this, the object passed to `loadSubScript` should be a
true global object, having its own instances of the built-in constructors. This
is exactly what sandboxes are for. A sandbox is a global object that lives in a
separate compartment. Compartments are a fairly recent addition to SpiderMonkey,
and can be seen as a separate memory space. Objects living in one compartment
cannot be accessed directly from another compartment: they need to be accessed
through an intermediate object, known as a wrapper. Compartments are very
useful from a security point of view: each compartment has a set of privileges
that determines what a script running in that compartment can and cannot do.
Compartments with chrome privileges have access to the `Components` object,
giving them full access to the host platform. In contrast, compartments with
content privileges can only use those features available to ordinary websites.
The `Sandbox` constructor takes a `URL` parameter, which is used to determine
the set of privileges for the compartment in which the sandbox will be created.
Passing an XUL URL will result in a compartment with chrome privileges (note,
however, that if you ever actually do this in any of your code, Gabor will be
forced to hunt you down and kill you). Otherwise, the compartment will have
content privileges by default. Rewriting the `loadScript` function using
sandboxes, we end up with:
function loadScript(url, imports) {
let global = Components.utils.Sandbox(url);
global.imports = imports;
global.exports = {};
loader.loadSubScript(url, global);
return global.exports;
}
Note that the object returned by `Sandbox` is a wrapper to the sandbox, not the
sandbox itself. A wrapper behaves exactly like the wrapped object, with one
difference: for each property access/function it performs an access check to
make sure that the calling script is actually allowed to access/call that
property/function. If the script being loaded is less privileged than the
loading script, the access is prevented, as the following example shows:
index.js:
let a = loadScript("www.foo.com/a.js", {
Components: Components
});
// index.js has chrome privileges
Components.utils; // => [object nsXPCComponents_Utils]
a.js:
// a.js has content privileges
imports.Components.utils; // => undefined
##Modules in the Add-on SDK
The module system used by the SDK is based on what we learned so far: it follows
the CommonJS specification, which attempts to define a standardized module API.
A CommonJS module defines three global variables: `require`, which is a function
that behaves like `loadScript` in our examples, `exports`, which behaves
like the `exports` object, and `module`, which is an object representing
the module itself. The `require` function has some extra features not provided
by `loadScript`: it solves the problem of resolving relative URLs (which we have
left unresolved), and provides a caching mechanism, so that when the same module
is loaded twice, it returns the cached module object rather than triggering
another download. The module system is implemented using a loader object, which
is actually provided as a module itself. It is defined in the module
“toolkit/loader”:
const { Loader } = require('toolkit/loader')
The `Loader` constructor allows you to create your own custom loader objects. It
takes a single argument, which is a named options object. For instance, the
option `paths` is used to specify a list of paths to be used by the loader to
resolve relative URLs:
let loader = Loader({
paths: ["./": http://www.foo.com/"]
});
CommonJS also defines the notion of a main module. The main module is always the
first to be loaded, and differs from ordinary modules in two respects. Firstly,
since they do not have a requiring module. Instead, the main module is loaded
using a special function, called `main`:
const { Loader, main } = require('toolkit/loader');
let loader = Loader({
paths: ["./": http://www.foo.com/"]
});
main(loader, "./main.js");
Secondly, the main module is defined as a property on `require`. This allows
modules to check if it they have been loaded as the main module:
function main() {
...
}
if (require.main === module)
main();
##The Cuddlefish Loader
The SDK uses its own internal loader, known as Cuddlefish (because we like crazy
names). Like any other custom loader, Cuddlefish is created using the `Loader`
constructor: Let's take a look at some of the options used by Cuddlefish to
customize its behavior. The way module ids are resolved can be customized by
passing a custom `resolve` function as an option. This function takes the id to
be resolved and the requiring module as an argument, and returns the resolved id
as its result. The resolved id is then further resolved using the paths array:
const { Loader, main } = require('toolkit/loader');
let loader = Loader({
paths: ["./": "http://www.foo.com/"],
resolve: function (id, requirer) {
// Your code here
return id;
}
});
main(loader, "./main.js");
Cuddlefish uses a custom `resolve` function to implement a form of access
control: modules can only require modules for which they have been explicitly
granted access. A whitelist of modules is generated statically when the add-on
is linked. It is possible to pass a list of predefined modules as an option to
the `Loader` constructor. This is useful if the API to be exposed does not have
a corresponding JS file, or is written in an incompatible format. Cuddlefish
uses this option to expose the `Components` object as a module called `chrome`,
in a way similar to the code here below:
const {
classes: Cc,
Constructor: CC,
interfaces: Ci,
utils: Cu,
results: Cr,
manager: Cm
} = Components;
let loader = Loader({
paths: ["./": "http://www.foo.com/"],
resolve: function (id, requirer) {
// Your logic here
return id;
},
modules: {
'chrome': {
components: Components,
Cc: Cc,
CC: bind(CC, Components),
Ci: Ci,
Cu: Cu,
Cr: Cr,
Cm: Cm
}
}
});
All accesses to the `chrome` module go through this one point. As a result, we
don't have to give modules chrome privileges on a case by case basis. More
importantly, however, any module that wants access to `Components` has to
explicitly express its intent via a call to `require("chrome")`. This makes it
possible to reason about which modules have chrome capabilities and which don't.

Просмотреть файл

@ -1,261 +0,0 @@
<!-- This Source Code Form is subject to the terms of the Mozilla Public
- License, v. 2.0. If a copy of the MPL was not distributed with this
- file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
#Private Properties
A private property is a property that is only accessible to member
functions of instances of the same class. Unlike other languages, JavaScript
does not have native support for private properties. However, people have come
up with several ways to emulate private properties using existing language
features. We will take a look at two different techniques, using prefixes, and
closures, respectively.
Prefixes and closures both have drawbacks in that they are either not
restrictive enough or too restrictive, respectively. We will therefore introduce
a third technique, based on the use of WeakMaps, that solves both these
problems. Note, however, that WeakMaps might not be supported by all
implementations yet. Next, we generalize the idea of using WeakMaps from
associating one or more private properties with an object to associating one or
more namespaces with each object. A namespace is simply an object on which one
or more private properties are defined.
The SDK uses namespaces internally to implement private properties. The last
section explains how to work with the particular namespace implementation used
by the SDK. It is possible to read this section on its own, but to fully
appreciate how namespaces work, and the problem they are supposed to solve, it
is recommended that you read the entire article.
##Using Prefixes
A common technique to implement private properties is to prefix each private
property name with an underscore. Consider the following example:
function Point(x, y) {
this._x = x;
this._y = y;
}
The properties `_x` and `_y` are private, and should only be accessed by member
functions.
To make a private property readable/writable from any function, it is common to
define a getter/setter function for the property, respectively:
Point.prototype.getX = function () {
return this._x;
};
Point.prototype.setX = function (x) {
this._x = x;
};
Point.prototype.getY = function () {
return this._y;
};
Point.prototype.setY = function (y) {
this._y = y;
};
The above technique is simple, and clearly expresses our intent. However, the
use of an underscore prefix is just a coding convention, and is not enforced by
the language: there is nothing to prevent a user from directly accessing a
property that is supposed to be private.
##Using Closures
Another common technique is to define private properties as variables, and their
getter and/or setter function as a closure over these variables:
function Point(_x, _y) {
this.getX = function () {
return _x;
};
this.setX = function (x) {
_x = x;
};
this.getY = function () {
return _y;
};
this.setY = function (y) {
_y = y;
};
}
Note that this technique requires member functions that need access to private
properties to be defined on the object itself, instead of its prototype. This is
slightly less efficient, but this is probably acceptable.
The advantage of this technique is that it offers more protection: there is no
way for the user to access a private property except by using its getter and/or
setter function. However, the use of closures makes private properties too
restrictive: since there is no way to access variables in one closure from
within another closure, there is no way for objects of the same class to access
each other's private properties.
##Using WeakMaps
The techniques we've seen so far ar either not restrictive enough (prefixes) or
too restrictive (closures). Until recently, a technique that solves both these
problems didn't exist. That changed with the introduction of WeakMaps. WeakMaps
were introduced to JavaScript in ES6, and have recently been implemented in
SpiderMonkey. Before we explain how WeakMaps work, let's take a look at how
ordinary objects can be used as hash maps, by creating a simple image cache:
let images = {};
function getImage(name) {
let image = images[name];
if (!image) {
image = loadImage(name);
images[name] = image;
}
return image;
}
Now suppose we want to associate a thumbnail with each image. Moreover, we want
to create each thumbnail lazily, when it is first required:
function getThumbnail(image) {
let thumbnail = image._thumbnail;
if (!thumbnail) {
thumbnail = createThumbnail(image);
image._thumbnail = thumbnail;
}
return thumbnail;
}
This approach is straightforward, but relies on the use of prefixes. A better
approach would be to store thumbnails in their own, separate hash map:
let thumbnails = {};
function getThumbnail(image) {
let thumbnail = thumbnails[image];
if (!thumbnail) {
thumbnail = createThumbnail(image);
thumbnails[image] = thumbnail;
}
return thumbnail;
}
There are two problems with the above approach. First, it's not possible to use
objects as keys. When an object is used as a key, it is converted to a string
using its toString method. To make the above code work, we'd have to associate a
unique identifier with each image, and override its `toString` method. The
second problem is more severe: the thumbnail cache maintains a strong reference
to each thumbnail object, so they will never be freed, even when their
corresponding image has gone out of scope. This is a memory leak waiting to
happen.
The above two problems are exactly what WeakMaps were designed to solve. A
WeakMap is very similar to an ordinary hash map, but differs from it in two
crucial ways:
1. It can use ordinary objects as keys
2. It does not maintain a strong reference to its values
To understand how WeakMaps are used in practice, let's rewrite the thumbnail
cache using WeakMaps:
let thumbnails = new WeakMap();
function getThumbnail(image) {
let thumbnail = thumbnails.get(image);
if (!thumbnail) {
thumbnail = createThumbnail(image);
thumbnails.set(image, thumbnail);
}
return thumbnail;
}
This version suffers of none of the problems we mentioned earlier. When a
thumbnail's image goes out of scope, the WeakMap ensures that its entry in the
thumbnail cache will eventually be garbage collected. As a final caveat: the
image cache we created earlier suffers from the same problem, so for the above
code to work properly, we'd have to rewrite the image cache using WeakMaps, too.
#From WeakMaps to Namespaces
In the previous section we used a WeakMap to associate a private property with
each object. Note that we need a separate WeakMap for each private property.
This is cumbersome if the number of private properties becomes large. A better
solution would be to store all private properties on a single object, called a
namespace, and then store the namespace as a private property on the original
object. Using namespaces, our earlier example can be rewritten as follows:
let map = new WeakMap();
let internal = function (object) {
if (!map.has(object))
map.set(object, {});
return map.get(object);
}
function Point(x, y) {
internal(this).x = x;
internal(this).y = y;
}
Point.prototype.getX = function () {
return internal(shape).x;
};
Point.prototype.setX = function (x) {
internal(shape).x = x;
};
Point.prototype.getY = function () {
return internal(shape).y;
};
Point.prototype.setY = function () {
internal(shape).y = y;
};
The only way for a function to access the properties `x` and `y` is if it has a
reference to an instance of `Point` and its `internal` namespace. By keeping the
namespace hidden from all functions except members of `Point`, we have
effectively implemented private properties. Moreover, because members of `Point`
have a reference to the `internal` namespace, they can access private properties
on other instances of `Point`.
##Namespaces in the Add-on SDK
The Add-on SDK is built on top of XPCOM, the interface between JavaScript and
C++ code. Since XPCOM allows the user to do virtually anything, security is very
important. Among other things, we don't want add-ons to be able to access
variables that are supposed to be private. The SDK uses namespaces internally to
ensure this. As always with code that is heavily reused, the SDK defines a
helper function to create namespaces. It is defined in the module
"core/namespace", and it's usage is straightforward. To illustrate this, let's
reimplement the class `Point` using namespaces:
const { ns } = require("./core/namespace");
var internal = ns();
function Point(x, y) {
internal(this).x = x;
internal(this).y = y;
}
Point.prototype.getX = function () {
return internal(shape).x;
};
Point.prototype.setX = function (x) {
internal(shape).x = x;
};
Point.prototype.getY = function () {
return internal(shape).y;
};
Point.prototype.setY = function () {
internal(shape).y = y;
};
As a final note, the function `ns` returns a namespace that uses the namespace
associated with the prototype of the object as its prototype.

Просмотреть файл

@ -1,156 +0,0 @@
<!-- This Source Code Form is subject to the terms of the Mozilla Public
- License, v. 2.0. If a copy of the MPL was not distributed with this
- file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
# Working with Events #
The Add-on SDK supports event-driven programming through its
[`EventEmitter`](modules/sdk/deprecated/events.html) framework.
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.
<span class="aside">
We talk about content
scripts in more detail in the
[Working with Content Scripts](dev-guide/guides/content-scripts/index.html)
guide.</span>
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.
So there are two main ways you will interact with the EventEmitter
framework:
* **listening to built-in events** emitted by objects in the SDK, such as tabs
opening, pages loading, mouse clicks
* **sending and receiving user-defined events** between content scripts and
add-on code
This guide only covers the first of these; the second is explained in the
[Working with Content Scripts](dev-guide/guides/content-scripts/index.html)
guide.
## Adding Listeners ##
You can add a listener to an event emitter by calling its `on(type, listener)`
method.
It takes two parameters:
* **`type`**: 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 `open` and `close` events. The list of valid event types
is specific to an event emitter and is included with its documentation.
* **`listener`**: 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.
For example, the following add-on registers a listener with the
[`tabs`](modules/sdk/tabs.html) module to
listen for the `ready` event, and logs a string to the console
reporting the event:
var tabs = require("sdk/tabs");
tabs.on("ready", function () {
console.log("tab loaded");
});
It is not possible to enumerate the set of listeners for a given event.
The value of `this` in the listener function is the object that emitted
the event.
### Adding Listeners in Constructors ###
Event emitters may be modules, as is the case for the `ready` event
above, or they may be objects returned by constructors.
In the latter case the `options` 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 `on()` method.
For example: the [`widget`](modules/sdk/widget.html) object emits
an event when the widget is clicked.
The following add-on creates a widget and assigns a listener to the
`onClick` property of the `options` object supplied to the widget's
constructor. The listener loads the Google home page:
var widgets = require("sdk/widget");
var tabs = require("sdk/tabs");
widgets.Widget({
id: "google-link",
label: "Widget with an image and a click handler",
contentURL: "http://www.google.com/favicon.ico",
onClick: function() {
tabs.open("http://www.google.com/");
}
});
This is exactly equivalent to constructing the widget and then calling the
widget's `on()` method:
var widgets = require("sdk/widget");
var tabs = require("sdk/tabs");
var widget = widgets.Widget({
id: "google-link-alternative",
label: "Widget with an image and a click handler",
contentURL: "http://www.google.com/favicon.ico"
});
widget.on("click", function() {
tabs.open("http://www.google.com/");
});
## Removing Event Listeners ##
Event listeners can be removed by calling `removeListener(type, listener)`,
supplying the type of event and the listener to remove.
The listener must have been previously been added using one of the methods
described above.
In the following add-on, we add two listeners to the
[`tabs` module's `ready` event](modules/sdk/tabs.html#ready).
One of the handler functions removes the listener again.
Then we open two tabs.
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");
We should see output like this:
<pre>
info: tabevents: Listener 1
info: tabevents: Listener 2
info: tabevents: Listener 2
</pre>
Listeners will be removed automatically when the add-on is unloaded.

Просмотреть файл

@ -1,103 +0,0 @@
<!-- This Source Code Form is subject to the terms of the Mozilla Public
- License, v. 2.0. If a copy of the MPL was not distributed with this
- file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
# Firefox Compatibility #
One of the promises the SDK makes is to maintain compatibility for its
["supported" or "high-level" APIs](modules/high-level-modules.html):
meaning that code written against them will not need to change as new
versions of Firefox are released.
This ties the SDK release cycle into the Firefox release cycle because
the SDK must absorb any changes made to Firefox APIs. The SDK
and Firefox both release every 6 weeks, and the releases are precisely
staggered: so the SDK releases three weeks before Firefox. Each SDK
release is tested against, and marked as compatible with, two
versions of Firefox:
* the currently shipping Firefox version at the time the SDK is released
* the Beta Firefox version at the time the SDK is released - which,
because SDK and Firefox releases are staggered, will become the
currently shipping Firefox three week later
Add-ons built using a particular version of the SDK are marked
as being compatible with those two versions of Firefox, meaning
that in the
[`targetApplication` field of the add-on's install.rdf](https://developer.mozilla.org/en/Install.rdf#targetApplication):
* the `minVersion` is set to the currently shipping Firefox
* the `maxVersion` is set to the current Firefox Beta
See the
[SDK Release Schedule](https://wiki.mozilla.org/Jetpack/SDK_2012_Release_Schedule)
for the list of all SDK releases scheduled for 2012, along with the Firefox
versions they are compatible with.
## "Compatible By Default" ##
<span class="aside">There are exceptions to the "compatible by default" rule:
add-ons with binary XPCOM components, add-ons that have their compatibility
set to less than Firefox 4, and add-ons that are repeatedly reported as
incompatible, which are added to a compatibility override list.
</span>
From Firefox 10 onwards, Firefox treats add-ons as
"compatible by default": that is, even if the Firefox installing the
add-on is not inside the range defined in `targetApplication`,
Firefox will happily install it.
For example, although an add-on developed using version 1.6 of the SDK will be
marked as compatible with only versions 11 and 12 of Firefox, users of
Firefox 10 will still be able to install it.
But before version 10, Firefox assumed that add-ons were incompatible unless
they were marked as compatible in the `targetApplication` field: so an add-on
developed using SDK 1.6 will not install on Firefox 9.
## Changing minVersion and maxVersion Values ##
The `minVersion` and `maxVersion` values that are written into add-ons
generated with the SDK are taken from the template file found at:
<pre>
app-extension/install.rdf
</pre>
If you need to create add-ons which are compatible with a wider range of
Firefox releases, you can edit this file to change the
`minVersion...maxVersion` range.
Obviously, you should be careful to test the resulting add-on on the extra
versions of Firefox you are including, because the version of the SDK you
are using will not have been tested against them.
## Repacking Add-ons ##
Suppose you create an add-on using version 1.6 of the SDK, and upload it to
[https://addons.mozilla.org/](https://addons.mozilla.org/). It's compatible
with versions 11 and 12 of Firefox, and indicates that in its
`minVersion...maxVersion` range.
Now Firefox 13 is released. Suppose Firefox 13 does not change any of the
APIs used by version 1.6 of the SDK. In this case the add-on will still
work with Firefox 13.
But if Firefox 13 makes some incompatible changes, then the add-on will
no longer work. In this case, we'll notify developers, who will need to:
* download and install a new version of the SDK
* rebuild their add-on using this version of the SDK
* update their XPI on [https://addons.mozilla.org/](https://addons.mozilla.org/)
with the new version.
If you created the add-on using the [Add-on Builder](https://builder.addons.mozilla.org/)
rather than locally using the SDK, then it will be repacked automatically
and you don't have to do this.
### Future Plans ###
The reason add-ons need to be repacked when Firefox changes is that the
SDK modules used by an add-on are packaged as part of the add-on, rather
than part of Firefox. In the future we plan to start shipping SDK modules
in Firefox, and repacking will not be needed any more.

Просмотреть файл

@ -1,249 +0,0 @@
<!-- This Source Code Form is subject to the terms of the Mozilla Public
- License, v. 2.0. If a copy of the MPL was not distributed with this
- file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
# Guides #
This page lists more theoretical in-depth articles about the SDK.
<hr>
<h2><a name="contributors-guide">Contributor's Guide</a></h2>
<table class="catalog">
<colgroup>
<col width="50%">
<col width="50%">
</colgroup>
<tr>
<td>
<h4><a href="dev-guide/guides/contributors-guide/getting-started.html">Getting Started</a></h4>
Learn how to contribute to the SDK: getting the code, opening/taking a
bug, filing a patch, getting reviews, and getting help.
</td>
<td>
<h4><a href="dev-guide/guides/contributors-guide/private-properties.html">Private Properties</a></h4>
Learn how private properties can be implemented in JavaScript using
prefixes, closures, and WeakMaps, and how the SDK supports private
properties by using namespaces (which are a generalization of WeakMaps).
</td>
</tr>
<tr>
<td>
<h4><a href="dev-guide/guides/contributors-guide/modules.html">Modules</a></h4>
Learn about the module system used by the SDK (which is based on the
CommonJS specification), how sandboxes and compartments can be used to
improve security, and about the built-in SDK module loader, known as
Cuddlefish.
</td>
<td>
<h4><a href="dev-guide/guides/contributors-guide/content-processes.html">Content Processes</a></h4>
The SDK was designed to work in an environment where the code to
manipulate web content runs in a different process from the main add-on
code. This article highlights the main features of that design.
</td>
</tr>
<tr>
<td>
<h4><a href="dev-guide/guides/contributors-guide/classes-and-inheritance.html">Classes and Inheritance</a></h4>
Learn how classes and inheritance can be implemented in JavaScript, using
constructors and prototypes, and about the helper functions provided by
the SDK to simplify this.
</td>
<td>
</td>
</tr>
</table>
<h2><a name="sdk-infrastructure">SDK Infrastructure</a></h2>
<table class="catalog">
<colgroup>
<col width="50%">
<col width="50%">
</colgroup>
<tr>
<td>
<h4><a href="dev-guide/guides/modules.html">Module structure of the SDK</a></h4>
The SDK, and add-ons built using it, are of composed from reusable JavaScript modules. This
explains what these modules are, how to load modules, and how the SDK's module
tree is structured.
</td>
<td>
<h4><a href="dev-guide/guides/program-id.html">Program ID</a></h4>
The Program ID is a unique identifier for your add-on. This guide
explains how it's created, what it's used for and how to define your
own.
</td>
</tr>
<tr>
<td>
</td>
<td>
<h4><a href="dev-guide/guides/firefox-compatibility.html">Firefox compatibility</a></h4>
Working out which Firefox releases a given SDK release is
compatible with, and dealing with compatibility problems.
</td>
</tr>
<tr>
<td>
<h4><a href="dev-guide/guides/stability.html">SDK API lifecycle</a></h4>
Definition of the lifecycle for the SDK's APIs, including the stability
ratings for APIs.
</td>
<td>
</td>
</tr>
</table>
<hr>
<h2><a name="sdk-idioms">SDK Idioms</a></h2>
<table class="catalog">
<colgroup>
<col width="50%">
<col width="50%">
</colgroup>
<tr>
<td>
<h4><a href="dev-guide/guides/events.html">Working With Events</a></h4>
Write event-driven code using the the SDK's event emitting framework.
</td>
<td>
<h4><a href="dev-guide/guides/two-types-of-scripts.html">Two Types of Scripts</a></h4>
This article explains the differences between the APIs
available to your main add-on code and those available
to content scripts.
</td>
</tr>
</table>
<hr>
<h2><a name="content-scripts">Content Scripts</a></h2>
<table class="catalog">
<colgroup>
<col width="50%">
<col width="50%">
</colgroup>
<tr>
<td>
<h4><a href="dev-guide/guides/content-scripts/index.html">Introducing content scripts</a></h4>
An overview of content scripts.
</td>
<td>
<h4><a href="dev-guide/guides/content-scripts/loading.html">Loading content scripts</a></h4>
Load content scripts into web pages, specified either as strings
or in separate files, and how to control the point at which they are
executed.
</td>
</tr>
<tr>
<td>
<h4><a href="dev-guide/guides/content-scripts/accessing-the-dom.html">Accessing the DOM</a></h4>
Detail about the access content scripts get to the DOM.
</td>
<td>
<h4><a href="dev-guide/guides/content-scripts/communicating-with-other-scripts.html">Communicating with other scripts</a></h4>
Detail about how content scripts can communicate with "main.js", with other
content scripts, and with scripts loaded by the web page itself.
</td>
</tr>
<tr>
<td>
<h4><a href="dev-guide/guides/content-scripts/using-port.html">Using "port"</a></h4>
Communicating between a content script and the rest of your add-on
using the <code>port</code> object.
</td>
<td>
<h4><a href="dev-guide/guides/content-scripts/using-postmessage.html">Using "postMessage()"</a></h4>
Communicating between a content script and the rest of your add-on
using the <code>postMessage()</code> API, and a comparison between
this technique and the <code>port</code> object.
</td>
</tr>
<tr>
<td>
<h4><a href="dev-guide/guides/content-scripts/cross-domain.html">Cross-domain content scripts</a></h4>
How to enable content scripts to interact with content served from different domains.
</td>
<td>
<h4><a href="dev-guide/guides/content-scripts/reddit-example.html">Reddit example</a></h4>
A simple add-on which uses content scripts.
</td>
</tr>
</table>
<hr>
<h2><a name="xul-migration">XUL Migration</a></h2>
<table class="catalog">
<colgroup>
<col width="50%">
<col width="50%">
</colgroup>
<tr>
<td>
<h4><a href="dev-guide/guides/xul-migration.html">XUL Migration Guide</a></h4>
Techniques to help port a XUL add-on to the SDK.
</td>
<td>
<h4><a href="dev-guide/guides/sdk-vs-xul.html">XUL versus the SDK</a></h4>
A comparison of the strengths and weaknesses of the SDK,
compared to traditional XUL-based add-ons.
</td>
</tr>
<tr>
<td>
<h4><a href="dev-guide/guides/library-detector.html">Porting Example</a></h4>
A walkthrough of porting a relatively simple XUL-based
add-on to the SDK.
</td>
<td>
</td>
</tr>
</table>

Просмотреть файл

@ -1,218 +0,0 @@
<!-- This Source Code Form is subject to the terms of the Mozilla Public
- License, v. 2.0. If a copy of the MPL was not distributed with this
- file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
# Porting the Library Detector #
This example walks through the process of porting a XUL-based add-on to the
SDK. It's a very simple add-on and a good candidate for porting because
there are suitable SDK APIs for all its features.
<img class="image-right" src="static-files/media/librarydetector/library-detector.png" alt="Library Detector Screenshot" />
The add-on is Paul Bakaus's
[Library Detector](https://addons.mozilla.org/en-US/firefox/addon/library-detector/).
The Library Detector tells you which JavaScript frameworks the current
web page is using. It does this by checking whether particular objects
that those libraries add to the global window object are defined.
For example, if `window.jQuery` is defined, then the page has loaded
[jQuery](http://jquery.com/).
For each library that it finds, the library detector adds an icon
representing that library to the status bar. It adds a tooltip to each
icon, which contains the library name and version.
You can browse and run the ported version in the SDK's `examples` directory.
### How the Library Detector Works ###
All the work is done inside a single file,
[`librarydetector.xul`](http://code.google.com/p/librarydetector/source/browse/trunk/chrome/content/librarydetector.xul)
This contains:
<ul>
<li>a XUL overlay</li>
<li>a script</li>
</ul>
The XUL overlay adds a `box` element to the browser's status bar:
<script type="syntaxhighlighter" class="brush: html"><![CDATA[
&lt;statusbar id="status-bar"&gt; &lt;box orient="horizontal" id="librarydetector"&gt; &lt;/box&gt; &lt;/statusbar&gt;
]]>
</script>
The script does everything else.
The bulk of the script is an array of test objects, one for each library.
Each test object contains a function called `test()`: if the
function finds the library, it defines various additional properties for
the test object, such as a `version` property containing the library version.
Each test also contains a `chrome://` URL pointing to the icon associated with
its library.
The script listens to [gBrowser](https://developer.mozilla.org/en/Code_snippets/Tabbed_browser)'s
`DOMContentLoaded` event. When this is triggered, the `testLibraries()`
function builds an array of libraries by iterating through the tests and
adding an entry for each library which passes.
Once the list is built, the `switchLibraries()` function constructs a XUL
`statusbarpanel` element for each library it found, populates it with the
icon at the corresponding `chrome://` URL, and adds it to the box.
Finally, it listens to gBrowser's `TabSelect` event, to update the contents
of the box for that window.
### Content Script Separation ###
The test objects in the original script need access to the DOM window object,
so in the SDK port, they need to run in a content script. In fact, they need
access to the un-proxied DOM window, so they can see the objects added by
libraries, so well need to use the experimental [unsafeWindow](dev-guide/guides/content-scripts/accessing-the-dom.html#unsafeWindow)
The main add-on script, `main.js`, will use a
[`page-mod`](modules/sdk/page-mod.html)
to inject the content script into every new page.
The content script, which we'll call `library-detector.js`, will keep most of
the logic of the `test` functions intact. However, instead of maintaining its
own state by listening for `gBrowser` events and updating the
user interface, the content script will just run when it's loaded, collect
the array of library names, and post it back to `main.js`:
function testLibraries() {
var win = unsafeWindow;
var libraryList = [];
for(var i in LD_tests) {
var passed = LD_tests[i].test(win);
if (passed) {
var libraryInfo = {
name: i,
version: passed.version
};
libraryList.push(libraryInfo);
}
}
self.postMessage(libraryList);
}
testLibraries();
`main.js` responds to that message by fetching the tab
corresponding to that worker using
[`worker.tab`](modules/sdk/content/worker.html#tab), and adding
the array of library names to that tab's `libraries` property:
pageMod.PageMod({
include: "*",
contentScriptWhen: 'end',
contentScriptFile: (data.url('library-detector.js')),
onAttach: function(worker) {
worker.on('message', function(libraryList) {
if (!worker.tab.libraries) {
worker.tab.libraries = [];
}
libraryList.forEach(function(library) {
if (worker.tab.libraries.indexOf(library) == -1) {
worker.tab.libraries.push(library);
}
});
if (worker.tab == tabs.activeTab) {
updateWidgetView(worker.tab);
}
});
}
});
The content script is executed once for every `window.onload` event, so
it will run multiple times when a single page containing multiple iframes
is loaded. So `main.js` needs to filter out any duplicates, in case
a page contains more than one iframe, and those iframes use the same library.
### Implementing the User Interface ###
#### Showing the Library Array ####
The [`widget`](modules/sdk/widget.html) module is a natural fit
for displaying the library list. We'll specify its content using HTML, so we
can display an array of icons. The widget must be able to display different
content for different windows, so we'll use the
[`WidgetView`](modules/sdk/widget.html) object.
`main.js` will create an array of icons corresponding to the array of library
names, and use that to build the widget's HTML content dynamically:
function buildWidgetViewContent(libraryList) {
widgetContent = htmlContentPreamble;
libraryList.forEach(function(library) {
widgetContent += buildIconHtml(icons[library.name],
library.name + "<br>Version: " + library.version);
});
widgetContent += htmlContentPostamble;
return widgetContent;
}
function updateWidgetView(tab) {
var widgetView = widget.getView(tab.window);
if (!tab.libraries) {
tab.libraries = [];
}
widgetView.content = buildWidgetViewContent(tab.libraries);
widgetView.width = tab.libraries.length * ICON_WIDTH;
}
`main.js` will
use the [`tabs`](modules/sdk/tabs.html) module to update the
widget's content when necessary (for example, when the user switches between
tabs):
tabs.on('activate', function(tab) {
updateWidgetView(tab);
});
tabs.on('ready', function(tab) {
tab.libraries = [];
});
#### Showing the Library Detail ####
The XUL library detector displayed the detailed information about each
library on mouseover in a tooltip: we can't do this using a widget, so
instead will use a panel. This means we'll need two additional content
scripts:
* one in the widget's context, which listens for icon mouseover events
and sends a message to `main.js` containing the name of the corresponding
library:
<pre><code>
function setLibraryInfo(element) {
self.port.emit('setLibraryInfo', element.target.title);
}
var elements = document.getElementsByTagName('img');
for (var i = 0; i &lt; elements.length; i++) {
elements[i].addEventListener('mouseover', setLibraryInfo, false);
}
</code></pre>
* one in the panel, which updates the panel's content with the library
information:
<pre><code>
self.on("message", function(libraryInfo) {
window.document.body.innerHTML = libraryInfo;
});
</code></pre>
Finally `main.js` relays the library information from the widget to the panel:
<pre><code>
widget.port.on('setLibraryInfo', function(libraryInfo) {
widget.panel.postMessage(libraryInfo);
});
</code></pre>
<img class="image-center" src="static-files/media/librarydetector/panel-content.png" alt="Updating panel content" />

Просмотреть файл

@ -1,191 +0,0 @@
<!-- This Source Code Form is subject to the terms of the Mozilla Public
- License, v. 2.0. If a copy of the MPL was not distributed with this
- file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
# Modules in the SDK #
[CommonJS](http://wiki.commonjs.org/wiki/CommonJS) is the underlying
infrastructure for both the SDK and the add-ons you build using the SDK.
A CommonJS module is a piece of reusable JavaScript: it exports certain
objects which are thus made available to dependent code. CommonJS defines:
* an object called `exports` which contains all the objects which a CommonJS
module wants to make available to other modules
* a function called `require` which a module can use to import the `exports`
object of another module.
![CommonJS modules](static-files/media/commonjs-modules.png)
Except for [scripts that interact directly with web content](dev-guide/guides/content-scripts/index.html),
all the JavaScript code you'll write or use when developing add-ons using
the SDK is part of a CommonJS module, including:
* [SDK modules](dev-guide/guides/modules.html#SDK Modules):
the JavaScript modules which the SDK provides, such as
[`panel`](modules/sdk/panel.html) and [page-mod](modules/sdk/page-mod.html).
* [Local modules](dev-guide/guides/modules.html#Local Modules):
each of the JavaScript files under your add-ons "lib" directory.
* [External modules](dev-guide/guides/modules.html#External Modules):
reusable modules developed and maintained outside the SDK, but usable by
SDK-based add-ons.
## SDK Modules ##
The modules supplied by the SDK are divided into two sorts:
* [High-level modules](dev-guide/high-level-apis.html) like
[`panel`](modules/sdk/panel.html) and
[`page-mod`](modules/sdk/page-mod.html) provide relatively simple,
stable APIs for the most common add-on development tasks.
* [Low-level modules](dev-guide/low-level-apis.html) like
[`heritage`](modules/sdk/core/heritage.html) and
[`namespace`](modules/sdk/core/heritage.html) provide more
powerful functionality, and are typically less stable and more
complex.
To use SDK modules, you can pass `require()` a complete path, starting with
"sdk", to the module you want to use. For high-level modules this is just
`sdk/<module_name>`, and for low-level
modules it is `sdk/<path_to_module>/<module_name>`:
// load the high-level "tabs" module
var tabs = require("sdk/tabs");
// load the low-level "uuid" module
var uuid = require('sdk/util/uuid');
The path to specify for a low-level module is given along with the module
name itself in the title of the module's documentation page (for example,
[system/environment](modules/sdk/system/environment.html)).
Although the [SDK repository in GitHub](https://github.com/mozilla/addon-sdk)
includes copies of these modules, they are built into Firefox and by
default, when you run or build an add-on using
[`cfx run`](dev-guide/cfx-tool.html#cfx-run)
or [`cfx xpi`](dev-guide/cfx-tool.html#cfx-xpi), it is the versions of
the modules in Firefox that are used. If you need to use a different version
of the modules, you can do this by checking out the version of the SDK
that you need and passing the `-o` or
`--overload-modules` option to `cfx run` or `cfx xpi`.
## Local Modules ##
At a minimum, an SDK-based add-on consists of a single module
named `main.js`, but you can factor your add-on's code into a collection
of separate CommonJS modules. Each module is a separate file stored under your
add-on's "lib" directory, and exports the objects you want to make available
to other modules in your add-on. See the tutorial on
[creating reusable modules](dev-guide/tutorials/reusable-modules.html) for
more details.
To import a local module, specify a path relative to the importing module.
For example, the following add-on contains an additional module directly under
"lib", and other modules under subdirectories of "lib":
<ul class="tree">
<li>my-addon
<ul>
<li>lib
<ul>
<li>main.js</li>
<li>password-dialog.js</li>
<li>secrets
<ul>
<li>hash.js</li>
</ul>
</li>
<li>storage
<ul>
<li>password-store.js</li>
</ul>
</li>
</ul>
</li>
</ul>
</li>
</ul>
To import modules into `main`:
// main.js code
var dialog = require("./password-dialog");
var hash = require("./secrets/hash");
To import modules into `password-store`:
// password-store.js code
var dialog = require("../password-dialog");
var hash = require("../secrets/hash");
<div style="clear:both"></div>
For backwards compatibility, you may also omit the leading "`./`"
or "`../`" characters, treating the path as an absolute path from
your add-on's "lib" directory:
var dialog = require("password-dialog");
var hash = require("secrets/hash");
This form is not recommended for new code, because the behavior of `require`
is more complex and thus less predictable than if you specify the target
module explicitly using a relative path.
## External Modules ##
As well as using the SDK's modules and writing your own, you
can use modules that have been developed outside the SDK and made
available to other add-on authors.
There's a list of these
["community-developed modules"](https://github.com/mozilla/addon-sdk/wiki/Community-developed-modules)
in the SDK's GitHub Wiki, and to learn how to use them, see
the tutorial on
[using external modules to add menu items to Firefox](dev-guide/tutorials/adding-menus.html).
To import external modules, treat them like local modules:
copy them somewhere under your add-ons "lib" directory and
reference them with a path relative to the importing module.
For example, this add-on places external modules in a "dependencies"
directory:
<ul class="tree">
<li>my-addon
<ul>
<li>lib
<ul>
<li>main.js</li>
<li>dependencies
<ul>
<li>geolocation.js</li>
</ul>
</li>
</ul>
</li>
</ul>
</li>
</ul>
It can then load them in the same way it would load a local module.
For example, to load from `main`:
// main.js code
var geo = require("./dependencies/geolocation");
<div style="clear:both"></div>
## Freezing ##
The SDK
[freezes](https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Object/freeze)
the `exports` object returned by `require`. So a if you import a module using
`require`, you can't change the properties of the object returned:
self = require("sdk/self");
// Attempting to define a new property
// will fail, or throw an exception in strict mode
self.foo = 1;
// Attempting to modify an existing property
// will fail, or throw an exception in strict mode
self.data = "foo";

Просмотреть файл

@ -1,33 +0,0 @@
<!-- This Source Code Form is subject to the terms of the Mozilla Public
- License, v. 2.0. If a copy of the MPL was not distributed with this
- file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
# The Program ID #
The Program ID is a unique identifier for your add-on. When you package your
add-on for distribution using `cfx xpi`, it will become the
[ID field in the add-on's Install Manifest](https://developer.mozilla.org/en/install.rdf#id).
The ID is used for a variety
of purposes. For example: [addons.mozilla.org](http://addons.mozilla.org) uses
it to distinguish between new add-ons and updates to existing add-ons, and the
[`simple-storage`](modules/sdk/simple-storage.html) module uses it
to figure out which stored data belongs to which add-on.
It is read from the `id` key in your add-on's [`package.json`](dev-guide/package-spec.html) file.
`cfx init` does not create this key, so if you don't set it yourself, the
first time you execute `cfx run` or `cfx xpi`, then `cfx` will create an
ID for you, and will show a message like this:
<pre>
No 'id' in package.json: creating a new ID for you.
package.json modified: please re-run 'cfx run'
</pre>
The ID generated by `cfx` in this way is a randomly-generated string, but
you can define your own ID by editing the `package.json` file
directly. In particular, you can use the `extensionname@example.org` format
described in the
[Install Manifest documentation](https://developer.mozilla.org/en/install.rdf#id).
However, you can't use the
[GUID-style](https://developer.mozilla.org/en/Generating_GUIDs) format.

Просмотреть файл

@ -1,107 +0,0 @@
<!-- This Source Code Form is subject to the terms of the Mozilla Public
- License, v. 2.0. If a copy of the MPL was not distributed with this
- file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
# SDK and XUL Comparison #
## Advantages of the SDK ##
<table>
<colgroup>
<col width="20%">
<col width="80%">
</colgroup>
<tr>
<td> <strong><a name="simplicity">Simplicity</a></strong></td>
<td><p>The SDK provides high-level JavaScript APIs to simplify many
common tasks in add-on development, and tool support which greatly simplifies
the process of developing, testing, and packaging an add-on.</p>
</td>
</tr>
<tr>
<td> <strong><a name="compatibility">Compatibility</a></strong></td>
<td><p>Although we can't promise we'll never break a High-Level API,
maintaining compatibility across Firefox versions is a top priority for us.</p>
<p>We've designed the APIs to be forward-compatible with the new
<a href="https://wiki.mozilla.org/Electrolysis/Firefox">multiple process architecture</a>
(codenamed Electrolysis) planned for Firefox.</p>
<p>We also expect to support both desktop and mobile Firefox using a single
edition of the SDK: so you'll be able to write one extension and have it work
on both products.</p></td>
</tr>
<tr>
<td> <strong><a name="security">Security</a></strong></td>
<td><p>If they're not carefully designed, Firefox add-ons can open the browser
to attack by malicious web pages. Although it's possible to write insecure
add-ons using the SDK, it's not as easy, and the damage that a compromised
add-on can do is usually more limited.</p></td>
</tr>
<tr>
<td> <strong><a name="restartlessness">Restartlessness</a></strong></td>
<td><p>Add-ons built with the SDK are can be installed without having
to restart Firefox.</p>
<p>Although you can write
<a href="https://developer.mozilla.org/en/Extensions/Bootstrapped_extensions">
traditional add-ons that are restartless</a>, you can't use XUL overlays in
them, so most traditional add-ons would have to be substantially rewritten
anyway.</p></td>
</tr>
<tr>
<td> <strong><a name="ux_best_practice">User Experience Best Practices</a></strong></td>
<td><p>The UI components available in the SDK are designed to align with the usability
guidelines for Firefox, giving your users a better, more consistent experience.</p></td>
</tr>
<tr>
<td> <strong><a name="mobile_support">Mobile Support</a></strong></td>
<td><p>Starting in SDK 1.5, we've added experimental support for developing
add-ons on the new native version of Firefox Mobile. See the
<a href="dev-guide/tutorials/mobile.html">tutorial on mobile development<a>.</p></td>
</tr>
</table>
## Advantages of XUL-based Add-ons ##
<table>
<colgroup>
<col width="20%">
<col width="80%">
</colgroup>
<tr>
<td><strong><a name="ui_flexibility">User interface flexibility</a></strong></td>
<td><p>XUL overlays offer a great deal of options for building a UI and
integrating it into the browser. Using only the SDK's supported APIs you have
much more limited options for your UI.</p></td>
</tr>
<tr>
<td><strong><a name="xpcom_access">XPCOM</a></strong></td>
<td><p>Traditional add-ons have access to a vast amount of Firefox
functionality via XPCOM. The SDK's supported APIs expose a relatively
small set of this functionality.</p></td>
</tr>
</table>
### Low-level APIs and Third-party Modules ###
That's not the whole story. If you need more flexibility than the SDK's
High-Level APIs provide, you can use its Low-level APIs to load
XPCOM objects directly or to manipulate the DOM directly as in a
traditional
<a href="https://developer.mozilla.org/en/Extensions/Bootstrapped_extensions">bootstrapped extension</a>.
Alternatively, you can load third-party modules, which extend the SDK's
core APIs.
Note that by doing this you lose some of the benefits of programming
with the SDK including simplicity, compatibility, and to a lesser extent
security.

Просмотреть файл

@ -1,112 +0,0 @@
<!-- This Source Code Form is subject to the terms of the Mozilla Public
- License, v. 2.0. If a copy of the MPL was not distributed with this
- file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
# SDK API Lifecycle #
Developers using the SDK's APIs need to know how far they can trust that
a given API will not change in future releases. At the same time, developers
maintaining and extending the SDK's APIs need to be able to introduce new
APIs that aren't yet fully proven, and to retire old APIs when they're
no longer optimal or supported by the underlying platform.
The API lifecycle aims to balance these competing demands. It has two
main components:
* a [stability index](dev-guide/guides/stability.html#Stability Index)
that defines how stable each module is
* a [deprecation process](dev-guide/guides/stability.html#Deprecation Process)
that defines when and how stable SDK APIs can be changed or removed from
future versions of the SDK while giving developers enough time to update
their code.
## Stability Index ##
The stability index is adopted from
[node.js](http://nodejs.org/api/documentation.html#documentation_stability_index).
The SDK uses only four of the six values defined by node.js:
<table>
<tr>
<td>Experimental</td>
<td>The module is not yet stabilized.
You can try it out and provide feedback, but we may change or remove
it in future versions without having to pass through a formal
deprecation process.</td>
</tr>
<tr>
<td>Unstable</td>
<td>The API is in the process of settling, but has not yet had sufficient
real-world testing to be considered stable.
Backwards-compatibility will be maintained if reasonable.
If we do have to make backwards-incompatible changes, we will not guarantee
to go through the formal deprecation process.</td>
</tr>
<tr>
<td>Stable</td>
<td>The module is a fully-supported part of
the SDK. We will avoid breaking backwards compatibility unless absolutely
necessary. If we do have to make backwards-incompatible changes, we will
go through the formal deprecation process.</td>
</tr>
<tr>
<td>Deprecated</td>
<td>We plan to change this module, and backwards compatibility
should not be expected. Dont start using it, and plan to migrate away from
this module to its replacement.</td>
</tr>
</table>
The stability index for each module is written into that modules
metadata structure, and is displayed at the top of each module's
documentation page.
## Deprecation Process ##
### Deprecation ###
In the chosen release, the SDK team will communicate the module's deprecation:
* update the module's stability index to be "deprecated"
* include a deprecation notice in the
[release notes](https://wiki.mozilla.org/Labs/Jetpack/Release_Notes),
the [Add-ons blog](https://blog.mozilla.org/addons/), and the
[Jetpack Google group](https://groups.google.com/forum/?fromgroups#!forum/mozilla-labs-jetpack).
The deprecation notice should point developers at a migration guide.
### Migration ###
The deprecation period defaults to 18 weeks (that is, three releases)
although in some cases, generally those out of our control, it might
be shorter than this.
During this time, the module will be in the deprecated state. The SDK
team will track usage of deprecated modules on
[addons.mozilla.org](https://addons.mozilla.org/en-US/firefox/) and support
developers migrating their code. The SDK will continue to provide warnings:
* API documentation will inform users that the module is deprecated.
* Attempts to use a deprecated module at runtime will log an error to
the error console.
* The AMO validator will throw errors when deprecated modules are used,
and these add-ons will therefore fail AMO review.
All warnings should include links to further information about what to
use instead of the deprecated module and when the module will be completely
removed.
### Removal ###
The target removal date is 18 weeks after deprecation. In preparation for
this date the SDK team will decide whether to go ahead with removal: this
will depend on how many developers have successfully migrated from the
deprecated module, and on how urgently the module needs to be removed.
If it's OK to remove the module, it will be removed. The SDK team will
remove the corresponding documentation, and communicate the removal in
the usual ways: the [release notes](https://wiki.mozilla.org/Labs/Jetpack/Release_Notes),
the [Add-ons blog](https://blog.mozilla.org/addons/), and the
[Jetpack Google group](https://groups.google.com/forum/?fromgroups#!forum/mozilla-labs-jetpack).
If it's not OK to remove it, the team will continue to support migration
and aim to remove the module in the next release.

Просмотреть файл

@ -1,119 +0,0 @@
<!-- This Source Code Form is subject to the terms of the Mozilla Public
- License, v. 2.0. If a copy of the MPL was not distributed with this
- file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
# Two Types of Scripts #
On the web, JavaScript executes in the context of a web page, and has access to
that page's DOM content. This enables you to call functions like:
window.alert("Hello there");
In an add-on's main scripts you can't do that, because the add-on code does
not execute in the context of a page, and the DOM is therefore not available.
If you need to access the DOM of a particular page, you need to use a
content script.
So there are two distinct sorts of JavaScript scripts you might include
in your add-on and they have access to different sets of APIs. In the SDK
documentation we call one sort "add-on code" and the other sort "content
scripts".
## Add-on Code ##
This is the place where the main logic of your add-on is implemented.
Your add-on is implemented as a collection of one or more
[CommonJS modules](dev-guide/guides/modules.html). Each module
is supplied as a script stored under the `lib` directory under your add-on's
root directory.
Minimally you'll have a single module implemented by a script called
"main.js", but you can include additional modules in `lib`, and import them
using the `require()` function. To learn how to implement and import your own
modules, see the tutorial on
[Implementing Reusable Modules](dev-guide/tutorials/reusable-modules.html).
## Content Scripts ##
While your add-on will always have a "main.js" module, you will only need
to write content scripts if your add-on needs to manipulate web content.
Content scripts are injected into web pages using APIs defined by some of the
SDK's modules such as `page-mod`, `panel` and `widget`.
Content scripts may be supplied as literal strings or maintained in separate
files and referenced by filename. If they are stored in separate files you
should store them under the `data` directory under your add-on's root.
To learn all about content scripts read the
[Working with Content Scripts](dev-guide/guides/content-scripts/index.html)
guide.
## API Access for Add-on Code and Content Scripts ##
The table below summarizes the APIs that are available to each type of
script.
<table>
<colgroup>
<col width="70%">
<col width="15%">
<col width="15%">
</colgroup>
<tr>
<th>API</th>
<th>Add-on code</th>
<th>Content script</th>
</tr>
<tr>
<td>The global objects defined in the core JavaScript language, such as
<code>Math</code>, <code>Array</code>, and <code>JSON</code>. See the
<a href= "https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects">reference at MDN</a>.
</td>
<td class="check"></td>
<td class="check"></td>
</tr>
<tr>
<td><p>The <code>require()</code> and <code>exports</code> globals defined
by version 1.0 of the
<a href="http://wiki.commonjs.org/wiki/Modules/1.0">CommonJS Module Specification</a>.
You use <code>require()</code> to import functionality from another module,
and <code>exports</code> to export functionality from your module.</p>
If <code>require()</code> is available, then so are the modules supplied in the
SDK.
</td>
<td class="check"></td>
<td class="cross"></td>
</tr>
<tr>
<td>The <a href="dev-guide/console.html">console</a>
global supplied by the SDK.
</td>
<td class="check"></td>
<td class="check"></td>
</tr>
<tr>
<td>Globals defined by the
<a href="http://dev.w3.org/html5/spec/Overview.html">HTML5</a> specification,
such as <code>window</code>, <code>document</code>, and
<code>localStorage</code>.
</td>
<td class="cross"></td>
<td class="check"></td>
</tr>
<tr>
<td>The <code>self</code> global, used for communicating between content
scripts and add-on code. See the guide to
<a href="dev-guide/guides/content-scripts/using-port.html">communicating with content scripts</a>
for more details.
</td>
<td class="cross"></td>
<td class="check"></td>
</tr>
</table>

Просмотреть файл

@ -1,377 +0,0 @@
<!-- This Source Code Form is subject to the terms of the Mozilla Public
- License, v. 2.0. If a copy of the MPL was not distributed with this
- file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
# XUL Migration Guide #
This guide aims to help you migrate a XUL-based add-on to the SDK.
First we'll outline how to decide whether
<a href="dev-guide/guides/xul-migration.html#should-you-migrate">
your add-on is a good candidate for migration</a> via a
[comparison of the benefits and limitations of the SDK versus XUL development](dev-guide/guides/sdk-vs-xul.html).
Next, we'll look at some of the main tasks involved in migrating:
* <a href="dev-guide/guides/xul-migration.html#content-scripts">
working with content scripts</a>
* <a href="dev-guide/guides/xul-migration.html#supported-apis">
using the SDK's supported APIs</a>
* how to
go beyond the supported APIs when necessary, by:
* <a href="dev-guide/guides/xul-migration.html#third-party-packages">
using third party modules</a>
* <a href="dev-guide/guides/xul-migration.html#low-level-apis">
using the SDK's low-level APIs</a>
* <a href="dev-guide/guides/xul-migration.html#xpcom">
getting direct access to XPCOM</a>
Finally, we'll walk through a
<a href="dev-guide/guides/xul-migration.html#library-detector">
simple example</a>.
## <a name="should-you-migrate">Should You Migrate?</a> ##
See this [comparison of the benefits and limitations of SDK development
and XUL development](dev-guide/guides/sdk-vs-xul.html).
Whether you should migrate a particular add-on is largely a matter of
how well the SDK's
<a href="dev-guide/guides/xul-migration.html#supported-apis">
supported APIs</a> meet its needs.
* If your add-on can accomplish everything it needs using only the
supported APIs, it's a good candidate for migration.
* If your add-on needs a lot of help from third party packages, low-level
APIs, or XPCOM, then the cost of migrating is high, and may not be worth
it at this point.
* If your add-on only needs a little help from those techniques, and can
accomplish most of what it needs using the supported APIs, then it might
still be worth migrating: we'll add more supported APIs in future releases
to meet important use cases.
## <a name="user-interface-components">User Interface Components</a>##
XUL-based add-ons typically implement a user interface using a combination
of two techniques: XUL overlays and XUL windows.
### XUL Overlays ###
XUL overlays are used to modify existing windows such as the main browser
window. In this way an extension can integrate its user interface into the
browser: for example, adding menu items, buttons, and toolbars.
Because SDK-based extensions are restartless, they can't use XUL overlays. To
add user interface components to the browser, there are a few different
options. In order of complexity, the main options are:
* the SDK includes modules that implement some basic user interface
components including [buttons](modules/sdk/widget.html),
[dialogs](modules/sdk/panel.html), and
[context menu items](modules/sdk/context-menu.html).
* there is a collection of
[community-developed modules](https://github.com/mozilla/addon-sdk/wiki/Community-developed-modules)
that includes various user interface components, including
[toolbar buttons](https://github.com/voldsoftware/toolbarbutton-jplib) and
[menu items](https://github.com/voldsoftware/menuitems-jplib).
* by using the SDK's
[low-level APIs](dev-guide/guides/xul-migration.html#Using the Low-level APIs)
you can directly modify the browser chrome.
### XUL Windows
XUL windows are used to define completely new windows to host user interface
elements specific to the add-on.
The SDK generally expects you to specify your user interface using HTML, not
XUL. However, you can include a
[chrome.manifest file](https://developer.mozilla.org/en-US/docs/Chrome_Registration)
in your add-on and it will be included in the generated XPI.
<ul class="tree">
<li>my-addon
<ul>
<li class="highlight-tree-node">chrome
<ul><li>content</li>
<li>locale</li>
<li>skin</li></ul>
</li>
<li class="highlight-tree-node">chrome.manifest</li>
<li>data</li>
<li>lib</li>
<li>package.json</li>
</ul>
</li>
</ul>
There are limitations on what you can do in this manifest file: for example,
you can't register overlays, `resource:` URIs, or components. However, you
can register a `chrome:` URI, with a skin and locale, and this means you
can include XUL windows in an SDK-based add-on.
You can keep the "chrome.manifest" file in your add-on's root directory
and create a directory there called "chrome". In that directory you can keep
your "content", "locale", and "skin" subdirectories:
This allows you to refer to objects in these directories from "chrome.manifest" using a relative path, like "chrome/content".
This is provided only as a migration aid, and it's still a good idea to port XUL windows to HTML.
<div style="clear:both"></div>
## <a name="content-scripts">Content Scripts</a> ##
In a XUL-based add-on, code that uses XPCOM objects, code that manipulates
the browser chrome, and code that interacts with web pages all runs in the
same context. But the SDK makes a distinction between:
* **add-on scripts**, which can use the SDK APIs, but are not able to interact
with web pages
* **content scripts**, which can access web pages, but do not have access to
the SDK's APIs
Content scripts and add-on scripts communicate by sending each other JSON
messages: in fact, the ability to communicate with the add-on scripts is the
only extra privilege a content script is granted over a normal remote web
page script.
A XUL-based add-on will need to be reorganized to respect this distinction.
The main reason for this design is security: it reduces the risk that a
malicious web page will be able to access privileged APIs.
There's much more information on content scripts in the
[Working With Content Scripts](dev-guide/guides/content-scripts/index.html) guide.
## <a name="supported-apis">Using the Supported APIs</a> ##
The SDK provides a set of high level APIs
providing some basic user interface components and functionality commonly
required by add-ons. Because we expect to keep these APIs compatible as new versions
of Firefox are released, we call them the "supported" APIs.
See the [tutorials](dev-guide/tutorials/index.html)
and the [High-Level API reference](modules/high-level-modules.html).
If the supported APIs do what you need, they're the best option: you get the
benefits of compatibility across Firefox releases and of the SDK's security
model.
APIs like [`widget`](modules/sdk/widget.html) and
[`panel`](modules/sdk/panel.html) are very generic and with the
right content can be used to replace many specific XUL elements. But there are
some notable limitations in the SDK APIs and even a fairly simple UI may need
some degree of redesign to work with them.
Some limitations are the result of intentional design choices. For example,
widgets always appear by default in the
[add-on bar](https://developer.mozilla.org/en/The_add-on_bar) (although users
may relocate them by
[toolbar customization](http://support.mozilla.com/en-US/kb/how-do-i-customize-toolbars))
because it makes for a better user experience for add-ons to expose their
interfaces in a consistent way. In such cases it's worth considering
changing your user interface to align with the SDK APIs.
Some limitations only exist because we haven't yet implemented the relevant
APIs: for example, there's currently no way to add items to the browser's main
menus using the SDK's supported APIs.
Many add-ons will need to make some changes to their user interfaces if they
are to use only the SDK's supported APIs, and add-ons which make drastic
changes to the browser chrome will very probably need more than the SDK's
supported APIs can offer.
Similarly, the supported APIs expose only a small fraction of the full range
of XPCOM functionality.
## <a name="third-party-packages">Using Third Party Packages</a> ##
The SDK is extensible by design: developers can create new modules filling gaps
in the SDK, and package them for distribution and reuse. Add-on developers can
install these packages and use the new modules.
If you can find a third party package that does what you want, this is a great
way to use features not supported in the SDK without having to use the
low-level APIs.
See the
[guide to adding Firefox menu items](dev-guide/tutorials/adding-menus.html).
Some useful third party packages are
[collected in the Jetpack Wiki](https://wiki.mozilla.org/Jetpack/Modules).
Note, though, that by using third party packages you're likely to lose the
security and compatibility benefits of using the SDK.
## <a name="low-level-apis">Using the Low-level APIs</a> ##
<span class="aside">
But note that unlike the supported APIs, low-level APIs do not come with a
compatibility guarantee, so we do not expect code using them will necessarily
continue to work as new versions of Firefox are released.
</span>
In addition to the High-Level APIs, the SDK includes a number of
[Low-Level APIs](modules/low-level-modules.html) some of which, such
[`xhr`](modules/sdk/net/xhr.html) and
[`window/utils`](modules/sdk/window/utils.html), expose powerful
browser capabilities.
In this section we'll use low-level modules how to:
* modify the browser chrome using dynamic manipulation of the DOM
* directly access the [tabbrowser](https://developer.mozilla.org/en/XUL/tabbrowser)
object
### <a name="browser-chrome">Modifying the Browser Chrome</a> ###
The [`window/utils`](modules/sdk/window/utils.html) module gives
you direct access to chrome windows, including the browser's chrome window.
Here's a really simple example add-on that modifies the browser chrome using
`window/utils`:
function removeForwardButton() {
var window = require("sdk/window/utils").getMostRecentBrowserWindow();
var forward = window.document.getElementById('forward-button');
var parent = window.document.getElementById('unified-back-forward-button');
parent.removeChild(forward);
}
var widgets = require("sdk/widget");
widgets.Widget({
id: "remove-forward-button",
label: "Remove the forward button",
contentURL: "http://www.mozilla.org/favicon.ico",
onClick: function() {
removeForwardButton();
}
});
There are more useful examples of this technique in the Jetpack Wiki's
collection of [third party modules](https://wiki.mozilla.org/Jetpack/Modules).
### <a name="accessing-tabbrowser">Accessing <a href="https://developer.mozilla.org/en/XUL/tabbrowser">tabbrowser</a> ###
The [`tabs/utils`](modules/sdk/tabs/utils.html) module gives
you direct access to the
[tabbrowser](https://developer.mozilla.org/en/XUL/tabbrowser) object and the
XUL tab objects it contains. This simple example modifies the selected tab's
CSS to enable the user to highlight the selected tab:
var widgets = require("sdk/widget");
var tabUtils = require("sdk/tabs/utils");
var self = require("sdk/self");
function highlightTab(tab) {
if (tab.style.getPropertyValue('background-color')) {
tab.style.setProperty('background-color','','important');
}
else {
tab.style.setProperty('background-color','rgb(255,255,100)','important');
}
}
var widget = widgets.Widget({
id: "tab highlighter",
label: "Highlight tabs",
contentURL: self.data.url("highlight.png"),
onClick: function() {
var window = require("sdk/window/utils").getMostRecentBrowserWindow();
highlightTab(tabUtils.getActiveTab(window));
}
});
### Security Implications ###
The SDK implements a security model in which an add-on only gets to access the
APIs it explicitly imports via `require()`. This is useful, because it means
that if a malicious web page is able to inject code into your add-on's
context, it is only able to use the APIs you have imported. For example, if
you have only imported the
[`notifications`](modules/sdk/notifications.html) module, then
even if a malicious web page manages to inject code into your add-on, it
can't use the SDK's [`file`](modules/sdk/io/file.html) module to
access the user's data.
But this means that the more powerful modules you `require()`, the greater
is your exposure if your add-on is compromised. Low-level modules like `xhr`,
`tab-browser` and `window-utils` are much more powerful than the modules in
`addon-kit`, so your add-on needs correspondingly more rigorous security
design and review.
## <a name="xpcom">Using XPCOM</a> ##
Finally, if none of the above techniques work for you, you can use the
`require("chrome")` statement to get direct access to the
[`Components`](https://developer.mozilla.org/en/Components_object) object,
which you can then use to load and access any XPCOM object.
The following complete add-on uses
[`nsIPromptService`](https://developer.mozilla.org/en/XPCOM_Interface_Reference/nsIPromptService)
to display an alert dialog:
var {Cc, Ci} = require("chrome");
var promptSvc = Cc["@mozilla.org/embedcomp/prompt-service;1"].
getService(Ci.nsIPromptService);
var widget = require("sdk/widget").Widget({
id: "xpcom example",
label: "Mozilla website",
contentURL: "http://www.mozilla.org/favicon.ico",
onClick: function() {
promptSvc.alert(null, "My Add-on", "Hello from XPCOM");
}
});
It's good practice to encapsulate code which uses XPCOM by
[packaging it in its own module](dev-guide/tutorials/reusable-modules.html).
For example, we could package the alert feature implemented above using a
script like:
var {Cc, Ci} = require("chrome");
var promptSvc = Cc["@mozilla.org/embedcomp/prompt-service;1"].
getService(Ci.nsIPromptService);
exports.alert = function(title, text) {
promptSvc.alert(null, title, text);
};
If we save this as "alert.js" in our add-on's `lib` directory, we can rewrite
`main.js` to use it as follows:
var widget = require("sdk/widget").Widget({
id: "xpcom example",
label: "Mozilla website",
contentURL: "http://www.mozilla.org/favicon.ico",
onClick: function() {
require("./alert").alert("My Add-on", "Hello from XPCOM");
}
});
One of the benefits of this is that we can control which parts of the add-on
are granted chrome privileges, making it easier to review and secure the code.
### Security Implications ###
We saw above that using powerful low-level modules like `tab-browser`
increases the damage that a malicious web page could do if it were able to
inject code into your add-ons context. This applies with even greater force
to `require("chrome")`, since this gives full access to the browser's
capabilities.
## <a name="library-detector">Example: Porting the Library Detector</a> ##
[Porting the Library Detector](dev-guide/guides/library-detector.html)
walks through the process of porting a XUL-based add-on to the
SDK. It's a very simple add-on and a good candidate for porting because
there are suitable SDK APIs for all its features.
Even so, we have to change its user interface slightly if we are to use only
the supported APIs.

Просмотреть файл

@ -1,16 +0,0 @@
<!-- This Source Code Form is subject to the terms of the Mozilla Public
- License, v. 2.0. If a copy of the MPL was not distributed with this
- file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
# High-Level APIs #
Modules in this section implement high-level APIs for
building add-ons:
* creating user interfaces
* interacting with the web
* interacting with the browser
These modules are "supported": meaning that they are relatively
stable, and that we'll avoid making incompatible changes to them
unless absolutely necessary.

Просмотреть файл

@ -1,172 +0,0 @@
<!-- This Source Code Form is subject to the terms of the Mozilla Public
- License, v. 2.0. If a copy of the MPL was not distributed with this
- file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
<h2 class="top">Welcome to the Add-on SDK!</h2>
Using the Add-on SDK you can create Firefox add-ons using standard Web
technologies: JavaScript, HTML, and CSS. The SDK includes JavaScript APIs which you can use to create add-ons, and tools for creating, running, testing, and packaging add-ons.
<hr>
## <a href="dev-guide/tutorials/index.html">Tutorials</a> ##
<table class="catalog">
<colgroup>
<col width="50%">
<col width="50%">
</colgroup>
<tr>
<td>
<h4><a href="dev-guide/tutorials/index.html#getting-started">Getting started</a></h4>
How to
<a href="dev-guide/tutorials/installation.html">install the SDK</a> and
<a href="dev-guide/tutorials/getting-started-with-cfx.html">use the cfx
tool</a> to develop, test, and package add-ons.
</td>
<td>
<h4><a href="dev-guide/tutorials/index.html#create-user-interfaces">Create user interface components</a></h4>
Create user interface components such as
<a href="dev-guide/tutorials/adding-toolbar-button.html">toolbar buttons</a>,
<a href="dev-guide/tutorials/adding-menus.html">menu items</a>, and
<a href="dev-guide/tutorials/display-a-popup.html">dialogs</a>
</td>
</tr>
<tr>
<td>
<h4><a href="dev-guide/tutorials/index.html#interact-with-the-browser">Interact with the browser</a></h4>
<a href="dev-guide/tutorials/open-a-web-page.html">Open web pages</a>,
<a href="dev-guide/tutorials/listen-for-page-load.html">listen for pages loading</a>, and
<a href="dev-guide/tutorials/list-open-tabs.html">list open pages</a>.
</td>
<td>
<h4><a href="dev-guide/tutorials/index.html#modify-web-pages">Modify web pages</a></h4>
<a href="dev-guide/tutorials/modifying-web-pages-url.html">Modify pages matching a URL pattern</a>
or <a href="dev-guide/tutorials/modifying-web-pages-tab.html">dynamically modify a particular tab</a>.
</td>
</tr>
<tr>
<td>
<h4><a href="dev-guide/tutorials/index.html#development-techniques">Development techniques</a></h4>
Learn about common development techniques, such as
<a href="dev-guide/tutorials/unit-testing.html">unit testing</a>,
<a href="dev-guide/tutorials/logging.html">logging</a>,
<a href="dev-guide/tutorials/reusable-modules.html">creating reusable modules</a>,
<a href="dev-guide/tutorials/l10n.html">localization</a>, and
<a href="dev-guide/tutorials/mobile.html">mobile development</a>.
</td>
<td>
<h4><a href="dev-guide/tutorials/index.html#putting-it-together">Putting it together</a></h4>
Walkthrough of the <a href="dev-guide/tutorials/annotator/index.html">Annotator</a> example add-on.
</td>
</tr>
</table>
<hr>
## <a href="dev-guide/guides/index.html">Guides</a> ##
<table class="catalog">
<colgroup>
<col width="50%">
<col width="50%">
</colgroup>
<tr>
<td>
<h4><a href="dev-guide/guides/index.html#contributors-guide">Contributor's Guide</a></h4>
Learn
<a href="dev-guide/guides/contributors-guide/getting-started.html">how to start contributing</a> to the SDK,
and about the most important idioms used in the SDK code, such as
<a href="dev-guide/guides/contributors-guide/modules.html">modules</a>,
<a href="dev-guide/guides/contributors-guide/classes-and-inheritance.html">classes and inheritance</a>,
<a href="dev-guide/guides/contributors-guide/private-properties.html">private properties</a>, and
<a href="dev-guide/guides/contributors-guide/content-processes.html">content processes</a>.
</td>
<td>
<h4><a href="dev-guide/guides/index.html#sdk-idioms">SDK idioms</a></h4>
The SDK's
<a href="dev-guide/guides/events.html">event framework</a> and the
<a href="dev-guide/guides/two-types-of-scripts.html">distinction between add-on scripts and content scripts</a>.
</td>
</tr>
<tr>
<td>
<h4><a href="dev-guide/guides/index.html#sdk-infrastructure">SDK infrastructure</a></h4>
Aspects of the SDK's underlying technology:
<a href="dev-guide/guides/modules.html">Modules</a>, the
<a href="dev-guide/guides/program-id.html">Program ID</a>,
and the rules defining
<a href="dev-guide/guides/firefox-compatibility.html">Firefox compatibility</a>.
</td>
<td>
<h4><a href="dev-guide/guides/index.html#xul-migration">XUL migration</a></h4>
A guide to <a href="dev-guide/guides/xul-migration.html">porting XUL add-ons to the SDK</a>.
This guide includes a
<a href="dev-guide/guides/sdk-vs-xul.html">comparison of the two toolsets</a> and a
<a href="dev-guide/guides/library-detector.html">worked example</a> of porting a XUL add-on.
</td>
</tr>
<tr>
<td>
<h4><a href="dev-guide/guides/index.html#content-scripts">Content scripts</a></h4>
A <a href="dev-guide/guides/content-scripts/index.html">detailed guide to working with content scripts</a>,
including: how to load content scripts, which objects
content scripts can access, and how to communicate
between content scripts and the rest of your add-on.
</td>
<td>
</td>
</tr>
</table>
<hr>
## Reference ##
<table class="catalog">
<colgroup>
<col width="50%">
<col width="50%">
</colgroup>
<tr>
<td>
<h4><a href="modules/high-level-modules.html">High-Level APIs</a></h4>
Reference documentation for the high-level SDK APIs.
</td>
<td>
<h4><a href="modules/low-level-modules.html">Low-Level APIs</a></h4>
Reference documentation for the low-level SDK APIs.
</td>
</tr>
<tr>
<td>
<h4>Tools reference</h4>
Reference documentation for the
<a href="dev-guide/cfx-tool.html">cfx tool</a>
used to develop, test, and package add-ons, the
<a href="dev-guide/console.html">console</a>
global used for logging, and the
<a href="dev-guide/package-spec.html">package.json</a> file.
</td>
<td>
</td>
</tr>
</table>

Просмотреть файл

@ -1,34 +0,0 @@
<!-- This Source Code Form is subject to the terms of the Mozilla Public
- License, v. 2.0. If a copy of the MPL was not distributed with this
- file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
# Low-Level APIs #
Modules in this section implement low-level APIs. These
modules fall roughly into three categories:
* fundamental utilities such as
[collection](modules/sdk/platform/xpcom.html) and
[url](modules/sdk/url.html). Many add-ons are likely to
want to use modules from this category.
* building blocks for higher level modules, such as
[events](modules/sdk/deprecated/events.html),
[worker](modules/sdk/content/worker.html), and
[api-utils](modules/sdk/deprecated/api-utils.html). You're more
likely to use these if you are building your own modules that
implement new APIs, thus extending the SDK itself.
* privileged modules that expose powerful low-level capabilities
such as [tab-browser](modules/sdk/deprecated/tab-browser.html),
[xhr](modules/sdk/net/xhr.html), and
[xpcom](modules/sdk/platform/xpcom.html). You can use these
modules in your add-on if you need to, but should be aware that
the cost of privileged access is the need to take more elaborate
security precautions. In many cases these modules have simpler,
more restricted analogs among the "High-Level APIs" (for
example, [tabs](modules/sdk/tabs.html) or
[request](modules/sdk/request.html)).
These modules are still in active development, and we expect to
make incompatible changes to them in future releases.

Просмотреть файл

@ -1,226 +0,0 @@
<!-- This Source Code Form is subject to the terms of the Mozilla Public
- License, v. 2.0. If a copy of the MPL was not distributed with this
- file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
# package.json #
The "package.json" file contains metadata for your add-on.
Some of its entries, such as [`icon`](dev-guide/package-spec.html#icon),
[`name`](dev-guide/package-spec.html#name), and
[`description`](dev-guide/package-spec.html#description), have
direct analogues in the
[install manifest](https://developer.mozilla.org/en-US/docs/Install_Manifests)
format, and entries from package.json are written into the install
manifest when the add-on is built using [`cfx xpi`](dev-guide/cfx-tool.html#cfx xpi).
Others, such as
[`lib`](dev-guide/package-spec.html#lib),
[`permissions`](dev-guide/package-spec.html#permissions),
and [`preferences`](dev-guide/package-spec.html#preferences),
represent instructions to the cfx tool itself to generate and include
particular code and data structures in your add-on.
The `package.json` file is initially generated in your add-on's root
directory the first time you run
[`cfx init`](dev-guide/cfx-tool.html#cfx init). It looks like this
(assuming the add-on's directory is "my-addon"):
<pre>
{
"name": "my-addon",
"fullName": "my-addon",
"description": "a basic add-on",
"author": "",
"license": "MPL 2.0",
"version": "0.1"
}
</pre>
`package.json` may contain the following keys:
<table>
<colgroup>
<col width="20%"></col>
<col width="80%"></col>
</colgroup>
<tr>
<td id="author"><code>author</code></td>
<td><p>The original author of the package. Defaults to an empty string.
It may include a optional URL in parentheses and an email
address in angle brackets.</p>
<p>This value will be used as the add-on's
<a href="https://developer.mozilla.org/en-US/docs/Install_Manifests#creator"><code>em:creator</code></a>
element in its "install.rdf".</p></td>
</tr>
<tr>
<td id="contributors"><code>contributors</code></td>
<td><p>An array of additional <a href="dev-guide/package-spec.html#author"><code>author</code></a>
strings.</p>
<p>These values will be used as the add-on's
<a href="https://developer.mozilla.org/en-US/docs/Install_Manifests#contributor"><code>em:contributor</code></a>
elements in its "install.rdf".</p></td>
</tr>
<tr>
<td id="dependencies"><code>dependencies</code></td>
<td><p>String or array of strings representing package
names that this add-on requires in order to function properly.</p></td>
</tr>
<tr>
<td id="description"><code>description</code></td>
<td><p>The add-on's description. This defaults to the text
<code>"a basic add-on"</code>.</p>
<p>This value will be used as the add-on's
<a href="https://developer.mozilla.org/en-US/docs/Install_Manifests#description"><code>em:description</code></a>
element in its "install.rdf".</p></td>
</tr>
<tr>
<td id="fullName"><code>fullName</code></td>
<td><p>The full name of the package. It can contain spaces.<p></p>
If this key is present its value will be used as the add-on's
<a href="https://developer.mozilla.org/en-US/docs/Install_Manifests#name"><code>em:name</code></a>
element in its "install.rdf".</p></td>
</tr>
<tr>
<td id="harnessClassID"><code>harnessClassID</code></td>
<td><p>String in the <a href="https://developer.mozilla.org/en-US/docs/Generating_GUIDs">GUID format</a>.</p>
<p>This is used as a
<a href="https://developer.mozilla.org/en-US/docs/Creating_XPCOM_Components/An_Overview_of_XPCOM#CID"><code>classID</code></a>
of the "harness service" XPCOM component. Defaults to a random GUID generated by <code>cfx</code>.</p></td>
</tr>
<tr>
<td id="homepage"><code>homepage</code></td>
<td><p>The URL of the add-on's website.</p>
<p>This value will be used as the add-on's
<a href="https://developer.mozilla.org/en-US/docs/Install_Manifests#homepageURL"><code>em:homepageURL</code></a>
element in its "install.rdf".</p></td>
</tr>
<tr>
<td id="icon"><code>icon</code></td>
<td><p>The relative path from the root of the add-on to a
PNG file containing the icon for the add-on. Defaults to
<code>"icon.png"</code>.</p>
<p>This value will be used as the add-on's
<a href="https://developer.mozilla.org/en-US/docs/Install_Manifests#iconURL"><code>em:iconURL</code></a>
element in its "install.rdf".</p>
<p>The icon may be up to 48x48 pixels in size.</p></td>
</tr>
<tr>
<td id="icon64"><code>icon64</code></td>
<td><p>The relative path from the root of the add-on to a
PNG file containing the large icon for the add-on. Defaults to
<code>"icon64.png"</code>.</p>
<p>This value will be used as the add-on's
<a href="https://developer.mozilla.org/en-US/docs/Install_Manifests#icon64URL"><code>em:icon64URL</code></a>
element in its "install.rdf".</p>
<p>The icon may be up to 64x64 pixels in size.</p></td>
</tr>
<tr>
<td id="id"><code>id</code></td>
<td><p>A globally unique identifier for the add-on.</p>
<p>This value will be used as the add-on's
<a href="https://developer.mozilla.org/en-US/docs/Install_Manifests#id"><code>em:id</code></a>
element in its "install.rdf".</p>
<p>See the <a href="dev-guide/guides/program-id.html">Program ID documentation</a>.</p></td>
</tr>
<tr>
<td id="lib"><code>lib</code></td>
<td><p>String representing the top-level module directory provided in
this add-on. Defaults to <code>"lib"</code>.</p></td>
</tr>
<tr>
<td id="license"><code>license</code></td>
<td><p>The name of the license under which the add-on is distributed, with an optional
URL in parentheses. Defaults to <code>"MPL 2.0"</code>.</p></td>
</tr>
<tr>
<td id="main"><code>main</code></td>
<td><p>String representing the name of a program module that is
located in one of the top-level module directories specified by
<a href="dev-guide/package-spec.html#lib"><code>lib</code></a>.
Defaults to <code>"main"</code>.</p></td>
</tr>
<tr>
<td id="name"><code>name</code></td>
<td><p>The add-on's name. This name cannot contain spaces or periods, and
defaults to the name of the parent directory.</p><p>When the add-on is
built as an XPI, if the <a href="dev-guide/package-spec.html#fullName"><code>fullName</code></a>
key is not present, <code>name</code> is used as the add-on's
<a href="https://developer.mozilla.org/en-US/docs/Install_Manifests#name"><code>em:name</code></a>
element in its "install.rdf".</p></td>
</tr>
<tr>
<td id="packages"><code>packages</code></td>
<td><p>String or array of strings representing paths to
directories containing additional packages. Defaults to
<code>"packages"</code>.</p></td>
</tr>
<tr>
<td id="permissions"><code>permissions</code></td>
<td><p>A set of permissions that the add-on needs.</p>
<p><strong><code>private-browsing</code></strong>: a boolean
indicating whether or not the
add-on supports private browsing. If this value is not <code>true</code>
or is omitted, then the add-on will not see any private windows or
objects, such as tabs, that are associated with private windows. See the
documentation for the
<a href="modules/sdk/private-browsing.html"><code>private-browsing</code> module</a>.</p>
<p><strong><code>cross-domain-content</code></strong>: a list of domains for
which content scripts are given cross-domain privileges to access content in
iframes or to make XMLHTTPRequests. See the documentation for
<a href="dev-guide/guides/content-scripts/cross-domain.html">enabling cross-domain content scripts</a>.</p>
</td>
</tr>
<tr>
<td id="preferences"><code>preferences</code></td>
<td><p>An array of JSON objects that use the following keys:
<code>name</code>,<code>type</code>, <code>value</code>,
<code>title</code>, and <code>description</code>. These JSON objects will be used to
create a preferences interface for the add-on in the Add-ons Manager.</p>
<p>See the documentation for the
<a href="modules/sdk/simple-prefs.html"><code>simple-prefs</code> module</a>.</p></td>
</tr>
<tr>
<td id="tests"><code>tests</code></td>
<td><p>String representing the top-level module directory containing
test suites for this package. Defaults to <code>"tests"</code>.</p></td>
</tr>
<tr>
<td id="translators"><code>translators</code></td>
<td><p>An array of strings listing translators of this add-on.</p>
<p>These values will be used as the add-on's
<a href="https://developer.mozilla.org/en-US/docs/Install_Manifests#translator"><code>em:translator</code></a>
elements in its "install.rdf".</p></td>
</tr>
<tr>
<td id="version"><code>version</code></td>
<td><p>String representing the version of the add-on. Defaults to
<code>"0.1"</code>.</p>
<p>This value will be used as the add-on's
<a href="https://developer.mozilla.org/en-US/docs/Install_Manifests#version"><code>em:version</code></a>
element in its "install.rdf".</p></td>
</tr>
</table>

Просмотреть файл

@ -1,190 +0,0 @@
<!-- This Source Code Form is subject to the terms of the Mozilla Public
- License, v. 2.0. If a copy of the MPL was not distributed with this
- file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
<div id="cse" style="width: 100%;">Loading</div>
<script src="https://www.google.com/jsapi" type="text/javascript"></script>
<script type="text/javascript">
function parseQueryFromUrl () {
var queryParamName = "q";
var search = window.location.search.substr(1);
var parts = search.split('&');
for (var i = 0; i < parts.length; i++) {
var keyvaluepair = parts[i].split('=');
if (decodeURIComponent(keyvaluepair[0]) == queryParamName) {
return decodeURIComponent(keyvaluepair[1].replace(/\+/g, ' '));
}
}
return '';
}
google.load('search', '1', {language : 'en'});
google.setOnLoadCallback(function() {
var customSearchOptions = {};
var customSearchControl = new google.search.CustomSearchControl(
'017013284162333743052:rvlazd1zehe', customSearchOptions);
customSearchControl.setResultSetSize(google.search.Search.FILTERED_CSE_RESULTSET);
var options = new google.search.DrawOptions();
options.enableSearchResultsOnly();
customSearchControl.draw('cse', options);
var queryFromUrl = parseQueryFromUrl();
if (queryFromUrl) {
var searchBox = document.getElementById("search-box");
searchBox.value = queryFromUrl;
searchBox.focus();
searchBox.blur();
customSearchControl.execute(queryFromUrl);
}
}, true);
</script>
<link rel="stylesheet" href="https://www.google.com/cse/style/look/default.css" type="text/css" />
<style type="text/css">
#cse table, #cse tr, #cse td {
border: none;
}
.gsc-above-wrapper-area, .gsc-result-info-container {
border: none;
}
.gsc-table-cell-snippet-close {
border-color: #a0d0fb;
}
.gsc-resultsHeader {
display : none;
}
.gsc-control-cse {
font-family: "Trebuchet MS", sans-serif;
border-color: #F0F8FF;
background: none;
}
.gsc-tabHeader.gsc-tabhInactive {
border-color: #E9E9E9;
background-color: none;
}
.gsc-tabHeader.gsc-tabhActive {
border-top-color: #FF9900;
border-left-color: #E9E9E9;
border-right-color: #E9E9E9;
background-color: #FFFFFF;
}
.gsc-tabsArea {
border-color: #E9E9E9;
}
.gsc-webResult.gsc-result,
.gsc-results .gsc-imageResult {
border-color: #F0F8FF;
background-color: #FFFFFF;
}
.gsc-webResult.gsc-result:hover,
.gsc-webResult.gsc-result.gsc-promotion:hover,
.gsc-imageResult:hover {
border-color: #F0F8FF;
background-color: #FFFFFF;
}
.gs-webResult.gs-result a.gs-title:link,
.gs-webResult.gs-result a.gs-title:link b,
.gs-imageResult a.gs-title:link,
.gs-imageResult a.gs-title:link b {
color: #000000;
}
.gs-webResult.gs-result a.gs-title:visited,
.gs-webResult.gs-result a.gs-title:visited b,
.gs-imageResult a.gs-title:visited,
.gs-imageResult a.gs-title:visited b {
color: #000000;
}
.gs-webResult.gs-result a.gs-title:hover,
.gs-webResult.gs-result a.gs-title:hover b,
.gs-imageResult a.gs-title:hover,
.gs-imageResult a.gs-title:hover b {
color: #000000;
}
.gs-webResult.gs-result a.gs-title:active,
.gs-webResult.gs-result a.gs-title:active b,
.gs-imageResult a.gs-title:active,
.gs-imageResult a.gs-title:active b {
color: #000000;
}
.gsc-cursor-page {
color: #000000;
}
a.gsc-trailing-more-results:link {
color: #000000;
}
.gs-webResult .gs-snippet,
.gs-imageResult .gs-snippet,
.gs-fileFormatType {
color: #000000;
}
.gs-webResult div.gs-visibleUrl,
.gs-imageResult div.gs-visibleUrl {
color: #003595;
}
.gs-webResult div.gs-visibleUrl-short {
color: #003595;
}
.gs-webResult div.gs-visibleUrl-short {
display: none;
}
.gs-webResult div.gs-visibleUrl-long {
display: block;
}
.gs-promotion div.gs-visibleUrl-short {
display: none;
}
.gs-promotion div.gs-visibleUrl-long {
display: block;
}
.gsc-cursor-box {
border-color: #F0F8FF;
}
.gsc-results .gsc-cursor-box .gsc-cursor-page {
border-color: #F0F8FF;
background-color: #FFFFFF;
color: #000000;
}
.gsc-results .gsc-cursor-box .gsc-cursor-current-page {
border-color: #FF9900;
background-color: #FFFFFF;
color: #000000;
}
.gsc-webResult.gsc-result.gsc-promotion {
border-color: #336699;
background-color: #FFFFFF;
}
.gs-promotion a.gs-title:link,
.gs-promotion a.gs-title:link *,
.gs-promotion .gs-snippet a:link {
color: #00ffff;
}
.gs-promotion a.gs-title:visited,
.gs-promotion a.gs-title:visited *,
.gs-promotion .gs-snippet a:visited {
color: #0000CC;
}
.gs-promotion a.gs-title:hover,
.gs-promotion a.gs-title:hover *,
.gs-promotion .gs-snippet a:hover {
color: #0000CC;
}
.gs-promotion a.gs-title:active,
.gs-promotion a.gs-title:active *,
.gs-promotion .gs-snippet a:active {
color: #0000CC;
}
.gs-promotion .gs-snippet,
.gs-promotion .gs-title .gs-promotion-title-right,
.gs-promotion .gs-title .gs-promotion-title-right * {
color: #000000;
}
.gs-promotion .gs-visibleUrl,
.gs-promotion .gs-visibleUrl-short {
color: #008000;
}
</style>

Просмотреть файл

@ -1,7 +0,0 @@
<!-- This Source Code Form is subject to the terms of the Mozilla Public
- License, v. 2.0. If a copy of the MPL was not distributed with this
- file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
# Third-Party APIs #
This section lists modules which you've downloaded and added to your SDK installation.

Просмотреть файл

@ -1,89 +0,0 @@
<!-- This Source Code Form is subject to the terms of the Mozilla Public
- License, v. 2.0. If a copy of the MPL was not distributed with this
- file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
# Add a Context Menu Item #
<span class="aside">
To follow this tutorial you'll need to have
[installed the SDK](dev-guide/tutorials/installation.html)
and learned the
[basics of `cfx`](dev-guide/tutorials/getting-started-with-cfx.html).
</span>
To add items and submenus to the Firefox context menu, use the
[`context-menu`](modules/sdk/context-menu.html) module.
Here's an add-on that adds a new context menu item. The item is
displayed whenever something in the page is selected. When it's
clicked, the selection is sent to the main add-on code, which just
logs it:
var contextMenu = require("sdk/context-menu");
var menuItem = contextMenu.Item({
label: "Log Selection",
context: contextMenu.SelectionContext(),
contentScript: 'self.on("click", function () {' +
' var text = window.getSelection().toString();' +
' self.postMessage(text);' +
'});',
onMessage: function (selectionText) {
console.log(selectionText);
}
});
Try it: run the add-on, load a web page, select some text and right-click.
You should see the new item appear:
<img class="image-center" src="static-files/media/screenshots/context-menu-selection.png"></img>
Click it, and the selection is
[logged to the console](dev-guide/tutorials/logging.html):
<pre>
info: elephantine lizard
</pre>
All this add-on does is to construct a context menu item. You don't need
to add it: once you have constructed the item, it is automatically added
in the correct context. The constructor in this case takes four options:
`label`, `context`, `contentScript`, and `onMessage`.
### label ###
The `label` is just the string that's displayed.
### context ###
The `context` describes the circumstances in which the item should be
shown. The `context-menu` module provides a number of simple built-in
contexts, including this `SelectionContext()`, which means: display
the item when something on the page is selected.
If these simple contexts aren't enough, you can define more sophisticated
contexts using scripts.
### contentScript ###
This attaches a script to the item. In this case the script listens for
the user to click on the item, then sends a message to the add-on containing
the selected text.
### onMessage ###
The `onMessage` property provides a way for the add-on code to respond to
messages from the script attached to the context menu item. In this case
it just logs the selected text.
So:
1. the user clicks the item
2. the content script's `click` event fires, and the content script retrieves
the selected text and sends a message to the add-on
3. the add-on's `message` event fires, and the add-on code's handler function
is passed the selected text, which it logs
## Learning More ##
To learn more about the `context-menu` module, see the
[`context-menu` API reference](modules/sdk/context-menu.html).

Просмотреть файл

@ -1,137 +0,0 @@
<!-- This Source Code Form is subject to the terms of the Mozilla Public
- License, v. 2.0. If a copy of the MPL was not distributed with this
- file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
# Add a Menu Item to Firefox #
<span class="aside">
To follow this tutorial you'll need to have
[installed the SDK](dev-guide/tutorials/installation.html)
and learned the
[basics of `cfx`](dev-guide/tutorials/getting-started-with-cfx.html).
</span>
The SDK doesn't yet provide an API to add new menu items to Firefox.
But it's extensible by design, so anyone can build and publish
modules for add-on developers to use. Luckily, Erik Vold has written
a [`menuitems`](https://github.com/erikvold/menuitems-jplib) module
that enables us to add menu items.
This tutorial does double-duty. It describes the general method for
using an external, third-party module in your add-on, and it
describes how to add a menu item using the `menuitems` module in particular.
First, create a new add-on. Make a directory called "clickme" wherever you
like, navigate to it and run `cfx init`.
<pre>
mkdir clickme
cd clickme
cfx init
</pre>
The usual directory structure will be created:
<ul class="tree">
<li>clickme
<ul>
<li>data</li>
<li>docs
<ul><li>main.md</li></ul>
</li>
<li>lib
<ul><li>main.js</li></ul>
</li>
<li>package.json</li>
<li>README.md</li>
<li>tests
<ul><li>test-main.js</li></ul>
</li>
</ul>
</li>
</ul>
<div style="clear:both"></div>
## Installing `menuitems` ##
Create a directory under "clickme" called "packages".
Then download the `menuitems` package from
[https://github.com/erikvold/menuitems-jplib](https://github.com/erikvold/menuitems-jplib/zipball/51080383cbb0fe2a05f8992a8aae890f4c014176) and extract it into the "packages" directory you just created:
<pre>
mkdir packages
cd packages
tar -xf ../erikvold-menuitems-jplib-d80630c.zip
</pre>
## Module Dependencies ##
If third-party modules only depend on SDK modules, you can use them right
away, but if they depend on other third-party modules, you'll have to install
those dependencies as well.
In the package's main directory you'll find a file called "package.json".
Open it up and look for an entry named "dependencies". The entry for the
`menuitems` package is:
<pre>
"dependencies": ["vold-utils"]
</pre>
This tells us that we need to install the `vold-utils` package,
which we can do by downloading it from
[https://github.com/erikvold/vold-utils-jplib](https://github.com/voldsoftware/vold-utils-jplib/zipball/1b2ad874c2d3b2070a1b0d43301aa3731233e84f)
and adding it under the `packages` directory alongside `menuitems`.
## Using `menuitems` ##
The [documentation for the `menuitems` module](https://github.com/erikvold/menuitems-jplib/blob/master/docs/menuitems.md)
tells us to create a menu item using `MenuItem()`. Of the options
accepted by `MenuItem()`, we'll use this minimal set:
* `id`: identifier for this menu item
* `label`: text the item displays
* `command`: function called when the user selects the item
* `menuid`: identifier for the item's parent element
* `insertbefore`: identifier for the item before which we want our item to
appear
<!--comment to terminate Markdown list -->
var menuitem = require("menuitems").Menuitem({
id: "clickme",
menuid: "menu_ToolsPopup",
label: "Click Me!",
onCommand: function() {
console.log("clicked");
},
insertbefore: "menu_pageInfo"
});
Next, we have to declare our dependency on the `menuitems` package.
In your add-on's `package.json` add the line:
<pre>
"dependencies": "menuitems"
</pre>
Note that due to
[bug 663480](https://bugzilla.mozilla.org/show_bug.cgi?id=663480), if you
add a `dependencies` line to `package.json`, and you use any modules from
the SDK, then you must also declare your dependency on that built-in package,
like this:
<pre>
"dependencies": ["menuitems", "addon-sdk"]
</pre>
Now we're done. Run the add-on and you'll see the new item appear in the
`Tools` menu: select it and you'll see `info: clicked` appear in the
console.
## Caveats ##
Third-party modules are a great way to use features not directly supported by
the SDK, but because third party modules typically use low-level APIs,
they may be broken by new releases of Firefox.

Просмотреть файл

@ -1,174 +0,0 @@
<!-- This Source Code Form is subject to the terms of the Mozilla Public
- License, v. 2.0. If a copy of the MPL was not distributed with this
- file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
# Adding a Button to the Toolbar #
<span class="aside">
To follow this tutorial you'll need to have
[installed the SDK](dev-guide/tutorials/installation.html)
and learned the
[basics of `cfx`](dev-guide/tutorials/getting-started-with-cfx.html).
</span>
To add a button to the toolbar, use the
[`widget`](modules/sdk/widget.html) module.
Create a new directory, navigate to it, and execute `cfx init`.
Then open the file called "main.js" in the "lib" directory and
add the following code to it:
var widgets = require("sdk/widget");
var tabs = require("sdk/tabs");
var widget = widgets.Widget({
id: "mozilla-link",
label: "Mozilla website",
contentURL: "http://www.mozilla.org/favicon.ico",
onClick: function() {
tabs.open("http://www.mozilla.org/");
}
});
The widget is added to the "Add-on Bar" at the bottom of the browser window:
<img class="image-right" src="static-files/media/screenshots/widget-mozilla.png"
alt="Mozilla icon widget" />
You can't change the initial location for the widget, but the user can move
it to a different toolbar. The `id` attribute is mandatory, and is used to
remember the position of the widget, so you should not change it in subsequent
versions of the add-on.
Clicking the button opens [http://www.mozilla.org](http://www.mozilla.org).
<div style="clear:both"></div>
## Specifying the Icon ##
If you're using the widget to make a toolbar button, specify the icon to
display using `contentURL`: this may refer to a remote file as in the
example above, or may refer to a local file. The example below will load
an icon file called "my-icon.png" from the add-on's `data` directory:
var widgets = require("sdk/widget");
var tabs = require("sdk/tabs");
var self = require("sdk/self");
var widget = widgets.Widget({
id: "mozilla-link",
label: "Mozilla website",
contentURL: self.data.url("my-icon.png"),
onClick: function() {
tabs.open("http://www.mozilla.org/");
}
});
You can change the icon at any time by setting the widget's `contentURL`
property. However, setting the `contentURL` property will break the
channel of communication between this widget and any content scripts it
contains. Messages sent from the content script will no longer be received
by the main add-on code, and vice versa. This issue is currently tracked as
[bug 825434](https://bugzilla.mozilla.org/show_bug.cgi?id=825434).
## Responding To the User ##
You can listen for `click`, `mouseover`, and `mouseout` events by passing
handler functions as the corresponding constructor options. The widget
example above assigns a listener to the `click` event using the `onClick`
option, and there are similar `onMouseover` and `onMouseout` options.
To handle user interaction in more detail, you can attach a content
script to the widget. Your add-on script and the content script can't
directly access each other's variables or call each other's functions, but
they can send each other messages.
Here's an example. The widget's built-in `onClick` property does not
distinguish between left and right mouse clicks, so to do this we need
to use a content script. The script looks like this:
window.addEventListener('click', function(event) {
if(event.button == 0 && event.shiftKey == false)
self.port.emit('left-click');
if(event.button == 2 || (event.button == 0 && event.shiftKey == true))
self.port.emit('right-click');
event.preventDefault();
}, true);
It uses the standard DOM `addEventListener()` function to listen for click
events, and handles them by sending the corresponding message to the main
add-on code. Note that the messages "left-click" and "right-click" are not
defined in the widget API itself, they're custom events defined by the add-on
author.
Save this script in your `data` directory as "click-listener.js".
Next, modify `main.js` to:
<ul>
<li>pass in the script by setting the <code>contentScriptFile</code>
property</li>
<li>listen for the new events:</li>
</ul>
var widgets = require("sdk/widget");
var tabs = require("sdk/tabs");
var self = require("sdk/self");
var widget = widgets.Widget({
id: "mozilla-link",
label: "Mozilla website",
contentURL: "http://www.mozilla.org/favicon.ico",
contentScriptFile: self.data.url("click-listener.js")
});
widget.port.on("left-click", function(){
console.log("left-click");
});
widget.port.on("right-click", function(){
console.log("right-click");
});
Now execute `cfx run` again, and try right- and left-clicking on the button.
You should see the corresponding string written to the command shell.
## Attaching a Panel ##
<!-- The icon the widget displays, shown in the screenshot, is taken from the
Circular icon set, http://prothemedesign.com/circular-icons/ which is made
available under the Creative Commons Attribution 2.5 Generic License:
http://creativecommons.org/licenses/by/2.5/ -->
<img class="image-right" src="static-files/media/screenshots/widget-panel-clock.png"
alt="Panel attached to a widget">
If you supply a `panel` object to the widget's constructor, then the panel
will be shown when the user clicks the widget:
data = require("sdk/self").data
var clockPanel = require("sdk/panel").Panel({
width:215,
height:160,
contentURL: data.url("clock.html")
});
require("sdk/widget").Widget({
id: "open-clock-btn",
label: "Clock",
contentURL: data.url("History.png"),
panel: clockPanel
});
To learn more about working with panels, see the tutorial on
[displaying a popup](dev-guide/tutorials/display-a-popup.html).
## Learning More ##
To learn more about the widget module, see its
[API reference documentation](modules/sdk/widget.html).
To learn more about content scripts, see the
[content scripts guide](dev-guide/guides/content-scripts/index.html).

Просмотреть файл

@ -1,344 +0,0 @@
<!-- This Source Code Form is subject to the terms of the Mozilla Public
- License, v. 2.0. If a copy of the MPL was not distributed with this
- file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
# Creating Annotations #
We'll use two objects to create annotations: a page-mod to find page elements
that the user can annotate, and a panel for the user to enter the annotation
text itself.
## Selector page-mod ##
### Selector Content Scripts ###
The content script for the selector page-mod uses [jQuery](http://jquery.com/)
to examine and manipulate the DOM.
Its main job is to maintain a "matched element": this is the page element that
is the current candidate for an annotation. The matched element is highlighted
and has a click handler bound to it which sends a message to the main add-on
code.
The selector page mod can be switched on and off using a message from the
main add-on code. It is initially off:
var matchedElement = null;
var originalBgColor = null;
var active = false;
function resetMatchedElement() {
if (matchedElement) {
$(matchedElement).css('background-color', originalBgColor);
$(matchedElement).unbind('click.annotator');
}
}
self.on('message', function onMessage(activation) {
active = activation;
if (!active) {
resetMatchedElement();
}
});
This selector listens for occurrences of the
[jQuery mouseenter](http://api.jquery.com/mouseenter/) event.
When a mouseenter event is triggered the selector checks whether the element
is eligible for annotation. An element is eligible if it, or one of its
ancestors in the DOM tree, has an attribute named `"id"`. The idea here is to
make it more likely that the annotator will be able to identify annotated
elements correctly later on.
If the page element is eligible for annotation, then the selector highlights
that element and binds a click handler to it. The click handler sends a message
called `show` back to the main add-on code. The `show` message contains: the URL
for the page, the ID attribute value, and the text content of the page element.
$('*').mouseenter(function() {
if (!active || $(this).hasClass('annotated')) {
return;
}
resetMatchedElement();
ancestor = $(this).closest("[id]");
matchedElement = $(this).first();
originalBgColor = $(matchedElement).css('background-color');
$(matchedElement).css('background-color', 'yellow');
$(matchedElement).bind('click.annotator', function(event) {
event.stopPropagation();
event.preventDefault();
self.port.emit('show',
[
document.location.toString(),
$(ancestor).attr("id"),
$(matchedElement).text()
]
);
});
});
Conversely, the add-on resets the matched element on
[mouseout](http://api.jquery.com/mouseout/):
$('*').mouseout(function() {
resetMatchedElement();
});
Save this code in a new file called `selector.js` in your add-on's `data`
directory.
Because this code uses jQuery, you'll need to
[download](http://docs.jquery.com/Downloading_jQuery) that as well, and save it in
`data`.
### Updating main.js ###
Go back to `main.js` and add the code to create the selector into the `main`
function:
var selector = pageMod.PageMod({
include: ['*'],
contentScriptWhen: 'ready',
contentScriptFile: [data.url('jquery-1.4.2.min.js'),
data.url('selector.js')],
onAttach: function(worker) {
worker.postMessage(annotatorIsOn);
selectors.push(worker);
worker.port.on('show', function(data) {
console.log(data);
});
worker.on('detach', function () {
detachWorker(this, selectors);
});
}
});
Make sure the name you use to load jQuery matches the name of the jQuery
version you downloaded.
The page-mod matches all pages, so each time the user loads a page the page-mod
emits the `attach` event, which will call the listener function we've assigned
to `onAttach`. The handler is passed a
[worker](modules/sdk/content/worker.html) object. Each worker
represents a channel of communication between the add-on code and any content
scripts running in that particular page context. For a more detailed discussion
of the way `page-mod` uses workers, see the
[page-mod documentation](modules/sdk/page-mod.html).
In the attach handler we do three things:
* send the content script a message with the current activation status
* add the worker to an array called `selectors` so we can send it messages
later on
* assign a message handler for messages from this worker. If the message is
`show` we will just log the content for the time being. If the message is
`detach` we remove the worker from the `selectors` array.
At the top of the file import the `page-mod` module and declare an array for
the workers:
var pageMod = require('sdk/page-mod');
var selectors = [];
Add `detachWorker`:
function detachWorker(worker, workerArray) {
var index = workerArray.indexOf(worker);
if(index != -1) {
workerArray.splice(index, 1);
}
}
Edit `toggleActivation` to notify the workers of a change in activation state:
function activateSelectors() {
selectors.forEach(
function (selector) {
selector.postMessage(annotatorIsOn);
});
}
function toggleActivation() {
annotatorIsOn = !annotatorIsOn;
activateSelectors();
return annotatorIsOn;
}
<span class="aside">We'll be using this URL in all our screenshots. Because
`cfx run` doesn't preserve browsing history, if you want to play along it's
worth taking a note of the URL.</span>
Save the file and execute `cfx run` again. Activate the annotator by clicking
the widget and load a page: the screenshot below uses
[http://blog.mozilla.com/addons/2011/02/04/
overview-amo-review-process/](http://blog.mozilla.com/addons/2011/02/04/overview-amo-review-process/).
You should see the highlight appearing when you move the mouse over certain elements:
<img class="image-center"
src="static-files/media/annotator/highlight.png" alt="Annotator Highlighting">
Click on the highlight and you should see something like this in the console
output:
<pre>
info: show
info: http://blog.mozilla.com/addons/2011/02/04/overview-amo-review-process/,
post-2249,When you submit a new add-on, you will have to choose between 2
review tracks: Full Review and Preliminary Review.
</pre>
## Annotation Editor Panel ##
So far we have a page-mod that can highlight elements and send information
about them to the main add-on code. Next we will create the editor panel,
which enables the user to enter an annotation associated with the selected
element.
We will supply the panel's content as an HTML file, and will also supply a
content script to execute in the panel's context.
So create a subdirectory under `data` called `editor`. This will contain
two files: the HTML content, and the content script.
### Annotation Editor HTML ###
The HTML is very simple:
<script type="syntaxhighlighter" class="brush: html"><![CDATA[
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en">
<head>
<title>Annotation</title>
<style type="text/css" media="all">
body {
font: 100% arial, helvetica, sans-serif;
background-color: #F5F5F5;
}
textarea {
width: 180px;
height: 180px;
margin: 10px;
padding: 0px;
}
</style>
</head>
<body>
<textarea rows='10' cols='20' id='annotation-box'>
</textarea>
</body>
</html>
]]>
</script>
Save this inside `data/editor` as `annotation-editor.html`.
### Annotation Editor Content Script ###
In the corresponding content script we do two things:
* handle a message from the add-on code by giving the text area focus
* listen for the return key and when it is pressed, send the contents of the
text area to the add-on.
Here's the code:
var textArea = document.getElementById('annotation-box');
textArea.onkeyup = function(event) {
if (event.keyCode == 13) {
self.postMessage(textArea.value);
textArea.value = '';
}
};
self.on('message', function() {
var textArea = document.getElementById('annotation-box');
textArea.value = '';
textArea.focus();
});
Save this inside `data/editor` as `annotation-editor.js`.
### Updating main.js Again ###
Now we'll update `main.js` again to create the editor and use it.
First, import the `panel` module:
var panels = require('sdk/panel');
Then add the following code to the `main` function:
var annotationEditor = panels.Panel({
width: 220,
height: 220,
contentURL: data.url('editor/annotation-editor.html'),
contentScriptFile: data.url('editor/annotation-editor.js'),
onMessage: function(annotationText) {
if (annotationText) {
console.log(this.annotationAnchor);
console.log(annotationText);
}
annotationEditor.hide();
},
onShow: function() {
this.postMessage('focus');
}
});
We create the editor panel but don't show it.
We will send the editor panel the `focus` message when it is shown, so it will
give the text area focus. When the editor panel sends us its message we log the
message and hide the panel.
The only thing left is to link the editor to the selector. So edit the message
handler assigned to the selector so that on receiving the `show` message we
assign the content of the message to the panel using a new property
"annotationAnchor", and show the panel:
var selector = pageMod.PageMod({
include: ['*'],
contentScriptWhen: 'ready',
contentScriptFile: [data.url('jquery-1.4.2.min.js'),
data.url('selector.js')],
onAttach: function(worker) {
worker.postMessage(annotatorIsOn);
selectors.push(worker);
worker.port.on('show', function(data) {
annotationEditor.annotationAnchor = data;
annotationEditor.show();
});
worker.on('detach', function () {
detachWorker(this, selectors);
});
}
});
Execute `cfx run` again, activate the annotator, move your mouse over an
element and click the element when it is highlighted. You should see a panel
with a text area for a note:
<img class="image-center"
src="static-files/media/annotator/editor-panel.png" alt="Annotator Editor Panel">
<br>
Enter the note and press the return key: you should see console output like
this:
<pre>
info: http://blog.mozilla.com/addons/2011/02/04/overview-amo-review-process/,
post-2249,When you submit a new add-on, you will have to choose between 2
review tracks: Full Review and Preliminary Review.
info: We should ask for Full Review if possible.
</pre>
That's a complete annotation, and in the next section we'll deal with
[storing it](dev-guide/tutorials/annotator/storing.html).

Просмотреть файл

@ -1,213 +0,0 @@
<!-- This Source Code Form is subject to the terms of the Mozilla Public
- License, v. 2.0. If a copy of the MPL was not distributed with this
- file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
# Displaying Annotations #
In this chapter we'll use a page-mod to locate elements of web pages that have
annotations associated with them, and a panel to display the annotations.
## Matcher page-mod ##
### Matcher Content Script ###
The content script for the matcher page-mod is initialized with a list
of all the annotations that the user has created.
When a page is loaded the matcher searches the DOM for elements that match
annotations. If it finds any it binds functions to that element's
[mouseenter](http://api.jquery.com/mouseenter/) and
[mouseleave](http://api.jquery.com/mouseleave/) events to send messages to the
`main` module, asking it to show or hide the annotation.
Like the selector, the matcher also listens for the window's `unload` event
and on unload sends a `detach` message to the `main` module, so the add-on
can clean it up.
The complete content script is here:
self.on('message', function onMessage(annotations) {
annotations.forEach(
function(annotation) {
if(annotation.url == document.location.toString()) {
createAnchor(annotation);
}
});
$('.annotated').css('border', 'solid 3px yellow');
$('.annotated').bind('mouseenter', function(event) {
self.port.emit('show', $(this).attr('annotation'));
event.stopPropagation();
event.preventDefault();
});
$('.annotated').bind('mouseleave', function() {
self.port.emit('hide');
});
});
function createAnchor(annotation) {
annotationAnchorAncestor = $('#' + annotation.ancestorId);
annotationAnchor = $(annotationAnchorAncestor).parent().find(
':contains(' + annotation.anchorText + ')').last();
$(annotationAnchor).addClass('annotated');
$(annotationAnchor).attr('annotation', annotation.annotationText);
}
Save this in `data` as `matcher.js`.
### Updating main.js ###
First, initialize an array to hold workers associated with the matcher's
content scripts:
var matchers = [];
In the `main` function, add the code to create the matcher:
var matcher = pageMod.PageMod({
include: ['*'],
contentScriptWhen: 'ready',
contentScriptFile: [data.url('jquery-1.4.2.min.js'),
data.url('matcher.js')],
onAttach: function(worker) {
if(simpleStorage.storage.annotations) {
worker.postMessage(simpleStorage.storage.annotations);
}
worker.port.on('show', function(data) {
annotation.content = data;
annotation.show();
});
worker.port.on('hide', function() {
annotation.content = null;
annotation.hide();
});
worker.on('detach', function () {
detachWorker(this, matchers);
});
matchers.push(worker);
}
});
When a new page is loaded the function assigned to `onAttach` is called. This
function:
* initializes the content script instance with the current set of
annotations
* provides a handler for messages from that content script, handling the three
messages - `show`, `hide` and `detach` - that the content script might send
* adds the worker to an array, so we it can send messages back later.
Then in the module's scope implement a function to update the matcher's
workers, and edit `handleNewAnnotation` to call this new function when the
user enters a new annotation:
function updateMatchers() {
matchers.forEach(function (matcher) {
matcher.postMessage(simpleStorage.storage.annotations);
});
}
<br>
function handleNewAnnotation(annotationText, anchor) {
var newAnnotation = new Annotation(annotationText, anchor);
simpleStorage.storage.annotations.push(newAnnotation);
updateMatchers();
}
<br>
## Annotation panel ##
The annotation panel just shows the content of an annotation.
There are two files associated with the annotation panel:
* a simple HTML file to use as a template
* a simple content script to build the panel's content
These files will live in a new subdirectory of `data` which we'll call
`annotation`.
### Annotation panel HTML ###
<script type="syntaxhighlighter" class="brush: html"><![CDATA[
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en">
<head>
<title>Annotation</title>
<style type="text/css" media="all">
body {
font: 100% arial, helvetica, sans-serif;
background-color: #F5F5F5;
}
div {
text-align:left;
}
</style>
</head>
<body>
<div id = "annotation">
</div>
</body>
</html>
]]>
</script>
Save this in `data/annotation` as `annotation.html`.
### Annotation panel Content Script ###
The annotation panel has a minimal content script that sets the text:
self.on('message', function(message) {
$('#annotation').text(message);
});
Save this in `data/annotation` as `annotation.js`.
### Updating main.js ###
Finally, update `main.js` with the code to construct the annotation panel:
var annotation = panels.Panel({
width: 200,
height: 180,
contentURL: data.url('annotation/annotation.html'),
contentScriptFile: [data.url('jquery-1.4.2.min.js'),
data.url('annotation/annotation.js')],
onShow: function() {
this.postMessage(this.content);
}
});
Execute `cfx run` one last time. Activate the annotator and enter an
annotation. You should see a yellow border around the item you annotated:
<img class="image-center"
src="static-files/media/annotator/matcher.png" alt="Annotator Matcher">
<br>
When you move your mouse over the item, the annotation should appear:
<img class="image-center"
src="static-files/media/annotator/annotation-panel.png" alt="Annotation Panel">
<br>
Obviously this add-on isn't complete yet. It could do with more beautiful
styling, it certainly needs a way to delete annotations, it should deal with
`OverQuota` more reliably, and the matcher could be made to match more
reliably.
But we hope this gives you an idea of the things that are possible with the
modules in the SDK.

Просмотреть файл

@ -1,31 +0,0 @@
<!-- This Source Code Form is subject to the terms of the Mozilla Public
- License, v. 2.0. If a copy of the MPL was not distributed with this
- file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
# Annotator: a More Complex Add-on #
In this tutorial we'll build an add-on that uses many of the SDK's
[high-level APIs](modules/high-level-modules.html).
The add-on is an annotator: it enables the user to select elements of web pages
and enter notes (annotations) associated with them. The annotator stores the
notes. Whenever the user loads a page containing annotated elements these
elements are highlighted, and if the user moves the mouse over an annotated
element its annotation is displayed.
Next we'll give a quick overview of the annotator's design, then go through
the implementation, step by step.
If you want to refer to the complete add-on you can find it under the
`examples` directory.
* [Design Overview](dev-guide/tutorials/annotator/overview.html)
* [Implementing the Widget](dev-guide/tutorials/annotator/widget.html)
* [Creating Annotations](dev-guide/tutorials/annotator/creating.html)
* [Storing Annotations](dev-guide/tutorials/annotator/storing.html)
* [Displaying Annotations](dev-guide/tutorials/annotator/displaying.html)

Просмотреть файл

@ -1,67 +0,0 @@
<!-- This Source Code Form is subject to the terms of the Mozilla Public
- License, v. 2.0. If a copy of the MPL was not distributed with this
- file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
# Annotator Design Overview #
The annotator uses content scripts to build user interfaces, get user input,
and examine the DOM of pages loaded by the user.
Meanwhile the `main` module contains the application logic and mediates
interactions between the different SDK objects.
We could represent the basic interactions between the `main` module and the
various content scripts like this:
<img class="image-center"
src="static-files/media/annotator/annotator-design.png" alt="Annotator Design">
## User Interface ##
The annotator's main user interface consists of a widget and three panels.
* The widget is used to switch the annotator on and off, and to display a list
of all the stored annotations.
* The **annotation-editor** panel enables the user to enter a new annotation.
* The **annotation-list** panel shows a list of all stored annotations.
* The **annotation** panel displays a single annotation.
Additionally, we use the `notifications` module to notify the user when the
add-on's storage quota is full.
## Working with the DOM ##
We'll use two page-mods to interact with the DOMs of pages that the user has
opened.
* The **selector** enables the user to choose an element to annotate.
It identifies page elements which are eligible for annotation, highlights them
on mouseover, and tells the main add-on code when the user clicks a highlighted
element.
* The **matcher** is responsible for finding annotated elements: it is
initialized with the list of annotations and searches web pages for the
elements they are associated with. It highlights any annotated elements that
are found. When the user moves the mouse over an annotated element
the matcher tells the main add-on code, which displays the annotation panel.
## Working with Data ##
We'll use the `simple-storage` module to store annotations.
Because we are recording potentially sensitive information, we want to prevent
the user creating annotations when in private browsing mode. The simplest way
to do this is to omit the
[`"private-browsing"` key](dev-guide/package-spec.html#permissions) from the
add-on's "package.json" file. If we do this, then the add-on won't see any
private windows, and the annotator's widget will not appear in any private
windows.
## Getting Started ##
Let's get started by creating a directory called "annotator". Navigate to it
and type `cfx init`.
Next, we will implement the
[widget](dev-guide/tutorials/annotator/widget.html).

Просмотреть файл

@ -1,296 +0,0 @@
<!-- This Source Code Form is subject to the terms of the Mozilla Public
- License, v. 2.0. If a copy of the MPL was not distributed with this
- file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
# Storing Annotations #
Now we are able to create annotations, let's store them using the
[`simple-storage`](modules/sdk/simple-storage.html) module. In
this chapter we will cover three topics relating to persistent storage:
* using `simple-storage` to persist objects
* handling exhaustion of the storage quota allocated to you
* respecting Private Browsing
## Storing New Annotations ##
In this section we are only touching the `main.js` file.
First, import the `simple-storage` module with a declaration like:
var simpleStorage = require('sdk/simple-storage');
In the module scope, initialize an array which will contain the stored annotations:
if (!simpleStorage.storage.annotations)
simpleStorage.storage.annotations = [];
Now we'll add a function to the module scope which deals with a new
annotation. The annotation is composed of the text the user entered and the
"annotation anchor", which consists of the URL, element ID and element content:
function handleNewAnnotation(annotationText, anchor) {
var newAnnotation = new Annotation(annotationText, anchor);
simpleStorage.storage.annotations.push(newAnnotation);
}
This function calls a constructor for an `Annotation` object, which we also
need to supply:
function Annotation(annotationText, anchor) {
this.annotationText = annotationText;
this.url = anchor[0];
this.ancestorId = anchor[1];
this.anchorText = anchor[2];
}
Now we need to link this code to the annotation editor, so that when the user
presses the return key in the editor, we create and store the new annotation:
var annotationEditor = panels.Panel({
width: 220,
height: 220,
contentURL: data.url('editor/annotation-editor.html'),
contentScriptFile: data.url('editor/annotation-editor.js'),
onMessage: function(annotationText) {
if (annotationText)
handleNewAnnotation(annotationText, this.annotationAnchor);
annotationEditor.hide();
},
onShow: function() {
this.postMessage('focus');
}
});
## Listing Stored Annotations ##
To prove that this works, let's implement the part of the add-on that displays
all the previously entered annotations. This is implemented as a panel that's
shown in response to the widget's `right-click` message.
The panel has three new files associated with it:
* a content-script which builds the panel content
* a simple HTML file used as a template for the panel's content
* a simple CSS file to provide some basic styling.
These three files can all go in a new subdirectory of `data` which we will call `list`.
### Annotation List Content Script ###
Here's the annotation list's content script:
self.on("message", function onMessage(storedAnnotations) {
var annotationList = $('#annotation-list');
annotationList.empty();
storedAnnotations.forEach(
function(storedAnnotation) {
var annotationHtml = $('#template .annotation-details').clone();
annotationHtml.find('.url').text(storedAnnotation.url)
.attr('href', storedAnnotation.url);
annotationHtml.find('.url').bind('click', function(event) {
event.stopPropagation();
event.preventDefault();
self.postMessage(storedAnnotation.url);
});
annotationHtml.find('.selection-text')
.text(storedAnnotation.anchorText);
annotationHtml.find('.annotation-text')
.text(storedAnnotation.annotationText);
annotationList.append(annotationHtml);
});
});
It builds the DOM for the panel from the array of annotations it is given.
The user will be able to click links in the panel, but we want to open them in
the main browser window rather than the panel. So the content script binds a
click handler to the links which will send the URL to the add-on.
Save this file in `data/list` as `annotation-list.js`.
### Annotation List HTML and CSS ###
Here's the HTML for the annotation list:
<pre class="brush: html">
&lt;html&gt;
&lt;head&gt;
&lt;meta http-equiv="Content-type" content="text/html; charset=utf-8" /&gt;
&lt;title&gt;Saved annotations&lt;/title&gt;
&lt;link rel="stylesheet" type="text/css" href="annotation-list.css" /&gt;
&lt;/head&gt;
&lt;body&gt;
&lt;div id="annotation-list"&gt;
&lt;/div&gt;
&lt;div id="template"&gt;
&lt;div class="annotation-details"&gt;
&lt;a class="url"&gt;&lt;/a&gt;
&lt;div class="selection-text"&gt;&lt;/div&gt;
&lt;div class="annotation-text"&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/body&gt;
&lt;/html&gt;
</pre>
Here's the corresponding CSS:
<script type="syntaxhighlighter" class="brush: css"><![CDATA[
#annotation-list .annotation-details
{
padding: 10px;
margin: 10px;
border: solid 3px #EEE;
background-color: white;
}
#annotation-list .url, .selection-text, .annotation-text
{
padding: 5px;
margin: 5px;
}
#annotation-list .selection-text,#annotation-list .annotation-text
{
border: solid 1px #EEE;
}
#annotation-list .annotation-text
{
font-style: italic;
}
body
{
background-color: #F5F5F5;
font: 100% arial, helvetica, sans-serif;
}
h1
{
font-family: georgia,serif;
font-size: 1.5em;
text-align:center;
}
]]>
</script>
Save these in `data/list` as `annotation-list.html` and `annotation-list.css`
respectively.
### Updating main.js ###
Here's the code to create the panel, which can go in the `main` function.
var annotationList = panels.Panel({
width: 420,
height: 200,
contentURL: data.url('list/annotation-list.html'),
contentScriptFile: [data.url('jquery-1.4.2.min.js'),
data.url('list/annotation-list.js')],
contentScriptWhen: 'ready',
onShow: function() {
this.postMessage(simpleStorage.storage.annotations);
},
onMessage: function(message) {
require('sdk/tabs').open(message);
}
});
Since this panel's content script uses jQuery we will pass that in too: again,
make sure the name of it matches the version of jQuery you downloaded.
When the panel is shown we send it the array of stored annotations. When the
panel sends us a URL we use the `tabs` module to open it in a new tab.
Finally we need to connect this to the widget's `right-click` message:
var widget = widgets.Widget({
id: 'toggle-switch',
label: 'Annotator',
contentURL: data.url('widget/pencil-off.png'),
contentScriptWhen: 'ready',
contentScriptFile: data.url('widget/widget.js')
});
widget.port.on('left-click', function() {
console.log('activate/deactivate');
widget.contentURL = toggleActivation() ?
data.url('widget/pencil-on.png') :
data.url('widget/pencil-off.png');
});
widget.port.on('right-click', function() {
console.log('show annotation list');
annotationList.show();
});
This time execute `cfx xpi` to build the XPI for the add-on, and install it in
Firefox. Activate the add-on, add an annotation, and then right-click the
widget. You should see something like this:
<img class="image-center"
src="static-files/media/annotator/annotation-list.png" alt="Annotation List">
<br>
<span class="aside">
Until now we've always run `cfx run` rather than building an XPI and installing
the add-on in Firefox. If the annotation does not reappear when you restart
Firefox, double check you installed the add-on and didn't just use `cfx run`
again.</span>
Restart Firefox, right-click the widget again, and check that the annotation
is still there.
## Responding To OverQuota events ##
Add-ons have a limited quota of storage space. If the add-on exits while
it is over quota, any data stored since the last time it was in quota will not
be persisted.
So we want to listen to the `OverQuota` event emitted by `simple-storage` and
respond to it. Add the following to your add-on's `main` function:
simpleStorage.on("OverQuota", function () {
notifications.notify({
title: 'Storage space exceeded',
text: 'Removing recent annotations'});
while (simpleStorage.quotaUsage > 1)
simpleStorage.storage.annotations.pop();
});
Because we use a notification to alert the user, we need to import the
`notifications` module:
var notifications = require("sdk/notifications");
(It should be obvious that this is an incredibly unhelpful way to deal with the
problem. A real add-on should give the user a chance to choose which data to
keep, and prevent the user from adding any more data until the add-on is back
under quota.)
## Respecting Private Browsing ##
Since annotations record the user's browsing history we should avoid recording
annotations in private windows.
There's a very simple way to do this: do nothing. By omitting the
[`"private-browsing"` key](dev-guide/package-spec.html#permissions) from the
annotator's "package.json" file, the annotator opts out of private browsing
altogether.
This means that its widget will not appear on any private windows and its
selector and matcher content scripts won't run, so the user won't be able to
enter any annotations in private windows.
Try it: execute cfx run and open a new private window: you should no longer
see the annotator's widget.
Now we can create and store annotations, the last piece is to
[display them when the user loads the page](dev-guide/tutorials/annotator/displaying.html).

Просмотреть файл

@ -1,115 +0,0 @@
<!-- This Source Code Form is subject to the terms of the Mozilla Public
- License, v. 2.0. If a copy of the MPL was not distributed with this
- file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
# Implementing the Widget #
We want the widget to do two things:
<span class="aside">
[Bug 634712](https://bugzilla.mozilla.org/show_bug.cgi?id=634712) asks that
the widget API should emit separate, or at least distinguishable, events for
left and right mouse clicks, and when it is fixed this widget won't need a
content script any more.</span>
* On a left-click, the widget should activate or deactivate the annotator.
* On a right-click, the widget should display a list of all the annotations
the user has created.
Because the widget's `click` event does not distinguish left and right mouse
clicks, we'll use a content script to capture the click events and send the
corresponding message back to our add-on.
The widget will have two icons: one to display when it's active, one to display
when it's inactive.
So there are three files we'll need to create: the widget's content script and
its two icons.
Inside the `data` subdirectory create another subdirectory `widget`. We'll
keep the widget's files here. (Note that this isn't mandatory: you could just
keep them all under `data`. But it seems tidier this way.)
## The Widget's Content Script ##
The widget's content script just listens for left- and right- mouse clicks and
posts the corresponding message to the add-on code:
this.addEventListener('click', function(event) {
if(event.button == 0 && event.shiftKey == false)
self.port.emit('left-click');
if(event.button == 2 || (event.button == 0 && event.shiftKey == true))
self.port.emit('right-click');
event.preventDefault();
}, true);
Save this in your `data/widget` directory as `widget.js`.
## The Widget's Icons ##
You can copy the widget's icons from here:
<img style="margin-left:40px; margin-right:20px;" src="static-files/media/annotator/pencil-on.png" alt="Active Annotator">
<img style="margin-left:20px; margin-right:20px;" src="static-files/media/annotator/pencil-off.png" alt="Inactive Annotator">
(Or make your own if you're feeling creative.) Save them in your `data/widget` directory.
## main.js ##
Now in the `lib` directory open `main.js` and add the following code:
var widgets = require('sdk/widget');
var data = require('sdk/self').data;
var annotatorIsOn = false;
function toggleActivation() {
annotatorIsOn = !annotatorIsOn;
return annotatorIsOn;
}
exports.main = function() {
var widget = widgets.Widget({
id: 'toggle-switch',
label: 'Annotator',
contentURL: data.url('widget/pencil-off.png'),
contentScriptWhen: 'ready',
contentScriptFile: data.url('widget/widget.js')
});
widget.port.on('left-click', function() {
console.log('activate/deactivate');
widget.contentURL = toggleActivation() ?
data.url('widget/pencil-on.png') :
data.url('widget/pencil-off.png');
});
widget.port.on('right-click', function() {
console.log('show annotation list');
});
}
The annotator is inactive by default. It creates the widget and responds to
messages from the widget's content script by toggling its activation state.
<span class="aside">Note that due to
[bug 626326](https://bugzilla.mozilla.org/show_bug.cgi?id=626326) the add-on
bar's context menu is displayed, despite the `event.preventDefault()` call
in the widget's content script.</span>
Since we don't have any code to display annotations yet, we just log the
right-click events to the console.
Now from the `annotator` directory type `cfx run`. You should see the widget
in the add-on bar:
<div align="center">
<img src="static-files/media/annotator/widget-icon.png" alt="Widget Icon">
</div>
<br>
Left- and right-clicks should produce the appropriate debug output, and a
left-click should also change the widget icon to signal that it is active.
Next we'll add the code to
[create annotations](dev-guide/tutorials/annotator/creating.html).

Просмотреть файл

@ -1,105 +0,0 @@
<!-- This Source Code Form is subject to the terms of the Mozilla Public
- License, v. 2.0. If a copy of the MPL was not distributed with this
- file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
<div class="warning">The API used to gain Chrome access is currently an
experimental feature of the SDK, and may change in future releases.</div>
# Chrome Authority #
## Using Chrome Authority ##
The most powerful low-level modules are run with "chrome privileges",
which gives them access to the infamous <code>Components</code> object, which
grants unfettered access to the host system. From this, the module can do
pretty much anything the browser is capable of. To obtain these privileges,
the module must declare its intent with a statement like the following:
var {Cc, Ci} = require("chrome");
The object returned by <code>require("chrome")</code>, when unpacked with the
"destructuring assignment" feature available in the Mozilla JS environment,
will provide the usual <code>Components.*</code> aliases:
<code>**Cc**</code>
An alias for `Components.classes`.
<code>**Ci**</code>
An alias for `Components.interfaces`.
<code>**Cu**</code>
An alias for `Components.utils`.
<code>**Cr**</code>
An alias for `Components.results`.
<code>**Cm**</code>
An alias for `Components.manager`.
<code>**components**</code>
An alias for `Components` itself (note the lower-case). From this you can
access less-frequently-used properties like `Components.stack` and
`Components.isSuccessCode`.
Note: the `require("chrome")` statement is the **only** way to access chrome
functionality and the `Components` API. The `Components` object should
**not** be accessed from modules. The SDK tools will emit a warning
if it sees module code which references `Components` directly.
Your modules should refrain from using chrome privileges unless they are
absolutely necessary. Chrome-authority-using modules must receive extra
security review, and most bugs in these modules are security-critical.
## Manifest Generation ##
The **manifest** is a list, included in the generated XPI, which
specifies which modules have requested `require()` access to which other
modules. It also records which modules have requested chrome access. This
list is generated by scanning all included modules for `require(XYZ)`
statements and recording the particular "XYZ" strings that they reference.
When the manifest implementation is complete the runtime loader will actually
prevent modules from `require()`ing modules that are not listed in the
manifest. Likewise, it will prevent modules from getting chrome authority
unless the manifest indicates that they have asked for it. This will ensure
that reviewers see the same authority restrictions that are enforced upon the
running code, increasing the effectiveness of the time spent reviewing the
add-on. (until this work is complete, modules may be able to sneak around these
restrictions).
The manifest is built with a simple regexp-based scanner, not a full
Javascript parser. Developers should stick to simple `require` statements,
with a single static string, one per line of code. If the scanner fails to
see a `require` entry, the manifest will not include that entry, and (once
the implementation is complete) the runtime code will get an exception.
For example, none of the following code will be matched by the manifest
scanner, leading to exceptions at runtime, when the `require()` call is
prohibited from importing the named modules:
// all of these will fail!
var xhr = require("x"+"hr");
var modname = "xpcom";
var xpcom = require(modname);
var one = require("one"); var two = require("two");
The intention is that developers use `require()` statements for two purposes:
to declare (to security reviewers) what sorts of powers the module wants to
use, and to control how those powers are mapped into the module's local
namespace. Their statements must therefore be clear and easy to parse. A
future manifest format may move the declaration portion out to a separate
file, to allow for more fine-grained expression of authority.
Commands that build a manifest, like "`cfx xpi`" or "`cfx run`", will scan
all included modules for use of `Cc`/`Ci` aliases (or the expanded
`Components.classes` forms). It will emit a warning if it sees the expanded
forms, or if it sees a use of e.g. "`Cc`" without a corresponding entry in
the `require("chrome")` statement. These warnings will serve to guide
developers to use the correct pattern. All module developers should heed the
warnings and correct their code until the warnings go away.

Просмотреть файл

@ -1,151 +0,0 @@
<!-- This Source Code Form is subject to the terms of the Mozilla Public
- License, v. 2.0. If a copy of the MPL was not distributed with this
- file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
# Display a Popup #
<span class="aside">
To follow this tutorial you'll need to have
[installed the SDK](dev-guide/tutorials/installation.html)
and learned the
[basics of `cfx`](dev-guide/tutorials/getting-started-with-cfx.html).
</span>
To display a popup dialog, use the
[`panel`](modules/sdk/panel.html) module. A panel's content is
defined using HTML. You can run content scripts in the panel: although the
script running in the panel can't directly access your main add-on code,
you can exchange messages between the panel script and the add-on code.
<img class="image-right" src="static-files/media/screenshots/text-entry-panel.png"
alt="Text entry panel">
In this tutorial we'll create an add-on that
[adds a widget to the toolbar](dev-guide/tutorials/adding-toolbar-button.html)
which displays a panel when clicked.
The panel just contains a
`<textarea>` element: when the user presses the `return` key, the contents
of the `<textarea>` is sent to the main add-on code.
The main add-on code
[logs the message to the console](dev-guide/tutorials/logging.html).
The add-on consists of three files:
* **`main.js`**: the main add-on code, that creates the widget and panel
* **`get-text.js`**: the content script that interacts with the panel content
* **`text-entry.html`**: the panel content itself, specified as HTML
<div style="clear:both"></div>
The "main.js" looks like this:
var data = require("sdk/self").data;
// Construct a panel, loading its content from the "text-entry.html"
// file in the "data" directory, and loading the "get-text.js" script
// into it.
var text_entry = require("sdk/panel").Panel({
width: 212,
height: 200,
contentURL: data.url("text-entry.html"),
contentScriptFile: data.url("get-text.js")
});
// Create a widget, and attach the panel to it, so the panel is
// shown when the user clicks the widget.
require("sdk/widget").Widget({
label: "Text entry",
id: "text-entry",
contentURL: "http://www.mozilla.org/favicon.ico",
panel: text_entry
});
// When the panel is displayed it generated an event called
// "show": we will listen for that event and when it happens,
// send our own "show" event to the panel's script, so the
// script can prepare the panel for display.
text_entry.on("show", function() {
text_entry.port.emit("show");
});
// Listen for messages called "text-entered" coming from
// the content script. The message payload is the text the user
// entered.
// In this implementation we'll just log the text to the console.
text_entry.port.on("text-entered", function (text) {
console.log(text);
text_entry.hide();
});
The content script "get-text.js" looks like this:
// When the user hits return, send the "text-entered"
// message to main.js.
// The message payload is the contents of the edit box.
var textArea = document.getElementById("edit-box");
textArea.addEventListener('keyup', function onkeyup(event) {
if (event.keyCode == 13) {
// Remove the newline.
text = textArea.value.replace(/(\r\n|\n|\r)/gm,"");
self.port.emit("text-entered", text);
textArea.value = '';
}
}, false);
// Listen for the "show" event being sent from the
// main add-on code. It means that the panel's about
// to be shown.
//
// Set the focus to the text area so the user can
// just start typing.
self.port.on("show", function onShow() {
textArea.focus();
});
Finally, the "text-entry.html" file defines the `<textarea>` element:
<pre class="brush: html">
&lt;html&gt;
&lt;head&gt;
&lt;style type="text/css" media="all"&gt;
textarea {
margin: 10px;
}
&lt;/style&gt;
&lt;/head&gt;
&lt;body&gt;
&lt;textarea rows="10" cols="20" id="edit-box">&lt;/textarea&gt;
&lt;/body&gt;
&lt;/html&gt;
</pre>
Try it out: "main.js" is saved in your add-on's `lib` directory,
and the other two files go in your add-on's `data` directory:
<pre>
my-addon/
data/
get-text.js
text-entry.html
lib/
main.js
</pre>
Run the add-on, click the widget, and you should see the panel.
Type some text and press "return" and you should see the output
in the console.
## Learning More ##
To learn more about the `panel` module, see the
[`panel` API reference](modules/sdk/panel.html).
To learn more about attaching panels to widgets, see the
[`widget` API reference](modules/sdk/widget.html).

Просмотреть файл

@ -1,273 +0,0 @@
<!-- This Source Code Form is subject to the terms of the Mozilla Public
- License, v. 2.0. If a copy of the MPL was not distributed with this
- file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
# Creating Event Targets #
<span class="aside">This tutorial describes the use of low-level APIs.
These APIs are still in active development, and we expect to make
incompatible changes to them in future releases.</span>
The [guide to event-driven programming with the SDK](dev-guide/guides/events.html)
describes how to consume events: that is, how to listen to events generated
by event targets. For example, you can listen to the [`tabs` module's `ready` event](modules/sdk/tabs.html#ready) or the
[`Panel` object's `show` event](modules/sdk/panel.html#show).
With the SDK, it's also simple to implement your own event targets.
This is especially useful if you want to
[build your own modules](dev-guide/tutorials/reusable-modules.html),
either to organize your add-on better or to enable other developers to
reuse your code. If you use the SDK's event framework for your
event targets, users of your module can listen for events using the SDK's
standard event API.
In this tutorial we'll create part of a module to access the browser's
[Places API](https://developer.mozilla.org/en/Places). It will emit events
when the user adds and visits bookmarks, enabling users of the module
to listen for these events using the SDK's standard event API.
## Using the Places API ##
First, let's write some code using Places API that logs the URIs of
bookmarks the user adds.
Create a new directory called "bookmarks", navigate to it, and run `cfx init`.
Then open "lib/main.js" and add the following code:
var {Cc, Ci, Cu} = require("chrome");
Cu.import("resource://gre/modules/XPCOMUtils.jsm", this);
var bookmarkService = Cc["@mozilla.org/browser/nav-bookmarks-service;1"]
.getService(Ci.nsINavBookmarksService);
var bookmarkObserver = {
onItemAdded: function(aItemId, aFolder, aIndex) {
console.log("added ", bookmarkService.getBookmarkURI(aItemId).spec);
},
onItemVisited: function(aItemId, aVisitID, time) {
console.log("visited ", bookmarkService.getBookmarkURI(aItemId).spec);
},
QueryInterface: XPCOMUtils.generateQI([Ci.nsINavBookmarkObserver])
};
exports.main = function() {
bookmarkService.addObserver(bookmarkObserver, false);
};
exports.onUnload = function() {
bookmarkService.removeObserver(bookmarkObserver);
}
Try running this add-on, adding and visiting bookmarks, and observing
the output in the console.
## Modules as Event Targets ##
We can adapt this code into a separate module that exposes the SDK's
standard event interface.
To do this we'll use the [`event/core`](modules/sdk/event/core.html)
module.
Create a new file in "lib" called "bookmarks.js", and add the following code:
var { emit, on, once, off } = require("sdk/event/core");
var {Cc, Ci, Cu} = require("chrome");
Cu.import("resource://gre/modules/XPCOMUtils.jsm", this);
var bookmarkService = Cc["@mozilla.org/browser/nav-bookmarks-service;1"]
.getService(Ci.nsINavBookmarksService);
var bookmarkObserver = {
onItemAdded: function(aItemId, aFolder, aIndex) {
emit(exports, "added", bookmarkService.getBookmarkURI(aItemId).spec);
},
onItemVisited: function(aItemId, aVisitID, time) {
emit(exports, "visited", bookmarkService.getBookmarkURI(aItemId).spec);
},
QueryInterface: XPCOMUtils.generateQI([Ci.nsINavBookmarkObserver])
};
bookmarkService.addObserver(bookmarkObserver, false);
exports.on = on.bind(null, exports);
exports.once = once.bind(null, exports);
exports.removeListener = function removeListener(type, listener) {
off(exports, type, listener);
};
This code implements a module which can emit `added` and `visited` events.
It duplicates the previous code, but with a few changes:
* import `emit()`, `on()`, `once()`, and `off()` from `event/core`
* replace listener functions with calls to `emit()`, passing the appropriate
event type
* export its own event API. This consists of three functions:
* `on()`: start listening for events or a given type
* `once()`: listen for the next occurrence of a given event, and then stop
* `removeListener()`: stop listening for events of a given type
The `on()` and `once()` exports delegate to the corresponding function from
`event/core`, and use `bind()` to pass the `exports` object itself as
the `target` argument to the underlying function. The `removeListener()`
function is implemented by calling the underlying `off()` function.
We can use this module in the same way we use any other module that emits
module-level events, such as
[`private-browsing`](modules/sdk/private-browsing.html). For example,
we can adapt "main.js" as follows:
var bookmarks = require("./bookmarks");
function logAdded(uri) {
console.log("added: " + uri);
}
function logVisited(uri) {
console.log("visited: " + uri);
}
exports.main = function() {
bookmarks.on("added", logAdded);
bookmarks.on("visited", logVisited);
};
exports.onUnload = function() {
bookmarks.removeListener("added", logAdded);
bookmarks.removeListener("visited", logVisited);
}
## Classes as Event Targets ##
Sometimes we want to emit events at the level of individual objects,
rather than at the level of the module.
To do this, we can inherit from the SDK's
[`EventTarget`](modules/sdk/event/target.html) class. `EventTarget`
provides an implementation of the functions needed to add and remove
event listeners: `on()`, `once()`, and `removeListener()`.
In this example, we could define a class `BookmarkManager` that inherits
from `EventTarget` and emits `added` and `visited` events.
Open "bookmarks.js" and replace its contents with this code:
var { emit } = require("sdk/event/core");
var { EventTarget } = require("sdk/event/target");
var { Class } = require("sdk/core/heritage");
var { merge } = require("sdk/util/object");
var {Cc, Ci, Cu} = require("chrome");
Cu.import("resource://gre/modules/XPCOMUtils.jsm", this);
var bookmarkService = Cc["@mozilla.org/browser/nav-bookmarks-service;1"]
.getService(Ci.nsINavBookmarksService);
function createObserver(target) {
var bookmarkObserver = {
onItemAdded: function(aItemId, aFolder, aIndex) {
emit(target, "added", bookmarkService.getBookmarkURI(aItemId).spec);
},
onItemVisited: function(aItemId, aVisitID, time) {
emit(target, "visited", bookmarkService.getBookmarkURI(aItemId).spec);
},
QueryInterface: XPCOMUtils.generateQI([Ci.nsINavBookmarkObserver])
};
bookmarkService.addObserver(bookmarkObserver, false);
}
var BookmarkManager = Class({
extends: EventTarget,
initialize: function initialize(options) {
EventTarget.prototype.initialize.call(this, options);
merge(this, options);
createObserver(this);
}
});
exports.BookmarkManager = BookmarkManager;
The code to interact with the Places API is the same here. However:
* we're now importing from four modules:
* [`event/core`](modules/sdk/event/core.html) gives us
`emit()`: note that we don't need `on`, `once`, or `off`,
since we will use `EventTarget` for adding and removing listeners
* [`event/target`](modules/sdk/event/target.html) gives us
`EventTarget`, which implements the interface for adding and removing
listeners
* [`heritage`](modules/sdk/core/heritage.html) gives us
`Class()`, which we can use to inherit from `EventTarget`
* `utils/object` gives us `merge()`, which just simplifies setting up the
`BookmarkManager`'s properties
* we use `Class` to inherit from `EventTarget`. In its `initialize()` function,
we:
* call the base class initializer
* use `merge()` to copy any supplied options into the newly created object
* call `createObserver()`, passing in the newly created object as the
event target
* `createObserver()` is the same as in the previous example, except that in
`emit()` we pass the newly created `BookmarkManager` as the event target
To use this event target we can create it and call the `on()`, `once()`, and
`removeListener()` functions that it has inherited:
var bookmarks = require("./bookmarks");
var bookmarkManager = bookmarks.BookmarkManager({});
function logAdded(uri) {
console.log("added: " + uri);
}
function logVisited(uri) {
console.log("visited: " + uri);
}
exports.main = function() {
bookmarkManager.on("added", logAdded);
bookmarkManager.on("visited", logVisited);
};
exports.onUnload = function() {
bookmarkManager.removeListener("added", logAdded);
bookmarkManager.removeListener("visited", logVisited);
}
### Implementing "onEvent" Options ###
Finally, most event targets accept options of the form "onEvent", where
"Event" is the capitalized form of the event type. For example, you
can listen to the
[`Panel` object's `show` event](modules/sdk/panel.html#show)
either by calling:
myPanel.on("show", listenerFunction);
or by passing the `onShow` option to `Panel`'s constructor:
var myPanel = require("sdk/panel").Panel({
onShow: listenerFunction,
contentURL: "https://en.wikipedia.org/w/index.php"
});
If your class inherits from `EventTarget`, options like this are automatically
handled for you. For example, given the implementation of `BookmarkManager`
above, your "main.js" could be rewritten like this:
var bookmarks = require("./bookmarks");
function logAdded(uri) {
console.log("added: " + uri);
}
function logVisited(uri) {
console.log("visited: " + uri);
}
var bookmarkManager = bookmarks.BookmarkManager({
onAdded: logAdded,
onVisited: logVisited
});
exports.onUnload = function() {
bookmarkManager.removeListener("added", logAdded);
bookmarkManager.removeListener("visited", logVisited);
}

Просмотреть файл

@ -1,191 +0,0 @@
<!-- This Source Code Form is subject to the terms of the Mozilla Public
- License, v. 2.0. If a copy of the MPL was not distributed with this
- file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
<span class="aside">This tutorial assumes that you've read and followed the instructions in
the [installation guide](dev-guide/tutorials/installation.html), to
install and activate the SDK.</span>
# Getting Started With cfx #
To create add-ons using the SDK you'll have to get to know the `cfx`
command-line tool. It's what you'll use for testing and packaging add-ons.
There's comprehensive
[reference documentation](dev-guide/cfx-tool.html) covering
everything you can do using `cfx`, but in this tutorial we'll introduce the
three commands you need to get going:
* [`cfx init`](dev-guide/tutorials/getting-started-with-cfx.html#cfx-init)
: creates the skeleton structure for your add-on
* [`cfx run`](dev-guide/tutorials/getting-started-with-cfx.html#cfx-run)
: runs an instance of Firefox with your add-on installed
* [`cfx xpi`](dev-guide/tutorials/getting-started-with-cfx.html#cfx-xpi)
: build an installable [XPI](https://developer.mozilla.org/en/XPI) file to
distribute your add-on
## <a name="cfx-init">cfx init</a> ##
You use `cfx init` to create the basic skeleton for your add-on.
Create a new directory, navigate to it in your command shell, and run
`cfx init`:
<pre>
mkdir my-addon
cd my-addon
cfx init
</pre>
You don't have to create this directory under the SDK root: once you have
activated from the SDK root, `cfx` will remember where the SDK is, and you
will be able to use it from any directory.
You'll see some output like this:
<pre>
* lib directory created
* data directory created
* test directory created
* doc directory created
* README.md written
* package.json written
* test/test-main.js written
* lib/main.js written
* doc/main.md written
Your sample add-on is now ready for testing:
try "cfx test" and then "cfx run". Have fun!"
</pre>
## <a name="cfx-run">cfx run</a> ##
Use `cfx run` to run a new instance of Firefox with your add-on installed.
This is the command you'll use to test out your add-on while developing it.
The main code for an add-on is always kept in a file called `main.js` in your
add-on's `lib` directory. Open the `main.js` for this add-on, and
add the following code:
var widgets = require("sdk/widget");
var tabs = require("sdk/tabs");
var widget = widgets.Widget({
id: "mozilla-link",
label: "Mozilla website",
contentURL: "http://www.mozilla.org/favicon.ico",
onClick: function() {
tabs.open("http://www.mozilla.org/");
}
});
Now type:
<pre>
cfx run
</pre>
The first time you do this, you'll see a message like this:
<pre>
No 'id' in package.json: creating a new ID for you.
package.json modified: please re-run 'cfx run'
</pre>
<img class="image-right" src="static-files/media/screenshots/widget-mozilla.png"
alt="Mozilla icon widget" />
Run `cfx run` again, and it will run an instance of Firefox. In the
bottom-right corner of the browser you'll see an icon with the Mozilla
logo. Click the icon, and a new tab will open with
[http://www.mozilla.org/](http://www.mozilla.org/) loaded into it.
This add-on uses two SDK modules: the
[`widget`](modules/sdk/widget.html) module, which enables you
to add buttons to the browser, and the
[`tabs`](modules/sdk/tabs.html) module, which enables you to
perform basic operations with tabs. In this case, we've created a widget
whose icon is the Mozilla favicon, and added a click handler that loads
the Mozilla home page in a new tab.
Try editing this file. For example, we could change the icon displayed
and the URL that gets loaded:
var widgets = require("sdk/widget");
var tabs = require("sdk/tabs");
var widget = widgets.Widget({
id: "jquery-link",
label: "jQuery website",
contentURL: "http://www.jquery.com/favicon.ico",
onClick: function() {
tabs.open("http://www.jquery.com/");
}
});
<img class="image-right" src="static-files/media/screenshots/widget-jquery.png"
alt="jQuery icon widget" />
At the command prompt, execute `cfx run` again, and this time the icon is the
jQuery favicon, and clicking it takes you to
[http://www.jquery.com](http://www.jquery.com).
<div style="clear:both"></div>
## <a name="cfx-xpi">cfx xpi</a> ##
You'll use `cfx run` while developing your add-on, but once you're done with
that, you use `cfx xpi` to build an [XPI](https://developer.mozilla.org/en/XPI)
file. This is the installable file format for Firefox add-ons. You can
distribute XPI files yourself or publish them to
[http://addons.mozilla.org](http://addons.mozilla.org) so other users can
download and install it.
To build an XPI, just execute the command `cfx xpi` from the add-on's
directory:
<pre>
cfx xpi
</pre>
You should see a message like:
<pre>
Exporting extension to my-addon.xpi.
</pre>
The `my-addon.xpi` file can be found in the directory in which you ran
the command.
To test it, install it in your own Firefox installation.
You can do this by pressing the Ctrl+O key combination (Cmd+O on Mac) from
within Firefox, or selecting the "Open" item from Firefox's "File" menu.
This will bring up a file selection dialog: navigate to the
`my-addon.xpi` file, open it and follow the prompts to install the
add-on.
Now you have the basic `cfx` commands, you can try out the
[SDK's features](dev-guide/tutorials/index.html).
## Overriding the Built-in Modules ##
The SDK modules you use to implement your add-on are built into Firefox.
When you run or package an add-on using `cfx run` or `cfx xpi`, the add-on
will use the versions of the modules in the version of Firefox that hosts
it.
As an add-on developer, this is usually what you want. But if you're
developing the SDK modules themselves, of course it won't work at all.
In this case it's assumed that you will have checked out the SDK from
its [GitHub repo](https://github.com/mozilla/addon-sdk) and will have
run [`source/activate`](dev-guide/tutorials/installation.html) from
the root of your checkout.
Then when you invoke `cfx run` or `cfx xpi`, you pass the `"-o"` option:
<pre>cfx run -o</pre>
This instructs cfx to use the local copies of the SDK modules, not the
ones in Firefox.

Просмотреть файл

@ -1,244 +0,0 @@
<!-- This Source Code Form is subject to the terms of the Mozilla Public
- License, v. 2.0. If a copy of the MPL was not distributed with this
- file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
# Tutorials #
This page lists practical explanations of how to develop add-ons with
the SDK. The tutorials don't yet cover all the high-level APIs: see the sidebar
on the left for the full list of APIs.
<hr>
<h2><a name="getting-started">Getting Started</a></h2>
<table class="catalog">
<colgroup>
<col width="50%">
<col width="50%">
</colgroup>
<tr>
<td>
<h4><a href="dev-guide/tutorials/installation.html">Installation</a></h4>
Download, install, and initialize the SDK on Windows, OS X and Linux.
</td>
<td>
<h4><a href="dev-guide/tutorials/getting-started-with-cfx.html">Getting started with cfx</a></h4>
The basic <code>cfx</code> commands you need to start creating add-ons.
</td>
</tr>
<tr>
<td>
<h4><a href="dev-guide/tutorials/troubleshooting.html">Troubleshooting</a></h4>
Some pointers for fixing common problems and getting more help.
</td>
<td>
</td>
</tr>
</table>
<hr>
<h2><a name="create-user-interfaces">Create User Interfaces</a></h2>
<table class="catalog">
<colgroup>
<col width="50%">
<col width="50%">
</colgroup>
<tr>
<td>
<h4><a href="dev-guide/tutorials/adding-toolbar-button.html">Add a toolbar button</a></h4>
Attach a button to the Firefox Add-on toolbar.
</td>
<td>
<h4><a href="dev-guide/tutorials/display-a-popup.html">Display a popup</a></h4>
Display a popup dialog implemented with HTML and JavaScript.
</td>
</tr>
<tr>
<td>
<h4><a href="dev-guide/tutorials/adding-menus.html">Add a menu item to Firefox</a></h4>
Add items to Firefox's main menus.
</td>
<td>
<h4><a href="dev-guide/tutorials/add-a-context-menu-item.html">Add a context menu item</a></h4>
Add items to Firefox's context menu.
</td>
</tr>
</table>
<hr>
<h2><a name="interact-with-the-browser">Interact with the Browser</a></h2>
<table class="catalog">
<colgroup>
<col width="50%">
<col width="50%">
</colgroup>
<tr>
<td>
<h4><a href="dev-guide/tutorials/open-a-web-page.html">Open a web page</a></h4>
Open a web page in a new browser tab or window using the
<code><a href="modules/sdk/tabs.html">tabs</a></code> module, and access its content.
</td>
<td>
<h4><a href="dev-guide/tutorials/list-open-tabs.html">Get the list of open tabs</a></h4>
Use the <code><a href="modules/sdk/tabs.html">tabs</a></code>
module to iterate through the currently open tabs, and access their content.
</td>
</tr>
<tr>
<td>
<h4><a href="dev-guide/tutorials/listen-for-page-load.html">Listen for page load</a></h4>
Use the <code><a href="modules/sdk/tabs.html">tabs</a></code>
module to get notified when new web pages are loaded, and access their content.
</td>
<td>
</td>
</tr>
</table>
<hr>
<h2><a name="modify-web-pages">Modify Web Pages</a></h2>
<table class="catalog">
<colgroup>
<col width="50%">
<col width="50%">
</colgroup>
<tr>
<td>
<h4><a href="dev-guide/tutorials/modifying-web-pages-url.html">Modify web pages based on URL</a></h4>
Create filters for web pages based on their URL: whenever a web page
whose URL matches the filter is loaded, execute a specified script in it.
</td>
<td>
<h4><a href="dev-guide/tutorials/modifying-web-pages-tab.html">Modify the active web page</a></h4>
Dynamically load a script into the currently active web page.
</td>
</tr>
</table>
<hr>
<h2><a name="development-techniques">Development Techniques</a></h2>
<table class="catalog">
<colgroup>
<col width="50%">
<col width="50%">
</colgroup>
<tr>
<td>
<h4><a href="dev-guide/tutorials/logging.html">Logging</a></h4>
Log messages to the console for diagnostic purposes.
</td>
<td>
<h4><a href="dev-guide/tutorials/load-and-unload.html">Listen for load and unload</a></h4>
Get notifications when your add-on is loaded or unloaded by Firefox,
and pass arguments into your add-on from the command line.
</td>
</tr>
<tr>
<td>
<h4><a href="dev-guide/tutorials/reusable-modules.html">Creating third-party modules</a></h4>
Structure your add-on in separate modules to make it easier to develop, debug, and maintain.
Create reusable packages containing your modules, so other add-on developers can use them too.
</td>
<td>
<h4><a href="dev-guide/tutorials/adding-menus.html">Using third-party modules</a></h4>
Install and use additional modules which don't ship with the SDK itself.
</td>
</tr>
<tr>
<td>
<h4><a href="dev-guide/tutorials/unit-testing.html">Unit testing</a></h4>
Writing and running unit tests using the SDK's test framework.
</td>
<td>
<h4><a href="dev-guide/tutorials/l10n.html">Localization</a></h4>
Writing localizable code.
</td>
</tr>
<tr>
<td>
<h4><a href="dev-guide/tutorials/chrome.html">Chrome authority</a></h4>
Get access to the <a href="https://developer.mozilla.org/en/Components_object">Components</a>
object, enabling your add-on to load and use any XPCOM object.
</td>
<td>
<h4><a href="dev-guide/tutorials/mobile.html">Mobile development</a></h4>
Get set up to develop add-ons for Firefox Mobile on Android.
</td>
</tr>
<tr>
<td>
<h4><a href="dev-guide/tutorials/event-targets.html">Writing Event Targets</a></h4>
Enable the objects you define to emit their own events.
</td>
<td>
</td>
</tr>
</table>
<hr>
<h2><a name="putting-it-together">Putting It Together</a></h2>
<table class="catalog">
<colgroup>
<col width="50%">
<col width="50%">
</colgroup>
<tr>
<td>
<h4><a href="dev-guide/tutorials/annotator/index.html">Annotator add-on</a></h4>
A walkthrough of a relatively complex add-on.
</td>
<td>
</td>
</tr>
</table>

Просмотреть файл

@ -1,155 +0,0 @@
<!-- This Source Code Form is subject to the terms of the Mozilla Public
- License, v. 2.0. If a copy of the MPL was not distributed with this
- file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
# Installation #
## Prerequisites
To develop with the Add-on SDK, you'll need:
* [Python](http://www.python.org/) 2.5 or 2.6. Note that versions 3.0 and 3.1
of Python are not supported. Make sure that Python is in your path.
* A [compatible version of Firefox](dev-guide/guides/firefox-compatibility.html).
That's either: the version of Firefox shipping at the time the SDK shipped,
or the Beta version of Firefox at the time the SDK shipped. See the
[SDK Release Schedule](https://wiki.mozilla.org/Jetpack/SDK_2012_Release_Schedule)
to map SDK releases to Firefox releases.
* The SDK itself: you can obtain the latest stable version of the SDK as a
[tarball](https://ftp.mozilla.org/pub/mozilla.org/labs/jetpack/jetpack-sdk-latest.tar.gz)
or a [zip file](https://ftp.mozilla.org/pub/mozilla.org/labs/jetpack/jetpack-sdk-latest.zip).
Alternatively, you can get the latest development version from its
[GitHub repository](https://github.com/mozilla/addon-sdk).
## Installation on Mac OS X / Linux ##
Extract the file contents wherever you choose, and navigate to the root
directory of the SDK with a shell/command prompt. For example:
<pre>
tar -xf addon-sdk.tar.gz
cd addon-sdk
</pre>
Then run if you're a Bash user (most people are):
<pre>
source bin/activate
</pre>
And if you're a non-Bash user, you should run:
<pre>
bash bin/activate
</pre>
Your command prompt should now have a new prefix containing the name of the
SDK's root directory:
<pre>
(addon-sdk)~/mozilla/addon-sdk >
</pre>
## Installation on Windows ##
Extract the file contents wherever you choose, and navigate to the root
directory of the SDK with a shell/command prompt. For example:
<pre>
7z.exe x addon-sdk.zip
cd addon-sdk
</pre>
Then run:
<pre>
bin\activate
</pre>
Your command prompt should now have a new prefix containing the full path to
the SDK's root directory:
<pre>
(C:\Users\mozilla\sdk\addon-sdk) C:\Users\Work\sdk\addon-sdk>
</pre>
## SDK Virtual Environment ##
The new prefix to your command prompt indicates that your shell has entered
a virtual environment that gives you access to the Add-on SDK's command-line
tools.
At any time, you can leave a virtual environment by running `deactivate`.
The virtual environment is specific to this particular command prompt. If you
close this command prompt, it is deactivated and you need to type
`source bin/activate` or `bin\activate` in a new command prompt to reactivate
it. If you open a new command prompt, the SDK will not be active in the new
prompt.
You can have multiple copies of the SDK in different locations on disk and
switch between them, or even have them both activated in different command
prompts at the same time.
### Making `activate` Permanent ###
All `activate` does is to set a number of environment variables for the
current command prompt, using a script located in the top-level `bin`
directory. By setting these variables permanently in your environment so
every new command prompt reads them, you can make activation permanent. Then
you don't need to type `activate` every time you open up a new command prompt.
Because the exact set of variables may change with new releases of the SDK,
it's best to refer to the activation scripts to determine which variables need
to be set. Activation uses different scripts and sets different variables for
bash environments (Linux and Mac OS X) and for Windows environments.
#### Windows ####
On Windows, `bin\activate` uses `activate.bat`, and you can make activation
permanent using the command line `setx` tool or the Control Panel.
#### Linux/Mac OS X ####
On Linux and Mac OS X, `source bin/activate` uses the `activate` bash
script, and you can make activation permanent using your `~/.bashrc`
(on Linux) or `~/.bashprofile` (on Mac OS X).
As an alternative to this, you can create a symbolic link to the `cfx`
program in your `~/bin` directory:
<pre>
ln -s PATH_TO_SDK/bin/cfx ~/bin/cfx
</pre>
## Sanity Check ##
Run this at your shell prompt:
<pre>
cfx
</pre>
It should produce output whose first line looks something like this, followed by
many lines of usage information:
<pre>
Usage: cfx [options] [command]
</pre>
This is the `cfx` command-line program. It's your primary interface to the
Add-on SDK. You use it to launch Firefox and test your add-on, package your
add-on for distribution, view documentation, and run unit tests.
## Problems? ##
Try the [Troubleshooting](dev-guide/tutorials/troubleshooting.html)
page.
## Next Steps ##
Next, take a look at the
[Getting Started With cfx](dev-guide/tutorials/getting-started-with-cfx.html)
tutorial, which explains how to create add-ons using the `cfx` tool.

Просмотреть файл

@ -1,441 +0,0 @@
<!-- This Source Code Form is subject to the terms of the Mozilla Public
- License, v. 2.0. If a copy of the MPL was not distributed with this
- file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
# Localization #
The SDK supports localization of strings appearing in:
* [your main add-on's JavaScript code](dev-guide/tutorials/l10n.html#Using Localized Strings in JavaScript)
* [HTML files packaged with your add-on](dev-guide/tutorials/l10n.html#Using Localized Strings in HTML)
* [the `title` and `description` fields of your add-on's preferences](dev-guide/tutorials/l10n.html#Using Localized Strings in Preferences).
It doesn't, yet, support localization of CSS or content scripts.
## Localized Strings ##
Translated strings are kept in a directory called "locale" under your
main add-on directory, one file for each locale. The files:
* use the [`.properties` format](http://en.wikipedia.org/wiki/.properties)
* are named "xx-YY.properties", where "xx-YY" is the [name of the locale](https://wiki.mozilla.org/L10n:Locale_Codes) in question
* contain one entry for each string you want to localize, consisting
of an identifier for the string and its translation in that locale,
in the format `identifier=translation`.
Suppose your add-on contains a single localizable string,
represented in English as "Hello!", and you want to supply US English
and French French localizations.
You'd add two files to the "locale" directory:
<pre>
my-addon/
data
lib
locale/
en-US.properties
fr-FR.properties
</pre>
"en.US.properties" contains this:
<pre>
hello_id= Hello!
</pre>
"fr.FR.properties" contains this:
<pre>
hello_id= Bonjour !
</pre>
Now whenever your JavaScript or HTML asks the localization system for
the translation of the `hello_id` identifier, it will get the correct
translation for the current locale.
## Using Localized Strings in HTML ##
To reference localized strings from HTML, add a `data-l10n-id` attribute to
the HTML tag where you want the localized string to appear, and assign
the identifier to it:
<pre class="brush: html">
&lt;html&gt;
&lt;body&gt;
&lt;h1 data-l10n-id="hello_id"&gt;&lt;/h1>&gt;
&lt;/body&gt;
&lt;/html&gt;
</pre>
Then you can use this HTML file to build your interface, for example
inside a panel:
var hello = require("sdk/panel").Panel({
height: 75,
width: 150,
contentURL: require("sdk/self").data.url("my-panel.html")
});
var widget = require("sdk/widget").Widget({
id: "mozilla-link",
label: "Mozilla website",
contentURL: "http://www.mozilla.org/favicon.ico",
panel: hello
});
<img class="image-right" src="static-files/media/screenshots/l10n-html-enUS.png"
alt="Example of panel containing US English text">
<img class="image-right" src="static-files/media/screenshots/l10n-html-frFR.png"
alt="Example of panel containing French French text">
Given locale files for "en-US" and "fr-FR" which provide translations
of `hello_id`, the panel will now display "Hello!" or "Bonjour !", according
to the current locale.
The translation is inserted into the node which has the `data-l10n-id`
attribute set. Any previously existing content is just replaced.
The string is inserted as text, so you can't insert HTML using a statement
like:
<pre>
hello_id= &lt;blink&gt;Hello!&lt;/blink&gt;
</pre>
## Using Localized Strings in JavaScript
To reference localized strings from your main add-on code, you do this:
var _ = require("sdk/l10n").get;
console.log(_("hello_id!"));
<span class="aside">Assigning to "`_`" in particular is not required, but
is a convention from the
[gettext](https://www.gnu.org/software/gettext/gettext.html) tools
and will make it possible to work with existing tools that expect "`_`"
to indicate localizable strings.</span>
1. Import the `l10n` module, and assign its `get` function to
"`_`" (underscore).
2. Wrap all references to localizable strings with the `_()`
function.
If you run it you'll see the expected output for the current locale:
<pre>
info: Hello!
</pre>
<pre>
info: Bonjour !
</pre>
Note that because you can't `require()` modules in content scripts,
you can't yet reference localized strings from content scripts.
### Plurals ###
The `l10n` module supports plural forms. Different languages have
different rules for the formation of plurals. For example,
English has two forms: a singular form for "one", and a plural form
for "everything else, including zero":
<pre>
one tomato
no tomatoes
two tomatoes
</pre>
But Russian has different forms for numbers ending in 1 (except 11),
numbers ending in 2-4 (except 12-14) and other numbers:
<pre>
один помидор // one tomato
два помидора // two tomatoes
пять помидоров // five tomatoes
</pre>
The SDK uses the [Unicode CLDR](http://cldr.unicode.org/index) data
to describe the different plural forms used by different languages.
#### Unicode CLDR Plural Forms ####
The Unicode CLDR project defines a scheme for describing a particular
language's plural rules. In this scheme a language maps each distinct
range of numbers on to one of up to six forms, identified by the
following categories: *zero*, *one*, *two*, *few*, *many*, and *other*.
English has two forms, which can be described by mapping "1" to "one"
and "everything else" to "other":
<pre>
one → n is 1;
other → everything else
</pre>
Russian uses four forms, that can be described as follows:
<pre>
one → n mod 10 is 1 and n mod 100 is not 11;
few → n mod 10 in 2..4 and n mod 100 not in 12..14;
many → n mod 10 is 0 or n mod 10 in 5..9 or n mod 100 in 11..14;
other → everything else
</pre>
Plural rules for all languages can be found in the CLDR
[Language Plural Rules](http://unicode.org/repos/cldr-tmp/trunk/diff/supplemental/language_plural_rules.html)
page (although this table is out of date compared to the
[CLDR XML source](http://unicode.org/repos/cldr/trunk/common/supplemental/plurals.xml)).
#### Plural Forms in the SDK ####
In the code, you supply an extra parameter alongside the identifier,
describing how many items there are:
var _ = require("sdk/l10n").get;
console.log(_("tomato_id"));
console.log(_("tomato_id", 1));
console.log(_("tomato_id", 2));
console.log(_("tomato_id", 5));
console.log(_("tomato_id", .5));
In the `.properties` file for each language you can define a different
localization for each plural form possible in that language, using the
CLDR keywords. So in English we could have two plural localizations
(note that the "other" category does **not** take the CLDR keyword):
<pre>
# en-US translations
tomato_id[one]= %d tomato
tomato_id= %d tomatoes
</pre>
In Russian we could have four plural localizations:
<pre>
# ru-RU translations
tomato_id[one]= %d помидор
tomato_id[few]= %d помидора
tomato_id[many]= %d помидоров
tomato_id= %d помидоры
</pre>
The localization module itself understands the CLDR definitions for each
language, enabling it to map between, for example, "2" in the code and
"few" in the `ru-RU.properties` file. Then it retrieves and returns
the localization appropriate for the count you supplied.
### Placeholders ###
The `l10n` module supports placeholders, allowing you to
insert a string which should not be localized into one which is.
The following "en-US" and "fr-FR" ".properties" files include
placeholders:
<pre>
# en-US translations
hello_id= Hello %s!
</pre>
<pre>
# fr-FR translations
hello_id= Bonjour %s !
</pre>
To use placeholders, supply the placeholder string after the identifier:
var _ = require("sdk/l10n").get;
console.log(_("hello_id", "Bob"));
console.log(_("hello_id", "Alice"));
In the "en-US" locale, this gives us:
<pre>
info: Hello Bob!
info: Hello Alice!
</pre>
In "fr-FR" we get:
<pre>
info: Bonjour Bob !
info: Bonjour Alice !
</pre>
### Ordering Placeholders ###
When a localizable string can take two or more placeholders, translators
can define the order in which placeholders are inserted, without affecting
the code.
Primarily, this is important because different languages have different
rules for word order. Even within the same language, though, translators
should have the freedom to define word order.
For example, suppose we want to include a localized string naming a
person's home town. There are two placeholders: the name of the person
and the name of the home town:
var _ = require("sdk/l10n").get;
console.log(_("home_town_id", "Bob", "London"));
An English translator might want to choose between the following:
<pre>
"&lt;town_name> is &lt;person_name>'s home town."
</pre>
<pre>
"&lt;person_name>'s home town is &lt;town_name>"
</pre>
To choose the first option, the `.properties` file can order the
placeholders as follows:
<pre>
home_town_id= %2s is %1s's home town.
</pre>
This gives us the following output:
<pre>
info: London is Bob's home town.
</pre>
## Using Localized Strings in Preferences ##
By including a
[`"preferences"` structure in your add-on's "package.json" file](modules/sdk/simple-prefs.html ), you can define
preferences for your add-on that the user can see and edit
using Firefox's
[Add-ons Manager](https://support.mozilla.org/en-US/kb/Using%20extensions%20with%20Firefox#w_how-to-change-extension-settings).
Preferences have mandatory `title` and optional `description` fields.
These are strings which appear alongside the preference in the Add-ons
Manager, to help explain to the user what the preference means.
* To provide the localized form of the preference title, include an
entry in your "properties" file whose identifier is the preference
name followed by `_title`, and whose value is the localized title.
* To provide the localized form of the preference description, include
an entry in your "properties" file whose identifier is the preference
name followed by `_description`, and whose value is the localized description.
For example, suppose your "package.json" defines a single preference:
<pre>
{
"preferences": [
{
"type": "string",
"name": "monster_name",
"value": "Gerald",
"title": "Name"
}
],
"name": "monster-builder",
"license": "MPL 2.0",
"author": "me",
"version": "0.1",
"fullName": "Monster Builder",
"id": "monster-builder@me.org",
"description": "Build your own monster"
}
</pre>
In your "en-US.properties" file, include these two items:
<pre>
monster_name_title= Name
monster_name_description= What is the monster's name?
</pre>
In your "fr-FR.properties" file, include the French translation:
<pre>
monster_name_title= Nom
monster_name_description= Quel est le nom du monstre ?
</pre>
Now when the browser's locale is set to "en-US", users see this
in the Add-ons Manager:
<img class="image-center" src="static-files/media/screenshots/preference-us.png" alt="screenshot of US preference localization">
When the browser's locale is set to "fr-FR", they see this:
<img class="image-center" src="static-files/media/screenshots/preference-french.png" alt="screenshot of French preference localization">
The `menulist` and the `radio` preference types have options.
The `label` attribute of each option is displayed to the user.
If the locale file has a entry with the value of the `label` attribute
prefixed with "{name}_options." as its key, where {name} is the name of
the preference, its value is used as a localized label.
## Using Identifiers ##
If the localization system can't find an entry for a particular identifier
using the current locale, then it just returns the identifier itself.
This has the nice property that you can write localizable, fully
functional add-ons without having to write any locale files. You can just
use the default language strings as your identifier, and subsequently supply
`.properties` files for all the additional locales you want to support.
For example, in the case above you could use "Hello!" as the identifier, and
just have one `.properties` file for the "fr-FR" locale:
<pre>
Hello!= Bonjour !
</pre>
Then when the locale is "en-US", the system would fail to find a `.properties`
file, and return "Hello!".
However, this approach makes it difficult to maintain an add-on which
has many localizations, because you're using the default language strings
both as user interface strings and as keys to look up your translations.
This means that if you want to change the wording of a string in the default
language, or fix a typo, then you break all your locale files.
## Locale Updater ##
The [locale updater](https://github.com/downloads/ochameau/locale-updater/locale-updater.xpi)
add-on makes it easier to update locale files. Once you've installed it,
open the Add-on Manager, and you'll see a see a new button labeled
"Update l10n" next to each add-on you've installed:
<img class="align-center" src="static-files/media/screenshots/locale-updater.png"
alt="Add-on manager with locale updater installed" />
Click the button and you'll be prompted for a new `.properties` file
for that add-on. If you provide a new file, the add-on's locale data
will be updated with the new file.
## <a name="limitations">Limitations</a> ##
The current localization support is a first step towards full support,
and contains a number of limitations.
* There's no support for content scripts or CSS files: at
the moment, you can only localize strings appearing in JavaScript files
that can `require()` SDK modules and in HTML.
* The set of locale files is global across an add-on. This means that
a module isn't able to override a more general translation: so a module
`informal.js` can't specify that "hello_id" occurring in its code
should be localized to "Hi!".
* The SDK tools compile the locale files into a JSON format when
producing an XPI. This means that translators can't localize an add-on
given the XPI alone, but must be given access to the add-on source.
* The add-on developer must manually assemble the set of localizable
strings that make up the locale files. In a future release we'll add
a command to `cfx` that scans the add-on for localizable strings and
builds a template `.properties` file listing all the strings that need
to be translated.

Просмотреть файл

@ -1,73 +0,0 @@
<!-- This Source Code Form is subject to the terms of the Mozilla Public
- License, v. 2.0. If a copy of the MPL was not distributed with this
- file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
# List Open Tabs #
<span class="aside">
To follow this tutorial you'll need to have
[installed the SDK](dev-guide/tutorials/installation.html)
and learned the
[basics of `cfx`](dev-guide/tutorials/getting-started-with-cfx.html).
</span>
To list the open tabs, you can iterate over the
[`tabs`](modules/sdk/tabs.html) object itself.
The following add-on adds a
[`widget`](modules/sdk/widget.html) that logs
the URLs of open tabs when the user clicks it:
var widget = require("sdk/widget").Widget({
id: "mozilla-link",
label: "Mozilla website",
contentURL: "http://www.mozilla.org/favicon.ico",
onClick: listTabs
});
function listTabs() {
var tabs = require("sdk/tabs");
for each (var tab in tabs)
console.log(tab.url);
}
If you run the add-on, load a couple of tabs, and click the
widget, you'll see output in the
[console](dev-guide/console.html) that looks like this:
<pre>
info: http://www.mozilla.org/en-US/about/
info: http://www.bbc.co.uk/
</pre>
You don't get direct access to any content hosted in the tab.
To access tab content you need to attach a script to the tab
using `tab.attach()`. This add-on attaches a script to all open
tabs. The script adds a red border to the tab's document:
var widget = require("sdk/widget").Widget({
id: "mozilla-link",
label: "Mozilla website",
contentURL: "http://www.mozilla.org/favicon.ico",
onClick: listTabs
});
function listTabs() {
var tabs = require("sdk/tabs");
for each (var tab in tabs)
runScript(tab);
}
function runScript(tab) {
tab.attach({
contentScript: "document.body.style.border = '5px solid red';"
});
}
## Learning More ##
To learn more about working with tabs in the SDK, see the
[`tabs` API reference](modules/sdk/tabs.html).
To learn more about running scripts in tabs, see the
[tutorial on using `tab.attach()`](dev-guide/tutorials/modifying-web-pages-tab.html).

Просмотреть файл

@ -1,55 +0,0 @@
<!-- This Source Code Form is subject to the terms of the Mozilla Public
- License, v. 2.0. If a copy of the MPL was not distributed with this
- file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
# Listen For Page Load #
<span class="aside">
To follow this tutorial you'll need to have
[installed the SDK](dev-guide/tutorials/installation.html)
and learned the
[basics of `cfx`](dev-guide/tutorials/getting-started-with-cfx.html).
</span>
You can get notifications about new pages loading using the
[`tabs`](modules/sdk/tabs.html) module. The following add-on
listens to the tab's built-in `ready` event and just logs the URL of each
tab as the user loads it:
require("sdk/tabs").on("ready", logURL);
function logURL(tab) {
console.log(tab.url);
}
You don't get direct access to any content hosted in the tab.
To access tab content you need to attach a script to the tab
using `tab.attach()`. This add-on attaches a script to all open
tabs. The script adds a red border to the tab's document:
require("sdk/tabs").on("ready", logURL);
function logURL(tab) {
runScript(tab);
}
function runScript(tab) {
tab.attach({
contentScript: "if (document.body) document.body.style.border = '5px solid red';"
});
}
(This example is only to show the idea: to implement something like this,
you should instead use
[`page-mod`](dev-guide/tutorials/modifying-web-pages-url.html),
and specify "*" as the match-pattern.)
## Learning More ##
To learn more about working with tabs in the SDK, see the
[`tabs` API reference](modules/sdk/tabs.html). You can listen
for a number of other tab events, including `open`, `close`, and `activate`.
To learn more about running scripts in tabs, see the
[tutorial on using `tab.attach()`](dev-guide/tutorials/modifying-web-pages-tab.html).

Просмотреть файл

@ -1,103 +0,0 @@
<!-- This Source Code Form is subject to the terms of the Mozilla Public
- License, v. 2.0. If a copy of the MPL was not distributed with this
- file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
# Listening for Load and Unload #
<span class="aside">
To follow this tutorial you'll need to have
[installed the SDK](dev-guide/tutorials/installation.html)
and learned the
[basics of `cfx`](dev-guide/tutorials/getting-started-with-cfx.html).
</span>
## exports.main() ##
Your add-on's `main.js` code is executed as soon as it is loaded. It is loaded
when it is installed, when it is enabled, or when Firefox starts.
If your add-on exports a function called `main()`, that function will be
called immediately after the overall `main.js` is evaluated, and after all
top-level `require()` statements have run (so generally after all dependent
modules have been loaded).
exports.main = function (options, callbacks) {};
`options` is an object describing the parameters with which your add-on was
loaded.
### options.loadReason ###
`options.loadReason` is one of the following strings
describing the reason your add-on was loaded:
<pre>
install
enable
startup
upgrade
downgrade
</pre>
### options.staticArgs ###
You can use the [`cfx`](dev-guide/cfx-tool.html)
`--static-args` option to pass arbitrary data to your
program.
The value of `--static-args` must be a JSON string. The object encoded by the
JSON becomes the `staticArgs` member of the `options` object passed as the
first argument to your program's `main` function. The default value of
`--static-args` is `"{}"` (an empty object), so you don't have to worry about
checking whether `staticArgs` exists in `options`.
For example, if your `main.js` looks like this:
exports.main = function (options, callbacks) {
console.log(options.staticArgs.foo);
};
And you run cfx like this:
<pre>
cfx run --static-args="{ \"foo\": \"Hello from the command line\" }"
</pre>
Then your console should contain this:
<pre>
info: Hello from the command line
</pre>
The `--static-args` option is recognized by `cfx run` and `cfx xpi`.
When used with `cfx xpi`, the JSON is packaged with the XPI's harness options
and will therefore be used whenever the program in the XPI is run.`
## exports.onUnload() ##
If your add-on exports a function called `onUnload()`, that function
will be called when the add-on is unloaded.
exports.onUnload = function (reason) {};
`reason` is one of the following strings describing the reason your add-on was
unloaded:
<span class="aside">But note that due to
[bug 627432](https://bugzilla.mozilla.org/show_bug.cgi?id=627432),
your `onUnload` listener will never be called with `uninstall`: it
will only be called with `disable`. See in particular
[comment 12 on that bug](https://bugzilla.mozilla.org/show_bug.cgi?id=627432#c12).</span>
<pre>
uninstall
disable
shutdown
upgrade
downgrade
</pre>
You don't have to use `exports.main()` or `exports.onUnload()`. You can just place
your add-on's code at the top level instead of wrapping it in a function
assigned to `exports.main()`. It will be loaded in the same circumstances, but
you won't get access to the `options` or `callbacks` arguments.

Просмотреть файл

@ -1,76 +0,0 @@
# Logging #
<!-- This Source Code Form is subject to the terms of the Mozilla Public
- License, v. 2.0. If a copy of the MPL was not distributed with this
- file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
<span class="aside">
To follow this tutorial you'll need to have
[installed the SDK](dev-guide/tutorials/installation.html)
and learned the
[basics of `cfx`](dev-guide/tutorials/getting-started-with-cfx.html).
</span>
The [DOM `console` object](https://developer.mozilla.org/en/DOM/console)
is useful for debugging JavaScript. Because DOM objects aren't available
to the main add-on code, the SDK provides its own global `console` object
with most of the same methods as the DOM `console`, including methods to
log error, warning, or informational messages. You don't have to
`require()` anything to get access to the console: it is automatically
made available to you.
The `console.log()` method prints an informational message:
console.log("Hello World");
Try it out:
* create a new directory, and navigate to it
* execute `cfx init`
* open "lib/main.js" and add the line above
* execute `cfx run`, then `cfx run` again
Firefox will start, and the following line will appear in the command
window you used to execute `cfx run`:
<pre>
info: Hello World!
</pre>
## `console` in Content Scripts ##
You can use the console in
[content scripts](dev-guide/guides/content-scripts/index.html) as well
as in your main add-on code. The following add-on logs the HTML content
of every tab the user loads, by calling `console.log()` inside a content
script:
require("sdk/tabs").on("ready", function(tab) {
tab.attach({
contentScript: "console.log(document.body.innerHTML);"
});
});
## `console` Output ##
If you are running your add-on from the command line (for example,
executing `cfx run` or `cfx test`) then the console's messages appear
in the command shell you used.
If you've installed the add-on in Firefox, or you're running the
add-on in the Add-on Builder, then the messages appear in Firefox's
[Error Console](https://developer.mozilla.org/en/Error_Console).
But note that **by default, calls to `console.log()` will not result
in any output in the Error Console for any installed add-ons**: this
includes add-ons installed using the Add-on Builder or using tools
like the
[Extension Auto-installer](https://addons.mozilla.org/en-US/firefox/addon/autoinstaller/).
See ["Logging Levels"](dev-guide/console.html#Logging Levels)
in the console reference documentation for more information on this.
## Learning More ##
For the complete `console` API, see its
[API reference](dev-guide/console.html).

Просмотреть файл

@ -1,335 +0,0 @@
<!-- This Source Code Form is subject to the terms of the Mozilla Public
- License, v. 2.0. If a copy of the MPL was not distributed with this
- file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
<div class="warning">Developing add-ons for Firefox Mobile is still
an experimental feature of the SDK. Although the SDK modules used are
stable, the setup instructions and cfx commands are likely to change.
</div>
# Developing for Firefox Mobile #
<span class="aside">
To follow this tutorial you'll need to have
[installed the SDK](dev-guide/tutorials/installation.html)
and learned the
[basics of `cfx`](dev-guide/tutorials/getting-started-with-cfx.html).
</span>
Mozilla has recently decided to
[reimplement the UI for Firefox Mobile on Android](http://starkravingfinkle.org/blog/2011/11/firefox-for-android-native-android-ui/)
using native Android widgets instead of XUL. With the add-on SDK you
can develop add-ons that run on this new version of Firefox Mobile as
well as on the desktop version of Firefox.
You can use the same code to target both desktop Firefox and Firefox
Mobile, and just specify some extra options to `cfx run`, `cfx test`,
and `cfx xpi` when targeting Firefox Mobile.
Right now not all modules are fully functional, but we're working on adding
support for more modules.
The [tables at the end of this guide](dev-guide/tutorials/mobile.html#modules-compatibility) list the modules that are currently supported on Firefox Mobile.
This tutorial explains how to run SDK add-ons on an Android
device connected via USB to your development machine.
We'll use the
[Android Debug Bridge](http://developer.android.com/guide/developing/tools/adb.html)
(adb) to communicate between the Add-on SDK and the device.
<img class="image-center" src="static-files/media/mobile-setup-adb.png"/>
It's possible to use the
[Android emulator](http://developer.android.com/guide/developing/tools/emulator.html)
to develop add-ons for Android without access to a device, but it's slow,
so for the time being it's much easier to use the technique described
below.
## Setting up the Environment ##
First you'll need an
[Android device capable of running the native version of
Firefox Mobile](https://wiki.mozilla.org/Mobile/Platforms/Android#System_Requirements).
Then:
* install the
[Nightly build of the native version of Firefox Mobile](https://wiki.mozilla.org/Mobile/Platforms/Android#Download_Nightly)
on the device.
* [enable USB debugging on the device (step 3 of this link only)](http://developer.android.com/guide/developing/device.html#setting-up)
On the development machine:
* install version 1.5 or higher of the Add-on SDK
* install the correct version of the
[Android SDK](http://developer.android.com/sdk/index.html)
for your device
* using the Android SDK, install the
[Android Platform Tools](http://developer.android.com/sdk/installing.html#components)
Next, attach the device to the development machine via USB.
Now open up a command shell. Android Platform Tools will have
installed `adb` in the "platform-tools" directory under the directory
in which you installed the Android SDK. Make sure the "platform-tools"
directory is in your path. Then type:
<pre>
adb devices
</pre>
You should see some output like:
<pre>
List of devices attached
51800F220F01564 device
</pre>
(The long hex string will be different.)
If you do, then `adb` has found your device and you can get started.
## Running Add-ons on Android ##
You can develop your add-on as normal, as long as you restrict yourself
to the supported modules.
When you need to run the add-on, first ensure that Firefox is not running
on the device. Then execute `cfx run` with some extra options:
<pre>
cfx run -a fennec-on-device -b /path/to/adb --mobile-app fennec --force-mobile
</pre>
See ["cfx Options for Mobile Development"](dev-guide/tutorials/mobile.html#cfx-options)
for the details of this command.
In the command shell, you should see something like:
<pre>
Launching mobile application with intent name org.mozilla.fennec
Pushing the addon to your device
Starting: Intent { act=android.activity.MAIN cmp=org.mozilla.fennec/.App (has extras) }
--------- beginning of /dev/log/main
--------- beginning of /dev/log/system
Could not read chrome manifest 'file:///data/data/org.mozilla.fennec/chrome.manifest'.
info: starting
info: starting
zerdatime 1329258528988 - browser chrome startup finished.
</pre>
This will be followed by lots of debug output.
On the device, you should see Firefox launch with your add-on installed.
`console.log()` output from your add-on is written to the command shell,
just as it is in desktop development. However, because there's a
lot of other debug output in the shell, it's not easy to follow.
The command `adb logcat` prints `adb`'s log, so you can filter the
debug output after running the add-on. For example, on Mac OS X
or Linux you can use a command like:
<pre>
adb logcat | grep info:
</pre>
Running `cfx test` is identical:
<pre>
cfx test -a fennec-on-device -b /path/to/adb --mobile-app fennec --force-mobile
</pre>
## <a name="cfx-options">cfx Options for Mobile Development</a> ##
As you see in the quote above, `cfx run` and `cfx test` need four options to
work on Android devices.
<table>
<colgroup>
<col width="30%">
<col width="70%">
</colgroup>
<tr>
<td>
<code>-a fennec-on-device</code>
</td>
<td>
This tells the Add-on SDK which application will host the
add-on, and should be set to "fennec-on-device" when running
an add-on on Firefox Mobile on a device.
</td>
</tr>
<tr>
<td>
<code>-b /path/to/adb</code>
</td>
<td>
<p>As we've seen, <code>cfx</code> uses the Android Debug Bridge (adb)
to communicate with the Android device. This tells <code>cfx</code>
where to find the <code>adb</code> executable.</p>
<p>You need to supply the full path to the <code>adb</code> executable.</p>
</td>
</tr>
<tr>
<td>
<code>--mobile-app</code>
</td>
<td>
<p>This is the name of the <a href="http://developer.android.com/reference/android/content/Intent.html">
Android intent</a>. Its value depends on the version of Firefox Mobile
that you're running on the device:</p>
<ul>
<li><code>fennec</code>: if you're running Nightly</li>
<li><code>fennec_aurora</code>: if you're running Aurora</li>
<li><code>firefox_beta</code>: if you're running Beta</li>
<li><code>firefox</code>: if you're running Release</li>
</ul>
<p>If you're not sure, run a command like this (on OS X/Linux, or the equivalent on Windows):</p>
<pre>adb shell pm list packages | grep mozilla</pre>
<p>You should see "package" followed by "org.mozilla." followed by a string.
The final string is the name you need to use. For example, if you see:</p>
<pre>package:org.mozilla.fennec</pre>
<p>...then you need to specify:</p>
<pre>--mobile-app fennec</pre>
<p>This option is not required if you have only one Firefox application
installed on the device.</p>
</td>
</tr>
<tr>
<td>
<code>--force-mobile</code>
</td>
<td>
<p>This is used to force compatibility with Firefox Mobile, and should
always be used when running on Firefox Mobile.</p>
</td>
</tr>
</table>
## Packaging Mobile Add-ons ##
To package a mobile add-on as an XPI, use the command:
<pre>
cfx xpi --force-mobile
</pre>
Actually installing the XPI on the device is a little tricky. The easiest way is
probably to copy the XPI somewhere on the device:
<pre>
adb push my-addon.xpi /mnt/sdcard/
</pre>
Then open Firefox Mobile and type this into the address bar:
<pre>
file:///mnt/sdcard/my-addon.xpi
</pre>
The browser should open the XPI and ask if you
want to install it.
Afterwards you can delete it using `adb` as follows:
<pre>
adb shell
cd /mnt/sdcard
rm my-addon.xpi
</pre>
<a name="modules-compatibility"></a>
## Module Compatibility
Modules not yet supported in Firefox Mobile are
<span class="unsupported-on-mobile">**marked**</span> in the tables below.
### High-Level APIs ###
- [**addon-page**](modules/sdk/addon-page.html)
- [base64](modules/sdk/clipboard.html)
- [**clipboard**](modules/sdk/clipboard.html)
- [**context-menu**](modules/sdk/context-menu.html)
- [hotkeys](modules/sdk/hotkeys.html)
- [indexed-db](modules/sdk/indexed-db.html)
- [l10n](modules/sdk/l10n.html)
- [notifications](modules/sdk/notifications.html)
- [page-mod](modules/sdk/notifications.html)
- [page-worker](modules/sdk/page-worker.html)
- [**panel**](modules/sdk/panel.html)
- [passwords](modules/sdk/passwords.html)
- [**private-browsing**](modules/sdk/private-browsing.html)
- [querystring](modules/sdk/querystring.html)
- [request](modules/sdk/request.html)
- [**selection**](modules/sdk/selection.html)
- [self](modules/sdk/self.html)
- [simple-prefs](modules/sdk/simple-prefs.html)
- [simple-storage](modules/sdk/simple-storage.html)
- [system](modules/sdk/system.html)
- [tabs](modules/sdk/tabs.html)
- [timers](modules/sdk/timers.html)
- [url](modules/sdk/url.html)
- [**widget**](modules/sdk/widget.html)
- [windows](modules/sdk/windows.html)
### Low-Level APIs ###
- [/loader](modules/toolkit/loader.html)
- [chrome](dev-guide/tutorials/chrome.html)
- [console/plain-text](modules/sdk/console/plain-text.html)
- [console/traceback](modules/sdk/console/traceback.html)
- [**content/content**](modules/sdk/content/content.html)
- [content/loader](modules/sdk/content/loader.html)
- [**content/symbiont**](modules/sdk/content/symbiont.html)
- [**content/worker**](modules/sdk/content/worker.html)
- core/disposable
- [core/heritage](modules/sdk/core/heritage.html)
- [core/namespace](modules/sdk/core/namespace.html)
- [core/promise](modules/sdk/core/promise.html)
- [deprecated/api-utils](modules/sdk/deprecated/api-utils.html)
- [deprecated/app-strings](modules/sdk/deprecated/app-strings.html)
- [deprecated/cortex](modules/sdk/deprecated/cortex.html)
- [deprecated/errors](modules/sdk/deprecated/errors.html)
- [deprecated/events](modules/sdk/deprecated/events.html)
- [deprecated/light-traits](modules/sdk/deprecated/light-traits.html)
- deprecated/list
- [deprecated/observer-service](modules/sdk/deprecated/observer-service.html)
- [**deprecated/tab-browser**](modules/sdk/deprecated/tab-browser.html)
- [deprecated/traits](modules/sdk/deprecated/traits.html)
- [**deprecated/window-utils**](modules/sdk/deprecated/window-utils.html)
- dom/events
- [event/core](modules/sdk/event/core.html)
- [event/target](modules/sdk/event/target.html)
- [frame/hidden-frame](modules/sdk/frame/hidden-frame.html)
- [frame/utils](modules/sdk/frame/utils.html)
- [io/byte-streams](modules/sdk/io/byte-streams.html)
- [io/file](modules/sdk/io/file.html)
- [io/text-streams](modules/sdk/io/text-streams.html)
- keyboard/observer
- keyboard/utils
- lang/functional
- lang/type
- [loader/cuddlefish](modules/sdk/loader/cuddlefish.html)
- [loader/sandbox](modules/sdk/loader/sandbox.html)
- [net/url](modules/sdk/net/url.html)
- [net/xhr](modules/sdk/net/xhr.html)
- [platform/xpcom](modules/sdk/platform/xpcom.html)
- [preferences/service](modules/sdk/preferences/service.html)
- [system/environment](modules/sdk/system/environment.html)
- [system/events](modules/sdk/system/events.html)
- system/globals
- [system/runtime](modules/sdk/system/runtime.html)
- [system/unload](modules/sdk/system/unload.html)
- [system/xul-app](modules/sdk/system/xul-app.html)
- [test/assert](modules/sdk/test/assert.html)
- [test/harness](modules/sdk/test/harness.html)
- [test/httpd](modules/sdk/test/httpd.html)
- [test/runner](modules/sdk/test/runner.html)
- test/tmp-file
- util/array
- [util/collection](modules/sdk/util/collection.html)
- [util/deprecate](modules/sdk/util/deprecate.html)
- [util/list](modules/sdk/util/list.html)
- [util/match-pattern](modules/sdk/util/match-pattern.html)
- util/registry
- [util/uuid](modules/sdk/util/uuid.html)
- [window/utils](modules/sdk/window/utils.html)

Просмотреть файл

@ -1,154 +0,0 @@
<!-- This Source Code Form is subject to the terms of the Mozilla Public
- License, v. 2.0. If a copy of the MPL was not distributed with this
- file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
# Modifying the Page Hosted by a Tab #
<span class="aside">
To follow this tutorial you'll need to have
[installed the SDK](dev-guide/tutorials/installation.html)
and learned the
[basics of `cfx`](dev-guide/tutorials/getting-started-with-cfx.html).
</span>
To modify the page hosted by a particular tab, load a script into it
using the `attach()` method of the
[tab](modules/sdk/tabs.html) object. Because their job is
to interact with web content, these scripts are called *content scripts*.
Here's a simple example:
var widgets = require("sdk/widget");
var tabs = require("sdk/tabs");
var widget = widgets.Widget({
id: "mozilla-link",
label: "Mozilla website",
contentURL: "http://www.mozilla.org/favicon.ico",
onClick: function() {
tabs.activeTab.attach({
contentScript:
'document.body.style.border = "5px solid red";'
})
}
});
This add-on creates a widget which contains the Mozilla favicon as an icon.
It has a click handler which fetches the active tab and loads a
script into the page hosted by the active tab. The script is specified using
`contentScript` option, and just draws
a red border around the page. Try it out:
* create a new directory and navigate to it
* run `cfx init`
* open the `lib/main.js` file, and add the code above
* run `cfx run`, then run `cfx run` again
You should see the Mozilla icon appear in the bottom-right corner of the
browser:
<img class="image-center" src="static-files/media/screenshots/widget-mozilla.png"
alt="Mozilla icon widget" />
Then open any web page in the browser window that opens, and click the
Mozilla icon. You should see a red border appear around the page, like this:
<img class="image-center" src="static-files/media/screenshots/tabattach-bbc.png"
alt="bbc.co.uk modded by tab.attach" />
## Keeping the Content Script in a Separate File ##
In the example above we've passed in the content script as a string. Unless
the script is extremely simple, you should instead maintain the script as a
separate file. This makes the code easier to maintain, debug, and review.
For example, if we save the script above under the add-on's `data` directory
in a file called `my-script.js`:
document.body.style.border = "5px solid red";
We can load this script by changing the add-on code like this:
var widgets = require("sdk/widget");
var tabs = require("sdk/tabs");
var self = require("sdk/self");
var widget = widgets.Widget({
id: "mozilla-link",
label: "Mozilla website",
contentURL: "http://www.mozilla.org/favicon.ico",
onClick: function() {
tabs.activeTab.attach({
contentScriptFile: self.data.url("my-script.js")
});
}
});
You can load more than one script, and the scripts can interact
directly with each other. So you can load [jQuery](http://jquery.com/),
and then your content script can use that.
## Communicating With the Content Script ##
Your add-on script and the content script can't directly
access each other's variables or call each other's functions, but they
can send each other messages.
To send a
message from one side to the other, the sender calls `port.emit()` and
the receiver listens using `port.on()`.
* In the content script, `port` is a property of the global `self` object.
* In the add-on script, `tab.attach()` returns a
[worker](modules/sdk/content/worker.html#Worker) object containing the
`port` property you use to send messages to the content script.
Let's rewrite the example above to pass a message from the add-on to
the content script. The content script now needs to look like this:
// "self" is a global object in content scripts
// Listen for a "drawBorder"
self.port.on("drawBorder", function(color) {
document.body.style.border = "5px solid " + color;
});
In the add-on script, we'll send the content script a "drawBorder" message
using the object returned from `attach()`:
var widgets = require("sdk/widget");
var tabs = require("sdk/tabs");
var self = require("sdk/self");
var widget = widgets.Widget({
id: "mozilla-link",
label: "Mozilla website",
contentURL: "http://www.mozilla.org/favicon.ico",
onClick: function() {
worker = tabs.activeTab.attach({
contentScriptFile: self.data.url("my-script.js")
});
worker.port.emit("drawBorder", "red");
}
});
The `drawBorder` message isn't a built-in message, it's one that this
add-on defines in the `port.emit()` call.
## Injecting CSS ##
Unlike the [`page-mod`](dev-guide/tutorials/modifying-web-pages-url.html) API,
`tab.attach()` doesn't enable you to inject CSS directly into a page.
To modify the style of a page you have to use JavaScript, as in
the example above.
## Learning More ##
To learn more about working with tabs in the SDK, see the
[Open a Web Page](dev-guide/tutorials/open-a-web-page.html)
tutorial, the
[List Open Tabs](dev-guide/tutorials/list-open-tabs.html)
tutorial, and the [`tabs` API reference](modules/sdk/tabs.html).
To learn more about content scripts, see the
[content scripts guide](dev-guide/guides/content-scripts/index.html).

Просмотреть файл

@ -1,243 +0,0 @@
<!-- This Source Code Form is subject to the terms of the Mozilla Public
- License, v. 2.0. If a copy of the MPL was not distributed with this
- file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
# Modifying Web Pages Based on URL #
<span class="aside">
To follow this tutorial you'll need to have
[installed the SDK](dev-guide/tutorials/installation.html)
and learned the
[basics of `cfx`](dev-guide/tutorials/getting-started-with-cfx.html).
</span>
To modify any pages that match a particular pattern
(for example, "http://example.org/") as they are loaded, use the
[`page-mod`](modules/sdk/page-mod.html) module.
To create a page-mod you need to specify two things:
* one or more scripts to run. Because their job is to interact with web
content, these scripts are called *content scripts*.
* one or more patterns to match the URLs for the pages you want to modify
Here's a simple example. The content script is supplied as the `contentScript`
option, and the URL pattern is given as the `include` option:
// Import the page-mod API
var pageMod = require("sdk/page-mod");
// Create a page mod
// It will run a script whenever a ".org" URL is loaded
// The script replaces the page contents with a message
pageMod.PageMod({
include: "*.org",
contentScript: 'document.body.innerHTML = ' +
' "<h1>Page matches ruleset</h1>";'
});
Try it out:
* create a new directory and navigate to it
* run `cfx init`
* open the `lib/main.js` file, and add the code above
* run `cfx run`, then run `cfx run` again
* open [ietf.org](http://www.ietf.org) in the browser window that opens
This is what you should see:
<img class="image-center" src="static-files/media/screenshots/pagemod-ietf.png"
alt="ietf.org eaten by page-mod" />
## Specifying the Match Pattern ##
The match pattern uses the
[`match-pattern`](modules/sdk/util/match-pattern.html)
syntax. You can pass a single match-pattern string, or an array.
## Keeping the Content Script in a Separate File ##
In the example above we've passed in the content script as a string. Unless
the script is extremely simple, you should instead maintain the script as a
separate file. This makes the code easier to maintain, debug, and review.
To do this, you need to:
* save the script in your add-on's `data` directory
* use the `contentScriptFile` option instead of `contentScript`, and pass
it the URL for the script. The URL can be obtained using `self.data.url()`
For example, if we save the script above under the add-on's `data` directory
in a file called `my-script.js`:
document.body.innerHTML = "<h1>Page matches ruleset</h1>";
We can load this script by changing the page-mod code like this:
// Import the page-mod API
var pageMod = require("sdk/page-mod");
// Import the self API
var self = require("sdk/self");
// Create a page mod
// It will run a script whenever a ".org" URL is loaded
// The script replaces the page contents with a message
pageMod.PageMod({
include: "*.org",
contentScriptFile: self.data.url("my-script.js")
});
## Loading Multiple Content Scripts ##
You can load more than one script, and the scripts can interact
directly with each other. So, for example, you could rewrite
`my-script.js` to use jQuery:
$("body").html("<h1>Page matches ruleset</h1>");
Then download jQuery to your add-on's `data` directory, and
load the script and jQuery together (making sure to load jQuery
first):
// Import the page-mod API
var pageMod = require("sdk/page-mod");
// Import the self API
var self = require("sdk/self");
// Create a page mod
// It will run a script whenever a ".org" URL is loaded
// The script replaces the page contents with a message
pageMod.PageMod({
include: "*.org",
contentScriptFile: [self.data.url("jquery-1.7.min.js"),
self.data.url("my-script.js")]
});
You can use both `contentScript` and `contentScriptFile`
in the same page-mod: if you do this, scripts loaded using
`contentScript` are loaded first:
// Import the page-mod API
var pageMod = require("sdk/page-mod");
// Import the self API
var self = require("sdk/self");
// Create a page mod
// It will run a script whenever a ".org" URL is loaded
// The script replaces the page contents with a message
pageMod.PageMod({
include: "*.org",
contentScriptFile: self.data.url("jquery-1.7.min.js"),
contentScript: '$("body").html("<h1>Page matches ruleset</h1>");'
});
Note, though, that you can't load a script from a web site. The script
must be loaded from `data`.
## Communicating With the Content Script ##
Your add-on script and the content script can't directly
access each other's variables or call each other's functions, but they
can send each other messages.
To send a
message from one side to the other, the sender calls `port.emit()` and
the receiver listens using `port.on()`.
* In the content script, `port` is a property of the global `self` object.
* In the add-on script, you need to listen for the `onAttach` event to get
passed a [worker](modules/sdk/content/worker.html#Worker) object that
contains `port`.
Let's rewrite the example above to pass a message from the add-on to
the content script. The message will contain the new content to insert into
the document. The content script now needs to look like this:
// "self" is a global object in content scripts
// Listen for a message, and replace the document's
// contents with the message payload.
self.port.on("replacePage", function(message) {
document.body.innerHTML = "<h1>" + message + "</h1>";
});
In the add-on script, we'll send the content script a message inside `onAttach`:
// Import the page-mod API
var pageMod = require("sdk/page-mod");
// Import the self API
var self = require("sdk/self");
// Create a page mod
// It will run a script whenever a ".org" URL is loaded
// The script replaces the page contents with a message
pageMod.PageMod({
include: "*.org",
contentScriptFile: self.data.url("my-script.js"),
// Send the content script a message inside onAttach
onAttach: function(worker) {
worker.port.emit("replacePage", "Page matches ruleset");
}
});
The `replacePage` message isn't a built-in message: it's a message defined by
the add-on in the `port.emit()` call.
<div class="experimental">
## Injecting CSS ##
**Note that the feature described in this section is experimental
at the moment: we'll very probably continue to support the feature,
but details of the API might need to change.**
Rather than injecting JavaScript into a page, you can inject CSS by
setting the page-mod's `contentStyle` option:
var pageMod = require("sdk/page-mod").PageMod({
include: "*",
contentStyle: "body {" +
" border: 5px solid green;" +
"}"
});
As with `contentScript`, there's a corresponding `contentStyleFile` option
that's given the URL of a CSS file in your "data" directory, and it is
good practice to use this option in preference to `contentStyle` if the
CSS is at all complex:
var pageMod = require("sdk/page-mod").PageMod({
include: "*",
contentStyleFile: require("sdk/self").data.url("my-style.css")
});
You can't currently use relative URLs in style sheets loaded with
`contentStyle` or `contentStyleFile`. If you do, the files referenced
by the relative URLs will not be found.
To learn more about this, and read about a workaround, see the
[relevant section in the page-mod API documentation](modules/sdk/page-mod.html#Working_with_Relative_URLs_in_CSS_Rules).
</div>
## Learning More ##
To learn more about `page-mod`, see its
[API reference page](modules/sdk/page-mod.html).
In particular, the `PageMod` constructor takes several additional options
to control its behavior:
* By default, content scripts are not attached to any tabs that are
already open when the page-mod is created, and are attached to iframes
as well as top-level documents. To control this behavior use the `attachTo`
option.
* Define read-only values accessible to content scripts using the
`contentScriptOptions` option.
* By default, content scripts are attached after all the content
(DOM, JS, CSS, images) for the page has been loaded, at the time the
[window.onload event](https://developer.mozilla.org/en/DOM/window.onload)
fires. To control this behavior use the `contentScriptWhen` option.
To learn more about content scripts in general, see the
[content scripts guide](dev-guide/guides/content-scripts/index.html).

Просмотреть файл

@ -1,57 +0,0 @@
<!-- This Source Code Form is subject to the terms of the Mozilla Public
- License, v. 2.0. If a copy of the MPL was not distributed with this
- file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
# Open a Web Page #
<span class="aside">
To follow this tutorial you'll need to have
[installed the SDK](dev-guide/tutorials/installation.html)
and learned the
[basics of `cfx`](dev-guide/tutorials/getting-started-with-cfx.html).
</span>
To open a new web page, you can use the
[`tabs`](modules/sdk/tabs.html) module:
var tabs = require("sdk/tabs");
tabs.open("http://www.example.com");
This function is asynchronous, so you don't immediately get back a
[`tab` object](modules/sdk/tabs.html#Tab) which you can examine.
To do this, pass a callback function into `open()`. The callback is assigned
to the `onReady` property, and will be passed the tab as an argument:
var tabs = require("sdk/tabs");
tabs.open({
url: "http://www.example.com",
onReady: function onReady(tab) {
console.log(tab.title);
}
});
Even then, you don't get direct access to any content hosted in the tab.
To access tab content you need to attach a script to the tab
using `tab.attach()`. This add-on loads a page, then attaches a script to
the page which adds a red border to it:
var tabs = require("sdk/tabs");
tabs.open({
url: "http://www.example.com",
onReady: runScript
});
function runScript(tab) {
tab.attach({
contentScript: "document.body.style.border = '5px solid red';"
});
}
## Learning More ##
To learn more about working with tabs in the SDK, see the
[`tabs` API reference](modules/sdk/tabs.html).
To learn more about running scripts in tabs, see the
[tutorial on using `tab.attach()`](dev-guide/tutorials/modifying-web-pages-tab.html).

Просмотреть файл

@ -1,387 +0,0 @@
<!-- This Source Code Form is subject to the terms of the Mozilla Public
- License, v. 2.0. If a copy of the MPL was not distributed with this
- file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
# Creating Reusable Modules #
<span class="aside">
To follow this tutorial you'll need to have
[installed the SDK](dev-guide/tutorials/installation.html)
and learned the
[basics of `cfx`](dev-guide/tutorials/getting-started-with-cfx.html).
</span>
With the SDK you don't have to keep all your add-on in a single "main.js"
file. You can split your code into separate modules with clearly defined
interfaces between them. You then import and use these modules from other
parts of your add-on using the `require()` statement, in exactly that same
way that you import core SDK modules like
[`widget`](modules/sdk/widget.html) or
[`panel`](modules/sdk/panel.html).
It can often make sense to structure a larger or more complex add-on as a
collection of modules. This makes the design of the add-on easier to
understand and provides some encapsulation as each module will export only
what it chooses to, so you can change the internals of the module without
breaking its users.
Once you've done this, you can package the modules and distribute them
independently of your add-on, making them available to other add-on developers
and effectively extending the SDK itself.
In this tutorial we'll do exactly that with a module that exposes the
geolocation API in Firefox.
## Using Geolocation in an Add-on ##
Suppose we want to use the
[geolocation API built into Firefox](https://developer.mozilla.org/en-US/docs/WebAPI/Using_geolocation).
The SDK doesn't provide an API to access geolocation, but we can
[access the underlying XPCOM API using `require("chrome")`](dev-guide/guides/xul-migration.html#xpcom).
The following add-on adds a [button to the toolbar](dev-guide/tutorials/adding-toolbar-button.html):
when the user clicks the button, it loads the
[XPCOM nsIDOMGeoGeolocation](https://developer.mozilla.org/en-US/docs/XPCOM_Interface_Reference/NsIDOMGeoGeolocation)
object, and retrieves the user's current position:
var {Cc, Ci} = require("chrome");
// Implement getCurrentPosition by loading the nsIDOMGeoGeolocation
// XPCOM object.
function getCurrentPosition(callback) {
var xpcomGeolocation = Cc["@mozilla.org/geolocation;1"]
.getService(Ci.nsIDOMGeoGeolocation);
xpcomGeolocation.getCurrentPosition(callback);
}
var widget = require("sdk/widget").Widget({
id: "whereami",
label: "Where am I?",
contentURL: "http://www.mozilla.org/favicon.ico",
onClick: function() {
getCurrentPosition(function(position) {
console.log("latitude: ", position.coords.latitude);
console.log("longitude: ", position.coords.longitude);
});
}
});
Try it out:
* create a new directory called "whereami" and navigate to it
* execute `cfx init`
* open "lib/main.js" and add the code above
* execute `cfx run`, then `cfx run` again
You should see a button added to the "Add-on Bar" at the bottom of
the browser window:
<img class="image-center" src="static-files/media/screenshots/widget-mozilla.png"
alt="Mozilla icon widget" />
Click the button, and after a short delay you should see output like
this in the console:
<pre>
info: latitude: 29.45799999
info: longitude: 93.0785269
</pre>
So far, so good. But the geolocation guide on MDN tells us that we must
[ask the user for permission](https://developer.mozilla.org/en-US/docs/WebAPI/Using_geolocation#Prompting_for_permission)
before using the API.
So we'll extend the add-on to include an adapted version of the code in
that MDN page:
<pre><code>
var activeBrowserWindow = require("sdk/window/utils").getMostRecentBrowserWindow();
var {Cc, Ci} = require("chrome");
// Ask the user to confirm that they want to share their location.
// If they agree, call the geolocation function, passing the in the
// callback. Otherwise, call the callback with an error message.
function getCurrentPositionWithCheck(callback) {
let pref = "extensions.whereami.allowGeolocation";
let message = "whereami Add-on wants to know your location."
let branch = Cc["@mozilla.org/preferences-service;1"]
.getService(Ci.nsIPrefBranch2);
if (branch.getPrefType(pref) === branch.PREF_STRING) {
switch (branch.getCharPref(pref)) {
case "always":
return getCurrentPosition(callback);
case "never":
return callback(null);
}
}
let done = false;
function remember(value, result) {
return function () {
done = true;
branch.setCharPref(pref, value);
if (result) {
getCurrentPosition(callback);
}
else {
callback(null);
}
}
}
let self = activeBrowserWindow.PopupNotifications.show(
activeBrowserWindow.gBrowser.selectedBrowser,
"geolocation",
message,
"geo-notification-icon",
{
label: "Share Location",
accessKey: "S",
callback: function (notification) {
done = true;
getCurrentPosition(callback);
}
}, [
{
label: "Always Share",
accessKey: "A",
callback: remember("always", true)
},
{
label: "Never Share",
accessKey: "N",
callback: remember("never", false)
}
], {
eventCallback: function (event) {
if (event === "dismissed") {
if (!done)
callback(null);
done = true;
PopupNotifications.remove(self);
}
},
persistWhileVisible: true
});
}
// Implement getCurrentPosition by loading the nsIDOMGeoGeolocation
// XPCOM object.
function getCurrentPosition(callback) {
var xpcomGeolocation = Cc["@mozilla.org/geolocation;1"]
.getService(Ci.nsIDOMGeoGeolocation);
xpcomGeolocation.getCurrentPosition(callback);
}
var widget = require("sdk/widget").Widget({
id: "whereami",
label: "Where am I?",
contentURL: "http://www.mozilla.org/favicon.ico",
onClick: function() {
getCurrentPositionWithCheck(function(position) {
if (!position) {
console.log("The user denied access to geolocation.");
}
else {
console.log("latitude: ", position.coords.latitude);
console.log("longitude: ", position.coords.longitude);
}
});
}
});
</code></pre>
This works fine: when we click the button, we get a notification box
asking for permission, and depending on our choice the add-on logs either
the position or an error message.
But the code is now somewhat long and complex, and if we want to do much
more in the add-on, it will be hard to maintain. So let's split the
geolocation code into a separate module.
## Creating a Separate Module ##
### Create `geolocation.js` ###
First create a new file in "lib" called "geolocation.js", and copy
everything except the widget code into this new file.
Next, add the following line somewhere in the new file:
exports.getCurrentPosition = getCurrentPositionWithCheck;
This defines the public interface of the new module. We export a single
a function to prompt the user for permission and get the current position
if they agree.
So "geolocation.js" should look like this:
<pre><code>
var activeBrowserWindow = require("sdk/window/utils").getMostRecentBrowserWindow();
var {Cc, Ci} = require("chrome");
// Ask the user to confirm that they want to share their location.
// If they agree, call the geolocation function, passing the in the
// callback. Otherwise, call the callback with an error message.
function getCurrentPositionWithCheck(callback) {
let pref = "extensions.whereami.allowGeolocation";
let message = "whereami Add-on wants to know your location."
let branch = Cc["@mozilla.org/preferences-service;1"]
.getService(Ci.nsIPrefBranch2);
if (branch.getPrefType(pref) === branch.PREF_STRING) {
switch (branch.getCharPref(pref)) {
case "always":
return getCurrentPosition(callback);
case "never":
return callback(null);
}
}
let done = false;
function remember(value, result) {
return function () {
done = true;
branch.setCharPref(pref, value);
if (result) {
getCurrentPosition(callback);
}
else {
callback(null);
}
}
}
let self = activeBrowserWindow.PopupNotifications.show(
activeBrowserWindow.gBrowser.selectedBrowser,
"geolocation",
message,
"geo-notification-icon",
{
label: "Share Location",
accessKey: "S",
callback: function (notification) {
done = true;
getCurrentPosition(callback);
}
}, [
{
label: "Always Share",
accessKey: "A",
callback: remember("always", true)
},
{
label: "Never Share",
accessKey: "N",
callback: remember("never", false)
}
], {
eventCallback: function (event) {
if (event === "dismissed") {
if (!done)
callback(null);
done = true;
PopupNotifications.remove(self);
}
},
persistWhileVisible: true
});
}
// Implement getCurrentPosition by loading the nsIDOMGeoGeolocation
// XPCOM object.
function getCurrentPosition(callback) {
var xpcomGeolocation = Cc["@mozilla.org/geolocation;1"]
.getService(Ci.nsIDOMGeoGeolocation);
xpcomGeolocation.getCurrentPosition(callback);
}
exports.getCurrentPosition = getCurrentPositionWithCheck;
</code></pre>
### Update `main.js` ###
Finally, update "main.js". First add a line to import the new module:
var geolocation = require("./geolocation");
When importing modules that are not SDK built in modules, it's a good
idea to specify the path to the module explicitly like this, rather than
relying on the module loader to find the module you intended.
Edit the widget's call to `getCurrentPositionWithCheck()` so it calls
the geolocation module's `getCurrentPosition()` function instead:
geolocation.getCurrentPosition(function(position) {
if (!position) {
Now "main.js" should look like this:
<pre><code>
var geolocation = require("./geolocation");
var widget = require("sdk/widget").Widget({
id: "whereami",
label: "Where am I?",
contentURL: "http://www.mozilla.org/favicon.ico",
onClick: function() {
geolocation.getCurrentPosition(function(position) {
if (!position) {
console.log("The user denied access to geolocation.");
}
else {
console.log("latitude: ", position.coords.latitude);
console.log("longitude: ", position.coords.longitude);
}
});
}
});
</code></pre>
## Packaging the Geolocation Module ##
So far, this is a useful technique for structuring your add-on.
But you can also package and distribute modules independently of
your add-on: then any other add-on developer can download your
module and use it in exactly the same way they use the SDK's built-in
modules.
### Code Changes ###
First we'll make a couple of changes to the code.
At the moment the message displayed in the prompt and the name of
the preference used to store the user's choice are hardcoded:
let pref = "extensions.whereami.allowGeolocation";
let message = "whereami Add-on wants to know your location."
Instead we'll use the `self` module to ensure that they are specific
to the add-on:
var addonName = require("sdk/self").name;
var addonId = require("sdk/self").id;
let pref = "extensions." + addonId + ".allowGeolocation";
let message = addonName + " Add-on wants to know your location."
### Repackaging ###
Next we'll repackage the geolocation module.
* create a new directory called "geolocation", and run `cfx init` in it.
* delete the "main.js" that `cfx` generated, and copy "geolocation.js"
there instead.
### Editing "package.json" ###
The "package.json" file in your package's root directory contains metadata
for your package. See the
[package specification](dev-guide/package-spec.html) for
full details. If you intend to distribute the package, this is a good place
to add your name as the author, choose a distribution license, and so on.
## Learning More ##
To see some of the modules people have already developed, see the page of
[community-developed modules](https://github.com/mozilla/addon-sdk/wiki/Community-developed-modules).
To learn how to use third-party modules in your own code, see the
[tutorial on adding menu items](dev-guide/tutorials/adding-menus.html).

Просмотреть файл

@ -1,5 +0,0 @@
# Creating Event Emitters #
The [guide to event-driven programming with the SDK](dev-guide/guides/events.html) describes
how to consume events: that is, how to listen to events generated
by event-emitting objects.

Просмотреть файл

@ -1,208 +0,0 @@
<!-- This Source Code Form is subject to the terms of the Mozilla Public
- License, v. 2.0. If a copy of the MPL was not distributed with this
- file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
# Troubleshooting #
If you're having trouble getting the Add-on SDK up and running, don't panic!
This page lists some starting points that might help you track down your
problem.
Quarantine Problem on Mac OS X
------------------------------
On Mac OS X, you might see the following error when you try to run `cfx`:
<pre>
/path/to/sdk/bin/cfx: /usr/bin/env: bad interpreter: Operation not permitted
</pre>
This might be because the `cfx` executable file has been placed in quarantine
during download from the Internet.
To get it out of quarantine, use the `xattr -d` command, specifying
`com.apple.quarantine` as the name of the attribute to delete, and `cfx` as
the file from which to delete that attribute:
<pre>
xattr -d com.apple.quarantine /path/to/sdk/bin/cfx
</pre>
Check Your Python
-----------------
The SDK's `cfx` tool runs on Python. If you're having trouble getting `cfx` to
run at all, make sure you have Python correctly installed.
Try running the following from a command line:
<pre>
python --version
</pre>
`cfx` currently expects Python 2.5 or 2.6. Older and newer versions may or may
not work.
Check Your Firefox or XULRunner
-------------------------------
`cfx` searches well known locations on your system for Firefox or XULRunner.
`cfx` may not have found an installation, or if you have multiple installations,
`cfx` may have found the wrong one. In those cases you need to use `cfx`'s
`--binary` option. See the [cfx Tool][] guide for more information.
When you run `cfx` to test your add-on or run unit tests, it prints out the
location of the Firefox or XULRunner binary that it found, so you can check its
output to be sure.
[cfx Tool]: dev-guide/cfx-tool.html
Check Your Text Console
-----------------------
When errors are generated in the SDK's APIs and your code, they are logged to
the text console. This should be the same console or shell from which you ran
the `cfx` command.
Don't Leave Non-SDK Files Lying Around
------------------------------------------
Currently the SDK does not gracefully handle files and directories that it does
not expect to encounter. If there are empty directories or directories or files
that are not related to the SDK inside your `addon-sdk` directory or its
sub-directories, try removing them.
Search for Known Issues
-----------------------
Someone else might have experienced your problem, too. Other users often post
problems to the [project mailing list][jetpack-list]. You can also browse the
list of [known issues][bugzilla-known] or [search][bugzilla-search] for
specific keywords.
[bugzilla-known]: https://bugzilla.mozilla.org/buglist.cgi?order=Bug%20Number&resolution=---&resolution=DUPLICATE&query_format=advanced&product=Add-on%20SDK
[bugzilla-search]: https://bugzilla.mozilla.org/query.cgi?format=advanced&product=Add-on%20SDK
Contact the Project Team and User Group
---------------------------------------
SDK users and project team members discuss problems and proposals on the
[project mailing list][jetpack-list]. Someone else may have had the same
problem you do, so try searching the list.
You're welcome to post a question, too.
You can also chat with other SDK users in [#jetpack][#jetpack] on
[Mozilla's IRC network][IRC].
And if you'd like to [report a bug in the SDK][bugzilla-report], that's always
welcome! You will need to create an account with Bugzilla, Mozilla's bug
tracker.
[jetpack-list]: http://groups.google.com/group/mozilla-labs-jetpack/topics
[#jetpack]:http://mibbit.com/?channel=%23jetpack&server=irc.mozilla.org
[IRC]: http://irc.mozilla.org/
[bugzilla-report]: https://bugzilla.mozilla.org/enter_bug.cgi?product=Add-on%20SDK&component=General
Run the SDK's Unit Tests
------------------------
The SDK comes with a suite of tests which ensures that its APIs work correctly.
You can run it with the following command:
<pre>
cfx testall
</pre>
Some of the tests will open Firefox windows to check APIs related to the user
interface, so don't be alarmed. Please let the suite finish before resuming
your work.
When the suite is finished, your text console should contain output that looks
something like this:
<pre>
Testing cfx...
.............................................................
----------------------------------------------------------------------
Ran 61 tests in 4.388s
OK
Testing reading-data...
Using binary at '/Applications/Firefox.app/Contents/MacOS/firefox-bin'.
Using profile at '/var/folders/FL/FLC+17D+ERKgQe4K+HC9pE+++TI/-Tmp-/tmpu26K_5.mozrunner'.
.info: My ID is 6724fc1b-3ec4-40e2-8583-8061088b3185
..
3 of 3 tests passed.
OK
Total time: 4.036381 seconds
Program terminated successfully.
Testing all available packages: nsjetpack, test-harness, api-utils, development-mode.
Using binary at '/Applications/Firefox.app/Contents/MacOS/firefox-bin'.
Using profile at '/var/folders/FL/FLC+17D+ERKgQe4K+HC9pE+++TI/-Tmp-/tmp-dzeaA.mozrunner'.
.........................................................................
.........................................................................
.........................................................................
.........................................................................
.........................................................................
.........................................................................
.........................................................................
.........................................................................
.........................................................................
.........................................................................
.........................................................................
.........................................................................
.........................................................................
.........................................................................
.........................................................................
.........................................................................
.........................................................................
.........................................................................
.........................................................................
.........................................................................
.........................................................................
.........................................................................
.........................................................................
.........................................................................
.........................................................................
.........................................................................
.........................................................................
.........................................................................
.........................................................................
.........................................................................
.........................................................................
.........................................................................
.........................................................................
.........................................................................
.........................................................................
.........................................................................
.........................................................................
.........................................................................
.........................................................................
.........................................................................
.........................................................................
.........................................................................
.........................................................................
.........................................................................
.........................................................................
.........................................................................
...............................................
3405 of 3405 tests passed.
OK
Total time: 43.105498 seconds
Program terminated successfully.
All tests were successful. Ship it!
</pre>
If you get lots of errors instead, that may be a sign that the SDK does not work
properly on your system. In that case, please file a bug or send a message to
the project mailing list. See the previous section for information on doing so.

Просмотреть файл

@ -1,163 +0,0 @@
<!-- This Source Code Form is subject to the terms of the Mozilla Public
- License, v. 2.0. If a copy of the MPL was not distributed with this
- file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
# Unit Testing #
<span class="aside">
To follow this tutorial you'll need to have
[installed the SDK](dev-guide/tutorials/installation.html),
learned the
[basics of `cfx`](dev-guide/tutorials/getting-started-with-cfx.html),
and followed the tutorial on
[writing reusable modules](dev-guide/tutorials/reusable-modules.html).
</span>
The SDK provides a framework to help creates and run unit tests for
your code. To demonstrate how it works we'll write some unit tests for
a simple [Base64](http://en.wikipedia.org/wiki/Base64) encoding module.
## A Simple Base64 Module ##
In a web page, you can perform Base64 encoding and decoding using the
`btoa()` and `atob()` functions. Unfortunately these functions are attached
to the `window` object: since this object is not available in your
main add-on code, `atob()` and `btoa()` aren't available either. Using the
low-level
[window-utils](modules/sdk/deprecated/window-utils.html) module you
can access `window`, enabling you to call these functions.
However, it's good practice to encapsulate the code that directly accesses
`window-utils` in its own module, and only export the `atob()`
and `btoa()` functions. So we'll create a Base64 module to do
exactly that.
To begin with, create a new directory, navigate to it, and run `cfx init`.
Now create a new file in "lib" called "base64.js", and give it the
following contents:
var window = require("sdk/window/utils").getMostRecentBrowserWindow();
exports.atob = function(a) {
return window.atob(a);
}
exports.btoa = function(b) {
return window.btoa(b);
}
This code exports two functions, which just call the corresponding
functions on the `window` object. To show the module in use, edit
the "main.js" file as follows:
var widgets = require("sdk/widget");
var base64 = require("./base64");
var widget = widgets.Widget({
id: "base64",
label: "Base64 encode",
contentURL: "http://www.mozilla.org/favicon.ico",
onClick: function() {
encoded = base64.btoa("hello");
console.log(encoded);
decoded = base64.atob(encoded);
console.log(decoded);
}
});
Now "main.js" imports the base64 module and calls its two exported
functions. If we run the add-on and click the widget, we should see
the following logging output:
<pre>
info: aGVsbG8=
info: hello
</pre>
## Testing the Base64 Module ##
Navigate to the add-on's `test` directory and delete the `test-main.js` file.
In its place create a file called `test-base64.js` with the following
contents:
<pre><code>
var base64 = require("./base64");
exports["test atob"] = function(assert) {
assert.ok(base64.atob("aGVsbG8=") == "hello", "atob works");
}
exports["test btoa"] = function(assert) {
assert.ok(base64.btoa("hello") == "aGVsbG8=", "btoa works");
}
exports["test empty string"] = function(assert) {
assert.throws(function() {
base64.atob();
},
"empty string check works");
}
require("sdk/test").run(exports);
</code></pre>
This file: exports three functions, each of which expects to receive a single
argument which is an `assert` object. `assert` is supplied by the
[`test/assert`](modules/sdk/test/assert.html) module and implements
the [CommonJS Unit Testing specification](http://wiki.commonjs.org/wiki/Unit_Testing/1.1).
* The first two functions call `atob()` and `btoa()` and use
[`assert.ok()`](modules/sdk/test/assert.html)
to check that the output is as expected.
* The second function tests the module's error-handling code by passing an
empty string into `atob()` and using
[`assert.throws()`](modules/sdk/test/assert.html)
to check that the expected exception is raised.
At this point your add-on ought to look like this:
<pre>
/base64
package.json
README.md
/doc
main.md
/lib
main.js
base64.js
/test
test-base64.js
</pre>
Now execute `cfx --verbose test` from the add-on's root directory.
You should see something like this:
<pre>
Running tests on Firefox 13.0/Gecko 13.0 ({ec8030f7-c20a-464f-9b0e-13a3a9e97384}) under darwin/x86.
info: executing 'test-base64.test atob'
info: pass: atob works
info: executing 'test-base64.test btoa'
info: pass: btoa works
info: executing 'test-base64.test empty string'
info: pass: empty string check works
3 of 3 tests passed.
Total time: 5.172589 seconds
Program terminated successfully.
</pre>
What happens here is that `cfx test`:
<span class="aside">Note the hyphen after "test" in the module name.
`cfx test` will include a module called "test-myCode.js", but will exclude
modules called "test_myCode.js" or "testMyCode.js".</span>
* looks in the `test` directory of your
package
* loads any modules whose names start with the word `test-`
* calls each exported function whose name starts with "test", passing it
an [`assert`](modules/sdk/test/assert.html) object as its only argument.
Obviously, you don't have to pass the `--verbose` option to `cfx` if you don't
want to; doing so just makes the output easier to read.

Просмотреть файл

@ -1,18 +0,0 @@
<!-- This Source Code Form is subject to the terms of the Mozilla Public
- License, v. 2.0. If a copy of the MPL was not distributed with this
- file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
# High-Level APIs #
Modules in this section implement high-level APIs for
building add-ons:
* creating user interfaces
* interacting with the web
* interacting with the browser
Unless the documentation explicitly says otherwise, all these modules are
"supported": meaning that they are relatively stable, and that we'll avoid
making incompatible changes to them unless absolutely necessary.
<ul id="module-index"></ul>

Просмотреть файл

@ -1,36 +0,0 @@
<!-- This Source Code Form is subject to the terms of the Mozilla Public
- License, v. 2.0. If a copy of the MPL was not distributed with this
- file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
# Low-Level APIs #
Modules in this section implement low-level APIs. These
modules fall roughly into three categories:
* fundamental utilities such as
[collection](modules/sdk/util/collection.html) and
[url](modules/sdk/url.html). Many add-ons are likely to
want to use modules from this category.
* building blocks for higher level modules, such as
[events](modules/sdk/event/core.html),
[worker](modules/sdk/content/worker.html), and
[api-utils](modules/sdk/deprecated/api-utils.html). You're more
likely to use these if you are building your own modules that
implement new APIs, thus extending the SDK itself.
* privileged modules that expose powerful low-level capabilities
such as [window/utils](modules/sdk/window/utils.html) and
[xhr](modules/sdk/net/xhr.html). You can use these
modules in your add-on if you need to, but should be aware that
the cost of privileged access is the need to take more elaborate
security precautions. In many cases these modules have simpler,
more restricted analogs among the "High-Level APIs" (for
example, [windows](modules/sdk/windows.html) or
[request](modules/sdk/request.html)).
These modules are still in active development, and we expect to
make incompatible changes to them in future releases.
<ul id="module-index"></ul>

Просмотреть файл

@ -1,32 +0,0 @@
<!-- This Source Code Form is subject to the terms of the Mozilla Public
- License, v. 2.0. If a copy of the MPL was not distributed with this
- file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
With the Add-on SDK you can present information to the user,
such as a guide to using your add-on, in a browser tab.
You can supply the content in an HTML file in your add-on's
"data" directory.
***Note:*** This module has no effect on Fennec.
For pages like this, navigational elements such as the
[Awesome Bar](http://support.mozilla.org/en-US/kb/Location%20bar%20autocomplete),
[Search Bar](http://support.mozilla.org/en-US/kb/Search%20bar), or
[Bookmarks Toolbar](http://support.mozilla.org/en-US/kb/Bookmarks%20Toolbar)
are not usually relevant and distract from the content
you are presenting. The `addon-page` module provides a simple
way to have a page which excludes these elements.
To use the module import it using `require()`. After this,
the page loaded from "data/index.html" will not contain
navigational elements:
var addontab = require("sdk/addon-page");
var data = require("sdk/self").data;
require("sdk/tabs").open(data.url("index.html"));
<img src="static-files/media/screenshots/addon-page.png" alt="Example add-on page" class="image-center"/>
This only affects the page at "data/index.html":
all other pages are displayed normally.

Просмотреть файл

@ -1,50 +0,0 @@
<!-- This Source Code Form is subject to the terms of the Mozilla Public
- License, v. 2.0. If a copy of the MPL was not distributed with this
- file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
The module provides data encoding and decoding using Base64 algorithms.
##Example
var base64 = require("sdk/base64");
var encodedData = base64.encode("Hello, World");
var decodedData = base64.decode(encodedData);
##Unicode Strings
In order to `encode` and `decode` properly Unicode strings, the `charset`
parameter needs to be set to `"utf-8"`:
var base64 = require("sdk/base64");
var encodedData = base64.encode(unicodeString, "utf-8");
var decodedData = base64.decode(encodedData, "utf-8");
<api name="encode">
@function
Creates a base-64 encoded ASCII string from a string of binary data.
@param data {string}
The data to encode
@param [charset] {string}
The charset of the string to encode (optional).
The only accepted value is `"utf-8"`.
@returns {string}
The encoded string
</api>
<api name="decode">
@function
Decodes a string of data which has been encoded using base-64 encoding.
@param data {string}
The encoded data
@param [charset] {string}
The charset of the string to encode (optional).
The only accepted value is `"utf-8"`.
@returns {string}
The decoded string
</api>

Просмотреть файл

@ -1,91 +0,0 @@
<!-- This Source Code Form is subject to the terms of the Mozilla Public
- License, v. 2.0. If a copy of the MPL was not distributed with this
- file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
<!-- contributed by Dietrich Ayala [dietrich@mozilla.com] -->
The `clipboard` module allows callers to interact with the system clipboard,
setting and retrieving its contents.
You can optionally specify the type of the data to set and retrieve.
The following types are supported:
* `text` (plain text)
* `html` (a string of HTML)
* `image` (a base-64 encoded png)
If no data type is provided, then the module will detect it for you.
Currently `image`'s type doesn't support transparency on Windows.
Examples
--------
Set and get the contents of the clipboard.
var clipboard = require("sdk/clipboard");
clipboard.set("Lorem ipsum dolor sit amet");
var contents = clipboard.get();
Set the clipboard contents to some HTML.
var clipboard = require("sdk/clipboard");
clipboard.set("<blink>Lorem ipsum dolor sit amet</blink>", "html");
If the clipboard contains HTML content, open it in a new tab.
var clipboard = require("sdk/clipboard");
if (clipboard.currentFlavors.indexOf("html") != -1)
require("sdk/tabs").open("data:text/html;charset=utf-8," + clipboard.get("html"));
Set the clipboard contents to an image.
var clipboard = require("sdk/clipboard");
clipboard.set("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYA" +
"AABzenr0AAAASUlEQVRYhe3O0QkAIAwD0eyqe3Q993AQ3cBSUKpygfsNTy" +
"N5ugbQpK0BAADgP0BRDWXWlwEAAAAAgPsA3rzDaAAAAHgPcGrpgAnzQ2FG" +
"bWRR9AAAAABJRU5ErkJggg%3D%3D");
If the clipboard contains an image, open it in a new tab.
var clipboard = require("sdk/clipboard");
if (clipboard.currentFlavors.indexOf("image") != -1)
require("sdk/tabs").open(clipboard.get());
As noted before, data type can be easily omitted for images.
If the intention is set the clipboard to a data URL as string and not as image,
it can be easily done specifying a different flavor, like `text`.
var clipboard = require("sdk/clipboard");
clipboard.set("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYA" +
"AABzenr0AAAASUlEQVRYhe3O0QkAIAwD0eyqe3Q993AQ3cBSUKpygfsNTy" +
"N5ugbQpK0BAADgP0BRDWXWlwEAAAAAgPsA3rzDaAAAAHgPcGrpgAnzQ2FG" +
"bWRR9AAAAABJRU5ErkJggg%3D%3D", "text");
<api name="set">
@function
Replace the contents of the user's clipboard with the provided data.
@param data {string}
The data to put on the clipboard.
@param [datatype] {string}
The type of the data (optional).
</api>
<api name="get">
@function
Get the contents of the user's clipboard.
@param [datatype] {string}
Retrieve the clipboard contents only if matching this type (optional).
The function will return null if the contents of the clipboard do not match
the supplied type.
</api>
<api name="currentFlavors">
@property {array}
Data on the clipboard is sometimes available in multiple types. For example,
HTML data might be available as both a string of HTML (the `html` type)
and a string of plain text (the `text` type). This function returns an array
of all types in which the data currently on the clipboard is available.
</api>

Просмотреть файл

@ -1,7 +0,0 @@
<!-- This Source Code Form is subject to the terms of the Mozilla Public
- License, v. 2.0. If a copy of the MPL was not distributed with this
- file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
SDK add-ons can
[log debug messages using the global `console` object](dev-guide/tutorials/logging.html),
and the `plain-text-console` module implements this object.

Просмотреть файл

@ -1,66 +0,0 @@
<!-- This Source Code Form is subject to the terms of the Mozilla Public
- License, v. 2.0. If a copy of the MPL was not distributed with this
- file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
<!-- contributed by Atul Varma [atul@mozilla.com] -->
<!-- edited by Noelle Murata [fiveinchpixie@gmail.com] -->
The `traceback` module contains functionality similar to
Python's [traceback](http://docs.python.org/library/traceback.html) module.
## JSON Traceback Objects ##
Tracebacks are stored in JSON format. The stack is represented as an
array in which the most recent stack frame is the last element; each
element thus represents a stack frame and has the following keys:
<table>
<tr>
<td><code>filename</code></td>
<td>The name of the file that the stack frame takes place in.</td>
</tr>
<tr>
<td><code>lineNo</code></td>
<td>The line number is being executed at the stack frame.</td>
</tr>
<tr>
<td><code>funcName</code></td>
<td>The name of the function being executed at the stack frame, or
<code>null</code> if the function is anonymous or the stack frame is
being executed in a top-level script or module.</td>
</tr>
</table>
<api name="fromException">
@function
Attempts to extract the traceback from *`exception`*.
@returns {traceback}
JSON representation of the traceback or `null` if not found.
@param exception {exception}
exception where exception is an `nsIException`.
</api>
See [nsIException](https://developer.mozilla.org/en/NsIException) for more
information.
<api name="get">
@function
@returns {JSON}
Returns the JSON representation of the stack at the point that this
function is called.
</api>
<api name="format">
@function
Given a JSON representation of the stack or an exception instance,
returns a formatted plain text representation of it, similar to
Python's formatted stack tracebacks. If no argument is provided, the
stack at the point this function is called is used.
@param [tbOrException] {object}
</api>

Просмотреть файл

@ -1,20 +0,0 @@
<!-- This Source Code Form is subject to the terms of the Mozilla Public
- License, v. 2.0. If a copy of the MPL was not distributed with this
- file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
<!-- contributed by Irakli Gozalishvili [gozala@mozilla.com] -->
The `content` module re-exports three objects from
three other modules: [`Loader`](modules/sdk/content/loader.html),
[`Worker`](modules/sdk/content/worker.html), and
[`Symbiont`](modules/sdk/content/symbiont.html).
These objects are used in the internal implementations of SDK modules which use
[content scripts to interact with web content](dev-guide/guides/content-scripts/index.html),
such as the [`panel`](modules/sdk/panel.html) or [`page-mod`](modules/sdk/page-mod.html)
modules.
[Loader]:
[Worker]:modules/sdk/content/worker.html
[Symbiont]:modules/sdk/content/symbiont.html

Просмотреть файл

@ -1,122 +0,0 @@
<!-- This Source Code Form is subject to the terms of the Mozilla Public
- License, v. 2.0. If a copy of the MPL was not distributed with this
- file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
<!-- contributed by Irakli Gozalishvili [gozala@mozilla.com] -->
The `loader` module provides one of the building blocks for those modules
in the SDK that use
[content scripts to interact with web content](dev-guide/guides/content-scripts/index.html),
such as the [`panel`](modules/sdk/panel.html) and [`page-mod`](modules/sdk/page-mod.html)
modules.
The module exports a constructor for the `Loader` object, which is composed
from the [EventEmitter](modules/sdk/deprecated/events.html) trait, so it
inherits `on()`, `once()`, and `removeListener()` functions that
enable its users to listen to events.
`Loader` adds code to initialize and validate a set of properties for
managing content scripts:
* `contentURL`
* `contentScript`
* `contentScriptFile`
* `contentScriptWhen`
* `contentScriptOptions`
* `allow`
When certain of these properties are set, the `Loader` emits a
`propertyChange` event, enabling its users to take the appropriate action.
The `Loader` is used by modules that use content scripts but don't
themselves load content, such as [`page-mod`](modules/sdk/page-mod.html).
Modules that load their own content, such as
[`panel`](modules/sdk/panel.html), [`page-worker`](modules/sdk/page-worker.html), and
[`widget`](modules/sdk/widget.html), use the
[`symbiont`](modules/sdk/content/symbiont.html) module instead.
`Symbiont` inherits from `Loader` but contains its own frame into which
it loads content supplied as the `contentURL` option.
**Example:**
The following code creates a wrapper on a hidden frame that reloads a web page
in the frame every time the `contentURL` property is changed:
var hiddenFrames = require("sdk/frame/hidden-frame");
var { Loader } = require("sdk/content/content");
var PageLoader = Loader.compose({
constructor: function PageLoader(options) {
options = options || {};
if (options.contentURL)
this.contentURL = options.contentURL;
this.on('propertyChange', this._onChange = this._onChange.bind(this));
let self = this;
hiddenFrames.add(hiddenFrames.HiddenFrame({
onReady: function onReady() {
let frame = self._frame = this.element;
self._emit('propertyChange', { contentURL: self.contentURL });
}
}));
},
_onChange: function _onChange(e) {
if ('contentURL' in e)
this._frame.setAttribute('src', this._contentURL);
}
});
<api name="Loader">
@class
<api name="contentScriptFile">
@property {array}
The local file URLs of content scripts to load. Content scripts specified by
this property are loaded *before* those specified by the `contentScript`
property.
</api>
<api name="contentScript">
@property {array}
The texts of content scripts to load. Content scripts specified by this
property are loaded *after* those specified by the `contentScriptFile` property.
</api>
<api name="contentScriptWhen">
@property {string}
When to load the content scripts. This may take one of the following
values:
* "start": load content scripts immediately after the document
element for the page is inserted into the DOM, but before the DOM content
itself has been loaded
* "ready": load content scripts once DOM content has been loaded,
corresponding to the
[DOMContentLoaded](https://developer.mozilla.org/en/Gecko-Specific_DOM_Events)
event
* "end": load content scripts once all the content (DOM, JS, CSS,
images) for the page has been loaded, at the time the
[window.onload event](https://developer.mozilla.org/en/DOM/window.onload)
fires
</api>
<api name="contentScriptOptions">
@property {object}
Read-only value exposed to content scripts under `self.options` property.
Any kind of jsonable value (object, array, string, etc.) can be used here.
Optional.
</api>
<api name="contentURL">
@property {string}
The URL of the content loaded.
</api>
<api name="allow">
@property {object}
Permissions for the content, with the following keys:
@prop script {boolean}
Whether or not to execute script in the content. Defaults to true.
</api>
</api>

Просмотреть файл

@ -1,105 +0,0 @@
<!-- This Source Code Form is subject to the terms of the Mozilla Public
- License, v. 2.0. If a copy of the MPL was not distributed with this
- file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
The `mod` module provides functions to modify a page content.
<api name="attachTo">
@function
Function applies given `modification` to a given `window`.
For example, the following code applies a style to a content window, adding
a border to all divs in page:
var attachTo = require("sdk/content/mod").attachTo;
var Style = require("sdk/stylesheet/style").Style;
var style = Style({
source: "div { border: 4px solid gray }"
});
// assuming window points to the content page we want to modify
attachTo(style, window);
@param modification {object}
The modification we want to apply to the target.
@param window {nsIDOMWindow}
The window to be modified.
</api>
<api name="detachFrom">
@function
Function removes attached `modification` from a given `window`.
If `window` is not specified, `modification` is removed from all the windows
it's being attached to.
For example, the following code applies and removes a style to a content
window, adding a border to all divs in page:
var { attachTo, detachFrom } = require("sdk/content/mod");
var Style = require("sdk/stylesheet/style").Style;
var style = Style({
source: "div { border: 4px solid gray }"
});
// assuming window points to the content page we want to modify
attachTo(style, window);
// ...
detachFrom(style, window);
@param modification {object}
The modification we want to remove from the target
@param window {nsIDOMWindow}
The window to be modified.
If `window` is not provided `modification` is removed from all targets it's
being attached to.
</api>
<api name="getTargetWindow">
@function
Function takes `target`, value representing content (page) and returns
`nsIDOMWindow` for that content.
If `target` does not represents valid content `null` is returned.
For example target can be a content window itself in which case it's will be
returned back.
@param target {object}
The object for which we want to obtain the window represented or contained.
If a `nsIDOMWindow` is given, it works as an identify function, returns
`target` itself.
@returns {nsIDOMWindow|null}
The window represented or contained by the `target`, if any. Returns `null`
otherwise.
</api>
<api name="attach">
@function
Function applies given `modification` to a given `target` representing a
content to be modified.
@param modification {object}
The modification we want to apply to the target
@param target {object}
Target is a value that representing content to be modified. It is valid only
when `getTargetWindow(target)` returns nsIDOMWindow of content it represents.
</api>
<api name="detach">
@function
Function removes attached `modification`. If `target` is specified
`modification` is removed from that `target` only, otherwise `modification` is
removed from all the targets it's being attached to.
@param modification {object}
The modification we want to remove from the target
@param target {object}
Target is a value that representing content to be modified. It is valid only
when `getTargetWindow(target)` returns `nsIDOMWindow` of content it represents.
If `target` is not provided `modification` is removed from all targets it's
being attached to.
</api>

Просмотреть файл

@ -1,149 +0,0 @@
<!-- This Source Code Form is subject to the terms of the Mozilla Public
- License, v. 2.0. If a copy of the MPL was not distributed with this
- file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
<!-- contributed by Myk Melez [myk@mozilla.org] -->
<!-- contributed by Irakli Gozalishvili [gozala@mozilla.com] -->
The `symbiont` module exports the `Symbiont` trait, which is used in
the internal implementation of SDK modules, such as
[`panel`](modules/sdk/panel.html) and
[`page-worker`](modules/sdk/page-mod.html), that can load web
content and attach
[content scripts](dev-guide/guides/content-scripts/index.html) to it.
A `Symbiont` loads the specified `contentURL` and content scripts into
a frame, and sets up an asynchronous channel between the content
scripts and the add-on code, enabling them to exchange messages using the
[`port`](dev-guide/guides/content-scripts/using-port.html) or
[`postMessage`](dev-guide/guides/content-scripts/using-postmessage.html)
APIs. You map optionally pass a frame into the `Symbiont`'s constructor:
if you don't, then a new hidden frame will be created to host the content.
This trait is composed from the
[`Loader`](modules/sdk/content/loader.html) and
[`Worker`](modules/sdk/content/worker.html) traits. It inherits
functions to load and configure content scripts from the `Loader`,
and functions to exchange messages between content scripts and the
main add-on code from the `Worker`.
var { Symbiont } = require('sdk/content/content');
var Thing = Symbiont.resolve({ constructor: '_init' }).compose({
constructor: function Thing(options) {
// `getMyFrame` returns the host application frame in which
// the page is loaded.
this._frame = getMyFrame();
this._init(options)
}
});
See the [panel][] module for a real-world example of usage of this module.
[panel]:modules/sdk/panel.html
<api name="Symbiont">
@class
Symbiont is composed from the [Worker][] trait, therefore instances
of Symbiont and their descendants expose all the public properties
exposed by [Worker][] along with additional public properties that
are listed below:
[Worker]:modules/sdk/content/worker.html
<api name="Symbiont">
@constructor
Creates a content symbiont.
@param options {object}
Options for the constructor. Includes all the keys that
the [Worker](modules/sdk/content/worker.html)
constructor accepts and a few more:
@prop [frame] {object}
The host application frame in which the page is loaded.
If frame is not provided hidden one will be created.
@prop [contentScriptWhen="end"] {string}
When to load the content scripts. This may take one of the following
values:
* "start": load content scripts immediately after the document
element for the page is inserted into the DOM, but before the DOM content
itself has been loaded
* "ready": load content scripts once DOM content has been loaded,
corresponding to the
[DOMContentLoaded](https://developer.mozilla.org/en/Gecko-Specific_DOM_Events)
event
* "end": load content scripts once all the content (DOM, JS, CSS,
images) for the page has been loaded, at the time the
[window.onload event](https://developer.mozilla.org/en/DOM/window.onload)
fires
This property is optional and defaults to "end".
@prop [contentScriptOptions] {object}
Read-only value exposed to content scripts under `self.options` property.
Any kind of jsonable value (object, array, string, etc.) can be used here.
Optional.
@prop [allow] {object}
Permissions for the content, with the following keys:
@prop [script] {boolean}
Whether or not to execute script in the content. Defaults to true.
Optional.
Optional.
</api>
<api name="contentScriptFile">
@property {array}
The local file URLs of content scripts to load. Content scripts specified by
this property are loaded *before* those specified by the `contentScript`
property.
</api>
<api name="contentScript">
@property {array}
The texts of content scripts to load. Content scripts specified by this
property are loaded *after* those specified by the `contentScriptFile` property.
</api>
<api name="contentScriptWhen">
@property {string}
When to load the content scripts. This may have one of the following
values:
* "start": load content scripts immediately after the document
element for the page is inserted into the DOM, but before the DOM content
itself has been loaded
* "ready": load content scripts once DOM content has been loaded,
corresponding to the
[DOMContentLoaded](https://developer.mozilla.org/en/Gecko-Specific_DOM_Events)
event
* "end": load content scripts once all the content (DOM, JS, CSS,
images) for the page has been loaded, at the time the
[window.onload event](https://developer.mozilla.org/en/DOM/window.onload)
fires
</api>
<api name="contentScriptOptions">
@property {object}
Read-only value exposed to content scripts under `self.options` property.
Any kind of jsonable value (object, array, string, etc.) can be used here.
Optional.
</api>
<api name="contentURL">
@property {string}
The URL of the content loaded.
</api>
<api name="allow">
@property {object}
Permissions for the content, with a single boolean key called `script` which
defaults to true and indicates whether or not to execute scripts in the
content.
</api>
</api>

Просмотреть файл

@ -1,148 +0,0 @@
<!-- This Source Code Form is subject to the terms of the Mozilla Public
- License, v. 2.0. If a copy of the MPL was not distributed with this
- file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
<!-- contributed by Irakli Gozalishvili [gozala@mozilla.com] -->
This module is used in the internal implementation of SDK modules
which use
[content scripts to interact with web content](dev-guide/guides/content-scripts/index.html),
such as the [`tabs`](modules/sdk/tabs.html), [`panel`](modules/sdk/panel.html),
or [`page-mod`](modules/sdk/page-mod.html) modules.
It exports the `Worker` trait, which enables content
scripts and the add-on code to exchange messages using the
[`port`](dev-guide/guides/content-scripts/using-port.html) or
[`postMessage`](dev-guide/guides/content-scripts/using-postmessage.html)
APIs.
The `Worker` is similar to the [web worker][] interface defined by the W3C.
But unlike "web workers," these workers run in the
same process as web content and browser chrome, so code within workers can
block the UI.
[web worker]:http://www.w3.org/TR/workers/#worker
<api name="Worker">
@class
Worker is composed from the [EventEmitter][] trait, therefore instances
of Worker and their descendants expose all the public properties
exposed by [EventEmitter][] along with additional public properties that
are listed below.
**Example**
var workers = require("sdk/content/worker");
let worker = workers.Worker({
window: require("sdk/window/utils").getMostRecentBrowserWindow(),
contentScript:
"self.port.on('hello', function(name) { " +
" self.port.emit('response', window.location.href); " +
"});"
});
worker.port.emit("hello", { name: "worker"});
worker.port.on("response", function (location) {
console.log(location);
});
[EventEmitter]:modules/sdk/deprecated/events.html
<api name="Worker">
@constructor
Creates a content worker.
@param options {object}
Options for the constructor, with the following keys:
@prop window {object}
The content window to create JavaScript sandbox for communication with.
@prop [contentScriptFile] {string,array}
The local file URLs of content scripts to load. Content scripts specified
by this option are loaded *before* those specified by the `contentScript`
option. Optional.
@prop [contentScript] {string,array}
The texts of content scripts to load. Content scripts specified by this
option are loaded *after* those specified by the `contentScriptFile` option.
Optional.
@prop [onMessage] {function}
Functions that will registered as a listener to a 'message' events.
@prop [onError] {function}
Functions that will registered as a listener to an 'error' events.
</api>
<api name="port">
@property {EventEmitter}
[EventEmitter](modules/sdk/deprecated/events.html) object that allows you to:
* send customized messages to the worker using the `port.emit` function
* receive events from the worker using the `port.on` function
</api>
<api name="postMessage">
@method
Asynchronously emits `"message"` events in the enclosed worker, where content
script was loaded.
@param data {number,string,JSON}
The data to send. Must be stringifiable to JSON.
</api>
<api name="destroy">
@method
Destroy the worker by removing the content script from the page and removing
all registered listeners. A `detach` event is fired just before removal.
</api>
<api name="url">
@property {string}
The URL of the content.
</api>
<api name="tab">
@property {object}
If this worker is attached to a content document, returns the related
[tab](modules/sdk/tabs.html).
</api>
<api name="message">
@event
This event allows the content worker to receive messages from its associated
content scripts. Calling the `self.postMessage()` function from a content
script will asynchronously emit the `message` event on the corresponding
worker.
@argument {value}
The event listener is passed the message, which must be a
<a href = "dev-guide/guides/content-scripts/using-port.html#json_serializable">JSON-serializable value</a>.
</api>
<api name="error">
@event
This event allows the content worker to react to an uncaught runtime script
error that occurs in one of the content scripts.
@argument {Error}
The event listener is passed a single argument which is an
[Error](https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Error)
object.
</api>
<api name="detach">
@event
This event is emitted when the document associated with this worker is unloaded
or the worker's `destroy()` method is called.
Note that you can't communicate with the content script in response to this
event. If you try, you'll see this error:
<pre>Error: Couldn't find the worker to receive this message.
The script may not be initialized yet, or may already have been unloaded</pre>
You can handle the `detach` event in the content script itself though:
// in content script
self.on("detach", function() {
window.close();
});
</api>
</api>

Просмотреть файл

@ -1,809 +0,0 @@
<!-- This Source Code Form is subject to the terms of the Mozilla Public
- License, v. 2.0. If a copy of the MPL was not distributed with this
- file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
<!-- contributed by Drew Willcoxon [adw@mozilla.com] -->
<!-- edited by Noelle Murata [fiveinchpixie@gmail.com] -->
The `context-menu` API provides a simple, declarative way to add items to the
page's context menu. You can add items that perform an action when clicked,
submenus, and menu separators.
Instead of manually adding items when particular contexts occur and then
removing them when those contexts go away, you *bind* items to contexts, and the
adding and removing is automatically handled for you. Items are bound to
contexts in much the same way that event listeners are bound to events. When
the user invokes the context menu, all of the items bound to the current context
are automatically added to the menu. If no items are bound, none are added.
Likewise, any items that were previously in the menu but are not bound to the
current context are automatically removed from the menu. You never need to
manually remove your items from the menu unless you want them to never appear
again.
For example, if your add-on needs to add a context menu item whenever the
user visits a certain page, don't create the item when that page loads, and
don't remove it when the page unloads. Rather, create your item only once and
supply a context that matches the target URL.
Context menu items are displayed in the order created or in the case of sub
menus the order added to the sub menu. Menu items for each add-on will be
grouped together automatically. If the total number of menu items in the main
context menu from all add-ons exceeds a certain number (normally 10 but
configurable with the `extensions.addon-sdk.context-menu.overflowThreshold`
preference) all of the menu items will instead appear in an overflow menu to
avoid making the context menu too large.
Specifying Contexts
-------------------
As its name implies, the context menu should be reserved for the occurrence of
specific contexts. Contexts can be related to page content or the page itself,
but they should never be external to the page.
For example, a good use of the menu would be to show an "Edit Image" item when
the user right-clicks an image in the page. A bad use would be to show a
submenu that listed all the user's tabs, since tabs aren't related to the page
or the node the user clicked to open the menu.
### The Page Context
First of all, you may not need to specify a context at all. When a top-level
item does not specify a context, the page context applies. An item that is in a
submenu is visible unless you specify a context.
The *page context* occurs when the user invokes the context menu on a
non-interactive portion of the page. Try right-clicking a blank spot in this
page, or on text. Make sure that no text is selected. The menu that appears
should contain the items "Back", "Forward", "Reload", "Stop", and so on. This
is the page context.
The page context is appropriate when your item acts on the page as a whole. It
does not occur when the user invokes the context menu on a link, image, or other
non-text node, or while a selection exists.
### Declarative Contexts
You can specify some simple, declarative contexts when you create a menu item by
setting the `context` property of the options object passed to its constructor,
like this:
var cm = require("sdk/context-menu");
cm.Item({
label: "My Menu Item",
context: cm.URLContext("*.mozilla.org")
});
These contexts may be specified by calling the following constructors. Each is
exported by the `context-menu` module.
<table>
<tr>
<th>Constructor</th>
<th>Description</th>
</tr>
<tr>
<td><code>
PageContext()
</code></td>
<td>
The page context.
</td>
</tr>
<tr>
<td><code>
SelectionContext()
</code></td>
<td>
This context occurs when the menu is invoked on a page in which the user
has made a selection.
</td>
</tr>
<tr>
<td><code>
SelectorContext(selector)
</code></td>
<td>
This context occurs when the menu is invoked on a node that either matches
<code>selector</code>, a CSS selector, or has an ancestor that matches.
<code>selector</code> may include multiple selectors separated by commas,
e.g., <code>"a[href], img"</code>.
</td>
</tr>
<tr>
<td><code>
URLContext(matchPattern)
</code></td>
<td>
This context occurs when the menu is invoked on pages with particular
URLs. <code>matchPattern</code> is a match pattern string or an array of
match pattern strings. When <code>matchPattern</code> is an array, the
context occurs when the menu is invoked on a page whose URL matches any of
the patterns. These are the same match pattern strings that you use with
the <a href="modules/sdk/page-mod.html"><code>page-mod</code></a>
<code>include</code> property.
<a href="modules/sdk/util/match-pattern.html">Read more about patterns</a>.
</td>
</tr>
<tr>
<td>
array
</td>
<td>
An array of any of the other types. This context occurs when all contexts
in the array occur.
</td>
</tr>
</table>
Menu items also have a `context` property that can be used to add and remove
declarative contexts after construction. For example:
var context = require("sdk/context-menu").SelectorContext("img");
myMenuItem.context.add(context);
myMenuItem.context.remove(context);
When a menu item is bound to more than one context, it appears in the menu when
all of those contexts occur.
### In Content Scripts
The declarative contexts are handy but not very powerful. For instance, you
might want your menu item to appear for any page that has at least one image,
but declarative contexts won't help you there.
When you need more control over the context in which your menu items are
shown, you can use content scripts. Like other APIs in the SDK, the
`context-menu` API uses
[content scripts](dev-guide/guides/content-scripts/index.html) to let your
add-on interact with pages in the browser. Each menu item you create in the
top-level context menu can have a content script.
A special event named `"context"` is emitted in your content scripts whenever
the context menu is about to be shown. If you register a listener function for
this event and it returns true, the menu item associated with the listener's
content script is shown in the menu.
For example, this item appears whenever the context menu is invoked on a page
that contains at least one image:
require("sdk/context-menu").Item({
label: "This Page Has Images",
contentScript: 'self.on("context", function (node) {' +
' return !!document.querySelector("img");' +
'});'
});
Note that the listener function has a parameter called `node`. This is the node
in the page that the user context-clicked to invoke the menu. You can use it to
determine whether your item should be shown.
You can both specify declarative contexts and listen for contexts in a content
script. In that case, the declarative contexts are evaluated first, and your
item is shown only when all declarative contexts are current and your
context listener returns true.
If any declarative contexts are not current, then your context listener
is never called. This example takes advantage of that fact. The listener
can be assured that `node` will always be an image:
var cm = require("sdk/context-menu");
cm.Item({
label: "A Mozilla Image",
context: cm.SelectorContext("img"),
contentScript: 'self.on("context", function (node) {' +
' return /mozilla/.test(node.src);' +
'});'
});
However, if you do combine `SelectorContext` and the `"context"` event,
be aware that the `node` argument passed to the `"context"` event will
not always match the type specified in `SelectorContext`.
`SelectorContext` will match if the menu is invoked on the node specified
*or any descendant of that node*, but the `"context"` event handler is
passed *the actual node* on which the menu was invoked. The example above
works because `<IMG>` elements can't contain other elements, but in the
example below, `node.nodeName` is not guaranteed to be "P" - for example,
it won't be "P" if the user context-clicked a link inside a paragraph:
var cm = require("sdk/context-menu");
cm.Item({
label: "A Paragraph",
context: cm.SelectorContext("p"),
contentScript: 'self.on("context", function (node) {' +
' console.log(node.nodeName);' +
' return true;' +
'});'
});
The content script is executed for every page that a context menu is shown for.
It will be executed the first time it is needed (i.e. when the context menu is
first shown and all of the declarative contexts for your item are current) and
then remains active until you destroy your context menu item or the page is
unloaded.
Handling Menu Item Clicks
-------------------------
In addition to using content scripts to listen for the `"context"` event as
described above, you can use content scripts to handle item clicks. When the
user clicks your menu item, an event named `"click"` is emitted in the item's
content script.
Therefore, to handle an item click, listen for the `"click"` event in that
item's content script like so:
require("sdk/context-menu").Item({
label: "My Item",
contentScript: 'self.on("click", function (node, data) {' +
' console.log("Item clicked!");' +
'});'
});
Note that the listener function has parameters called `node` and `data`.
### The "node" Argument ###
`node` is the node that the user context-clicked to invoke the menu.
* If you did not use `SelectorContext` to decide whether to show the menu item,
then this is the actual node clicked.
* If you did use `SelectorContext`, then this is the node that matched your
selector.
For example, suppose your add-on looks like this:
var script = "self.on('click', function (node, data) {" +
" console.log('clicked: ' + node.nodeName);" +
"});";
var cm = require("sdk/context-menu");
cm.Item({
label: "body context",
context: cm.SelectorContext("body"),
contentScript: script
});
This add-on creates a context-menu item that uses `SelectorContext` to display
the item whenever the context menu is activated on any descendant of the
`<BODY>` element. When clicked, the item just logs the
[`nodeName`](https://developer.mozilla.org/en-US/docs/DOM/Node.nodeName)
property for the node passed to the click handler.
If you run this add-on you'll see that it always logs "BODY", even if you
click on a paragraph element inside the page:
<pre>
info: contextmenu-example: clicked: BODY
</pre>
By contrast, this add-on uses the `PageContext`:
var script = "self.on('click', function (node, data) {" +
" console.log('clicked: ' + node.nodeName);" +
"});";
var cm = require("sdk/context-menu");
cm.Item({
label: "body context",
context: cm.PageContext(),
contentScript: script
});
It will log the name of the actual node clicked:
<pre>
info: contextmenu-example: clicked: P
</pre>
### The "data" Argument ###
`data` is the `data` property of the menu item
that was clicked. Note that when you have a hierarchy of menu items the click
event will be sent to the content script of the item clicked and all ancestors
so be sure to verify that the `data` value passed matches the item you expect.
You can use this to simplify click handling by providing just a single click
listener on a `Menu` that reacts to clicks for any child items.:
var cm = require("sdk/context-menu");
cm.Menu({
label: "My Menu",
contentScript: 'self.on("click", function (node, data) {' +
' console.log("You clicked " + data);' +
'});',
items: [
cm.Item({ label: "Item 1", data: "item1" }),
cm.Item({ label: "Item 2", data: "item2" }),
cm.Item({ label: "Item 3", data: "item3" })
]
});
### Communicating With the Add-on ###
Often you will need to collect some kind of information in the click listener
and perform an action unrelated to content. To communicate to the menu item
associated with the content script, the content script can call the
`postMessage` function attached to the global `self` object, passing it some
JSON-able data. The menu item's `"message"` event listener will be called with
that data.
var cm = require("sdk/context-menu");
cm.Item({
label: "Edit Image",
context: cm.SelectorContext("img"),
contentScript: 'self.on("click", function (node, data) {' +
' self.postMessage(node.src);' +
'});',
onMessage: function (imgSrc) {
openImageEditor(imgSrc);
}
});
Updating a Menu Item's Label
----------------------------
Each menu item must be created with a label, but you can change its label later
using a couple of methods.
The simplest method is to set the menu item's `label` property. This example
updates the item's label based on the number of times it's been clicked:
var numClicks = 0;
var myItem = require("sdk/context-menu").Item({
label: "Click Me: " + numClicks,
contentScript: 'self.on("click", self.postMessage);',
onMessage: function () {
numClicks++;
this.label = "Click Me: " + numClicks;
// Setting myItem.label is equivalent.
}
});
Sometimes you might want to update the label based on the context. For
instance, if your item performs a search with the user's selected text, it would
be nice to display the text in the item to provide feedback to the user. In
these cases you can use the second method. Recall that your content scripts can
listen for the `"context"` event and if your listeners return true, the items
associated with the content scripts are shown in the menu. In addition to
returning true, your `"context"` listeners can also return strings. When a
`"context"` listener returns a string, it becomes the item's new label.
This item implements the aforementioned search example:
var cm = require("sdk/context-menu");
cm.Item({
label: "Search Google",
context: cm.SelectionContext(),
contentScript: 'self.on("context", function () {' +
' var text = window.getSelection().toString();' +
' if (text.length > 20)' +
' text = text.substr(0, 20) + "...";' +
' return "Search Google for " + text;' +
'});'
});
The `"context"` listener gets the window's current selection, truncating it if
it's too long, and includes it in the returned string. When the item is shown,
its label will be "Search Google for `text`", where `text` is the truncated
selection.
## Private Windows ##
If your add-on has not opted into private browsing, then any menus or
menu items that you add will not appear in context menus belonging to
private browser windows.
To learn more about private windows, how to opt into private browsing, and how
to support private browsing, refer to the
[documentation for the `private-browsing` module](modules/sdk/private-browsing.html).
More Examples
-------------
For conciseness, these examples create their content scripts as strings and use
the `contentScript` property. In your own add-on, you will probably want to
create your content scripts in separate files and pass their URLs using the
`contentScriptFile` property. See
[Working with Content Scripts](dev-guide/guides/content-scripts/index.html)
for more information.
<div class="warning">
<p>Unless your content script is extremely simple and consists only of a
static string, don't use <code>contentScript</code>: if you do, you may
have problems getting your add-on approved on AMO.</p>
<p>Instead, keep the script in a separate file and load it using
<code>contentScriptFile</code>. This makes your code easier to maintain,
secure, debug and review.</p>
</div>
Show an "Edit Page Source" item when the user right-clicks a non-interactive
part of the page:
require("sdk/context-menu").Item({
label: "Edit Page Source",
contentScript: 'self.on("click", function (node, data) {' +
' self.postMessage(document.URL);' +
'});',
onMessage: function (pageURL) {
editSource(pageURL);
}
});
Show an "Edit Image" item when the menu is invoked on an image:
var cm = require("sdk/context-menu");
cm.Item({
label: "Edit Image",
context: cm.SelectorContext("img"),
contentScript: 'self.on("click", function (node, data) {' +
' self.postMessage(node.src);' +
'});',
onMessage: function (imgSrc) {
openImageEditor(imgSrc);
}
});
Show an "Edit Mozilla Image" item when the menu is invoked on an image in a
mozilla.org or mozilla.com page:
var cm = require("sdk/context-menu");
cm.Item({
label: "Edit Mozilla Image",
context: [
cm.URLContext(["*.mozilla.org", "*.mozilla.com"]),
cm.SelectorContext("img")
],
contentScript: 'self.on("click", function (node, data) {' +
' self.postMessage(node.src);' +
'});',
onMessage: function (imgSrc) {
openImageEditor(imgSrc);
}
});
Show an "Edit Page Images" item when the page contains at least one image:
var cm = require("sdk/context-menu");
cm.Item({
label: "Edit Page Images",
// This ensures the item only appears during the page context.
context: cm.PageContext(),
contentScript: 'self.on("context", function (node) {' +
' var pageHasImgs = !!document.querySelector("img");' +
' return pageHasImgs;' +
'});' +
'self.on("click", function (node, data) {' +
' var imgs = document.querySelectorAll("img");' +
' var imgSrcs = [];' +
' for (var i = 0 ; i < imgs.length; i++)' +
' imgSrcs.push(imgs[i].src);' +
' self.postMessage(imgSrcs);' +
'});',
onMessage: function (imgSrcs) {
openImageEditor(imgSrcs);
}
});
Show a "Search With" menu when the user right-clicks an anchor that searches
Google or Wikipedia with the text contained in the anchor:
var cm = require("sdk/context-menu");
var googleItem = cm.Item({
label: "Google",
data: "http://www.google.com/search?q="
});
var wikipediaItem = cm.Item({
label: "Wikipedia",
data: "http://en.wikipedia.org/wiki/Special:Search?search="
});
var searchMenu = cm.Menu({
label: "Search With",
context: cm.SelectorContext("a[href]"),
contentScript: 'self.on("click", function (node, data) {' +
' var searchURL = data + node.textContent;' +
' window.location.href = searchURL;' +
'});',
items: [googleItem, wikipediaItem]
});
<api name="Item">
@class
A labeled menu item that can perform an action when clicked.
<api name="Item">
@constructor
Creates a labeled menu item that can perform an action when clicked.
@param options {object}
An object with the following keys:
@prop label {string}
The item's label. It must either be a string or an object that implements
`toString()`.
@prop [image] {string}
The item's icon, a string URL. The URL can be remote, a reference to an
image in the add-on's `data` directory, or a data URI.
@prop [data] {string}
An optional arbitrary value to associate with the item. It must be either a
string or an object that implements `toString()`. It will be passed to
click listeners.
@prop [context] {value}
If the item is contained in the top-level context menu, this declaratively
specifies the context under which the item will appear; see Specifying
Contexts above.
@prop [contentScript] {string,array}
If the item is contained in the top-level context menu, this is the content
script or an array of content scripts that the item can use to interact with
the page.
@prop [contentScriptFile] {string,array}
If the item is contained in the top-level context menu, this is the local
file URL of the content script or an array of such URLs that the item can
use to interact with the page.
@prop [onMessage] {function}
If the item is contained in the top-level context menu, this function will
be called when the content script calls `self.postMessage`. It will be
passed the data that was passed to `postMessage`.
</api>
<api name="label">
@property {string}
The menu item's label. You can set this after creating the item to update its
label later.
</api>
<api name="image">
@property {string}
The item's icon, a string URL. The URL can be remote, a reference to an image
in the add-on's `data` directory, or a data URI. You can set this after
creating the item to update its image later. To remove the item's image, set
it to `null`.
</api>
<api name="data">
@property {string}
An optional arbitrary value to associate with the item. It must be either a
string or an object that implements `toString()`. It will be passed to
click listeners. You can set this after creating the item to update its data
later.
</api>
<api name="context">
@property {list}
A list of declarative contexts for which the menu item will appear in the
context menu. Contexts can be added by calling `context.add()` and removed by
called `context.remove()`.
</api>
<api name="parentMenu">
@property {Menu}
The item's parent `Menu`, or `null` if the item is contained in the top-level
context menu. This property is read-only. To add the item to a new menu,
call that menu's `addItem()` method.
</api>
<api name="contentScript">
@property {string,array}
The content script or the array of content scripts associated with the menu
item during creation.
</api>
<api name="contentScriptFile">
@property {string,array}
The URL of a content script or the array of such URLs associated with the menu
item during creation.
</api>
<api name="destroy">
@method
Permanently removes the item from its parent menu and frees its resources.
The item must not be used afterward. If you need to remove the item from its
parent menu but use it afterward, call `removeItem()` on the parent menu
instead.
</api>
<api name="message">
@event
If you listen to this event you can receive message events from content
scripts associated with this menu item. When a content script posts a
message using `self.postMessage()`, the message is delivered to the add-on
code in the menu item's `message` event.
@argument {value}
Listeners are passed a single argument which is the message posted
from the content script. The message can be any
<a href = "dev-guide/guides/content-scripts/using-port.html#json_serializable">JSON-serializable value</a>.
</api>
</api>
<api name="Menu">
@class
A labeled menu item that expands into a submenu.
<api name="Menu">
@constructor
Creates a labeled menu item that expands into a submenu.
@param options {object}
An object with the following keys:
@prop label {string}
The item's label. It must either be a string or an object that implements
`toString()`.
@prop items {array}
An array of menu items that the menu will contain. Each must be an `Item`,
`Menu`, or `Separator`.
@prop [image] {string}
The menu's icon, a string URL. The URL can be remote, a reference to an
image in the add-on's `data` directory, or a data URI.
@prop [context] {value}
If the menu is contained in the top-level context menu, this declaratively
specifies the context under which the menu will appear; see Specifying
Contexts above.
@prop [contentScript] {string,array}
If the menu is contained in the top-level context menu, this is the content
script or an array of content scripts that the menu can use to interact with
the page.
@prop [contentScriptFile] {string,array}
If the menu is contained in the top-level context menu, this is the local
file URL of the content script or an array of such URLs that the menu can
use to interact with the page.
@prop [onMessage] {function}
If the menu is contained in the top-level context menu, this function will
be called when the content script calls `self.postMessage`. It will be
passed the data that was passed to `postMessage`.
</api>
<api name="label">
@property {string}
The menu's label. You can set this after creating the menu to update its
label later.
</api>
<api name="items">
@property {array}
An array containing the items in the menu. The array is read-only, meaning
that modifications to it will not affect the menu. However, setting this
property to a new array will replace all the items currently in the menu with
the items in the new array.
</api>
<api name="image">
@property {string}
The menu's icon, a string URL. The URL can be remote, a reference to an image
in the add-on's `data` directory, or a data URI. You can set this after
creating the menu to update its image later. To remove the menu's image, set
it to `null`.
</api>
<api name="context">
@property {list}
A list of declarative contexts for which the menu will appear in the context
menu. Contexts can be added by calling `context.add()` and removed by called
`context.remove()`.
</api>
<api name="parentMenu">
@property {Menu}
The menu's parent `Menu`, or `null` if the menu is contained in the top-level
context menu. This property is read-only. To add the menu to a new menu,
call that menu's `addItem()` method.
</api>
<api name="contentScript">
@property {string,array}
The content script or the array of content scripts associated with the menu
during creation.
</api>
<api name="contentScriptFile">
@property {string,array}
The URL of a content script or the array of such URLs associated with the menu
during creation.
</api>
<api name="addItem">
@method
Appends a menu item to the end of the menu. If the item is already contained
in another menu or in the top-level context menu, it's automatically removed
first. If the item is already contained in this menu it will just be moved
to the end of the menu.
@param item {Item,Menu,Separator}
The `Item`, `Menu`, or `Separator` to add to the menu.
</api>
<api name="removeItem">
@method
Removes the given menu item from the menu. If the menu does not contain the
item, this method does nothing.
@param item {Item,Menu,Separator}
The menu item to remove from the menu.
</api>
<api name="destroy">
@method
Permanently removes the menu from its parent menu and frees its resources.
The menu must not be used afterward. If you need to remove the menu from its
parent menu but use it afterward, call `removeItem()` on the parent menu
instead.
</api>
<api name="message">
@event
If you listen to this event you can receive message events from content
scripts associated with this menu item. When a content script posts a
message using `self.postMessage()`, the message is delivered to the add-on
code in the menu item's `message` event.
@argument {value}
Listeners are passed a single argument which is the message posted
from the content script. The message can be any
<a href = "dev-guide/guides/content-scripts/using-port.html#json_serializable">JSON-serializable value</a>.
</api>
</api>
<api name="Separator">
@class
A menu separator. Separators can be contained only in `Menu`s, not in the
top-level context menu.
<api name="Separator">
@constructor
Creates a menu separator.
</api>
<api name="parentMenu">
@property {Menu}
The separator's parent `Menu`. This property is read-only. To add the
separator to a new menu, call that menu's `addItem()` method.
</api>
<api name="destroy">
@method
Permanently removes the separator from its parent menu and frees its
resources. The separator must not be used afterward. If you need to remove
the separator from its parent menu but use it afterward, call `removeItem()`
on the parent menu instead.
</api>
</api>
<api name="PageContext">
@class
<api name="PageContext">
@constructor
Creates a page context. See Specifying Contexts above.
</api>
</api>
<api name="SelectionContext">
@class
<api name="SelectionContext">
@constructor
Creates a context that occurs when a page contains a selection. See
Specifying Contexts above.
</api>
</api>
<api name="SelectorContext">
@class
<api name="SelectorContext">
@constructor
Creates a context that matches a given CSS selector. See Specifying Contexts
above.
@param selector {string}
A CSS selector.
</api>
</api>
<api name="URLContext">
@class
<api name="URLContext">
@constructor
Creates a context that matches pages with particular URLs. See Specifying
Contexts above.
@param matchPattern {string,array}
A [match pattern](modules/sdk/util/match-pattern.html) string, regexp or an
array of match pattern strings or regexps.
</api>
</api>

Просмотреть файл

@ -1,276 +0,0 @@
<!-- This Source Code Form is subject to the terms of the Mozilla Public
- License, v. 2.0. If a copy of the MPL was not distributed with this
- file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
Doing [inheritance in JavaScript](https://developer.mozilla.org/en/Introduction_to_Object-Oriented_JavaScript)
is both verbose and painful. Reading or writing such code requires sharp eye
and lot's of discipline, mainly due to code fragmentation and lots of machinery
being exposed:
// Defining a simple Class
function Dog(name) {
// Classes are for creating instances, calling them without `new` changes
// behavior, which in majority cases you need to handle, so you end up
// with additional boilerplate.
if (!(this instanceof Dog)) return new Dog(name);
this.name = name;
};
// To define methods you need to make a dance with a special 'prototype'
// property of the constructor function. This is too much machinery exposed.
Dog.prototype.type = 'dog';
Dog.prototype.bark = function bark() {
return 'Ruff! Ruff!'
};
// Subclassing a `Dog`
function Pet(name, breed) {
// Once again we do our little dance
if (!(this instanceof Pet)) return new Pet(name, breed);
Dog.call(this, name);
this.breed = breed;
}
// To subclass, you need to make another special dance with special
// 'prototype' properties.
Pet.prototype = Object.create(Dog.prototype);
// If you want correct instanceof behavior you need to make a dance with
// another special `constructor` property of the `prototype` object.
Object.defineProperty(Pet.prototype, 'contsructor', { value: Pet });
// Finally you can define some properties.
Pet.prototype.call = function(name) {
return this.name === name ? this.bark() : '';
};
Since SDK APIs may be interacting with untrusted code an extra security
measures are required to guarantee that documented behavior can't be changed
at runtime. To do that we need to freeze constructor's prototype chain to
make sure functions are frozen:
Object.freeze(Dog.prototype);
Object.freeze(Pet.prototype);
*Note: Also, this is not quite enough as `Object.prototype` stays mutable &
in fact we do little bit more in SDK to address that, but it's not in the scope
of this documentation.*
Doing all of this manually is both tedious and error prone task. That is why
SDK provides utility functions to make it more declarative and less verbose.
## Class
Module exports `Class` utility function for making `constructor` functions
with a proper `prototype` chain setup in declarative manner:
var { Class } = require('sdk/core/heritage');
var Dog = Class({
initialize: function initialize(name) {
this.name = name;
},
type: 'dog',
bark: function bark() {
return 'Ruff! Ruff!'
}
});
Note: We use term `Class` to refer an exemplar constructs in a form of
constructor functions with a proper prototype chain setup. Constructors
created using `Class` function don't require `new` keyword (even though
it can be used) for instantiation. Also, idiomatic SDK code does not uses
optional `new` keywords, but you're free to use it in your add-on code:
var fluffy = Dog('Fluffy'); // instatiation
fluffy instanceof Dog // => true
fluffy instanceof Class // => true
As you could notice from example above classes created via `Class` function
by default inherits from a `Class` itself. Also you could specify which class
you want to inherit from by passing special `extends` property:
var Pet = Class({
extends: Dog, // should inherit from Dog
initialize: function initialize(breed, name) {
// To call ancestor methods you will have to access them
// explicitly
Dog.prototype.initialize.call(this, name);
this.breed = breed;
},
call: function call(name) {
return this.name === name ? this.bark() : '';
}
});
var tsuga = Pet('Labrador', 'Tsuga');
tsuga instanceof Pet // => true
tsuga instanceof Dog // => true
tsuga.call('Tsuga') // => 'Ruff! Ruff!'
Please note that `Class` is just an utility function which we use in SDK, and
recommend our users to use it, but it's in no way enforced. As a matter of fact
since result is just a plain constructor function with proper prototype chain
setup you could sub-class it as any other constructor:
function Labrador() {
// ...
}
Labrador.prototype = Object.create(Dog.prototype);
Labrador.prototype.jump = function() {
// ...
}
var leo = new Labrador()
leo.bark(); // => 'Ruff! Ruff!'
leo.instanceof Labrador // => true
leo.instanceof Dog // => true
Also, you could use `Class` function to subclass constructor functions that
were not created by a `Class` itself:
var Foo = Class({
extends: Labrador
// ...
})
Sometimes (single) inheritance is not enough and defining reusable, composable
pieces of functionality does a better job:
var HEX = Class({
hex: function hex() {
return '#' + this.color;
}
});
var RGB = Class({
red: function red() {
return parseInt(this.color.substr(0, 2), 16);
},
green: function green() {
return parseInt(this.color.substr(2, 2), 16);
},
blue: function blue() {
return parseInt(this.color.substr(4, 2), 16);
}
});
var CMYK = Class({
black: function black() {
var color = Math.max(Math.max(this.red(), this.green()), this.blue());
return (1 - color / 255).toFixed(4);
},
magenta: function magenta() {
var K = this.black();
return (((1 - this.green() / 255).toFixed(4) - K) / (1 - K)).toFixed(4);
},
yellow: function yellow() {
var K = this.black();
return (((1 - this.blue() / 255).toFixed(4) - K) / (1 - K)).toFixed(4);
},
cyan: function cyan() {
var K = this.black();
return (((1 - this.red() / 255).toFixed(4) - K) / (1 - K)).toFixed(4);
}
});
Such composable pieces can be combined into a single class definition by
passing special `implements` option to a `Class` function:
// Composing `Color` prototype out of reusable components:
var Color = Class({
implements: [ HEX, RGB, CMYK ],
initialize: function initialize(color) {
this.color = color;
}
});
var pink = Color('FFC0CB');
// RGB
pink.red() // => 255
pink.green() // => 192
pink.blue() // => 203
// CMYK
pink.magenta() // => 0.2471
pink.yellow() // => 0.2039
pink.cyan() // => 0.0000
pink instanceof Color // => true
Be aware though that it's not multiple inheritance and ancestors prototypes of
the classes passed under `implements` option are ignored. As mentioned before
you could pass constructors that were not created using `Class` function as
long as they have proper `prototype` setup.
Also you can mix inheritance and composition together if necessary:
var Point = Class({
initialize: function initialize(x, y) {
this.x = x;
this.y = y;
},
toString: function toString() {
return this.x + ':' + this.y;
}
})
var Pixel = Class({
extends: Point,
implements: [ Color ],
initialize: function initialize(x, y, color) {
Color.prototype.initialize.call(this, color);
Point.prototype.initialize.call(this, x, y);
},
toString: function toString() {
return this.hex() + '@' + Point.prototype.toString.call(this)
}
});
var pixel = Pixel(11, 23, 'CC3399');
pixel.toString(); // => #CC3399@11:23
pixel instanceof Pixel // => true
pixel instanceof Point // => true
## extend
Module exports `extend` utility function, that is useful for creating objects
that inherit from other objects, without associated classes. It's very similar
to `Object.create`, only difference is that second argument is an object
containing properties to be defined, instead of property descriptor map. Also,
keep in mind that returned object will be frozen.
var { extend } = require('sdk/core/heritage');
var base = { a: 1 };
var derived = extend(base, { b: 2 });
derived.a // => 1
derived.b // => 2
base.isPrototypeOf(derived) // => true
## mix
Module exports `mix` utility function that is useful for mixing properties of
multiple objects into a single one. Function takes arbitrary number of source
objects and returns a fresh object that inherits from the same prototype as a
first one and implements all own properties of all given objects. If two or
more argument objects have own properties with the same name, the property is
overridden, with precedence from right to left, implying, that properties of
the object on the left are overridden by a same named property of the object
on the right.
var { mix } = require('sdk/core/heritage');
var object = mix({ a: 1, b: 1 }, { b: 2 }, { c: 3 });
JSON.stringify(object) // => { "a": 1, "b": 2, "c": 3 }
## obscure
Module exports `obscure` utility function that is useful for defining
`non-enumerable` properties. Function takes an object and returns a new one
in return that inherits from the same object as given one and implements all
own properties of given object but as non-enumerable ones:
var { obscure } = require('api-utils/heritage');
var object = mix({ a: 1 }, obscure({ b: 2 }));
Object.getOwnPropertyNames(foo); // => [ 'a' ]

Просмотреть файл

@ -1,71 +0,0 @@
<!-- This Source Code Form is subject to the terms of the Mozilla Public
- License, v. 2.0. If a copy of the MPL was not distributed with this
- file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
Provides an API for creating namespaces for any given objects, which
effectively may be used for creating fields that are not part of objects
public API.
let { ns } = require('sdk/core/namespace');
let aNamespace = ns();
aNamespace(publicAPI).secret = secret;
One namespace may be used with multiple objects:
let { ns } = require('sdk/core/namespace');
let dom = ns();
function View(element) {
let view = Object.create(View.prototype);
dom(view).element = element;
// ....
}
View.prototype.destroy = function destroy() {
let { element } = dom(this);
element.parentNode.removeChild(element);
// ...
};
// ...
exports.View = View;
// ...
Also, multiple namespaces can be used with one object:
// ./widget.js
let { Cu } = require('chrome');
let { ns } = require('sdk/core/namespace');
let { View } = require('./view');
// Note this is completely independent from View's internal Namespace object.
let sandboxes = ns();
function Widget(options) {
let { element, contentScript } = options;
let widget = Object.create(Widget.prototype);
View.call(widget, options.element);
sandboxes(widget).sandbox = Cu.Sandbox(element.ownerDocument.defaultView);
// ...
}
Widget.prototype = Object.create(View.prototype);
Widget.prototype.postMessage = function postMessage(message) {
let { sandbox } = sandboxes(this);
sandbox.postMessage(JSON.stringify(JSON.parse(message)));
...
};
Widget.prototype.destroy = function destroy() {
View.prototype.destroy.call(this);
// ...
delete sandboxes(this).sandbox;
};
exports.Widget = Widget;
In addition access to the namespace can be shared with other code by just
handing them a namespace accessor function.
let { dom } = require('./view');
Widget.prototype.setInnerHTML = function setInnerHTML(html) {
dom(this).element.innerHTML = String(html);
};

Просмотреть файл

@ -1,421 +0,0 @@
<!-- This Source Code Form is subject to the terms of the Mozilla Public
- License, v. 2.0. If a copy of the MPL was not distributed with this
- file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
## Rationale
Most of the JS APIs are asynchronous complementing its non-blocking nature.
While this has a good reason and many advantages, it comes with a price.
Instead of structuring our programs into logical black boxes:
function blackbox(a, b) {
var c = assemble(a);
return combine(b, c);
}
We're forced into continuation passing style, involving lots of machinery:
function sphagetti(a, b, callback) {
assemble(a, function continueWith(error, c) {
if (error) callback(error);
else combine(b, c, callback);
});
}
This style also makes doing things in sequence hard:
widget.on('click', function onClick() {
promptUserForTwitterHandle(function continueWith(error, handle) {
if (error) return ui.displayError(error);
twitter.getTweetsFor(handle, funtion continueWith(error, tweets) {
if (error) return ui.displayError(error);
ui.showTweets(tweets);
});
});
});
Doing things in parallel is even harder:
var tweets, answers, checkins;
twitter.getTweetsFor(user, function continueWith(result) {
tweets = result;
somethingFinished();
});
stackOverflow.getAnswersFor(question, function continueWith(result) {
answers = result;
somethingFinished();
});
fourSquare.getCheckinsBy(user, function continueWith(result) {
checkins=result;
somethingFinished();
});
var finished = 0;
functions somethingFinished() {
if (++finished === 3)
ui.show(tweets, answers, checkins);
}
This also makes error handling quite of an adventure.
## Promises
Consider another approach, where instead of continuation passing via `callback`,
function returns an object, that represents eventual result, either successful
or failed. This object is a promise, both figuratively and by name, to
eventually resolve. We can call a function on the promise to observe
either its fulfillment or rejection. If the promise is rejected and the
rejection is not explicitly observed, any derived promises will be implicitly
rejected for the same reason.
In the Add-on SDK we follow
[CommonJS Promises/A](http://wiki.commonjs.org/wiki/Promises/A) specification
and model a promise as an object with a `then` method, which can be used to get
the eventual return (fulfillment) value or thrown exception (rejection):
foo().then(function success(value) {
// ...
}, function failure(reason) {
// ...
});
If `foo` returns a promise that gets fulfilled with the `value`, `success`
callback (the value handler) will be called with that `value`. However,
if the returned promise gets rejected, the `failure` callback (the error
handler) will be called with the `reason` of an error.
## Propagation
The `then` method of a promise returns a new promise that is resolved with the
return value of either handler. Since function can either return value or throw
an exception, only one handler will be ever called.
var bar = foo().then(function success(value) {
// compute something from a value...
}, function failure(reason) {
// handle an error...
});
In this example `bar` is a promise and it's fulfilled by one of two handlers
that are responsible for:
- If handler returns a value, `bar` will be resolved with it.
- If handler throws an exception, `bar` will be rejected with it.
- If handler returns a **promise**, `bar` will "become" that promise. To be
more precise it will be resolved with a resolution value of the returned
promise, which will appear and feel as if it was that returned promise.
If the `foo()` promise gets rejected and you omit the error handler, the
**error** will propagate to `bar` (`bar` will be rejected with that error):
var bar = foo().then(function success(value) {
// compute something out of the value...
});
If the `foo()` promise gets fulfilled and you omit the value handler, the
**value** will propagate to `bar` (`bar` will be fulfilled with that value):
var bar = foo().then(null, function failure(error) {
// handle error...
});
## Chaining
There are two ways to chain promise operations. You can chain them using either
inside or outside handlers.
### Flat chaining
You can use `then` for chaining intermediate operations on promises
(`var data = readAsync().then(parse).then(extract)`). You can chain multiple
`then` functions, because `then` returns a promise resolved to a return value
of an operation and errors propagate through the promise chains. In general
good rule of thumb is to prefer `then` based flat chaining. It makes code
easier to read and make changes later:
var data = readAsync(url). // read content of url asynchronously
then(parse). // parse content from the url
then(extractQuery). // extract SQL query
then(readDBAsync); // exectue extracted query against DB
### Nested chaining
Flat chaining is not always an option though, as in some cases you may want to
capture an intermediate values of the chain:
var result = readAsync(url).then(function(source) {
var json = parse(source)
return readDBAsync(extractQuery(json)).then(function(data) {
return writeAsync(json.url, data);
});
});
In general, nesting is useful for computing values from more then one promise:
function eventualAdd(a, b) {
return a.then(function (a) {
return b.then(function (b) {
return a + b;
});
});
}
var c = eventualAdd(aAsync(), bAsync());
## Error handling
One sometimes-unintuitive aspect of promises is that if you throw an exception
in the value handler, it will not be be caught by the error handler.
readAsync(url).then(function (value) {
throw new Error("Can't bar.");
}, function (error) {
// We only get here if `readAsync` fails.
});
To see why this is, consider the parallel between promises and `try`/`catch`.
We are `try`-ing to execute `readAsync()`: the error handler represents a
`catch` for `readAsync()`, while the value handler represents code that happens
*after* the `try`/`catch` block. That code then needs its own `try`/`catch`
block to handle errors there.
In terms of promises, this means chaining your error handler:
readAsync(url).
then(parse).
then(null, function handleParseError(error) {
// handle here both `readAsync` and `parse` errors.
});
# Consuming promises
In general, whole purpose of promises is to avoid a callback spaghetti in the
code. As a matter of fact it would be great if we could convert any synchronous
functions to asynchronous by making it aware of promises. Module exports
`promised` function to do exactly that:
const { promised } = require('sdk/core/promise');
function sum(x, y) { return x + y };
var asyncSum = promised(sum);
var c = sum(a, b);
var cAsync = asyncSum(aAsync(), bAsync());
`promised` takes normal function and composes new promise aware version of it
that may take both normal values and promises as arguments and returns promise
that will resolve to value that would have being returned by an original
function if it was called with fulfillment values of given arguments.
This technique is so powerful that it can replace most of the promise utility
functions provided by other promise libraries. For example grouping promises
to observe single resolution of all of them is as simple as this:
var group = promised(Array);
var abc = group(aAsync, bAsync, cAsync).then(function(items) {
return items[0] + items[1] + items[2];
});
## all
The `all` function is provided to consume an array of promises and return
a promise that will be accepted upon the acceptance of all the promises
in the initial array. This can be used to perform an action that requires
values from several promises, like getting user information and server
status, for example:
const { all } = require('sdk/core/promise');
all([getUser, getServerStatus]).then(function (result) {
return result[0] + result[1]
});
If one of the promises in the array is rejected, the rejection handler
handles the first failed promise and remaining promises remain unfulfilled.
const { all } = require('sdk/core/promise');
all([aAsync, failAsync, bAsync]).then(function (result) {
// success function will not be called
return result;
}, function (reason) {
// rejection handler called because `failAsync` promise
// was rejected with its reason propagated
return reason;
});
# Making promises
Everything above assumes you get a promise from somewhere else. This
is the common case, but every once in a while, you will need to create a
promise from scratch. Add-on SDK's `promise` module provides API for doing
that.
## defer
Module exports `defer` function, which is where all promises ultimately
come from. Lets see implementation of `readAsync` that we used in lot's
of examples above:
const { defer } = require('sdk/core/promise');
function readAsync(url) {
var deferred = defer();
let xhr = new XMLHttpRequest();
xhr.open("GET", url, true);
xhr.onload = function() {
deferred.resolve(xhr.responseText);
}
xhr.onerror = function(event) {
deferred.reject(event);
}
xhr.send();
return deferred.promise;
}
So `defer` returns an object that contains `promise` and two `resolve`, `reject`
functions that can be used to resolve / reject that `promise`. **Note:** that
promise can be rejected or resolved and only once. All subsequent attempts will be
ignored.
Another simple example may be `delay` function that returns promise which
is fulfilled with a given `value` in a given `ms`, kind of promise based
alternative to `setTimeout`:
function delay(ms, value) {
let { promise, resolve } = defer();
setTimeout(resolve, ms, value);
return promise;
}
delay(10, 'Hello world').then(console.log);
// After 10ms => 'Helo world'
# Advanced usage
If general `defer` and `promised` should be enough to doing almost anything
you may think of with promises, but once you start using promises extensively
you may discover some missing pieces and this section of documentation may
help you to discover them.
## Doing things concurrently
So far we have being playing with promises that do things sequentially, but
there are bunch of cases where one would need to do things concurrently. In the
following example we implement functions that takes multiple promises and
returns one that resolves to first on being fulfilled:
function race() {
let { promise, resolve } = defer();
Array.slice(arguments).forEach(function(promise) {
promise.then(resolve);
});
return promise;
}
var asyncAorB = race(readAsync(urlA), readAsync(urlB));
*Note: that this implementation forgives failures and would fail if all
promises fail to resolve.*
There are cases when promise may or may not be fulfilled in a reasonable time.
In such cases it's useful to put a timer on such tasks:
function timeout(promise, ms) {
let deferred = defer();
promise.then(deferred.resolve, deferred.reject);
delay(ms, 'timeout').then(deferred.reject);
return deferred.promise;
}
var tweets = readAsync(url);
timeout(tweets, 20).then(function(data) {
ui.display(data);
}, function() {
alert('Network is being too slow, try again later');
});
## Alternative promise APIs
There may be a cases where you will want to provide more than just `then`
method on your promises. In fact some other promise frameworks do that.
Such use cases are also supported. Earlier described `defer` may be passed
optional `prototype` argument, in order to make returned promise and all
the subsequent promises decedents of that `prototype`:
let { promise, resolve } = defer({
get: function get(name) {
return this.then(function(value) {
return value[name];
})
}
});
promise.get('foo').get('bar').then(console.log);
resolve({ foo: { bar: 'taram !!' } });
// => 'taram !!'
Also `promised` function maybe passed second optional `prototype` argument to
achieve same effect.
## Treat all values as promises
Module provides a simple function for wrapping values into promises:
const { resolve } = require('sdk/core/promise');
var a = resolve(5).then(function(value) {
return value + 2
});
a.then(console.log); // => 7
Also `resolve` not only takes values, but also promises. If you pass it
a promise it will return new identical one:
const { resolve } = require('sdk/core/promise');
resolve(resolve(resolve(3))).then(console.log); // => 3
If this construct may look strange at first, but it becomes quite handy
when writing functions that deal with both promises and values. In such
cases it's usually easier to wrap value into promise than branch on value
type:
function or(a, b) {
var second = resolve(b).then(function(bValue) { return !!bValue });
return resolve(a).then(function(aValue) {
return !!aValue || second;
}, function() {
return second;
})
}
*Note: We could not use `promised` function here, as they reject returned
promise if any of the given arguments is rejected.*
If you need to customize your promises even further you may pass `resolve` a
second optional `prototype` argument that will have same effect as with `defer`.
## Treat errors as promises
Now that we can create all kinds of eventual values, it's useful to have a
way to create eventual errors. Module exports `reject` exactly for that.
It takes anything as an argument and returns a promise that is rejected with
it.
const { reject } = require('sdk/core/promise');
var boom = reject(Error('boom!'));
future(function() {
return Math.random() < 0.5 ? boom : value
})
As with rest of the APIs error may be given second optional `prototype`
argument to customize resulting promise to your needs.

Просмотреть файл

@ -1,169 +0,0 @@
<!-- This Source Code Form is subject to the terms of the Mozilla Public
- License, v. 2.0. If a copy of the MPL was not distributed with this
- file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
<!-- contributed by Drew Willcoxon [adw@mozilla.com] -->
<!-- edited by Noelle Murata [fiveinchpixie@gmail.com] -->
<div class="warning">
<p>The <code>api-utils</code> module is deprecated.</p>
<ul>
<li>The <code>publicConstructor</code> function is not needed if you use the
<a href="modules/sdk/core/heritage.html"><code>heritage</code></a> module
to construct objects, as it creates public constructors automatically.</li>
<li>The <code>validateOptions</code> will be moved to a different module.</li>
<li>The <a href="modules/sdk/util/list.html"><code>util/list</code></a> module
provides a better alternative to <code>addIterator</code>.</li>
</ul>
</div>
The `api-utils` module provides some helpers useful to the SDK's high-level API
implementations.
Introduction
------------
The SDK high-level API design guidelines make a number of recommendations.
This module implements some of those patterns so that your own implementations
don't need to reinvent them.
For example, public constructors should be callable both with and without the
`new` keyword. Your module can implement this recommendation using the
`publicConstructor` function.
Options objects or "dictionaries" are also common throughout the high-level
APIs. The guidelines recommend that public constructors should generally define
a single `options` parameter rather than defining many parameters. Since one of
the SDK's principles is to be friendly to developers, ideally all properties on
options dictionaries should be checked for correct type, and informative error
messages should be generated when clients make mistakes. With the
`validateOptions` function, your module can easily do so.
And objects are sometimes iterable over a custom set of key/value pairs.
Such objects should have custom iterators that let consumers iterate keys,
values, or [key, value] pairs. The `addIterator` function makes it easy to do
so in a way that is consistent with the behavior of default iterators during
`for...in`, `for each...in`, and `for...in Iterator()` loops.
<api name="publicConstructor">
@function
Returns a function *C* that creates an instance of `privateConstructor`. *C*
may be called with or without the `new` keyword.
The prototype of each instance returned from *C* is *C*.`prototype`, and
*C*.`prototype` is an object whose prototype is
`privateConstructor.prototype`. Instances returned from *C* are therefore
instances of both *C* and `privateConstructor`.
Additionally, the constructor of each instance returned from *C* is *C*.
Instances returned from *C* are automatically memory tracked using
`memory.track` under the bin name `privateConstructor.name`.
**Example**
function MyObject() {}
exports.MyObject = apiUtils.publicConstructor(MyObject);
@returns {function}
A function that makes new instances of `privateConstructor`.
@param privateConstructor {constructor}
</api>
<api name="validateOptions">
@function
A function to validate an options dictionary according to the specified
constraints.
`map`, `is`, and `ok` are used in that order.
The return value is an object whose keys are those keys in `requirements` that
are also in `options` and whose values are the corresponding return values of
`map` or the corresponding values in `options`. Note that any keys not shared
by both `requirements` and `options` are not in the returned object.
**Examples**
A typical use:
var opts = { foo: 1337 };
var requirements = {
foo: {
map: function (val) val.toString(),
is: ["string"],
ok: function (val) val.length > 0,
msg: "foo must be a non-empty string."
}
};
var validatedOpts = apiUtils.validateOptions(opts, requirements);
// validatedOpts == { foo: "1337" }
If the key `foo` is optional and doesn't need to be mapped:
var opts = { foo: 1337 };
var validatedOpts = apiUtils.validateOptions(opts, { foo: {} });
// validatedOpts == { foo: 1337 }
opts = {};
validatedOpts = apiUtils.validateOptions(opts, { foo: {} });
// validatedOpts == {}
@returns {object}
A validated options dictionary given some requirements. If any of the
requirements are not met, an exception is thrown.
@param options {object}
The options dictionary to validate. It's not modified. If it's null or
otherwise falsey, an empty object is assumed.
@param requirements {object}
An object whose keys are the expected keys in `options`. Any key in
`options` that is not present in `requirements` is ignored. Each
value in `requirements` is itself an object describing the requirements
of its key. The keys of that object are the following, and each is optional:
@prop [map] {function}
A function that's passed the value of the key in the `options`. `map`'s
return value is taken as the key's value in the final validated options,
`is`, and `ok`. If `map` throws an exception it is caught and discarded,
and the key's value is its value in `options`.
@prop [is] {array}
An array containing the number of `typeof` type names. If the key's value is
none of these types it fails validation. Arrays and nulls are identified by
the special type names "array" and "null"; "object" will not match either.
No type coercion is done.
@prop [ok] {function}
A function that is passed the key's value. If it returns false, the value
fails validation.
@prop [msg] {string}
If the key's value fails validation, an exception is thrown. This string
will be used as its message. If undefined, a generic message is used, unless
`is` is defined, in which case the message will state that the value needs to
be one of the given types.
</api>
<api name="addIterator">
@function
Adds an iterator to the specified object that iterates keys, values,
or [key, value] pairs depending on how it is invoked, i.e.:
for (var key in obj) { ... } // iterate keys
for each (var val in obj) { ... } // iterate values
for (var [key, val] in Iterator(obj)) { ... } // iterate pairs
If your object only iterates either keys or values, you don't need this
function. Simply assign a generator function that iterates the keys/values
to your object's `__iterator__` property instead, f.e.:
obj.__iterator__ = function () { for each (var i in items) yield i; }
@param obj {object}
the object to which to add the iterator
@param keysValsGen {function}
a generator function that yields [key, value] pairs
</api>

Просмотреть файл

@ -1,72 +0,0 @@
<!-- This Source Code Form is subject to the terms of the Mozilla Public
- License, v. 2.0. If a copy of the MPL was not distributed with this
- file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
<div class="warning">
<p>The <code>app-strings</code> module is deprecated.</p>
<p>If its functionality is still considered useful it will be added
to the <a href="modules/sdk/l10n.html"><code>l10n</code></a> module,
otherwise it will just be removed.</p>
</div>
The `app-strings` module gives you access to the host application's localized
string bundles (`.properties` files).
The module exports the `StringBundle` constructor function. To access a string
bundle, construct an instance of `StringBundle`, passing it the bundle's URL:
var StringBundle = require("app-strings").StringBundle;
var bundle = StringBundle("chrome://browser/locale/browser.properties");
To get the value of a string, call the object's `get` method, passing it
the name of the string:
var accessKey = bundle.get("contextMenuSearchText.accesskey");
// "S" in the en-US locale
To get the formatted value of a string that accepts arguments, call the object's
`get` method, passing it the name of the string and an array of arguments
with which to format the string:
var searchText = bundle.get("contextMenuSearchText",
["universe", "signs of intelligent life"]);
// 'Search universe for "signs of intelligent life"' in the en-US locale
To get all strings in the bundle, iterate the object, which returns arrays
of the form [name, value]:
for (var [name, value] in Iterator(bundle))
console.log(name + " = " + value);
Iteration
---------
<code>for (var name in bundle) { ... }</code>
Iterate the names of strings in the bundle.
<code>for each (var val in bundle) { ... }</code>
Iterate the values of strings in the bundle.
<code>for (var [name, value] in Iterator(bundle)) { ... }</code>
Iterate the names and values of strings in the bundle.
<api name="StringBundle">
@class
The `StringBundle` object represents a string bundle.
<api name="StringBundle">
@constructor
Creates a StringBundle object that gives you access to a string bundle.
@param url {string} the URL of the string bundle
@returns {StringBundle} the string bundle
</api>
<api name="get">
@method Get the value of the string with the given name.
@param [name] {string} the name of the string to get
@param [args] {array} (optional) strings that replace placeholders in the string
@returns {string} the value of the string
</api>
</api>

Просмотреть файл

@ -1,165 +0,0 @@
<!-- This Source Code Form is subject to the terms of the Mozilla Public
- License, v. 2.0. If a copy of the MPL was not distributed with this
- file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
<div class="warning">
<p>The <code>cortex</code> module is deprecated.</p>
<p>To provide property encapsulation for modules you implement, use the
<a href="modules/sdk/core/namespace.html"><code>namespace</code></a> module.</p>
</div>
## Property Encapsulation ##
In JavaScript it is not possible to create properties that have limited or
controlled accessibility. It is possible to create non-enumerable and
non-writable properties, but still they can be discovered and accessed.
Usually so called "closure capturing" is used to encapsulate such properties
in lexical scope:
function Foo() {
var _secret = 'secret';
this.hello = function hello() {
return 'Hello ' + _secret;
}
}
This provides desired result, but has side effect of degrading code readability,
especially with object-oriented programs. Another disadvantage with this pattern
is that there is no immediate solution for inheriting access to the privates
(illustrated by the following example):
function Derived() {
this.hello = function hello() {
return _secret;
}
this.bye = function bye() {
return _secret;
}
}
Derived.prototype = Object.create(Foo.prototype);
## Facade Objects ##
Alternatively constructor can returned facade objects - proxies to the
instance's public properties:
function Foo() {
var foo = Object.create(Foo.prototype);
return {
bar: foo.hello.bind(foo);
}
}
Foo.prototype._secret = 'secret';
Foo.prototype.hello = function hello() {
return 'Hello ' + this._secret;
}
function Derived() {
var derived = Object.create(Derived.prototype);
return {
bar: derived.hello.bind(derived);
bye: derived.bye.bind(derived);
}
}
Derived.prototype = Object.create(Foo.prototype);
Derived.prototype.bye = function bye() {
return 'Bye ' + this._secret;
};
While this solution solves given issue and provides proper encapsulation for
both own and inherited private properties, it does not addresses following:
- Privates defined on the `prototype` can be compromised, since they are
accessible through the constructor (`Foo.prototype._secret`).
- Behavior of `instanceof` is broken, since `new Derived() instanceof Derived`
is going to evaluate to `false`.
## Tamper Proofing with Property Descriptor Maps ##
In ES5 new property descriptor maps were introduced, which can be used as a
building blocks for defining reusable peace of functionality. To some degree
they are similar to a `prototype` objects, and can be used so to define pieces
of functionality that is considered to be private (In contrast to `prototype`
they are not exposed by default).
function Foo() {
var foo = Object.create(Foo.prototype, FooDescriptor);
var facade = Object.create(Foo.prototype);
facade.hello = foo.hello.bind(foo);
return facade;
}
Foo.prototype.hello = function hello() {
return 'Hello ' + this._secret;
}
var FooDescriptor = {
_secret: { value: 'secret' };
}
function Derived() {
var derived = Object.create(Derived.prototype, DerivedDescriptor);
var facade = Object.create(Derived.prototype);
facade.hello = derived.hello.bind(derived);
facade.bye = derived.bye.bind(derived);
return facade;
}
Derived.prototype = Object.create(Foo.prototype);
Derived.prototype.bye = function bye() {
return 'Bye ' + this._secret;
};
DerivedDescriptor = {};
Object.keys(FooDescriptor).forEach(function(key) {
DerivedDescriptor[key] = FooDescriptor[key];
});
## Cortex Objects ##
Last approach solves all of the concerns, but adds complexity, verbosity
and decreases code readability. Combination of `Cortex`'s and `Trait`'s
will gracefully solve all these issues and keep code clean:
var Cortex = require('cortex').Cortex;
var Trait = require('light-traits').Trait;
var FooTrait = Trait({
_secret: 'secret',
hello: function hello() {
return 'Hello ' + this._secret;
}
});
function Foo() {
return Cortex(FooTrait.create(Foo.prototype));
}
var DerivedTrait = Trait.compose(FooTrait, Trait({
bye: function bye() {
return 'Bye ' + this._secret;
}
}));
function Derived() {
var derived = DerivedTrait.create(Derived.prototype);
return Cortex(derived);
}
Function `Cortex` takes any object and returns a proxy for its public
properties. By default properties are considered to be public if they don't
start with `"_"`, but default behavior can be overridden if needed, by passing
array of public property names as a second argument.
## Gotchas ##
`Cortex` is just a utility function to create a proxy object, and it does not
solve the `prototype`-related issues highlighted earlier, but since traits make
use of property descriptor maps instead of `prototype`s, there aren't any
issues with using `Cortex` to wrap objects created from traits.
If you want to use `Cortex` with an object that uses a `prototype` chain,
however, you should either make sure you don't have any private properties
in the prototype chain or pass the optional third `prototype` argument.
In the latter case, the returned proxy will inherit from the given prototype,
and the `prototype` chain of the wrapped object will be inaccessible.
However, note that the behavior of the `instanceof` operator will vary,
as `proxy instanceof Constructor` will return false even if the Constructor
function's prototype is in the wrapped object's prototype chain.

Просмотреть файл

@ -1,46 +0,0 @@
<!-- This Source Code Form is subject to the terms of the Mozilla Public
- License, v. 2.0. If a copy of the MPL was not distributed with this
- file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
<!-- contributed by Drew Willcoxon [adw@mozilla.com] -->
<div class="warning">
<p>The <code>errors</code> module is deprecated.</p>
</div>
The `errors` module provides helpers for safely invoking user callbacks.
<api name="catchAndLog">
@function
Wraps a callback in a function that when invoked will catch and log any
exception thrown by the callback.
@param callback {function}
The callback to wrap.
@param [defaultResponse] {value}
This value will be returned by the wrapper if `callback` throws an exception.
If not given, `undefined` is used.
@param [logException] {function}
When `callback` throws an exception, it will be passed to this function. If
not given, the exception is logged using `console.exception()`.
@returns {function}
A function that will invoke `callback` when called. The return value of this
function is the return value of `callback` unless `callback` throws an
exception. In that case, `defaultResponse` is returned or `undefined` if
`defaultResponse` is not given.
</api>
<api name="catchAndLogProps">
@function
Replaces methods of an object with wrapped versions of those methods returned
by `catchAndLog()`.
@param object {object}
The object whose methods to replace.
@param props {string,array}
The names of the methods of `object` to replace, either a string for a single
method or an array of strings for multiple methods.
@param [defaultResponse] {value}
This value will be returned by any wrapper whose wrapped method throws an
exception. If not given, `undefined` is used.
@param [logException] {function}
See `catchAndLog()`.
</api>

Просмотреть файл

@ -1,83 +0,0 @@
<!-- This Source Code Form is subject to the terms of the Mozilla Public
- License, v. 2.0. If a copy of the MPL was not distributed with this
- file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
<div class="warning">
<p>The <code>events</code> module is deprecated.</p>
<p>To implement your own event
targets, use the
<a href="modules/sdk/event/core.html"><code>event/core</code></a> and
<a href="modules/sdk/event/target.html"><code>event/target</code><a/> modules,
and refer to the
<a href="dev-guide/tutorials/event-targets.html">tutorial on creating event emitters</a>.</p>
</div>
<api name="EventEmitter">
@class
The EventEmitter is the base building block for all compositions that
would need to broadcast data to multiple consumers.
Please note that `EventEmitter` does not expose either a method for emitting
events or a list of available event listeners as its public API. Obviously
both are accessible but from the instance itself through the private API.
<api name="EventEmitter">
@constructor
Creates an EventEmitter object.
</api>
<api name="on">
@method
Registers an event `listener` that will be called when events of
specified `type` are emitted.
If the `listener` is already registered for this `type`, a call to this
method has no effect.
If the event listener is being registered while an event is being processed,
the event listener is not called during the current emit.
**Example:**
// worker is instance of EventEmitter
worker.on('message', function (data) {
console.log('data received: ' + data)
});
@param type {String}
The type of the event.
@param listener {Function}
The listener function that processes the event.
</api>
<api name="once">
@method
Registers an event `listener` that will only be called once, the next time
an event of the specified `type` is emitted.
If the event listener is registered while an event of the specified `type`
is being emitted, the event listener will not be called during the current
emit.
@param type {String}
The type of the event.
@param listener {Function}
The listener function that processes the event.
</api>
<api name="removeListener">
@method
Unregisters an event `listener` for the specified event `type`.
If the `listener` is not registered for this `type`, a call to this
method has no effect.
If an event listener is removed while an event is being processed, it is
still triggered by the current emit. After it is removed, the event listener
is never invoked again (unless registered again for future processing).
@param type {String}
The type of the event.
@param listener {Function}
The listener function that processes the event.
</api>
</api>

Просмотреть файл

@ -1,301 +0,0 @@
<!-- This Source Code Form is subject to the terms of the Mozilla Public
- License, v. 2.0. If a copy of the MPL was not distributed with this
- file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
<div class="warning">
<p>The <code>light-traits</code> module is deprecated.</p>
<p>To implement inheritance, use the
<a href="modules/sdk/core/heritage.html"><code>heritage</code></a>
module.</p>
</div>
[Traits](http://en.wikipedia.org/wiki/Trait_%28computer_science%29) are a simple
mechanism for structuring object-oriented programs. They represent reusable and
composable building blocks of functionality that factor out the common
attributes and behavior of objects.
They are a more robust alternative to
[mixins](http://en.wikipedia.org/wiki/Mixins) and
[multiple inheritance](http://en.wikipedia.org/wiki/Multiple_inheritance),
because name clashes must be explicitly resolved and composition is commutative
and associative (i.e. the order of traits in a composition is irrelevant).
Use traits to share functionality between similar objects without duplicating
code or creating complex inheritance chains.
## Trait Creation ##
To create a trait, call the `Trait` constructor function exported by this
module, passing it a JavaScript object that specifies the properties of the
trait.
let Trait = require('light-traits').Trait;
let t = Trait({
foo: "foo",
bar: function bar() {
return "Hi!"
},
baz: Trait.required
});
Traits can both provide and require properties. A *provided* property is a
property for which the trait itself provides a value. A *required* property is a
property that the trait needs in order to function correctly but for which
it doesn't provide a value.
Required properties must be provided by another trait or by an object with a
trait. Creation of an object with a trait will fail if required properties are
not provided. Specify a required property by setting the value of the property
to `Trait.required`.
## Object Creation ##
Create objects with a single trait by calling the trait's `create` method. The
method takes a single argument, the object to serve as the new object's
prototype. If no prototype is specified, the new object's prototype will be
`Object.prototype`.
let t = Trait({
foo: 'foo',
bar: 2
});
let foo1 = t.create();
let foo2 = t.create({ name: 'Super' });
## Trait Composition ##
Traits are designed to be composed with other traits to create objects with the
properties of multiple traits. To compose an object with multiple traits, you
first create a composite trait and then use it to create the object. A composite
trait is a trait that contains all of the properties of the traits from which it
is composed. In the following example, MagnitudeTrait is a composite trait.
let EqualityTrait = Trait({
equal: Trait.required,
notEqual: function notEqual(x) {
return !this.equal(x)
}
});
let ComparisonTrait = Trait({
less: Trait.required,
notEqual: Trait.required,
greater: function greater(x) {
return !this.less(x) && this.notEqual(x)
}
});
let MagnitudeTrait = Trait.compose(EqualityTrait, ComparisonTrait);
<?xml version="1.0"?>
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xl="http://www.w3.org/1999/xlink" version="1.1" viewBox="-11 121 490 190" width="490px" height="190px">
<defs>
<marker orient="auto" overflow="visible" markerUnits="strokeWidth" id="SharpArrow_Marker" viewBox="-4 -4 10 8" markerWidth="10" markerHeight="8" color="black">
<g>
<path d="M 5 0 L -3 -3 L 0 0 L 0 0 L -3 3 Z" fill="currentColor" stroke="currentColor" stroke-width="1px"/>
</g>
</marker>
</defs>
<g stroke="none" stroke-opacity="1" stroke-dasharray="none" fill="none" fill-opacity="1">
<g>
<rect x="9" y="165.33334" width="141" height="14"/>
<rect x="9" y="165.33334" width="141" height="14" stroke="black" stroke-width="1px"/>
<text transform="translate(14 165.33334)" fill="black">
<tspan font-family="Helvetica" font-size="12" font-weight="500" x="0" y="11" textLength="47.373047">notEqual</tspan>
</text>
<rect x="9" y="151.33334" width="141" height="14"/>
<rect x="9" y="151.33334" width="141" height="14" stroke="black" stroke-width="1px"/>
<text transform="translate(14 151.33334)" fill="red">
<tspan font-family="Helvetica" font-size="12" font-weight="500" fill="red" x="0" y="11" textLength="29.361328">equal</tspan>
</text>
<rect x="9" y="137.33334" width="141" height="14"/>
<rect x="9" y="137.33334" width="141" height="14" stroke="black" stroke-width="1px"/>
<text transform="translate(14 137.33334)" fill="black">
<tspan font-family="Helvetica" font-size="12" font-weight="bold" x="38.49707" y="11" textLength="54.00586">EqualityTrait</tspan>
</text>
<rect x="9" y="273" width="141" height="14"/>
<rect x="9" y="273" width="141" height="14" stroke="black" stroke-width="1px"/>
<text transform="translate(14 273)" fill="black">
<tspan font-family="Helvetica" font-size="12" font-weight="500" x="0" y="11" textLength="38.021484">greater</tspan>
</text>
<rect x="9" y="259" width="141" height="14"/>
<rect x="9" y="259" width="141" height="14" stroke="black" stroke-width="1px"/>
<text transform="translate(14 259)" fill="red">
<tspan font-family="Helvetica" font-size="12" font-weight="500" fill="red" x="0" y="11" textLength="47.373047">notEqual</tspan>
</text>
<rect x="9" y="245" width="141" height="14"/>
<rect x="9" y="245" width="141" height="14" stroke="black" stroke-width="1px"/>
<text transform="translate(14 245)" fill="red">
<tspan font-family="Helvetica" font-size="12" font-weight="500" fill="red" x="0" y="11" textLength="21.339844">less</tspan>
</text>
<rect x="9" y="231" width="141" height="14"/>
<rect x="9" y="231" width="141" height="14" stroke="black" stroke-width="1px"/>
<text transform="translate(14 231)" fill="black">
<tspan font-family="Helvetica" font-size="12" font-weight="bold" x=".15332031" y="11" textLength="112.67578">ComparisonTrait</tspan>
</text>
<rect x="317.75" y="235.5" width="141" height="14"/>
<rect x="317.75" y="235.5" width="141" height="14" stroke="black" stroke-width="1px"/>
<text transform="translate(322.75 235.5)" fill="black">
<tspan font-family="Helvetica" font-size="12" font-weight="500" x="0" y="11" textLength="38.021484">greater</tspan>
</text>
<rect x="317.75" y="221.5" width="141" height="14"/>
<rect x="317.75" y="221.5" width="141" height="14" stroke="black" stroke-width="1px"/>
<text transform="translate(322.75 221.5)" fill="red">
<tspan font-family="Helvetica" font-size="12" font-weight="500" fill="red" x="0" y="11" textLength="21.339844">less</tspan>
</text>
<rect x="317.75" y="207.5" width="141" height="14"/>
<rect x="317.75" y="207.5" width="141" height="14" stroke="black" stroke-width="1px"/>
<text transform="translate(322.75 207.5)" fill="black">
<tspan font-family="Helvetica" font-size="12" font-weight="500" x="0" y="11" textLength="47.373047">notEqual</tspan>
</text>
<rect x="317.75" y="193.5" width="141" height="14"/>
<rect x="317.75" y="193.5" width="141" height="14" stroke="black" stroke-width="1px"/>
<text transform="translate(322.75 193.5)" fill="red">
<tspan font-family="Helvetica" font-size="12" font-weight="500" fill="red" x="0" y="11" textLength="29.361328">equal</tspan>
</text>
<rect x="317.75" y="179.5" width="141" height="14"/>
<rect x="317.75" y="179.5" width="141" height="14" stroke="black" stroke-width="1px"/>
<text transform="translate(322.75 179.5)" fill="black">
<tspan font-family="Helvetica" font-size="12" font-weight="bold" x="31.83789" y="11" textLength="67.32422">MagnitudeTrait</tspan>
</text>
<path d="M 150 248.83887 L 158.89999 248.83887 L 235.9 248.83887 L 235.9 224.66113 L 308.85 224.66113 L 310.85 224.66113" marker-end="url(#SharpArrow_Marker)" stroke="black" stroke-linecap="butt" stroke-linejoin="miter" stroke-width="1px"/>
<path d="M 150 171.15845 L 158.89999 171.15845 L 233.9 171.15845 L 233.9 201.6749 L 308.85 201.6749 L 310.85 201.6749" marker-end="url(#SharpArrow_Marker)" stroke="black" stroke-linecap="butt" stroke-linejoin="miter" stroke-width="1px"/>
</g>
</g>
</svg>
## Trait Resolution ##
Composite traits have conflicts when two of the traits in the composition
provide properties with the same name but different values (when compared using
the `===` strict equality operator). In the following example, `TC` has a
conflict because `T1` and `T2` both define a `foo` property:
let T1 = Trait({
foo: function () {
// do something
},
bar: 'bar',
t1: 1
});
let T2 = Trait({
foo: function() {
// do something else
},
bar: 'bar',
t2: 2
});
let TC = Trait.compose(T1, T2);
Attempting to create an object from a composite trait with conflicts throws a
`remaining conflicting property` exception. To create objects from such traits,
you must resolve the conflict.
You do so by excluding or renaming the conflicting property of one of the
traits. Excluding a property removes it from the composition, so the composition
only acquires the property from the other trait. Renaming a property gives it a
new, non-conflicting name at which it can be accessed.
In both cases, you call the `resolve` method on the trait whose property you
want to exclude or rename, passing it an object. Each key in the object is the
name of a conflicting property; each value is either `null` to exclude the
property or a string representing the new name of the property.
For example, the conflict in the previous example could be resolved by excluding
the `foo` property of the second trait.
let TC = Trait(T1, T2.resolve({ foo: null }));
It could also be resolved by renaming the `foo` property of the first trait to
`foo2`:
let TC = Trait(T1.resolve({ foo: "foo2" }), T2);
When you resolve a conflict, the same-named property of the other trait (the one
that wasn't excluded or renamed) remains available in the composition under its
original name.
## Constructor Functions ##
When your code is going to create more than one object with traits, you may want
to define a constructor function to create them. To do so, create a composite
trait representing the traits the created objects should have, then define a
constructor function that creates objects with that trait and whose prototype is
the prototype of the constructor:
let PointTrait = Trait.compose(T1, T2, T3);
function Point(options) {
let point = PointTrait.create(Point.prototype);
return point;
}
## Property Descriptor Maps ##
Traits are designed to work with the new object manipulation APIs defined in
[ECMAScript-262, Edition
5](http://www.ecma-international.org/publications/standards/Ecma-262.htm) (ES5).
Traits are also property descriptor maps that inherit from `Trait.prototype` to
expose methods for creating objects and resolving conflicts.
The following trait definition:
let FooTrait = Trait({
foo: "foo",
bar: function bar() {
return "Hi!"
},
baz: Trait.required
});
Creates the following property descriptor map:
{
foo: {
value: 'foo',
enumerable: true,
configurable: true,
writable: true
},
bar: {
value: function b() {
return 'bar'
},
enumerable: true,
configurable: true,
writable: true
},
baz: {
get baz() { throw new Error('Missing required property: `baz`') }
set baz() { throw new Error('Missing required property: `baz`') }
},
__proto__: Trait.prototype
}
Since Traits are also property descriptor maps, they can be used with built-in
`Object.*` methods that accept such maps:
Object.create(proto, FooTrait);
Object.defineProperties(myObject, FooTrait);
Note that conflicting and required properties won't cause exceptions to be
thrown when traits are used with the `Object.*` methods, since those methods are
not aware of those constraints. However, such exceptions will be thrown when the
property with the conflict or the required but missing property is accessed.
Property descriptor maps can also be used in compositions. This may be useful
for defining non-enumerable properties, for example:
let TC = Trait.compose(
Trait({ foo: 'foo' }),
{ bar: { value: 'bar', enumerable: false } }
);
_When using property descriptor maps in this way, make sure the map is not the
only argument to `Trait.compose`, since in that case it will be interpreted as
an object literal with properties to be defined._

Просмотреть файл

@ -1,81 +0,0 @@
<!-- This Source Code Form is subject to the terms of the Mozilla Public
- License, v. 2.0. If a copy of the MPL was not distributed with this
- file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
<!-- contributed by Atul Varma [atul@mozilla.com] -->
<!-- edited by Noelle Murata [fiveinchpixie@gmail.com] -->
<div class="warning">
<p>The <code>observer-service</code> module is deprecated.</p>
<p>To access the
<a href="https://developer.mozilla.org/en-US/docs/Observer_Notifications">observer service</a>, use the
<a href="modules/sdk/system/events.html"><code>system/events</code></a>
module.</p>
</div>
The `observer-service` module provides access to the
application-wide observer service singleton.
For a list of common observer topics across a variety of Mozilla-based
applications, see the MDC page on
[Observer Notifications](https://developer.mozilla.org/en/Observer_Notifications).
## Observer Callbacks ##
Observer callbacks are functions of the following form:
function callback(subject, data) {
/* Respond to the event notification here... */
}
In the above example, `subject` is any JavaScript object, as is
`data`. The particulars of what the two contain are specific
to the notification topic.
<api name="add">
@function
Adds an observer callback to be triggered whenever a notification matching the
topic is broadcast throughout the application.
@param topic {string}
The topic to observe.
@param callback {function,object}
Either a function or an object that implements [`nsIObserver`](http://mxr.mozilla.org/mozilla-central/source/xpcom/ds/nsIObserver.idl).
If a function, then it is called when the notification occurs. If an object,
then its `observe()` method is called when the notification occurs.
@param [thisObject] {object}
An optional object to use as `this` when a function callback is called.
</api>
<api name="remove">
@function
Unsubscribes a callback from being triggered whenever a notification
matching the topic is broadcast throughout the application.
@param topic {string}
The topic being observed by the previous call to `add()`.
@param callback {function,object}
The callback subscribed in the previous call to `add()`, either a function or
object.
@param [thisObject] {object}
If `thisObject` was passed to the previous call to `add()`, it should be
passed to `remove()` as well.
</api>
<api name="notify">
@function
Broadcasts a notification event for a topic, passing a subject and data to all
applicable observers in the application.
@param topic {string}
The topic about which to broadcast a notification.
@param [subject] {value}
Optional information about the topic. This can be any JS object or primitive.
If you have multiple values to pass to observers, wrap them in an object,
e.g., `{ foo: 1, bar: "some string", baz: myObject }`.
</api>

Просмотреть файл

@ -1,147 +0,0 @@
<!-- This Source Code Form is subject to the terms of the Mozilla Public
- License, v. 2.0. If a copy of the MPL was not distributed with this
- file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
<!-- contributed by Dietrich Ayala [dietrich@mozilla.com] -->
<div class="warning">
<p>The <code>tab-browser</code> module is deprecated.</p>
<p>For low-level access to tabs, use the
<a href="modules/sdk/tabs/utils.html"><code>tabs/utils</code></a>
module.</p>
</div>
The `tab-browser` module is a low-level API that provides privileged
access to browser tab events and actions.
Introduction
------------
The `tab-browser` module contains helpers for tracking tabbrowser elements
and tabs, as well as a few utilities for actions such as opening a new
tab, and catching all tab content loads.
This is a low-level API that has full privileges, and is intended to be used
by SDK internal modules. If you just need easy access to tab events for your
add-on, use the Tabs module (JEP 110).
<api name="activeTab">
@property {element}
The XUL tab element of the currently active tab.
</api>
<api name="addTab">
@function
Adds a new tab.
**Example**
var tabBrowser = require("tab-browser");
tabBrowser.addTab("http://google.com");
var tabBrowser = require("tab-browser");
tabBrowser.addTab("http://google.com", {
inBackground: true
});
var tabBrowser = require("tab-browser");
tabBrowser.addTab("http://google.com", {
inNewWindow: true,
onLoad: function(tab) {
console.log("tab is open.");
}
});
@returns {element}
The XUL tab element of the newly created tab.
@param URL {string}
The URL to be opened in the new tab.
@param options {object}
Options for how and where to open the new tab.
@prop [inNewWindow] {boolean}
An optional parameter whose key can be set in `options`.
If true, the tab is opened in a new window. Default is false.
@prop [inBackground] {boolean}
An optional parameter whose key can be set in `options`.
If true, the tab is opened adjacent to the active tab, but not
switched to. Default is false.
@prop [onLoad] {function}
An optional parameter whose key can be set in `options`.
A callback function that is called once the tab has loaded.
The XUL element for the tab is passed as a parameter to
this function.
</api>
<api name="Tracker">
@function
Register a delegate object to be notified when tabbrowsers are created
and destroyed.
The onTrack method will be called once per pre-existing tabbrowser, upon
tracker registration.
**Example**
var tabBrowser = require("tab-browser");
let tracker = {
onTrack: function(tabbrowser) {
console.log("A new tabbrowser is being tracked.");
},
onUntrack: function(tabbrowser) {
console.log("A tabbrowser is no longer being tracked.");
}
};
tabBrowser.Tracker(tracker);
@param delegate {object}
Delegate object to be notified each time a tabbrowser is created or destroyed.
The object should contain the following methods:
@prop [onTrack] {function}
Method of delegate that is called when a new tabbrowser starts to be tracked.
The tabbrowser element is passed as a parameter to this method.
@prop [onUntrack] {function}
Method of delegate that is called when a tabbrowser stops being tracked.
The tabbrowser element is passed as a parameter to this method.
</api>
<api name="TabTracker">
@function
Register a delegate object to be notified when tabs are opened and closed.
The onTrack method will be called once per pre-existing tab, upon
tracker registration.
**Example**
var tabBrowser = require("tab-browser");
let tracker = {
onTrack: function(tab) {
console.log("A new tab is being tracked.");
},
onUntrack: function(tab) {
console.log("A tab is no longer being tracked.");
}
};
tabBrowser.TabTracker(tracker);
@param delegate {object}
Delegate object to be notified each time a tab is opened or closed.
The object should contain the following methods:
@prop [onTrack] {function}
Method of delegate that is called when a new tab starts to be tracked.
The tab element is passed as a parameter to this method.
@prop [onUntrack] {function}
Method of delegate that is called when a tab stops being tracked.
The tab element is passed as a parameter to this method.
</api>

Просмотреть файл

@ -1,251 +0,0 @@
<!-- This Source Code Form is subject to the terms of the Mozilla Public
- License, v. 2.0. If a copy of the MPL was not distributed with this
- file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
<!-- contributed by Irakli Gozalishvil [gozala@mozilla.com] -->
<div class="warning">
<p>The <code>traits</code> module is deprecated.</p>
<p>To implement inheritance, use the
<a href="modules/sdk/core/heritage.html"><code>heritage</code></a>
module.</p>
</div>
The `traits` module provides base building blocks for secure object
composition. It exports base trait / constructor function that
constructs an instance of `Trait`.
[Traits](http://en.wikipedia.org/wiki/Trait_%28computer_science%29) are a
simple composition mechanism for structuring object-oriented programs. Traits
are similar to
[interfaces](http://en.wikipedia.org/wiki/Interface_%28object-oriented_programming%29),
except that they often define only a part of an object's data and behavior and
are intended to be used in conjunction with other traits to completely define
the object.
Traits are also considered to be a more robust alternative to
[mixins](http://en.wikipedia.org/wiki/Mixins) because, name conflicts have to
be resolved explicitly by composer & because trait composition is
order-independent (hence more declarative).
There are some other implementations of traits in JavaScript & some ideas /
APIs are borrowed from them:
- [traitsjs](http://www.traitsjs.org/)
- [joose](http://code.google.com/p/joose-js/)
Object-capability security model
--------------------------------
Implementation uses an
[object-capability security model](http://en.wikipedia.org/wiki/Object-capability_model)
to allow protection of private APIs. At the same private APIs can be shared
between among trait composition parties. To put it simply: All the properties
whose names start with `"_"` are considered to be **private**, and are
unaccessible from anywhere except other **public** methods / accessors of the
instance that had been defined during composition.
<api name="Trait">
@class
<api name="Trait">
@constructor
Creates an instance of Trait and returns it if it has no `constructor` method
defined. If instance has `constructor` method, then it is called with all the
arguments passed to this function and returned value is returned instead,
unless it's `undefined`. In that case instance is returned.
`Trait` function represents a base trait. As with any other trait it represents
a constructor function for creating instances of its own & a placeholder
for a trait compositions functions.
</api>
<api name="compose">
@method
Composes new trait out of itself and traits / property maps passed as an
arguments. If two or more traits / property maps have properties with the same
name, the new trait will contain a "conflict" property for that name (see
examples in Examples section to find out more about "conflict" properties).
This is a commutative and associative operation, and the order of its
arguments is not significant.
**Examples:**
Let's say we want to define a reusable piece of code for a lists of elements.
var { Trait } = require('traits');
var List = Trait.compose({
// private API:
_list: null,
// public API
constructor: function List() {
this._list = [];
},
get length() this._list.length,
add: function add(item) this._list.push(item),
remove: function remove(item) {
let list = this._list;
let index = list.indexOf(item);
if (0 <= index) list.splice(index, 1);
}
});
Instances of `List` can be created by calling `List` function with or without
`new` keyword.
let l1 = List();
l1 instanceof List; // true
let l2 = new List();
l2 instanceof List; // true
As you can see `add` and `remove` functions are capable of accessing private
`_list` property, but thats about it, there's nothing else that will be able
to access this property:
'_list' in l1; // false
'_list' in l2; // false
'_list' in List.protoype; // false
l1.has = function(name) name in this
l1.has('_list'); // false
l1.length; // 0
l1.add('test')
l1.length // 1
@param trait1 {Object|Function}
Trait or property map to compose new trait from.
@param trait2 {Object|Function}
Trait or property map to compose new trait from.
@param ... {Object|Function}
Traits or property maps to compose new trait from.
@returns {Function}
New trait containing the combined properties of all the traits.
</api>
<api name="required">
@property {Object}
Singleton, used during trait composition to define "required" properties.
**Example:**
var Enumerable = Trait.compose({
list: Trait.required,
forEach: function forEach(consumer) {
return this.list.forEach(consumer);
}
});
let c1 = Enumerable(); // Error: Missing required property: list
var EnumerableList = List.compose({
get list() this._list.slice(0)
}, Enumerable);
let c2 = EnumerableList();
c2.add('test')
c2.length // 1
c2.list[0] // 'test'
c2.forEach(console.log) // > info: 'test 0 test'
</api>
<api name="resolve">
@method
Composes a new trait that has all the same properties
as the trait on which it is called, except that each property listed
in the `resolutions` argument will be renamed from the name
of the property in the `resolutions` argument to its value.
And if its value is `null`, the property will become required.
**Example:**
var Range = List.resolve({
constructor: null,
add: '_add',
}).compose({
min: null,
max: null,
get list() this._list.slice(0),
constructor: function Range(min, max) {
this.min = min;
this.max = max;
this._list = [];
},
add: function(item) {
if (item <= this.max && item >= this.min)
this._add(item)
}
});
let r = Range(0, 10);
r.min; // 0
r.max; // 10
r.length; // 0;
r.add(5);
r.length; // 1
r.add(12);
r.length; // 1 (12 was not in a range)
@param resolutions {Object}
@returns {Function}
New resolved trait.
</api>
<api name="override">
@method
Composes a new trait with all of the combined properties of `this` and the
argument traits. In contrast to `compose`, `override` immediately resolves
all conflicts resulting from this composition by overriding the properties of
later traits. Trait priority is from left to right. I.e. the properties of
the leftmost trait are never overridden.
**Example:**
// will compose trait with conflict property 'constructor'
var ConstructableList = List.compose({
constructor: function List() this._list = Array.slice(arguments)
});
// throws error with message 'Remaining conflicting property: constructor'
ConstructableList(1, 2, 3);
var ConstructableList = List.override({
constructor: function List() this._list = Array.slice(arguments)
});
ConstructableList(1, 2, 3).length // 3
@param trait1 {Object|Function}
Trait or property map to compose new trait from.
@param trait2 {Object|Function}
Trait or property map to compose new trait from.
@param ... {Object|Function}
Traits or property maps to compose new trait from.
@returns {Function}
New trait containing the combined properties of all the traits.
</api>
<api name="_public">
@property {Object}
Internal property of instance representing public API that is exposed to the
consumers of an instance.
</api>
<api name='toString'>
@method
Textual representation of an object. All the traits will return:
`'[object Trait]'` string, unless they have `constructor` property, in that
case string `'Trait'` is replaced with the name of `constructor` property.
**Example:**
var MyTrait = Trait.compose({
constructor: function MyTrait() {
// do your initialization here
}
});
MyTrait().toString(); // [object MyTrait]
</api>
</api>

Просмотреть файл

@ -1,96 +0,0 @@
<!-- This Source Code Form is subject to the terms of the Mozilla Public
- License, v. 2.0. If a copy of the MPL was not distributed with this
- file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
<!-- contributed by Drew Willcoxon [adw@mozilla.com] -->
<!-- edited by Erik Vold [erikvvold@gmail.com] -->
<div class="warning">
<p>The <code>window-utils</code> module is deprecated.</p>
<p>For low-level access to windows, use the
<a href="modules/sdk/window/utils.html"><code>window/utils</code></a>
module.</p>
</div>
The `window-utils` module provides helpers for accessing and tracking
application windows. These windows implement the [`nsIDOMWindow`][nsIDOMWindow]
interface.
[nsIDOMWindow]: http://mxr.mozilla.org/mozilla-central/source/dom/interfaces/base/nsIDOMWindow.idl
<api name="WindowTracker">
@class
`WindowTracker` objects make it easy to "monkeypatch" windows when a program is
loaded and "un-monkeypatch" those windows when the program is unloaded. For
example, if a Firefox add-on needs to add a status bar icon to all browser
windows, it can use a single `WindowTracker` object to gain access to windows
when they are opened and closed and also when the add-on is loaded and unloaded.
When a window is opened or closed, a `WindowTracker` notifies its delegate
object, which is passed to the constructor. The delegate is also notified of
all windows that are open at the time that the `WindowTracker` is created and
all windows that are open at the time that the `WindowTracker` is unloaded. The
caller can therefore use the same code to act on all windows, regardless of
whether they are currently open or are opened in the future, or whether they are
closed while the parent program is loaded or remain open when the program is
unloaded.
When a window is opened or when a window is open at the time that the
`WindowTracker` is created, the delegate's `onTrack()` method is called and
passed the window.
When a window is closed or when a window is open at the time that the
`WindowTracker` is unloaded, the delegate's `onUntrack()` method is called and
passed the window. (The `WindowTracker` is unloaded when its its `unload()`
method is called, or when its parent program is unloaded, disabled, or
uninstalled, whichever comes first.)
**Example**
var delegate = {
onTrack: function (window) {
console.log("Tracking a window: " + window.location);
// Modify the window!
},
onUntrack: function (window) {
console.log("Untracking a window: " + window.location);
// Undo your modifications!
}
};
var winUtils = require("window-utils");
var tracker = new winUtils.WindowTracker(delegate);
<api name="WindowTracker">
@constructor
A `WindowTracker` object listens for openings and closings of application
windows.
@param delegate {object}
An object that implements `onTrack()` and `onUntrack()` methods.
@prop onTrack {function}
A function to be called when a window is open or loads, with the window as the
first and only argument.
@prop [onUntrack] {function}
A function to be called when a window unloads, with the window as the first
and only argument.
</api>
</api>
<api name="windowIterator">
@function
An iterator for windows currently open in the application. Only windows whose
document is already loaded will be dispatched.
**Example**
var winUtils = require("window-utils");
for (window in winUtils.windowIterator())
console.log("An open window! " + window.location);
</api>
<api name="closeOnUnload">
@function
Marks an application window to be closed when the program is unloaded.
@param window {window}
The window to close.
</api>

Просмотреть файл

@ -1,128 +0,0 @@
<!-- This Source Code Form is subject to the terms of the Mozilla Public
- License, v. 2.0. If a copy of the MPL was not distributed with this
- file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
Many modules in the SDK can broadcast events. For example, the
[`tabs`](modules/sdk/tabs.html) module emits an `open` event when a new tab
is opened.
The `event/core` module enables you to create APIs that broadcast events.
Users of your API can listen to the events using the standard `on()` and
`once()` functions.
Also see the
[tutorial on implementing event targets](dev-guide/tutorials/event-targets.html)
to get started with this API.
An event `listener` may be registered to any event `target` using the
`on` function:
var { on, once, off, emit } = require('sdk/event/core');
var target = { name: 'target' };
on(target, 'message', function listener(event) {
console.log('hello ' + event);
});
on(target, 'data', console.log);
An event of a specific `type` may be emitted on any event `target`
object using the `emit` function. This will call all registered
`listener`s for the given `type` on the given event `target` in the
same order they were registered.
emit(target, 'message', 'event');
// info: 'hello event'
emit(target, 'data', { type: 'data' }, 'second arg');
// info: [Object object] 'second arg'
Registered event listeners may be removed using `off` function:
off(target, 'message');
emit(target, 'message', 'bye');
// info: 'hello bye'
Sometimes listener only cares about first event of specific `type`. To avoid
hassles of removing such listeners there is a convenient `once` function:
once(target, 'load', function() {
console.log('ready');
});
emit(target, 'load')
// info: 'ready'
emit(target, 'load')
There are also convenient ways to remove registered listeners. All listeners of
the specific type can be easily removed (only two argument must be passed):
off(target, 'message');
Also, removing all registered listeners is possible (only one argument must be
passed):
off(target);
<api name="on">
@function
Registers an event `listener` that is called every time events of
the specified `type` is emitted on the given event `target`.
@param target {Object}
Event target object.
@param type {String}
The type of event.
@param listener {Function}
The listener function that processes the event.
</api>
<api name="once">
@function
Registers an event `listener` that is called only once:
the next time an event of the specified `type` is emitted
on the given event `target`.
@param target {Object}
Event target object.
@param type {String}
The type of event.
@param listener {Function}
The listener function that processes the event.
</api>
<api name="emit">
@function
Execute each of the listeners in order with the supplied arguments.
All the exceptions that are thrown by listeners during the emit
are caught and can be handled by listeners of 'error' event. Thrown
exceptions are passed as an argument to an 'error' event listener.
If no 'error' listener is registered exception will be logged into an
error console.
@param target {Object}
Event target object.
@param type {String}
The type of event.
@param message {Object|Number|String|Boolean}
First argument that will be passed to listeners.
@param arguments {Object|Number|String|Boolean}
More arguments that will be passed to listeners.
</api>
<api name="off">
@function
Removes an event `listener` for the given event `type` on the given event
`target`. If no `listener` is passed removes all listeners of the given
`type`. If `type` is not passed removes all the listeners of the given
event `target`.
@param target {Object}
Event target object.
@param type {String}
The type of event.
@param listener {Function}
The listener function that processes the event.
</api>
<api name="count">
@function
Returns a number of event listeners registered for the given event `type`
on the given event `target`.
</api>

Просмотреть файл

@ -1,174 +0,0 @@
<!-- This Source Code Form is subject to the terms of the Mozilla Public
- License, v. 2.0. If a copy of the MPL was not distributed with this
- file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
Many objects in the SDK can broadcast events. For example, a
[`panel`](modules/sdk/panel.html) instance emits an `show` event when the
panel is shown.
The `event/target` module enables you to create objects that broadcast
events. Users of the object can listen to the events using the standard
`on()` and `once()` functions.
Also see the
[tutorial on implementing event targets](dev-guide/tutorials/event-targets.html)
to get started with this API.
This module provides an exemplar `EventTarget` object, that implements
an interface for adding and removing event listeners of a specific type.
`EventTarget` is the base class for all objects in SDK on which events
are emitted.
## Instantiation
It's easy to create event target objects, no special arguments are required.
const { EventTarget } = require("sdk/event/target");
let target = EventTarget();
For a convenience though optional `options` arguments may be used, in which
case all the function properties with keys like: `onMessage`, `onMyEvent`...
will be auto registered for associated `'message'`, `'myEvent'` events on
the created instance. _All other properties of `options` will be ignored_.
## Adding listeners
`EventTarget` interface defines `on` method, that can be used to register
event listeners on them for the given event type:
target.on('message', function onMessage(message) {
// Note: `this` pseudo variable is an event `target` unless
// intentionally overridden via `.bind()`.
console.log(message);
});
Sometimes event listener may care only about very first event of specific
`type`. `EventTarget` interface defines convenience method for adding one
shot event listeners via method `once`. Such listeners are called only once
next time event of the specified type is emitted:
target.once('ready', function onReady() {
// Do the thing once ready!
});
## Removing listeners
`EventTarget` interface defines API for unregistering event listeners, via
`removeListener` method:
target.removeListener('message', onMessage);
## Emitting events
`EventTarget` interface intentionally does not defines an API for emitting
events. In majority of cases party emitting events is different from party
registering listeners. In order to emit events one needs to use `event/core`
module instead:
let { emit } = require('sdk/event/core');
target.on('hi', function(person) { console.log(person + ' says hi'); });
emit(target, 'hi', 'Mark'); // info: 'Mark says hi'
For more details see **event/core** documentation.
## More details
Listeners registered during the event propagation (by one of the listeners)
won't be triggered until next emit of the matching type:
let { emit } = require('sdk/event/core');
target.on('message', function onMessage(message) {
console.log('listener triggered');
target.on('message', function() {
console.log('nested listener triggered');
});
});
emit(target, 'message'); // info: 'listener triggered'
emit(target, 'message'); // info: 'listener triggered'
// info: 'nested listener triggered'
Exceptions in the listeners can be handled via `'error'` event listeners:
target.on('boom', function() {
throw Error('Boom!');
});
target.once('error', function(error) {
console.log('caught an error: ' + error.message);
});
emit(target, 'boom');
// info: caught an error: Boom!
If there is no listener registered for `error` event or if it also throws
exception then such exceptions are logged into a console.
## Chaining
Emitters can also have their methods chained:
target.on('message', handleMessage)
.on('data', parseData)
.on('error', handleError);
<api name="EventTarget">
@class
`EventTarget` is an exemplar for creating an objects that can be used to
add / remove event listeners on them. Events on these objects may be emitted
via `emit` function exported by [`event/core`](modules/sdk/event/core.html)
module.
<api name="initialize">
@method
Method initializes `this` event source. It goes through properties of a
given `options` and registers listeners for the ones that look like
event listeners.
</api>
<api name="on">
@method
Registers an event `listener` that is called every time events of
specified `type` are emitted.
worker.on('message', function (data) {
console.log('data received: ' + data)
});
@param type {String}
The type of event.
@param listener {Function}
The listener function that processes the event.
@returns {EventTarget}
Returns the EventTarget instance
</api>
<api name="once">
@method
Registers an event `listener` that is called only once:
the next time an event of the specified `type` is emitted.
@param type {String}
The type of event.
@param listener {Function}
The listener function that processes the event.
@returns {EventTarget}
Returns the EventTarget instance
</api>
<api name="removeListener">
@method
Removes an event `listener` for the given event `type`.
@param type {String}
The type of event.
@param listener {Function}
The listener function that processes the event.
@returns {EventTarget}
Returns the EventTarget instance
</api>
<api name="off">
@method
An alias for [removeListener](modules/sdk/event/target.html#removeListener(type, listener)).
</api>
</api>

Просмотреть файл

@ -1,78 +0,0 @@
<!-- This Source Code Form is subject to the terms of the Mozilla Public
- License, v. 2.0. If a copy of the MPL was not distributed with this
- file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
<!-- contributed by Myk Melez [myk@mozilla.org] -->
The `hidden-frame` module creates Firefox frames (i.e. XUL `<iframe>`
elements) that are not displayed to the user. It is useful in the construction
of APIs that load web content not intended to be directly seen or accessed
by users, like
[`page-worker`](modules/sdk/page-worker.html).
It is also useful in the construction of APIs
that load web content for intermittent display, such as
[`panel`](modules/sdk/panel.html).
The module exports a constructor function, `HiddenFrame`, and two other
functions, `add` and `remove`.
`HiddenFrame` constructs a new hidden frame. `add` registers a hidden frame,
preparing it to load content. `remove` unregisters a frame, unloading any
content that was loaded in it.
The following code creates a hidden frame, loads a web page into it, and then
logs its title:
var hiddenFrames = require("sdk/frame/hidden-frame");
let hiddenFrame = hiddenFrames.add(hiddenFrames.HiddenFrame({
onReady: function() {
this.element.contentWindow.location = "http://www.mozilla.org/";
let self = this;
this.element.addEventListener("DOMContentLoaded", function() {
console.log(self.element.contentDocument.title);
}, true, true);
}
}));
See the `panel` module for a real-world example of usage of this module.
<api name="HiddenFrame">
@class
`HiddenFrame` objects represent hidden frames.
<api name="HiddenFrame">
@constructor
Creates a hidden frame.
@param options {object}
Options for the frame, with the following keys:
@prop onReady {function,array}
Functions to call when the frame is ready to load content. You must specify
an `onReady` callback and refrain from using the hidden frame until
the callback gets called, because hidden frames are not always ready to load
content the moment they are added.
</api>
<api name="element">
@property {DOMElement}
The host application frame in which the page is loaded.
</api>
<api name="ready">
@event
This event is emitted when the DOM for a hidden frame content is ready.
It is equivalent to the `DOMContentLoaded` event for the content page in
a hidden frame.
</api>
</api>
<api name="add">
@function
Register a hidden frame, preparing it to load content.
@param hiddenFrame {HiddenFrame} the frame to add
</api>
<api name="remove">
@function
Unregister a hidden frame, unloading any content that was loaded in it.
@param hiddenFrame {HiddenFrame} the frame to remove
</api>

Просмотреть файл

@ -1,55 +0,0 @@
<!-- This Source Code Form is subject to the terms of the Mozilla Public
- License, v. 2.0. If a copy of the MPL was not distributed with this
- file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
The `frame/utils` module provides helper functions for working with platform
internals like [frames](https://developer.mozilla.org/en/XUL/iframe) and
[browsers](https://developer.mozilla.org/en/XUL/browser).
Module exports `create` function that takes the `nsIDOMDocument` of a
[privileged document](https://developer.mozilla.org/en/Working_with_windows_in_chrome_code)
and creates a `browser` element in its `documentElement`:
let { open } = require('sdk/window/utils');
let { create } = require('sdk/frame/utils');
let window = open('data:text/html,Foo');
let frame = create(window.document);
Optionally `create` can be passed set of `options` to configure created frame
even further.
Execution of scripts may easily be enabled:
let { open } = require('sdk/window/utils');
let { create } = require('sdk/frame/utils');
let window = open('data:text/html,top');
let frame = create(window.document, {
uri: 'data:text/html,<script>alert("Hello")</script>',
allowJavascript: true
});
<api name="create">
@function
Creates a XUL `browser` element in a privileged document.
@param document {nsIDOMDocument}
@param options {object}
Options for the new frame, with the following properties:
@prop type {String}
String that defines access type of the document loaded into it. Defaults to
`"content"`. For more details and other possible values see
[documentation on MDN](https://developer.mozilla.org/en/XUL/Attribute/browser.type)
@prop uri {String}
URI of the document to be loaded into the new frame. Defaults to `about:blank`.
@prop remote {Boolean}
If `true` separate process will be used for this frame, and
all the following options are ignored.
@prop allowAuth {Boolean}
Whether to allow auth dialogs. Defaults to `false`.
@prop allowJavascript {Boolean}
Whether to allow Javascript execution. Defaults to `false`.
@prop allowPlugins {Boolean}
Whether to allow plugin execution. Defaults to `false`.
@returns {frame}
The new [`browser`](https://developer.mozilla.org/en-US/docs/XUL/browser)
element.
</api>

Просмотреть файл

@ -1,100 +0,0 @@
<!-- This Source Code Form is subject to the terms of the Mozilla Public
- License, v. 2.0. If a copy of the MPL was not distributed with this
- file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
<!-- contributed by Irakli Gozalishvili [gozala@mozilla.com] -->
The `hotkeys` module enables add-on developers to define hotkey combinations.
To define a hotkey combination, create a `Hotkey` object, passing it the
combination and a function to be called when the user presses that
combination. For example, this add-on defines two hotkey combinations,
to show and hide a panel:
// Define keyboard shortcuts for showing and hiding a custom panel.
var { Hotkey } = require("sdk/hotkeys");
var showHotKey = Hotkey({
combo: "accel-shift-o",
onPress: function() {
showMyPanel();
}
});
var hideHotKey = Hotkey({
combo: "accel-alt-shift-o",
onPress: function() {
hideMyPanel();
}
});
## Choosing Hotkeys ##
Choose hotkey combinations with care. It's very easy to choose combinations
which clash with hotkeys defined for Firefox or for other add-ons.
If you choose any of the following commonly used Firefox combinations your
add-on will not pass AMO review:
<pre>
accel+Z, accel+C, accel+X, accel+V or accel+Q
</pre>
If you choose to use a key combination that's already defined, choose one
which makes sense for the operation it will perform. For example, `accel-S`
is typically used to save a file, but if you use it for something
completely different then it would be extremely confusing for users.
No matter what you choose, it's likely to annoy some people, and to clash
with some other add-on, so consider making the combination you choose
user-configurable.
<api name="Hotkey">
@class
Module exports `Hotkey` constructor allowing users to create a `hotkey` for the
host application.
<api name="Hotkey">
@constructor
Creates a hotkey whose `onPress` listener method is invoked when key combination
defined by `hotkey` is pressed.
If more than one `hotkey` is created for the same key combination, the listener
is executed only on the last one created.
@param options {Object}
Options for the hotkey, with the following keys:
@prop combo {String}
Any function key: `"f1, f2, ..., f24"` or key combination in the format
of `'modifier-key'`:
"accel-s"
"meta-shift-i"
"control-alt-d"
Modifier keynames:
- **shift**: The Shift key.
- **alt**: The Alt key. On the Macintosh, this is the Option key. On
Macintosh this can only be used in conjunction with another modifier,
since `Alt-Letter` combinations are reserved for entering special
characters in text.
- **meta**: The Meta key. On the Macintosh, this is the Command key.
- **control**: The Control key.
- **accel**: The key used for keyboard shortcuts on the user's platform,
which is Control on Windows and Linux, and Command on Mac. Usually, this
would be the value you would use.
@prop onPress {Function}
Function that is invoked when the key combination `hotkey` is pressed.
</api>
<api name="destroy">
@method
Stops this instance of `Hotkey` from reacting on the key combinations. Once
destroyed it can no longer be used.
</api>
</api>
[Mozilla keyboard planning FAQ]:http://www.mozilla.org/access/keyboard/
[keyboard shortcuts]:https://developer.mozilla.org/en/XUL_Tutorial/Keyboard_Shortcuts

Просмотреть файл

@ -1,177 +0,0 @@
<!-- This Source Code Form is subject to the terms of the Mozilla Public
- License, v. 2.0. If a copy of the MPL was not distributed with this
- file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
The `indexed-db` module exposes the
[IndexedDB API](https://developer.mozilla.org/en-US/docs/IndexedDB)
to add-ons.
Scripts running in web pages can access IndexedDB via the `window` object.
For example:
window.indexedDB = window.indexedDB || window.webkitIndexedDB || window.mozIndexedDB || window.msIndexedDB;
var request = window.indexedDB.open("MyDatabase");
request.onerror = function(event) {
console.log("failure");
};
request.onsuccess = function(event) {
console.log("success");
};
Because your main add-on code can't access the DOM, you can't do this.
So you can use the `indexed-db` module to access the same API:
var { indexedDB } = require('indexed-db');
var request = indexedDB.open('MyDatabase');
request.onerror = function(event) {
console.log("failure");
};
request.onsuccess = function(event) {
console.log("success");
};
Most of the objects that implement the IndexedDB API, such as
[IDBTransaction](https://developer.mozilla.org/en-US/docs/IndexedDB/IDBTransaction),
[IDBOpenDBRequest](https://developer.mozilla.org/en-US/docs/IndexedDB/IDBOpenDBRequest),
and [IDBObjectStore](https://developer.mozilla.org/en-US/docs/IndexedDB/IDBObjectStore),
are accessible through the indexedDB object itself.
The API exposed by `indexed-db` is almost identical to the DOM IndexedDB API,
so we haven't repeated its documentation here, but refer you to the
[IndexedDB API documentation](https://developer.mozilla.org/en-US/docs/IndexedDB)
for all the details.
The database created will be unique and private per add-on, and is not linked
to any website database. The module cannot be used to interact with a given
website database. See
[bug 778197](https://bugzilla.mozilla.org/show_bug.cgi?id=779197) and
[bug 786688](https://bugzilla.mozilla.org/show_bug.cgi?id=786688).
## Example
Here's a complete add-on that adds two widgets to the browser: the widget labeled
"Add" add the title of the current tab to a database, while the widget labeled
"List" lists all the titles in the database.
The add-on implements helper functions `open()`, `addItem()` and `getItems()`
to open the database, add a new item to the database, and get all items in the
database.
var { indexedDB, IDBKeyRange } = require('sdk/indexed-db');
var widgets = require("sdk/widget");
var database = {};
database.onerror = function(e) {
console.error(e.value)
}
function open(version) {
var request = indexedDB.open("stuff", version);
request.onupgradeneeded = function(e) {
var db = e.target.result;
e.target.transaction.onerror = database.onerror;
if(db.objectStoreNames.contains("items")) {
db.deleteObjectStore("items");
}
var store = db.createObjectStore("items",
{keyPath: "time"});
};
request.onsuccess = function(e) {
database.db = e.target.result;
};
request.onerror = database.onerror;
};
function addItem(name) {
var db = database.db;
var trans = db.transaction(["items"], "readwrite");
var store = trans.objectStore("items");
var time = new Date().getTime();
var request = store.put({
"name": name,
"time": time
});
request.onerror = database.onerror;
};
function getItems(callback) {
var cb = callback;
var db = database.db;
var trans = db.transaction(["items"], "readwrite");
var store = trans.objectStore("items");
var items = new Array();
trans.oncomplete = function() {
cb(items);
}
var keyRange = IDBKeyRange.lowerBound(0);
var cursorRequest = store.openCursor(keyRange);
cursorRequest.onsuccess = function(e) {
var result = e.target.result;
if(!!result == false)
return;
items.push(result.value.name);
result.continue();
};
cursorRequest.onerror = database.onerror;
};
function listItems(itemList) {
console.log(itemList);
}
open("1");
widgets.Widget({
id: "add-it",
width: 50,
label: "Add",
content: "Add",
onClick: function() {
addItem(require("sdk/tabs").activeTab.title);
}
});
widgets.Widget({
id: "list-them",
width: 50,
label: "List",
content: "List",
onClick: function() {
getItems(listItems);
}
});
<api name="indexedDB">
@property {object}
Enables you to create, open, and delete databases.
See the [IDBFactory documentation](https://developer.mozilla.org/en-US/docs/IndexedDB/IDBFactory).
</api>
<api name="IDBKeyRange">
@property {object}
Defines a range of keys.
See the [IDBKeyRange documentation](https://developer.mozilla.org/en-US/docs/IndexedDB/IDBKeyRange).
</api>
<api name="DOMException">
@property {object}
Provides more detailed information about an exception.
See the [DOMException documentation](https://developer.mozilla.org/en-US/docs/DOM/DOMException).
</api>

Просмотреть файл

@ -1,68 +0,0 @@
<!-- This Source Code Form is subject to the terms of the Mozilla Public
- License, v. 2.0. If a copy of the MPL was not distributed with this
- file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
<!-- contributed by Drew Willcoxon [adw@mozilla.com] -->
<!-- edited by Noelle Murata [fiveinchpixie@gmail.com] -->
The `byte-streams` module provides streams for reading and writing bytes.
<api name="ByteReader">
@class
<api name="ByteReader">
@constructor
Creates a binary input stream that reads bytes from a backing stream.
@param inputStream {stream}
The backing stream, an <a href="http://mxr.mozilla.org/mozilla-central/
source/xpcom/io/nsIInputStream.idl"><code>nsIInputStream</code></a>.
</api>
<api name="closed">
@property {boolean}
True if the stream is closed.
</api>
<api name="close">
@method
Closes both the stream and its backing stream. If the stream is already
closed, an exception is thrown.
</api>
<api name="read">
@method
Reads a string from the stream. If the stream is closed, an exception is
thrown.
@param [numBytes] {number}
The number of bytes to read. If not given, the remainder of the entire stream
is read.
@returns {string}
A string containing the bytes read. If the stream is at the end, returns the
empty string.
</api>
</api>
<api name="ByteWriter">
@class
<api name="ByteWriter">
@constructor
Creates a binary output stream that writes bytes to a backing stream.
@param outputStream {stream}
The backing stream, an <a href="http://mxr.mozilla.org/mozilla-central/
source/xpcom/io/nsIOutputStream.idl"><code>nsIOutputStream</code></a>.
</api>
<api name="closed">
@property {boolean}
True if the stream is closed.
</api>
<api name="close">
@method
Closes both the stream and its backing stream. If the stream is already
closed, an exception is thrown.
</api>
<api name="write">
@method
Writes a string to the stream. If the stream is closed, an exception is
thrown.
@param str {string}
The string to write.
</api>
</api>

Просмотреть файл

@ -1,150 +0,0 @@
<!-- This Source Code Form is subject to the terms of the Mozilla Public
- License, v. 2.0. If a copy of the MPL was not distributed with this
- file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
<!-- contributed by Drew Willcoxon [adw@mozilla.com] -->
<!-- contributed by Atul Varma [atul@mozilla.com] -->
<!-- edited by Noelle Murata [fiveinchpixie@gmail.com] -->
The `file` module provides access to the local filesystem.
Paths
-----
Path specifications in this API are platform-specific. This means that on
Windows paths are specified using the backslash path separator (`\`), and on
Unix-like systems like Linux and OS X paths are specified using the forward
slash path separator (`/`).
If your add-on uses literal Windows-style path specifications with this API,
your add-on likely won't work when users run it on Unix-like systems. Likewise,
if your add-on uses literal Unix-style path specifications, it won't work for
users on Windows.
To ensure your add-on works for everyone, generate paths using the
[`join`](modules/sdk/io/file.html#join(...)) function. Unfortunately
this API does not currently provide a way to obtain an absolute base path which
you could then use with `join`. For now, you need to
[`require("chrome")`](dev-guide/tutorials/chrome.html) and use the
XPCOM directory service as described at
[MDN](https://developer.mozilla.org/en/Code_snippets/File_I%2F%2FO#Getting_special_files).
Note that if you do decide to hardcode Windows-style paths, be sure to escape
backslashes in strings. For example, to specify the file at `C:\Users\Myk`, you
need to use the string `"C:\\Users\\Myk"`, not `"C:\Users\Myk"`. You can read
more about escaping characters in strings at
[MDN](https://developer.mozilla.org/en/JavaScript/Guide/Values,_Variables,_and_Literals#Escaping_Characters).
<api name="basename">
@function
Returns the last component of the given path. For example,
`basename("/foo/bar/baz")` returns `"baz"`. If the path has no components,
the empty string is returned.
@param path {string}
The path of a file.
@returns {string}
The last component of the given path.
</api>
<api name="dirname">
@function
Returns the path of the directory containing the given file. If the file is
at the top of the volume, the empty string is returned.
@param path {string}
The path of a file.
@returns {string}
The path of the directory containing the file.
</api>
<api name="exists">
@function
Returns true if a file exists at the given path and false otherwise.
@param path {string}
The path of a file.
@returns {boolean}
True if the file exists and false otherwise.
</api>
<api name="join">
@function
Takes a variable number of strings, joins them on the file system's path
separator, and returns the result.
@param ... {strings}
A variable number of strings to join. The first string must be an absolute
path.
@returns {string}
A single string formed by joining the strings on the file system's path
separator.
</api>
<api name="list">
@function
Returns an array of file names in the given directory.
@param path {string}
The path of the directory.
@returns {array}
An array of file names. Each is a basename, not a full path.
</api>
<api name="mkpath">
@function
Makes a new directory named by the given path. Any subdirectories that do not
exist are also created. `mkpath` can be called multiple times on the same
path.
@param path {string}
The path to create.
</api>
<api name="open">
@function
Returns a stream providing access to the contents of a file.
@param path {string}
The path of the file to open.
@param [mode] {string}
An optional string, each character of which describes a characteristic of the
returned stream. If the string contains `"r"`, the file is opened in
read-only mode. `"w"` opens the file in write-only mode. `"b"` opens the
file in binary mode. If `"b"` is not present, the file is opened in text
mode, and its contents are assumed to be UTF-8. If *`mode`* is not given,
`"r"` is assumed, and the file is opened in read-only text mode.
@returns {stream}
If the file is opened in text read-only `mode`, a `TextReader` is returned,
and if text write-only mode, a `TextWriter` is returned. See
[`text-streams`](modules/sdk/io/text-streams.html) for information on
these text stream objects. If the file is opened in binary read-only `mode`,
a `ByteReader` is returned, and if binary write-only mode, a `ByteWriter` is
returned. See
[`byte-streams`](modules/sdk/io/byte-streams.html) for more
information on these byte stream objects. Opened files should always be
closed after use by calling `close` on the returned stream.
</api>
<api name="read">
@function
Opens a file and returns a string containing its entire contents.
@param path {string}
The path of the file to read.
@param [mode] {string}
An optional string, each character of which describes a characteristic of the
returned stream. If the string contains `"b"`, the contents will be returned
in binary mode. If `"b"` is not present or `mode` is not given, the file
contents will be returned in text mode.
@returns {string}
A string containing the file's entire contents.
</api>
<api name="remove">
@function
Removes a file from the file system. To remove directories, use `rmdir`.
@param path {string}
The path of the file to remove.
</api>
<api name="rmdir">
@function
Removes a directory from the file system. If the directory is not empty, an
exception is thrown.
@param path {string}
The path of the directory to remove.
</api>

Просмотреть файл

@ -1,102 +0,0 @@
<!-- This Source Code Form is subject to the terms of the Mozilla Public
- License, v. 2.0. If a copy of the MPL was not distributed with this
- file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
<!-- contributed by Drew Willcoxon [adw@mozilla.com] -->
<!-- edited by Noelle Murata [fiveinchpixie@gmail.com] -->
The `text-streams` module provides streams for reading and writing text using
particular character encodings.
<api name="TextReader">
@class
<api name="TextReader">
@constructor
Creates a buffered input stream that reads text from a backing stream using a
given text encoding.
@param inputStream {stream}
The backing stream, an [`nsIInputStream`](http://mxr.mozilla.org/mozilla-central/source/xpcom/io/nsIInputStream.idl).
It must already be opened.
@param [charset] {string}
`inputStream` is expected to be in the character encoding named by this value.
If not specified, "UTF-8" is assumed. See [`nsICharsetConverterManager.idl`](http://mxr.mozilla.org/mozilla-central/source/intl/uconv/idl/nsICharsetConverterManager.idl)
for documentation on how to determine other valid values for this.
</api>
<api name="closed">
@property {boolean}
True if the stream is closed.
</api>
<api name="close">
@method
Closes both the stream and its backing stream.
</api>
<api name="read">
@method
Reads and returns a string from the stream. If the stream is closed, an
exception is thrown.
@param [numChars] {number}
The number of characters to read. If not given, the remainder of the stream
is read.
@returns {string}
The string read. If the stream is at the end, the empty string is returned.
</api>
</api>
<api name="TextWriter">
@class
<api name="TextWriter">
@constructor
Creates a buffered output stream that writes text to a backing stream using a
given text encoding.
@param outputStream {stream}
The backing stream, an [`nsIOutputStream`](http://mxr.mozilla.org/mozilla-central/source/xpcom/io/nsIOutputStream.idl).
It must already be opened.
@param [charset] {string}
Text will be written to `outputStream` using the character encoding named by
this value. If not specified, "UTF-8" is assumed. See [`nsICharsetConverterManager.idl`](http://mxr.mozilla.org/mozilla-central/source/intl/uconv/idl/nsICharsetConverterManager.idl)
for documentation on how to determine other valid values for this.
</api>
<api name="closed">
@property {boolean}
True if the stream is closed.
</api>
<api name="close">
@method
Flushes the backing stream's buffer and closes both the stream and the backing
stream. If the stream is already closed, an exception is thrown.
</api>
<api name="flush">
@method
Flushes the backing stream's buffer.
</api>
<api name="write">
@method
Writes a string to the stream. If the stream is closed, an exception is
thrown.
@param str {string}
The string to write.
</api>
<api name="writeAsync">
@method
Writes a string on a background thread. After the write completes, the
backing stream's buffer is flushed, and both the stream and the backing stream
are closed, also on the background thread. If the stream is already closed,
an exception is thrown immediately.
@param str {string}
The string to write.
@param [callback] {callback}
*`callback`*, if given, must be a function. It's called as `callback(error)`
when the write completes. `error` is an `Error` object or undefined if there
was no error. Inside *`callback`*, `this` is the `TextWriter` object.
</api>
</api>

Некоторые файлы не были показаны из-за слишком большого количества измененных файлов Показать больше