зеркало из https://github.com/mozilla/gecko-dev.git
Merge m-c to s-c.
This commit is contained in:
Коммит
46d49d4caa
2
CLOBBER
2
CLOBBER
|
@ -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 O’Shannessy
|
||||
* 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;
|
||||
}
|
||||
</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[
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head></head>
|
||||
<body>
|
||||
<script>
|
||||
window.addEventListener('message', function(event) {
|
||||
window.alert(event.data);
|
||||
}, false);
|
||||
</script>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
]]>
|
||||
</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[
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head></head>
|
||||
<body>
|
||||
<script>
|
||||
function sendMessage() {
|
||||
window.postMessage("Message from page script", "http://my-domain.org/");
|
||||
}
|
||||
</script>
|
||||
<button onclick="sendMessage()">Send Message</button>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
</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[
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head></head>
|
||||
<body>
|
||||
<script>
|
||||
document.documentElement.addEventListener("addon-message", function(event) {
|
||||
window.alert(JSON.stringify(event.detail))
|
||||
}, false);
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
</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[
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head></head>
|
||||
<body>
|
||||
<script>
|
||||
function sendMessage() {
|
||||
var event = document.createEvent('CustomEvent');
|
||||
event.initCustomEvent("addon-message", true, true, { hello: 'world' });
|
||||
document.documentElement.dispatchEvent(event);
|
||||
}
|
||||
</script>
|
||||
<button onclick="sendMessage()">Send Message</button>
|
||||
</body>
|
||||
</html>
|
||||
</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">
|
||||
<!doctype html>
|
||||
<!-- page.html -->
|
||||
<html>
|
||||
<head></head>
|
||||
<body>
|
||||
<iframe id="wikipedia" src="http://en.m.wikipedia.org/"></iframe>
|
||||
</body>
|
||||
</html>
|
||||
</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">
|
||||
<!doctype HTML>
|
||||
<!-- panel.html -->
|
||||
|
||||
<html>
|
||||
<head></head>
|
||||
<body>
|
||||
<div id="forecast_summary"></div>
|
||||
</body>
|
||||
</html>
|
||||
</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 don’t 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 won’t 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 Bugzilla’s 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[
|
||||
<statusbar id="status-bar"> <box orient="horizontal" id="librarydetector"> </box> </statusbar>
|
||||
]]>
|
||||
</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 we’ll 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 < 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. Don’t 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 module’s
|
||||
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">
|
||||
<html>
|
||||
<head>
|
||||
<meta http-equiv="Content-type" content="text/html; charset=utf-8" />
|
||||
<title>Saved annotations</title>
|
||||
<link rel="stylesheet" type="text/css" href="annotation-list.css" />
|
||||
</head>
|
||||
|
||||
<body>
|
||||
|
||||
<div id="annotation-list">
|
||||
</div>
|
||||
|
||||
<div id="template">
|
||||
<div class="annotation-details">
|
||||
<a class="url"></a>
|
||||
<div class="selection-text"></div>
|
||||
<div class="annotation-text"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</body>
|
||||
|
||||
</html>
|
||||
|
||||
</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">
|
||||
|
||||
<html>
|
||||
|
||||
<head>
|
||||
<style type="text/css" media="all">
|
||||
textarea {
|
||||
margin: 10px;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<textarea rows="10" cols="20" id="edit-box"></textarea>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
|
||||
</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">
|
||||
<html>
|
||||
<body>
|
||||
<h1 data-l10n-id="hello_id"></h1>>
|
||||
</body>
|
||||
</html>
|
||||
</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= <blink>Hello!</blink>
|
||||
</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>
|
||||
"<town_name> is <person_name>'s home town."
|
||||
</pre>
|
||||
|
||||
<pre>
|
||||
"<person_name>'s home town is <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>
|
Некоторые файлы не были показаны из-за слишком большого количества измененных файлов Показать больше
Загрузка…
Ссылка в новой задаче