зеркало из https://github.com/mozilla/gecko-dev.git
Bug 731779: Integrate the Add-on SDK loader and API libraries into Firefox (uplifting from addon-sdk a16bbd5772880b578a939eeb65102bca6560d494)
This commit is contained in:
Родитель
a3425a057a
Коммит
37d1fc5b0d
|
@ -0,0 +1,5 @@
|
|||
.gitignore export-ignore
|
||||
.hgignore export-ignore
|
||||
.hgtags export-ignore
|
||||
.gitattributes export-ignore
|
||||
python-lib/cuddlefish/_version.py export-subst
|
|
@ -0,0 +1,20 @@
|
|||
local.json
|
||||
python-lib/cuddlefish/app-extension/components/jetpack.xpt
|
||||
testdocs.tgz
|
||||
jetpack-sdk-docs.tgz
|
||||
.test_tmp/
|
||||
doc/dev-guide/
|
||||
doc/index.html
|
||||
doc/modules/
|
||||
doc/status.md5
|
||||
packages/*
|
||||
|
||||
# Python
|
||||
*.pyc
|
||||
|
||||
# OSX
|
||||
*.DS_Store
|
||||
|
||||
# Windows
|
||||
*Thumbs.db
|
||||
|
|
@ -0,0 +1,14 @@
|
|||
syntax: glob
|
||||
local.json
|
||||
python-lib/cuddlefish/app-extension/components/jetpack.xpt
|
||||
testdocs.tgz
|
||||
jetpack-sdk-docs.tgz
|
||||
.test_tmp
|
||||
jetpack-sdk-docs
|
||||
|
||||
# These should really be in a global .hgignore, but such a thing
|
||||
# seems ridiculously confusing to set up, so we'll include some
|
||||
# common intermediate files here.
|
||||
*.pyc
|
||||
*~
|
||||
*.DS_Store
|
|
@ -0,0 +1,64 @@
|
|||
39c45211aa250d43f6e54b776a60ef07a7a4cbb6 jep-31-examples
|
||||
0120642297b217680e75e68c4005a93b16466001 0.1rc1
|
||||
1fd7993ff2cec39c5948a1b2535b78c2d4c65858 0.1rc2
|
||||
530e51d02922ad05fd43e8cf113618068216b0cf 0.1
|
||||
ed11a9e3ae23aefd56ac88042b9816887d10a04f 0.2rc1
|
||||
f70ab8998abdce809bda93408435f83648909cdd 0.2rc2
|
||||
f70ab8998abdce809bda93408435f83648909cdd 0.2
|
||||
436968c6ec833d9c18a15426be26595a44b79283 0.3rc1
|
||||
a3b091d29607f0bcecf77fe27801ba901142f1c9 0.3
|
||||
06031b56312ad7f1880a9620b4d26ce253a23b23 0.3rc2
|
||||
a3b091d29607f0bcecf77fe27801ba901142f1c9 0.3
|
||||
4b08c8a8c1eef71ac3aa54e004b68e87049ca56b 0.3
|
||||
6884e54fd6a1cfa9ec63910c46d2cbf51495b584 0.3rc3
|
||||
4b08c8a8c1eef71ac3aa54e004b68e87049ca56b 0.3
|
||||
cd70b7140ec20689255f1248512a1e5b9cf90a32 0.3
|
||||
d9d733666ca8738e9665ba816d5d12f41fc0d216 0.4rc1
|
||||
8d2871fc10df16740273f53ef05815310da4b210 0.4
|
||||
201e60065b1aef23259a71756f93207cffa2abd5 0.4rc2
|
||||
8d2871fc10df16740273f53ef05815310da4b210 0.4
|
||||
c0e5bbdcafad4572a5de22e533543bf3330a869e 0.4
|
||||
201e60065b1aef23259a71756f93207cffa2abd5 0.4rc2
|
||||
a8dceaefd6f3376b7bc1619cccd854591b76ecc0 0.4rc2
|
||||
c0e5bbdcafad4572a5de22e533543bf3330a869e 0.4
|
||||
dfa3bdfedb12d743b5e0ac19d0ecf116b567a0b3 0.4
|
||||
b519a0848585e2a665a14aac2fb6f15c0a839e6c 0.4rc3
|
||||
dfa3bdfedb12d743b5e0ac19d0ecf116b567a0b3 0.4
|
||||
05acdf4987c28d8cc1d5a12d87e71b1445fdd6b7 0.4
|
||||
04b5afe48d1cee819d9f0af72ed16556a075b67d 0.5rc1
|
||||
1968150561097bcfeeb28a75801ef881d2fcbbf5 0.5
|
||||
3236e68e6f2e36353e47f01a9b8acfce56686263 0.5rc2
|
||||
1968150561097bcfeeb28a75801ef881d2fcbbf5 0.5
|
||||
0097da78aa23be8b7a5a811c4be0e3e35395af43 0.5
|
||||
be7016807a69c910ac31ff068a6c2bde0a25c9d4 0.5rc3
|
||||
0097da78aa23be8b7a5a811c4be0e3e35395af43 0.5
|
||||
83e97107e4a381b898c780b32e30e48b0e4d37f3 0.5
|
||||
182a405aa393dbd412e671e8516d2decd8e8d80c 0.6rc1
|
||||
96799a3d96abfdb1408f9b8e30479668f1feed70 0.6
|
||||
c5f6cdfa36b2ad33d8017e6844992de0b0a6c0e0 0.6rc2
|
||||
96799a3d96abfdb1408f9b8e30479668f1feed70 0.6
|
||||
65c393fbc6af7db79a4a2e903d06b17e90d254bf 0.6
|
||||
3e747e3e307a4e853d468b03357f96b2172bad37 0.7rc1
|
||||
b0e97e12eb9115f08127cc447d459f871c5046ed 0.7
|
||||
37288479d249168668abd60a907620fa7f593128 0.7rc2
|
||||
b0e97e12eb9115f08127cc447d459f871c5046ed 0.7
|
||||
db0eabc310619d00ce47372ecaafba7d2539b601 0.7
|
||||
37288479d249168668abd60a907620fa7f593128 0.7rc2
|
||||
b90bedb83525d8f32ebb4d3833e8d91efa991b6d 0.7rc2
|
||||
db0eabc310619d00ce47372ecaafba7d2539b601 0.7
|
||||
e6ea2e5c5274cbbb0278dc976844dbf51818a95e 0.7
|
||||
b90bedb83525d8f32ebb4d3833e8d91efa991b6d 0.7rc2
|
||||
9b978d823509de5d52ca222b9907f72591c68c5a 0.7rc2
|
||||
e6ea2e5c5274cbbb0278dc976844dbf51818a95e 0.7
|
||||
cd71e1e77c93adf169b9f38184b1f95d423876ba 0.7
|
||||
67e3fd285fa955898a2641226c86725365d68b95 0.8rc1
|
||||
b2f9405db6d74c21601ca9c250887411c837396a 0.8
|
||||
67e3fd285fa955898a2641226c86725365d68b95 0.8rc1
|
||||
56e8179ff5b467959ddf33978558d7eb5346ee53 0.8rc1
|
||||
b2f9405db6d74c21601ca9c250887411c837396a 0.8
|
||||
51a22a186b1c7d8e7292a5ec9670752dad223ca1 0.8
|
||||
d150cca77cfef60dee2f2fe7c892fd7fc5ce83c4 0.9rc1
|
||||
1e106beb57f65fabc32682f277de51a8101c919b 0.9
|
||||
173d56fa8cc1c5622eae70f1ae88ef789de4722b 0.9rc2
|
||||
1e106beb57f65fabc32682f277de51a8101c919b 0.9
|
||||
cde12eefd178d7a0ee7d5e9ae77f3089bcbdc6ec 0.9
|
|
@ -0,0 +1,30 @@
|
|||
The files which make up the SDK are developed by Mozilla and licensed
|
||||
under the MPL 2.0 (http://mozilla.org/MPL/2.0/), with the exception of the
|
||||
components listed below, which are made available by their authors under
|
||||
the licenses listed alongside.
|
||||
|
||||
syntaxhighlighter
|
||||
------------------
|
||||
doc/static-files/syntaxhighlighter
|
||||
Made available under the MIT license.
|
||||
|
||||
jQuery
|
||||
------
|
||||
examples/reddit-panel/data/jquery-1.4.4.min.js
|
||||
examples/annotator/data/jquery-1.4.2.min.js
|
||||
Made available under the MIT license.
|
||||
|
||||
simplejson
|
||||
----------
|
||||
python-lib/simplejson
|
||||
Made available under the MIT license.
|
||||
|
||||
Python Markdown
|
||||
---------------
|
||||
python-lib/markdown
|
||||
Made available under the BSD license.
|
||||
|
||||
LibraryDetector
|
||||
---------------
|
||||
examples/library-detector/data/library-detector.js
|
||||
Made available under the MIT license.
|
|
@ -0,0 +1,45 @@
|
|||
Add-on SDK README
|
||||
==================
|
||||
|
||||
Before proceeding, please make sure you've installed Python 2.5,
|
||||
2.6, or 2.7 (if it's not already on your system):
|
||||
|
||||
http://python.org/download/
|
||||
|
||||
Note that Python 3.0 and 3.1 are not supported in this release.
|
||||
|
||||
For Windows users, MozillaBuild (https://wiki.mozilla.org/MozillaBuild)
|
||||
will install the correct version of Python and the MSYS package, which
|
||||
will make it easier to work with the SDK.
|
||||
|
||||
To get started, first enter the same directory that this README file
|
||||
is in (the SDK's root directory) using a shell program. On Unix systems
|
||||
or on Windows with MSYS, you can execute the following command:
|
||||
|
||||
source bin/activate
|
||||
|
||||
Windows users using cmd.exe should instead run:
|
||||
|
||||
bin\activate.bat
|
||||
|
||||
Then run:
|
||||
|
||||
cfx docs
|
||||
|
||||
This should start a documentation server and open a web browser
|
||||
with further instructions.
|
||||
|
||||
If you get an error when running cfx or have any other problems getting
|
||||
started, see the "Troubleshooting" guide at:
|
||||
https://addons.mozilla.org/en-US/developers/docs/sdk/latest/dev-guide/tutorials/troubleshooting.html
|
||||
|
||||
Bugs
|
||||
-------
|
||||
|
||||
* file a bug: https://bugzilla.mozilla.org/enter_bug.cgi?product=Add-on%20SDK
|
||||
|
||||
|
||||
Style Guidelines
|
||||
--------------------
|
||||
|
||||
* https://github.com/mozilla/addon-sdk/wiki/Coding-style-guide
|
|
@ -0,0 +1,11 @@
|
|||
[App]
|
||||
Vendor=Varma
|
||||
Name=Test App
|
||||
Version=1.0
|
||||
BuildID=20060101
|
||||
Copyright=Copyright (c) 2009 Atul Varma
|
||||
ID=xulapp@toolness.com
|
||||
|
||||
[Gecko]
|
||||
MinVersion=1.9.2.0
|
||||
MaxVersion=2.0.*
|
|
@ -0,0 +1,275 @@
|
|||
/* vim:set ts=2 sw=2 sts=2 expandtab */
|
||||
/* 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/. */
|
||||
|
||||
// @see http://mxr.mozilla.org/mozilla-central/source/js/src/xpconnect/loader/mozJSComponentLoader.cpp
|
||||
|
||||
'use strict';
|
||||
|
||||
// IMPORTANT: Avoid adding any initialization tasks here, if you need to do
|
||||
// something before add-on is loaded consider addon/runner module instead!
|
||||
|
||||
const { classes: Cc, Constructor: CC, interfaces: Ci, utils: Cu,
|
||||
results: Cr, manager: Cm } = Components;
|
||||
const ioService = Cc['@mozilla.org/network/io-service;1'].
|
||||
getService(Ci.nsIIOService);
|
||||
const resourceHandler = ioService.getProtocolHandler('resource').
|
||||
QueryInterface(Ci.nsIResProtocolHandler);
|
||||
const systemPrincipal = CC('@mozilla.org/systemprincipal;1', 'nsIPrincipal')();
|
||||
const scriptLoader = Cc['@mozilla.org/moz/jssubscript-loader;1'].
|
||||
getService(Ci.mozIJSSubScriptLoader);
|
||||
|
||||
const REASON = [ 'unknown', 'startup', 'shutdown', 'enable', 'disable',
|
||||
'install', 'uninstall', 'upgrade', 'downgrade' ];
|
||||
|
||||
const bind = Function.call.bind(Function.bind);
|
||||
|
||||
let loader = null;
|
||||
let unload = null;
|
||||
let cuddlefishSandbox = null;
|
||||
let nukeTimer = null;
|
||||
|
||||
// Utility function that synchronously reads local resource from the given
|
||||
// `uri` and returns content string.
|
||||
function readURI(uri) {
|
||||
let ioservice = Cc['@mozilla.org/network/io-service;1'].
|
||||
getService(Ci.nsIIOService);
|
||||
let channel = ioservice.newChannel(uri, 'UTF-8', null);
|
||||
let stream = channel.open();
|
||||
|
||||
let cstream = Cc['@mozilla.org/intl/converter-input-stream;1'].
|
||||
createInstance(Ci.nsIConverterInputStream);
|
||||
cstream.init(stream, 'UTF-8', 0, 0);
|
||||
|
||||
let str = {};
|
||||
let data = '';
|
||||
let read = 0;
|
||||
do {
|
||||
read = cstream.readString(0xffffffff, str);
|
||||
data += str.value;
|
||||
} while (read != 0);
|
||||
|
||||
cstream.close();
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
// Utility function that converts cfx-py generated paths to a
|
||||
// module ids.
|
||||
function path2id(path) {
|
||||
// Strips out `/lib` and `.js` from package/lib/path.js
|
||||
return path.replace(/([^\/]*)\/lib/, '$1').replace(/.js$/, '');
|
||||
}
|
||||
// Utility function that takes old manifest format and creates a manifest
|
||||
// in a new format: https://github.com/mozilla/addon-sdk/wiki/JEP-Linker
|
||||
function manifestV2(manifest) {
|
||||
return Object.keys(manifest).reduce(function(result, path) {
|
||||
let entry = manifest[path];
|
||||
let id = path2id(path);
|
||||
let requirements = entry.requirements || {};
|
||||
result[id] = {
|
||||
requirements: Object.keys(requirements).reduce(function(result, path) {
|
||||
result[path] = path2id(requirements[path].path);
|
||||
return result;
|
||||
}, {})
|
||||
};
|
||||
return result
|
||||
}, {});
|
||||
}
|
||||
|
||||
// We don't do anything on install & uninstall yet, but in a future
|
||||
// we should allow add-ons to cleanup after uninstall.
|
||||
function install(data, reason) {}
|
||||
function uninstall(data, reason) {}
|
||||
|
||||
function startup(data, reasonCode) {
|
||||
try {
|
||||
let reason = REASON[reasonCode];
|
||||
// URI for the root of the XPI file.
|
||||
// 'jar:' URI if the addon is packed, 'file:' URI otherwise.
|
||||
// (Used by l10n module in order to fetch `locale` folder)
|
||||
let rootURI = data.resourceURI.spec;
|
||||
|
||||
// TODO: Maybe we should perform read harness-options.json asynchronously,
|
||||
// since we can't do anything until 'sessionstore-windows-restored' anyway.
|
||||
let options = JSON.parse(readURI(rootURI + './harness-options.json'));
|
||||
|
||||
let id = options.jetpackID;
|
||||
let name = options.name;
|
||||
// Register a new resource 'domain' for this addon which is mapping to
|
||||
// XPI's `resources` folder.
|
||||
// Generate the domain name by using jetpack ID, which is the extension ID
|
||||
// by stripping common characters that doesn't work as a domain name:
|
||||
let uuidRe =
|
||||
/^\{([0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})\}$/;
|
||||
|
||||
let domain = id.
|
||||
toLowerCase().
|
||||
replace(/@/g, '-at-').
|
||||
replace(/\./g, '-dot-').
|
||||
replace(uuidRe, '$1');
|
||||
|
||||
let prefixURI = 'resource://' + domain + '/';
|
||||
let resourcesURI = ioService.newURI(rootURI + '/resources/', null, null);
|
||||
resourceHandler.setSubstitution(domain, resourcesURI);
|
||||
|
||||
// Create path to URLs mapping supported by loader.
|
||||
let paths = Object.keys(options.metadata).reduce(function(result, name) {
|
||||
result[name + '/'] = prefixURI + name + '/lib/'
|
||||
result[name + '/tests/'] = prefixURI + name + '/tests/'
|
||||
return result
|
||||
}, {
|
||||
// Relative modules resolve to add-on package lib
|
||||
'./': prefixURI + name + '/lib/',
|
||||
'toolkit/': 'resource://gre/modules/toolkit/',
|
||||
'': 'resources:///modules/'
|
||||
});
|
||||
|
||||
// Make version 2 of the manifest
|
||||
let manifest = manifestV2(options.manifest);
|
||||
|
||||
// Import `cuddlefish.js` module using a Sandbox and bootstrap loader.
|
||||
let cuddlefishURI = prefixURI + options.loader;
|
||||
cuddlefishSandbox = loadSandbox(cuddlefishURI);
|
||||
let cuddlefish = cuddlefishSandbox.exports;
|
||||
|
||||
// Normalize `options.mainPath` so that it looks like one that will come
|
||||
// in a new version of linker.
|
||||
let main = path2id(options.mainPath);
|
||||
|
||||
unload = cuddlefish.unload;
|
||||
loader = cuddlefish.Loader({
|
||||
paths: paths,
|
||||
// modules manifest.
|
||||
manifest: manifest,
|
||||
|
||||
// Add-on ID used by different APIs as a unique identifier.
|
||||
id: id,
|
||||
// Add-on name.
|
||||
name: name,
|
||||
// Add-on version.
|
||||
version: options.metadata[name].version,
|
||||
// Add-on package descriptor.
|
||||
metadata: options.metadata[name],
|
||||
// Add-on load reason.
|
||||
loadReason: reason,
|
||||
|
||||
prefixURI: prefixURI,
|
||||
// Add-on URI.
|
||||
rootURI: rootURI,
|
||||
// options used by system module.
|
||||
// File to write 'OK' or 'FAIL' (exit code emulation).
|
||||
resultFile: options.resultFile,
|
||||
// File to write stdout.
|
||||
logFile: options.logFile,
|
||||
// Arguments passed as --static-args
|
||||
staticArgs: options.staticArgs,
|
||||
|
||||
// Arguments related to test runner.
|
||||
modules: {
|
||||
'@test/options': {
|
||||
allTestModules: options.allTestModules,
|
||||
iterations: options.iterations,
|
||||
filter: options.filter,
|
||||
profileMemory: options.profileMemory,
|
||||
stopOnError: options.stopOnError,
|
||||
verbose: options.verbose,
|
||||
parseable: options.parseable,
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
let module = cuddlefish.Module('addon-sdk/sdk/loader/cuddlefish', cuddlefishURI);
|
||||
let require = cuddlefish.Require(loader, module);
|
||||
|
||||
require('sdk/addon/runner').startup(reason, {
|
||||
loader: loader,
|
||||
main: main,
|
||||
prefsURI: rootURI + 'defaults/preferences/prefs.js'
|
||||
});
|
||||
} catch (error) {
|
||||
dump('Bootstrap error: ' + error.message + '\n' +
|
||||
(error.stack || error.fileName + ': ' + error.lineNumber) + '\n');
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
function loadSandbox(uri) {
|
||||
let proto = {
|
||||
sandboxPrototype: {
|
||||
loadSandbox: loadSandbox,
|
||||
ChromeWorker: ChromeWorker
|
||||
}
|
||||
};
|
||||
let sandbox = Cu.Sandbox(systemPrincipal, proto);
|
||||
// Create a fake commonjs environnement just to enable loading loader.js
|
||||
// correctly
|
||||
sandbox.exports = {};
|
||||
sandbox.module = { uri: uri, exports: sandbox.exports };
|
||||
sandbox.require = function (id) {
|
||||
if (id !== "chrome")
|
||||
throw new Error("Bootstrap sandbox `require` method isn't implemented.");
|
||||
|
||||
return Object.freeze({ Cc: Cc, Ci: Ci, Cu: Cu, Cr: Cr, Cm: Cm,
|
||||
CC: bind(CC, Components), components: Components,
|
||||
ChromeWorker: ChromeWorker });
|
||||
};
|
||||
scriptLoader.loadSubScript(uri, sandbox, 'UTF-8');
|
||||
return sandbox;
|
||||
}
|
||||
|
||||
function unloadSandbox(sandbox) {
|
||||
if ("nukeSandbox" in Cu)
|
||||
Cu.nukeSandbox(sandbox);
|
||||
}
|
||||
|
||||
function setTimeout(callback, delay) {
|
||||
let timer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
|
||||
timer.initWithCallback({ notify: callback }, delay,
|
||||
Ci.nsITimer.TYPE_ONE_SHOT);
|
||||
return timer;
|
||||
}
|
||||
|
||||
function shutdown(data, reasonCode) {
|
||||
let reason = REASON[reasonCode];
|
||||
if (loader) {
|
||||
unload(loader, reason);
|
||||
unload = null;
|
||||
// Avoid leaking all modules when something goes wrong with one particular
|
||||
// module. Do not clean it up immediatly in order to allow executing some
|
||||
// actions on addon disabling.
|
||||
// We need to keep a reference to the timer, otherwise it is collected
|
||||
// and won't ever fire.
|
||||
nukeTimer = setTimeout(nukeModules, 1000);
|
||||
}
|
||||
};
|
||||
|
||||
function nukeModules() {
|
||||
nukeTimer = null;
|
||||
// module objects store `exports` which comes from sandboxes
|
||||
// We should avoid keeping link to these object to avoid leaking sandboxes
|
||||
for (let key in loader.modules) {
|
||||
delete loader.modules[key];
|
||||
}
|
||||
// Direct links to sandboxes should be removed too
|
||||
for (let key in loader.sandboxes) {
|
||||
let sandbox = loader.sandboxes[key];
|
||||
delete loader.sandboxes[key];
|
||||
// Bug 775067: From FF17 we can kill all CCW from a given sandbox
|
||||
unloadSandbox(sandbox);
|
||||
}
|
||||
loader = null;
|
||||
|
||||
// both `toolkit/loader` and `system/xul-app` are loaded as JSM's via
|
||||
// `cuddlefish.js`, and needs to be unloaded to avoid memory leaks, when
|
||||
// the addon is unload.
|
||||
|
||||
unloadSandbox(cuddlefishSandbox.loaderSandbox);
|
||||
unloadSandbox(cuddlefishSandbox.xulappSandbox);
|
||||
|
||||
// Bug 764840: We need to unload cuddlefish otherwise it will stay alive
|
||||
// and keep a reference to this compartment.
|
||||
unloadSandbox(cuddlefishSandbox);
|
||||
cuddlefishSandbox = null;
|
||||
}
|
|
@ -0,0 +1,33 @@
|
|||
<?xml version="1.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/. -->
|
||||
|
||||
|
||||
<RDF xmlns="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:em="http://www.mozilla.org/2004/em-rdf#">
|
||||
<Description about="urn:mozilla:install-manifest">
|
||||
<em:id>xulapp@toolness.com</em:id>
|
||||
<em:version>1.0</em:version>
|
||||
<em:type>2</em:type>
|
||||
<em:bootstrap>true</em:bootstrap>
|
||||
<em:unpack>false</em:unpack>
|
||||
|
||||
<!-- Firefox -->
|
||||
<em:targetApplication>
|
||||
<Description>
|
||||
<em:id>{ec8030f7-c20a-464f-9b0e-13a3a9e97384}</em:id>
|
||||
<em:minVersion>18.0</em:minVersion>
|
||||
<em:maxVersion>21.0a1</em:maxVersion>
|
||||
</Description>
|
||||
</em:targetApplication>
|
||||
|
||||
<!-- Front End MetaData -->
|
||||
<em:name>Test App</em:name>
|
||||
<em:description>Harness for tests.</em:description>
|
||||
<em:creator>Mozilla Corporation</em:creator>
|
||||
<em:homepageURL></em:homepageURL>
|
||||
<em:optionsType></em:optionsType>
|
||||
<em:updateURL></em:updateURL>
|
||||
</Description>
|
||||
</RDF>
|
|
@ -0,0 +1,86 @@
|
|||
# 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/.
|
||||
|
||||
# This file must be used with "source bin/activate" *from bash*
|
||||
# you cannot run it directly
|
||||
|
||||
deactivate () {
|
||||
if [ -n "$_OLD_VIRTUAL_PATH" ] ; then
|
||||
PATH="$_OLD_VIRTUAL_PATH"
|
||||
export PATH
|
||||
unset _OLD_VIRTUAL_PATH
|
||||
fi
|
||||
|
||||
# This should detect bash and zsh, which have a hash command that must
|
||||
# be called to get it to forget past commands. Without forgetting
|
||||
# past commands the $PATH changes we made may not be respected
|
||||
if [ -n "$BASH" -o -n "$ZSH_VERSION" ] ; then
|
||||
hash -r
|
||||
fi
|
||||
|
||||
if [ -n "$_OLD_VIRTUAL_PS1" ] ; then
|
||||
PS1="$_OLD_VIRTUAL_PS1"
|
||||
export PS1
|
||||
unset _OLD_VIRTUAL_PS1
|
||||
fi
|
||||
|
||||
if [ -n "$_OLD_PYTHONPATH" ] ; then
|
||||
PYTHONPATH="$_OLD_PYTHONPATH"
|
||||
export PYTHONPATH
|
||||
unset _OLD_PYTHONPATH
|
||||
fi
|
||||
|
||||
unset CUDDLEFISH_ROOT
|
||||
|
||||
unset VIRTUAL_ENV
|
||||
if [ ! "$1" = "nondestructive" ] ; then
|
||||
# Self destruct!
|
||||
unset deactivate
|
||||
fi
|
||||
}
|
||||
|
||||
# unset irrelavent variables
|
||||
deactivate nondestructive
|
||||
|
||||
_OLD_PYTHONPATH="$PYTHONPATH"
|
||||
_OLD_VIRTUAL_PATH="$PATH"
|
||||
|
||||
VIRTUAL_ENV="`pwd`"
|
||||
|
||||
if [ "x$OSTYPE" = "xmsys" ] ; then
|
||||
CUDDLEFISH_ROOT="`pwd -W | sed s,/,\\\\\\\\,g`"
|
||||
PATH="`pwd`/bin:$PATH"
|
||||
# msys will convert any env vars with PATH in it to use msys
|
||||
# form and will unconvert before launching
|
||||
PYTHONPATH="`pwd -W`/python-lib;$PYTHONPATH"
|
||||
else
|
||||
CUDDLEFISH_ROOT="$VIRTUAL_ENV"
|
||||
PYTHONPATH="$VIRTUAL_ENV/python-lib:$PYTHONPATH"
|
||||
PATH="$VIRTUAL_ENV/bin:$PATH"
|
||||
fi
|
||||
|
||||
VIRTUAL_ENV="`pwd`"
|
||||
|
||||
export CUDDLEFISH_ROOT
|
||||
export PYTHONPATH
|
||||
export PATH
|
||||
|
||||
_OLD_VIRTUAL_PS1="$PS1"
|
||||
if [ "`basename \"$VIRTUAL_ENV\"`" = "__" ] ; then
|
||||
# special case for Aspen magic directories
|
||||
# see http://www.zetadev.com/software/aspen/
|
||||
PS1="[`basename \`dirname \"$VIRTUAL_ENV\"\``] $PS1"
|
||||
else
|
||||
PS1="(`basename \"$VIRTUAL_ENV\"`)$PS1"
|
||||
fi
|
||||
export PS1
|
||||
|
||||
# This should detect bash and zsh, which have a hash command that must
|
||||
# be called to get it to forget past commands. Without forgetting
|
||||
# past commands the $PATH changes we made may not be respected
|
||||
if [ -n "$BASH" -o -n "$ZSH_VERSION" ] ; then
|
||||
hash -r
|
||||
fi
|
||||
|
||||
python -c "from jetpack_sdk_env import welcome; welcome()"
|
|
@ -0,0 +1,134 @@
|
|||
@echo off
|
||||
rem This Source Code Form is subject to the terms of the Mozilla Public
|
||||
rem License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
rem file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
set VIRTUAL_ENV=%~dp0
|
||||
set VIRTUAL_ENV=%VIRTUAL_ENV:~0,-5%
|
||||
set CUDDLEFISH_ROOT=%VIRTUAL_ENV%
|
||||
|
||||
SET PYTHONKEY=SOFTWARE\Python\PythonCore
|
||||
|
||||
rem look for 32-bit windows and python, or 64-bit windows and python
|
||||
|
||||
SET PYTHONVERSION=2.7
|
||||
call:CheckPython PYTHONINSTALL %PYTHONKEY%\%PYTHONVERSION%\InstallPath
|
||||
if "%PYTHONINSTALL%" NEQ "" goto FoundPython
|
||||
|
||||
SET PYTHONVERSION=2.6
|
||||
call:CheckPython PYTHONINSTALL %PYTHONKEY%\%PYTHONVERSION%\InstallPath
|
||||
if "%PYTHONINSTALL%" NEQ "" goto FoundPython
|
||||
|
||||
SET PYTHONVERSION=2.5
|
||||
call:CheckPython PYTHONINSTALL %PYTHONKEY%\%PYTHONVERSION%\InstallPath
|
||||
if "%PYTHONINSTALL%" NEQ "" goto FoundPython
|
||||
|
||||
if not defined ProgramFiles(x86) goto win32
|
||||
|
||||
rem look for 32-bit python on 64-bit windows
|
||||
|
||||
SET PYTHONKEY=SOFTWARE\Wow6432Node\Python\PythonCore
|
||||
|
||||
SET PYTHONVERSION=2.7
|
||||
call:CheckPython PYTHONINSTALL %PYTHONKEY%\%PYTHONVERSION%\InstallPath
|
||||
if "%PYTHONINSTALL%" NEQ "" goto FoundPython
|
||||
|
||||
SET PYTHONVERSION=2.6
|
||||
call:CheckPython PYTHONINSTALL %PYTHONKEY%\%PYTHONVERSION%\InstallPath
|
||||
if "%PYTHONINSTALL%" NEQ "" goto FoundPython
|
||||
|
||||
SET PYTHONVERSION=2.5
|
||||
call:CheckPython PYTHONINSTALL %PYTHONKEY%\%PYTHONVERSION%\InstallPath
|
||||
if "%PYTHONINSTALL%" NEQ "" goto FoundPython
|
||||
|
||||
:win32
|
||||
|
||||
SET PYTHONVERSION=
|
||||
set PYTHONKEY=
|
||||
echo Warning: Failed to find Python installation directory
|
||||
goto :EOF
|
||||
|
||||
:FoundPython
|
||||
|
||||
if defined _OLD_PYTHONPATH (
|
||||
set PYTHONPATH=%_OLD_PYTHONPATH%
|
||||
)
|
||||
if not defined PYTHONPATH (
|
||||
set PYTHONPATH=;
|
||||
)
|
||||
set _OLD_PYTHONPATH=%PYTHONPATH%
|
||||
set PYTHONPATH=%VIRTUAL_ENV%\python-lib;%PYTHONPATH%
|
||||
|
||||
if not defined PROMPT (
|
||||
set PROMPT=$P$G
|
||||
)
|
||||
|
||||
if defined _OLD_VIRTUAL_PROMPT (
|
||||
set PROMPT=%_OLD_VIRTUAL_PROMPT%
|
||||
)
|
||||
|
||||
set _OLD_VIRTUAL_PROMPT=%PROMPT%
|
||||
set PROMPT=(%VIRTUAL_ENV%) %PROMPT%
|
||||
|
||||
if defined _OLD_VIRTUAL_PATH goto OLDPATH
|
||||
goto SKIPPATH
|
||||
:OLDPATH
|
||||
PATH %_OLD_VIRTUAL_PATH%
|
||||
|
||||
:SKIPPATH
|
||||
set _OLD_VIRTUAL_PATH=%PATH%
|
||||
PATH %VIRTUAL_ENV%\bin;%PYTHONINSTALL%;%PATH%
|
||||
set PYTHONKEY=
|
||||
set PYTHONINSTALL=
|
||||
set PYTHONVERSION=
|
||||
set key=
|
||||
set reg=
|
||||
set _tokens=
|
||||
python -c "from jetpack_sdk_env import welcome; welcome()"
|
||||
GOTO :EOF
|
||||
|
||||
:CheckPython
|
||||
::CheckPython(retVal, key)
|
||||
::Reads the registry at %2% and checks if a Python exists there.
|
||||
::Checks both HKLM and HKCU, then checks the executable actually exists.
|
||||
SET key=%2%
|
||||
SET "%~1="
|
||||
SET reg=reg
|
||||
if defined ProgramFiles(x86) (
|
||||
rem 32-bit cmd on 64-bit windows
|
||||
if exist %WINDIR%\sysnative\reg.exe SET reg=%WINDIR%\sysnative\reg.exe
|
||||
)
|
||||
rem On Vista+, the last line of output is:
|
||||
rem (default) REG_SZ the_value
|
||||
rem (but note the word "default" will be localized.
|
||||
rem On XP, the last line of output is:
|
||||
rem <NO NAME>\tREG_SZ\tthe_value
|
||||
rem (not sure if "NO NAME" is localized or not!)
|
||||
rem SO: we use ")>" as the tokens to split on, then nuke
|
||||
rem the REG_SZ and any tabs or spaces.
|
||||
FOR /F "usebackq tokens=2 delims=)>" %%A IN (`%reg% QUERY HKLM\%key% /ve 2^>NUL`) DO SET "%~1=%%A"
|
||||
rem Remove the REG_SZ
|
||||
set PYTHONINSTALL=%PYTHONINSTALL:REG_SZ=%
|
||||
rem Remove tabs (note the literal \t in the next line
|
||||
set PYTHONINSTALL=%PYTHONINSTALL: =%
|
||||
rem Remove spaces.
|
||||
set PYTHONINSTALL=%PYTHONINSTALL: =%
|
||||
if exist %PYTHONINSTALL%\python.exe goto :EOF
|
||||
rem It may be a 32bit Python directory built from source, in which case the
|
||||
rem executable is in the PCBuild directory.
|
||||
if exist %PYTHONINSTALL%\PCBuild\python.exe (set "PYTHONINSTALL=%PYTHONINSTALL%\PCBuild" & goto :EOF)
|
||||
rem Or maybe a 64bit build directory.
|
||||
if exist %PYTHONINSTALL%\PCBuild\amd64\python.exe (set "PYTHONINSTALL=%PYTHONINSTALL%\PCBuild\amd64" & goto :EOF)
|
||||
|
||||
rem And try HKCU
|
||||
FOR /F "usebackq tokens=2 delims=)>" %%A IN (`%reg% QUERY HKCU\%key% /ve 2^>NUL`) DO SET "%~1=%%A"
|
||||
set PYTHONINSTALL=%PYTHONINSTALL:REG_SZ=%
|
||||
set PYTHONINSTALL=%PYTHONINSTALL: =%
|
||||
set PYTHONINSTALL=%PYTHONINSTALL: =%
|
||||
if exist %PYTHONINSTALL%\python.exe goto :EOF
|
||||
if exist %PYTHONINSTALL%\PCBuild\python.exe (set "PYTHONINSTALL=%PYTHONINSTALL%\PCBuild" & goto :EOF)
|
||||
if exist %PYTHONINSTALL%\PCBuild\amd64\python.exe (set "PYTHONINSTALL=%PYTHONINSTALL%\PCBuild\amd64" & goto :EOF)
|
||||
rem can't find it here, so arrange to try the next key
|
||||
set PYTHONINSTALL=
|
||||
|
||||
GOTO :EOF
|
|
@ -0,0 +1,99 @@
|
|||
# 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/.
|
||||
|
||||
$Env:VIRTUAL_ENV = (gl);
|
||||
$Env:CUDDLEFISH_ROOT = $Env:VIRTUAL_ENV;
|
||||
|
||||
# http://stackoverflow.com/questions/5648931/powershell-test-if-registry-value-exists/5652674#5652674
|
||||
Function Test-RegistryValue {
|
||||
param(
|
||||
[Alias("PSPath")]
|
||||
[Parameter(Position = 0, Mandatory = $true, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true)]
|
||||
[String]$Path
|
||||
,
|
||||
[Parameter(Position = 1, Mandatory = $true)]
|
||||
[String]$Name
|
||||
,
|
||||
[Switch]$PassThru
|
||||
)
|
||||
|
||||
process {
|
||||
if (Test-Path $Path) {
|
||||
$Key = Get-Item -LiteralPath $Path
|
||||
if ($Key.GetValue($Name, $null) -ne $null) {
|
||||
if ($PassThru) {
|
||||
Get-ItemProperty $Path $Name
|
||||
} else {
|
||||
$true
|
||||
}
|
||||
} else {
|
||||
$false
|
||||
}
|
||||
} else {
|
||||
$false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$WINCURVERKEY = 'HKLM:SOFTWARE\Microsoft\Windows\CurrentVersion';
|
||||
$WIN64 = (Test-RegistryValue $WINCURVERKEY 'ProgramFilesDir (x86)');
|
||||
|
||||
if($WIN64) {
|
||||
$PYTHONKEY='HKLM:SOFTWARE\Wow6432Node\Python\PythonCore';
|
||||
}
|
||||
else {
|
||||
$PYTHONKEY='HKLM:SOFTWARE\Python\PythonCore';
|
||||
}
|
||||
|
||||
$Env:PYTHONVERSION = '';
|
||||
$Env:PYTHONINSTALL = '';
|
||||
|
||||
foreach ($version in @('2.6', '2.5', '2.4')) {
|
||||
if (Test-RegistryValue "$PYTHONKEY\$version\InstallPath" '(default)') {
|
||||
$Env:PYTHONVERSION = $version;
|
||||
}
|
||||
}
|
||||
|
||||
if ($Env:PYTHONVERSION) {
|
||||
$Env:PYTHONINSTALL = (Get-Item "$PYTHONKEY\$version\InstallPath)").'(default)';
|
||||
}
|
||||
|
||||
if ($Env:PYTHONINSTALL) {
|
||||
$Env:Path += ";$Env:PYTHONINSTALL";
|
||||
}
|
||||
|
||||
if (Test-Path Env:_OLD_PYTHONPATH) {
|
||||
$Env:PYTHONPATH = $Env:_OLD_PYTHONPATH;
|
||||
}
|
||||
else {
|
||||
$Env:PYTHONPATH = '';
|
||||
}
|
||||
|
||||
$Env:_OLD_PYTHONPATH=$Env:PYTHONPATH;
|
||||
$Env:PYTHONPATH= "$Env:VIRTUAL_ENV\python-lib;$Env:PYTHONPATH";
|
||||
|
||||
if (Test-Path Function:_OLD_VIRTUAL_PROMPT) {
|
||||
Set-Content Function:Prompt (Get-Content Function:_OLD_VIRTUAL_PROMPT);
|
||||
}
|
||||
else {
|
||||
function global:_OLD_VIRTUAL_PROMPT {}
|
||||
}
|
||||
|
||||
Set-Content Function:_OLD_VIRTUAL_PROMPT (Get-Content Function:Prompt);
|
||||
|
||||
function global:prompt { "($Env:VIRTUAL_ENV) $(_OLD_VIRTUAL_PROMPT)"; };
|
||||
|
||||
if (Test-Path Env:_OLD_VIRTUAL_PATH) {
|
||||
$Env:PATH = $Env:_OLD_VIRTUAL_PATH;
|
||||
}
|
||||
else {
|
||||
$Env:_OLD_VIRTUAL_PATH = $Env:PATH;
|
||||
}
|
||||
|
||||
$Env:Path="$Env:VIRTUAL_ENV\bin;$Env:Path"
|
||||
|
||||
[System.Console]::WriteLine("Note: this PowerShell SDK activation script is experimental.")
|
||||
|
||||
python -c "from jetpack_sdk_env import welcome; welcome()"
|
||||
|
|
@ -0,0 +1,33 @@
|
|||
#! /usr/bin/env python
|
||||
# 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/.
|
||||
|
||||
|
||||
import os
|
||||
import sys
|
||||
|
||||
# set the cuddlefish "root directory" for this process if it's not already
|
||||
# set in the environment
|
||||
cuddlefish_root = os.path.dirname(os.path.dirname(os.path.realpath(sys.argv[0])))
|
||||
|
||||
if 'CUDDLEFISH_ROOT' not in os.environ:
|
||||
os.environ['CUDDLEFISH_ROOT'] = cuddlefish_root
|
||||
|
||||
# add our own python-lib path to the python module search path.
|
||||
python_lib_dir = os.path.join(cuddlefish_root, "python-lib")
|
||||
if python_lib_dir not in sys.path:
|
||||
sys.path.insert(0, python_lib_dir)
|
||||
|
||||
# now export to env so sub-processes get it too
|
||||
if 'PYTHONPATH' not in os.environ:
|
||||
os.environ['PYTHONPATH'] = python_lib_dir
|
||||
elif python_lib_dir not in os.environ['PYTHONPATH'].split(os.pathsep):
|
||||
paths = os.environ['PYTHONPATH'].split(os.pathsep)
|
||||
paths.insert(0, python_lib_dir)
|
||||
os.environ['PYTHONPATH'] = os.pathsep.join(paths)
|
||||
|
||||
import cuddlefish
|
||||
|
||||
if __name__ == '__main__':
|
||||
cuddlefish.run()
|
|
@ -0,0 +1,6 @@
|
|||
@echo off
|
||||
rem This Source Code Form is subject to the terms of the Mozilla Public
|
||||
rem License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
rem file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
python "%VIRTUAL_ENV%\bin\cfx" %*
|
|
@ -0,0 +1,23 @@
|
|||
@echo off
|
||||
rem This Source Code Form is subject to the terms of the Mozilla Public
|
||||
rem License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
rem file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
if defined _OLD_VIRTUAL_PROMPT (
|
||||
set "PROMPT=%_OLD_VIRTUAL_PROMPT%"
|
||||
)
|
||||
set _OLD_VIRTUAL_PROMPT=
|
||||
|
||||
if defined _OLD_VIRTUAL_PATH (
|
||||
set "PATH=%_OLD_VIRTUAL_PATH%"
|
||||
)
|
||||
set _OLD_VIRTUAL_PATH=
|
||||
|
||||
if defined _OLD_PYTHONPATH (
|
||||
set "PYTHONPATH=%_OLD_PYTHONPATH%"
|
||||
)
|
||||
set _OLD_PYTHONPATH=
|
||||
|
||||
set CUDDLEFISH_ROOT=
|
||||
|
||||
:END
|
|
@ -0,0 +1,14 @@
|
|||
#!/bin/bash
|
||||
# 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/.
|
||||
|
||||
|
||||
source ./bin/activate
|
||||
if [ type -P xvfb-run ]
|
||||
then
|
||||
xvfb-run cfx $*
|
||||
else
|
||||
cfx $*
|
||||
fi
|
||||
deactivate
|
|
@ -0,0 +1,364 @@
|
|||
#!/usr/bin/env python
|
||||
# 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/.
|
||||
|
||||
import os
|
||||
import signal
|
||||
import threading
|
||||
import urllib2, urllib
|
||||
import zipfile
|
||||
import tarfile
|
||||
import subprocess
|
||||
import optparse
|
||||
import sys, re
|
||||
#import win32api
|
||||
|
||||
|
||||
class SDK:
|
||||
def __init__(self):
|
||||
try:
|
||||
# Take the current working directory
|
||||
self.default_path = os.getcwd()
|
||||
if sys.platform == "win32":
|
||||
self.mswindows = True
|
||||
else:
|
||||
self.mswindows = False
|
||||
# Take the default home path of the user.
|
||||
home = os.path.expanduser('~')
|
||||
|
||||
# The following are the parameters that can be used to pass a dynamic URL, a specific path or a binry. The binary is not used yet. It will be used in version 2.0
|
||||
# If a dynamic path is to be mentioned, it should start with a '/'. For eg. "/Desktop"
|
||||
parser = optparse.OptionParser()
|
||||
parser.add_option('-u', '--url', dest = 'url', default = 'https://ftp.mozilla.org/pub/mozilla.org/labs/jetpack/addon-sdk-latest.zip')
|
||||
parser.add_option('-p', '--path', dest = 'path', default = self.default_path)
|
||||
parser.add_option('-b', '--binary', dest = 'binary')#, default='/Applications/Firefox.app')
|
||||
(options, args) = parser.parse_args()
|
||||
|
||||
# Get the URL from the parameter
|
||||
self.link = options.url
|
||||
# Set the base path for the user. If the user supplies the path, use the home variable as well. Else, take the default path of this script as the installation directory.
|
||||
if options.path!=self.default_path:
|
||||
if self.mswindows:
|
||||
self.base_path = home + str(options.path).strip() + '\\'
|
||||
else:
|
||||
self.base_path = home + str(options.path).strip() + '/'
|
||||
else:
|
||||
if self.mswindows:
|
||||
self.base_path = str(options.path).strip() + '\\'
|
||||
else:
|
||||
self.base_path = str(options.path).strip() + '/'
|
||||
assert ' ' not in self.base_path, "You cannot have a space in your home path. Please remove the space before you continue."
|
||||
print('Your Base path is =' + self.base_path)
|
||||
|
||||
# This assignment is not used in this program. It will be used in version 2 of this script.
|
||||
self.bin = options.binary
|
||||
# if app or bin is empty, dont pass anything
|
||||
|
||||
# Search for the .zip file or tarball file in the URL.
|
||||
i = self.link.rfind('/')
|
||||
|
||||
self.fname = self.link[i+1:]
|
||||
z = re.search('zip',self.fname,re.I)
|
||||
g = re.search('gz',self.fname,re.I)
|
||||
if z:
|
||||
print 'zip file present in the URL.'
|
||||
self.zip = True
|
||||
self.gz = False
|
||||
elif g:
|
||||
print 'gz file present in the URL'
|
||||
self.gz = True
|
||||
self.zip = False
|
||||
else:
|
||||
print 'zip/gz file not present. Check the URL.'
|
||||
return
|
||||
print("File name is =" + self.fname)
|
||||
|
||||
# Join the base path and the zip/tar file name to crate a complete Local file path.
|
||||
self.fpath = self.base_path + self.fname
|
||||
print('Your local file path will be=' + self.fpath)
|
||||
except AssertionError, e:
|
||||
print e.args[0]
|
||||
sys.exit(1)
|
||||
|
||||
# Download function - to download the SDK from the URL to the local machine.
|
||||
def download(self,url,fpath,fname):
|
||||
try:
|
||||
# Start the download
|
||||
print("Downloading...Please be patient!")
|
||||
urllib.urlretrieve(url,filename = fname)
|
||||
print('Download was successful.')
|
||||
except ValueError: # Handles broken URL errors.
|
||||
print 'The URL is ether broken or the file does not exist. Please enter the correct URL.'
|
||||
raise
|
||||
except urllib2.URLError: # Handles URL errors
|
||||
print '\nURL not correct. Check again!'
|
||||
raise
|
||||
|
||||
# Function to extract the downloaded zipfile.
|
||||
def extract(self, zipfilepath, extfile):
|
||||
try:
|
||||
# Timeout is set to 30 seconds.
|
||||
timeout = 30
|
||||
# Change the directory to the location of the zip file.
|
||||
try:
|
||||
os.chdir(zipfilepath)
|
||||
except OSError:
|
||||
# Will reach here if zip file doesnt exist
|
||||
print 'O/S Error:' + zipfilepath + 'does not exist'
|
||||
raise
|
||||
|
||||
# Get the folder name of Jetpack to get the exact version number.
|
||||
if self.zip:
|
||||
try:
|
||||
f = zipfile.ZipFile(extfile, "r")
|
||||
except IOError as (errno, strerror): # Handles file errors
|
||||
print "I/O error - Cannot perform extract operation: {1}".format(errno, strerror)
|
||||
raise
|
||||
list = f.namelist()[0]
|
||||
temp_name = list.split('/')
|
||||
print('Folder Name= ' +temp_name[0])
|
||||
self.folder_name = temp_name[0]
|
||||
elif self.gz:
|
||||
try:
|
||||
f = tarfile.open(extfile,'r')
|
||||
except IOError as (errno, strerror): # Handles file errors
|
||||
print "I/O error - Cannot perform extract operation: {1}".format(errno, strerror)
|
||||
raise
|
||||
list = f.getnames()[0]
|
||||
temp_name = list.split('/')
|
||||
print('Folder Name= ' +temp_name[0])
|
||||
self.folder_name = temp_name[0]
|
||||
|
||||
print ('Starting to Extract...')
|
||||
|
||||
# Timeout code. The subprocess.popen exeutes the command and the thread waits for a timeout. If the process does not finish within the mentioned-
|
||||
# timeout, the process is killed.
|
||||
kill_check = threading.Event()
|
||||
|
||||
if self.zip:
|
||||
# Call the command to unzip the file.
|
||||
if self.mswindows:
|
||||
zipfile.ZipFile.extractall(f)
|
||||
else:
|
||||
p = subprocess.Popen('unzip '+extfile, stdout=subprocess.PIPE, shell=True)
|
||||
pid = p.pid
|
||||
elif self.gz:
|
||||
# Call the command to untar the file.
|
||||
if self.mswindows:
|
||||
tarfile.TarFile.extractall(f)
|
||||
else:
|
||||
p = subprocess.Popen('tar -xf '+extfile, stdout=subprocess.PIPE, shell=True)
|
||||
pid = p.pid
|
||||
|
||||
#No need to handle for windows because windows automatically replaces old files with new files. It does not ask the user(as it does in Mac/Unix)
|
||||
if self.mswindows==False:
|
||||
watch = threading.Timer(timeout, kill_process, args=(pid, kill_check, self.mswindows ))
|
||||
watch.start()
|
||||
(stdout, stderr) = p.communicate()
|
||||
watch.cancel() # if it's still waiting to run
|
||||
success = not kill_check.isSet()
|
||||
|
||||
# Abort process if process fails.
|
||||
if not success:
|
||||
raise RuntimeError
|
||||
kill_check.clear()
|
||||
print('Extraction Successful.')
|
||||
except RuntimeError:
|
||||
print "Ending the program"
|
||||
sys.exit(1)
|
||||
except:
|
||||
print "Error during file extraction: ", sys.exc_info()[0]
|
||||
raise
|
||||
|
||||
# Function to run the cfx testall comands and to make sure the SDK is not broken.
|
||||
def run_testall(self, home_path, folder_name):
|
||||
try:
|
||||
timeout = 500
|
||||
|
||||
self.new_dir = home_path + folder_name
|
||||
try:
|
||||
os.chdir(self.new_dir)
|
||||
except OSError:
|
||||
# Will reach here if the jetpack 0.X directory doesnt exist
|
||||
print 'O/S Error: Jetpack directory does not exist at ' + self.new_dir
|
||||
raise
|
||||
print '\nStarting tests...'
|
||||
# Timeout code. The subprocess.popen exeutes the command and the thread waits for a timeout. If the process does not finish within the mentioned-
|
||||
# timeout, the process is killed.
|
||||
kill_check = threading.Event()
|
||||
|
||||
# Set the path for the logs. They will be in the parent directory of the Jetpack SDK.
|
||||
log_path = home_path + 'tests.log'
|
||||
|
||||
# Subprocess call to set up the jetpack environment and to start the tests. Also sends the output to a log file.
|
||||
if self.bin != None:
|
||||
if self.mswindows:
|
||||
p = subprocess.Popen("bin\\activate && cfx testall -a firefox -b \"" + self.bin + "\"" , stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=True)
|
||||
proc_handle = p._handle
|
||||
(stdout,stderr) = p.communicate()
|
||||
else:
|
||||
p = subprocess.Popen('. bin/activate; cfx testall -a firefox -b ' + self.bin , stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=True)
|
||||
pid = p.pid
|
||||
(stdout,stderr) = p.communicate()
|
||||
elif self.bin == None:
|
||||
if self.mswindows:
|
||||
p=subprocess.Popen('bin\\activate && cfx testall -a firefox > '+log_path, stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=True)
|
||||
proc_handle = p._handle
|
||||
(stdout,stderr) = p.communicate()
|
||||
else:
|
||||
p = subprocess.Popen('. bin/activate; cfx testall -a firefox > '+log_path, stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=True)
|
||||
pid = p.pid
|
||||
(stdout,stderr) = p.communicate()
|
||||
|
||||
#Write the output to log file
|
||||
f=open(log_path,"w")
|
||||
f.write(stdout+stderr)
|
||||
f.close()
|
||||
|
||||
#Watchdog for timeout process
|
||||
if self.mswindows:
|
||||
watch = threading.Timer(timeout, kill_process, args=(proc_handle, kill_check, self.mswindows))
|
||||
else:
|
||||
watch = threading.Timer(timeout, kill_process, args=(pid, kill_check, self.mswindows))
|
||||
watch.start()
|
||||
watch.cancel() # if it's still waiting to run
|
||||
success = not kill_check.isSet()
|
||||
if not success:
|
||||
raise RuntimeError
|
||||
kill_check.clear()
|
||||
|
||||
if p.returncode!=0:
|
||||
print('\nAll tests were not successful. Check the test-logs in the jetpack directory.')
|
||||
result_sdk(home_path)
|
||||
#sys.exit(1)
|
||||
raise RuntimeError
|
||||
else:
|
||||
ret_code=result_sdk(home_path)
|
||||
if ret_code==0:
|
||||
print('\nAll tests were successful. Yay \o/ . Running a sample package test now...')
|
||||
else:
|
||||
print ('\nThere were errors during the tests.Take a look at logs')
|
||||
raise RuntimeError
|
||||
except RuntimeError:
|
||||
print "Ending the program"
|
||||
sys.exit(1)
|
||||
except:
|
||||
print "Error during the testall command execution:", sys.exc_info()[0]
|
||||
raise
|
||||
|
||||
def package(self, example_dir):
|
||||
try:
|
||||
timeout = 30
|
||||
|
||||
print '\nNow Running packaging tests...'
|
||||
|
||||
kill_check = threading.Event()
|
||||
|
||||
# Set the path for the example logs. They will be in the parent directory of the Jetpack SDK.
|
||||
exlog_path = example_dir + 'test-example.log'
|
||||
# Subprocess call to test the sample example for packaging.
|
||||
if self.bin!=None:
|
||||
if self.mswindows:
|
||||
p = subprocess.Popen('bin\\activate && cfx run --pkgdir examples\\reading-data --static-args="{\\"quitWhenDone\\":true}" -b \"" + self.bin + "\"' , stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=True)
|
||||
proc_handle = p._handle
|
||||
(stdout, stderr) = p.communicate()
|
||||
else:
|
||||
p = subprocess.Popen('. bin/activate; cfx run --pkgdir examples/reading-data --static-args=\'{\"quitWhenDone\":true}\' -b ' + self.bin , stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=True)
|
||||
pid = p.pid
|
||||
(stdout, stderr) = p.communicate()
|
||||
elif self.bin==None:
|
||||
if self.mswindows:
|
||||
p = subprocess.Popen('bin\\activate && cfx run --pkgdir examples\\reading-data --static-args="{\\"quitWhenDone\\":true}"', stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=True)
|
||||
proc_handle = p._handle
|
||||
(stdout, stderr) = p.communicate()
|
||||
else:
|
||||
p = subprocess.Popen('. bin/activate; cfx run --pkgdir examples/reading-data --static-args=\'{\"quitWhenDone\":true}\' ', stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=True)
|
||||
pid = p.pid
|
||||
(stdout, stderr) = p.communicate()
|
||||
|
||||
#Write the output to log file
|
||||
f=open(exlog_path,"w")
|
||||
f.write(stdout+stderr)
|
||||
f.close()
|
||||
|
||||
#Watch dog for timeout process
|
||||
if self.mswindows:
|
||||
watch = threading.Timer(timeout, kill_process, args=(proc_handle, kill_check, self.mswindows))
|
||||
else:
|
||||
watch = threading.Timer(timeout, kill_process, args=(pid, kill_check, self.mswindows))
|
||||
watch.start()
|
||||
watch.cancel() # if it's still waiting to run
|
||||
success = not kill_check.isSet()
|
||||
if not success:
|
||||
raise RuntimeError
|
||||
kill_check.clear()
|
||||
|
||||
if p.returncode != 0:
|
||||
print('\nSample tests were not executed correctly. Check the test-example log in jetpack diretory.')
|
||||
result_example(example_dir)
|
||||
raise RuntimeError
|
||||
else:
|
||||
ret_code=result_example(example_dir)
|
||||
if ret_code==0:
|
||||
print('\nAll tests pass. The SDK is working! Yay \o/')
|
||||
else:
|
||||
print ('\nTests passed with warning.Take a look at logs')
|
||||
sys.exit(1)
|
||||
|
||||
except RuntimeError:
|
||||
print "Ending program"
|
||||
sys.exit(1)
|
||||
except:
|
||||
print "Error during running sample tests:", sys.exc_info()[0]
|
||||
raise
|
||||
|
||||
def result_sdk(sdk_dir):
|
||||
log_path = sdk_dir + 'tests.log'
|
||||
print 'Results are logged at:' + log_path
|
||||
try:
|
||||
f = open(log_path,'r')
|
||||
# Handles file errors
|
||||
except IOError :
|
||||
print 'I/O error - Cannot open test log at ' + log_path
|
||||
raise
|
||||
|
||||
for line in reversed(open(log_path).readlines()):
|
||||
if line.strip()=='FAIL':
|
||||
print ('\nOverall result - FAIL. Look at the test log at '+log_path)
|
||||
return 1
|
||||
return 0
|
||||
|
||||
|
||||
def result_example(sdk_dir):
|
||||
exlog_path = sdk_dir + 'test-example.log'
|
||||
print 'Sample test results are logged at:' + exlog_path
|
||||
try:
|
||||
f = open(exlog_path,'r')
|
||||
# Handles file errors
|
||||
except IOError :
|
||||
print 'I/O error - Cannot open sample test log at ' + exlog_path
|
||||
raise
|
||||
|
||||
#Read the file in reverse and check for the keyword 'FAIL'.
|
||||
for line in reversed(open(exlog_path).readlines()):
|
||||
if line.strip()=='FAIL':
|
||||
print ('\nOverall result for Sample tests - FAIL. Look at the test log at '+exlog_path)
|
||||
return 1
|
||||
return 0
|
||||
|
||||
def kill_process(process, kill_check, mswindows):
|
||||
print '\nProcess Timedout. Killing the process. Please Rerun this script.'
|
||||
if mswindows:
|
||||
win32api.TerminateProcess(process, -1)
|
||||
else:
|
||||
os.kill(process, signal.SIGKILL)
|
||||
kill_check.set()# tell the main routine to kill. Used SIGKILL to hard kill the process.
|
||||
return
|
||||
|
||||
if __name__ == "__main__":
|
||||
obj = SDK()
|
||||
obj.download(obj.link,obj.fpath,obj.fname)
|
||||
obj.extract(obj.base_path,obj.fname)
|
||||
obj.run_testall(obj.base_path,obj.folder_name)
|
||||
obj.package(obj.base_path)
|
|
@ -0,0 +1,13 @@
|
|||
<!-- 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/. -->
|
||||
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Add-on Page</title>
|
||||
</head>
|
||||
<body>
|
||||
<p>This is an add-on page test!</p>
|
||||
</body>
|
||||
</html>
|
|
@ -0,0 +1,49 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!-- Generator: Adobe Illustrator 12.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 51448) -->
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd" [
|
||||
<!ENTITY ns_svg "http://www.w3.org/2000/svg">
|
||||
<!ENTITY ns_xlink "http://www.w3.org/1999/xlink">
|
||||
]>
|
||||
<svg version="1.1" id="svg2997" xmlns:svg="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200" viewBox="-0.5 -0.5 200 200" overflow="visible" enable-background="new -0.5 -0.5 200 200" xml:space="preserve">
|
||||
<path id="path85" fill="#FFFFFF" d="M-2559.936,6359.65c19.127,26.489,46.371,47.744,80.988,44.707 C-2479.339,6371.648-2523.898,6352.641-2559.936,6359.65z"/>
|
||||
<path id="path93" fill="#FFFFFF" d="M-2634.014,6073.506c4.082-7.232,2.571-17.81,3.831-26.684 c-9.019,4.635-15.506,14.72-24.551,18.641C-2652.134,6070.818-2640.447,6070.829-2634.014,6073.506z"/>
|
||||
<path id="path95" fill="#FFFFFF" d="M-2687.179,6018.109l-18.654,18.212c4.158,3.301,9.676,8.132,17.11,6.36L-2687.179,6018.109z"/>
|
||||
<path id="path97" fill="#FFFFFF" d="M-2731.117,5998.391c-4.356-0.363-14.41,20.156-18.785,19.857l14.762,8.728 C-2730.055,6027.281-2732.816,6001.961-2731.117,5998.391L-2731.117,5998.391z"/>
|
||||
<path id="path99" fill="#FFFFFF" d="M-2776.505,5989.09l4.736-11.668c-10.245,0.813-15.511,20.68-21.719,26.244 C-2776.37,6007.109-2775.727,6011.81-2776.505,5989.09z"/>
|
||||
<path id="path101" fill="#FFFFFF" d="M-2825.16,5962.186l-18.405,31.539l17.091,3.962L-2825.16,5962.186L-2825.16,5962.186z"/>
|
||||
<path id="path103" fill="#FFFFFF" d="M-2872.173,5967.636l-21.059,16.139C-2872.035,5992.07-2870.821,5995.181-2872.173,5967.636z"/>
|
||||
<path id="path105" fill="#FFFFFF" d="M-2922.987,5980.398c-3.084-9.172-0.18-20.226,0.396-29.914 c-4.952,9.422-16.338,16.166-17.745,27.299C-2935.489,5981.802-2928.796,5980.021-2922.987,5980.398L-2922.987,5980.398z"/>
|
||||
<g>
|
||||
<path id="path9" d="M70.165,21.946C62.09,27.292,49.432,38.35,43.382,44.795c1.491,0.208,3.068-0.957,4.751-1.179l11.635-3.192 c-4.833,4.151-15.001,11.47-17.946,17.234c3.632-1.309,11.254-5.699,15.043-6.059c-3.309,3.038-10.214,8.801-12.378,12.469 c0.261,0.124,0.08,0.445,0.12,0.669c2.846-1.635,9.763-5.91,12.9-6.199c-3.229,3.484-9.169,10.193-10.47,14.688 c2.804-1.913,9.856-5.792,12.944-6.969c-0.703,1.773-2.262,3.09-3.276,4.864c-1.399,2.453-4.711,5.246-4.834,8.161 c2.46-2.375,5.51-4.474,8.571-6.148c-1.365,2.947-3.876,5.384-4.561,8.892c2.007-2.02,5.813-3.071,8.399-4.005l-0.312,0.311 c-0.224,0.191-3.136,2.335-5.121,4.727c1.765-0.114,0.449-0.334,2.707,0.18l-0.043,0.114 c-0.154,13.494,25.501,18.427,41.224,17.743c3.314-6.419,1.525-16.789,10.088-21.247c5.764-3.067,6.416-4.742,13.068-3.491 c3.596,0.674,11.461,3.392,15.227,2.534c3.514-2.033,5.221-2.928,8.332-5.37c3.01-4.574-23.988-19.398-2.164-0.171 c5.236,1.121,6.588,0.954,8.139-1.376c0.693-1.88,2.348-6.877,3.859-10.133l0.107-1.76c-1.467-4.414-1.111-11.965-1.852-13.053 c-4.842-6.713-22.34-7.764-30.256-16.596c1.35-4.384-1.482-7.997-4.367-10.738c-4.332-3.187-10.313-1.722-14.925-0.399 c-5.255-1.099-10.117-2.986-15.233-4.48C81.381,13.626,70.165,21.946,70.165,21.946L70.165,21.946z"/>
|
||||
<path id="path11" fill="#FF1A00" d="M103.517,25.68l-3.771,1.438c-0.453-0.901,0.676-1.757,1.126-2.599 c0.322-0.616,1.247-0.961,1.465-1.705c-4.877-0.786-9.486-3.178-14.688-2.354c-5.563,0.878-8.802,3.003-12.005,5.54 c-1.617,0.885-2.396,2.142-3.968,3.201c-1.92,1.729-2.755,5.176-3.233,5.833c2.785-1.206,5.587-1.026,7.479-1.663 c-7.115,4.447-8.358,13.134-6.39,20.38l-0.116,0.159c-2.391-1.429-3.083-4.065-3.745-6.371c-0.471,1.515-0.538,3.44-0.859,5.132 c-0.764-0.713-1.184-1.734-1.61-2.644c-0.799,7.668,2.938,13.913,7.521,19.966c3.068,4.058-2.923,6.744-1.512,11.838l7.206-0.404 c0.929-0.348,0.64-0.223,0.64-0.223c-1.806,3.613-11.365,6.352-13.201,8.417c0.513,0.425,8.917-2.164,11.991-2.687l-9.045,5.112 c0.899,0.277-0.028,3.311,1.946,3.023c12.508,3.713,26.597,8.866,28.792,3.488c1.831-3.419,4.541-9.21,4.122-12.49l1.941-2.949 c-2.59,0.643-5.199,1.691-7.202,3.823c-0.599-1.256,0.342-2.763,0.81-3.951c1.343-2.41,4.026-3.686,6.151-5.056l-4.464-1.976 c-2.25-3.864-6.523-7.047-6.639-11.586l1.057,0.36c4.069,4.621,9.854,8.371,15.647,8.639c-3.943-0.338,16.471-2.743,15.271-4.472 c2.17,3.121,8.729,4.432,13.52,6.071c2.201-0.792,3.162-0.566,4.816-2.057c-0.705-0.765-1.795-1.39-2.844-1.787 c-6.295-1.738-21.162-4.796-24.365-10.986c-0.604,2.637,1.838,4.812,4.566,7.061l-5.873-0.826 c-10.331-4.782-9.083-12.984-6.813-20.78l0.785,6.508l2.025,3.548c3.851-6.966,1.835-6.1-5.898-15.494l5.832,2.228 c2.663,2.142,7.04,5.845,10.921,8.417c10.453,6.845,15.625,11.381,28.891,12.311l4.092,0.908c2.896-7.59-2.719-14.597-1.021-3.989 c-2.166-2.631-4.283-5.066-3.842-6.439c-0.76-1.147,3.143-4.014,2.811-5.319c-3.83-3.894-20.389-5.984-29.381-14.907 c-0.264,1.492,4.158,5.354,4.924,6.683c-8.918,0.088-14.174-2.651-15.127-5.542c-0.195-0.481,0.047-2.623,0.998-3.313 c1.287-0.672,2.541-1.693,4.02-1.83c-2.316-1.891-5.25,0.643-7.302,1.788c-1.594,2.737,0.889,5.788,1.202,8.014 c-2.175-0.549-5.111-4.378-5.471-6.782c-3.306,0.362-6.34-0.065-8.881-1.974c2.243-0.492,4.56-1.331,6.906-1.755 c6.143-1.34,12.598-7.655,18.803-2.72l-2.889-3.672C113.848,21.654,108.812,24.342,103.517,25.68L103.517,25.68z"/>
|
||||
<path id="path13" d="M83.254,28.459c-1.649,1.924-3.302,3.85-3.687,6.437l0.369,0.434c1.102-1.326,2.753-2.437,4.274-3.207 c0.002,2.726,1.76,5.399,4.11,6.713l0.395,0.028c-0.498-2.879-0.166-6.399,0.96-8.998c1.793-1.508,4.151-2.106,5.668-3.692 c-3.797-0.888-7.411-0.136-10.658,1.1L83.254,28.459L83.254,28.459z"/>
|
||||
<path id="path15" fill="#FFFFFF" d="M121.379,36.471c-1.561-2.157-3.777-3.888-6.594-3.642 C114.814,35.491,118.443,37.043,121.379,36.471z"/>
|
||||
<path id="path17" d="M98.854,36.815c-2.445,0.187-4.736,0.622-7.041,1.224c1.017,0.937,2.302,1.252,3.266,2.129 c-3.017,1.137-6.176,1.861-8.813,4.186l0.546,0.325c4.878-1.015,10.645-0.929,15.732,0.16l0.294-0.212l-2.948-5.361l4.533-0.981 c-2.405-2.249-5.156-4.46-8.322-5.422C96.585,34.171,97.917,35.479,98.854,36.815L98.854,36.815L98.854,36.815z"/>
|
||||
<path id="path19" d="M116.564,42.189c2.406,0.445,5.064,0.503,7.357,0.074c-3.787-1.932-8.625-1.552-11.639-4.944 C112.852,39.099,114.086,41.968,116.564,42.189z"/>
|
||||
<path id="path21" d="M80.567,55.828c0.943,3.023-2.23,6.707,0.312,9.485c2.165,2.522,4.81,4.614,7.354,6.464l-0.225-0.943 c-2.266-2.647-4.349-5.569-5.396-8.716c2.656,0.928,5.463,2.216,8.496,2.646c-3.416-5.104-9.678-9.984-7.319-16.97 c0.129-1.154,1.655-2.041,1.271-3.113C81.49,47.408,79.996,51.435,80.567,55.828L80.567,55.828z"/>
|
||||
<path id="path23" fill="#FFFFFF" d="M127.412,59.77c-0.334,0.589-0.213,1.45-0.316,2.174c0.738-0.379,1.264-1.199,2-1.518 C128.883,59.988,127.934,59.986,127.412,59.77z"/>
|
||||
<path id="path25" fill="#FFFFFF" d="M131.738,64.284l1.518-1.487c-0.336-0.267-0.785-0.661-1.395-0.518L131.738,64.284z"/>
|
||||
<path id="path27" fill="#FFFFFF" d="M135.316,65.887c0.355,0.026,1.174-1.643,1.531-1.618l-1.203-0.711 C135.229,63.532,135.455,65.593,135.316,65.887z"/>
|
||||
<path id="path29" fill="#FFFFFF" d="M139.012,66.644l-0.385,0.95c0.836-0.067,1.264-1.684,1.766-2.138 C139.002,65.177,138.949,64.793,139.012,66.644z"/>
|
||||
<path id="path31" fill="#FFFFFF" d="M142.977,68.834l1.498-2.569l-1.395-0.32L142.977,68.834z"/>
|
||||
<path id="path33" fill="#FFFFFF" d="M146.801,68.39l1.715-1.312C146.789,66.4,146.691,66.147,146.801,68.39z"/>
|
||||
<path id="path35" fill="#FFFFFF" d="M150.938,67.35c0.254,0.748,0.02,1.647-0.031,2.436c0.404-0.766,1.332-1.313,1.445-2.221 C151.957,67.233,151.412,67.382,150.938,67.35L150.938,67.35z"/>
|
||||
<path id="path37" d="M99.399,96.021c0.547,1.031,0.572,2.285,1.119,3.315c1.295-2.793,1.147-6.148-0.533-8.659 c-2.923-1.288-5.745-2.625-8.264-4.671C92.768,89.577,96.192,93.513,99.399,96.021L99.399,96.021z"/>
|
||||
<path id="path71" d="M117.387,36.775c-0.953-0.248-1.51-1.317-1.25-2.393c0.268-1.079,1.252-1.75,2.207-1.502 c0.955,0.245,1.512,1.318,1.25,2.393C119.328,36.348,118.34,37.021,117.387,36.775z"/>
|
||||
<path id="path75" d="M98.356,103.161l2.835-2.31c-2.758-1.791-7.426-5.004-9.95-7.387C93.224,97.589,93.284,102.152,98.356,103.161 z"/>
|
||||
<path id="path2225" d="M57.859,84.722c11.552,15.175,27.262,21.19,46.412,19.542l-1.629-3.542 c-17.031,0.631-31.21-3.592-40.875-17.588L57.859,84.722z"/>
|
||||
<path id="path2343" d="M10.063,170.061c2.903,0.027,5.809-0.039,8.712,0.014l-0.375,2.293l-5.096,0.02l-0.021,3.24h4.088v2.342 h-4.088v6.334h-3.22V170.061z"/>
|
||||
<path id="path3315" d="M30.648,184.227c-2.242-0.338-3.854-1.586-4.75-3.68c-0.511-1.191-0.656-2.105-0.596-3.748 c0.061-1.664,0.254-2.451,0.886-3.605c0.814-1.488,1.881-2.344,3.555-2.846c0.877-0.266,1.065-0.291,2.274-0.293 c1.095-0.004,1.446,0.029,2.078,0.201c2.303,0.627,3.802,2.172,4.426,4.559c0.288,1.105,0.265,3.764-0.042,4.846 c-0.697,2.463-2.385,4.098-4.674,4.527C33.093,184.322,31.412,184.342,30.648,184.227z M32.991,181.963 c0.926-0.281,1.579-1.137,1.876-2.463c0.205-0.91,0.238-3.596,0.058-4.547c-0.335-1.762-1.328-2.654-2.957-2.656 c-1.589-0.002-2.456,0.793-2.912,2.67c-0.214,0.883-0.238,3.438-0.041,4.396c0.279,1.357,0.877,2.211,1.761,2.512 C31.381,182.082,32.455,182.123,32.991,181.963z"/>
|
||||
<path id="flowRoot5258" d="M47.563,170.053h3.446v8.381c0,1.154,0.179,1.98,0.537,2.48c0.364,0.492,0.955,0.883,1.772,0.883 c0.823,0,1.414-0.391,1.772-0.883c0.364-0.5,0.546-1.326,0.546-2.48v-8.381h3.445v8.381c0,1.979-0.474,3.451-1.423,4.418 c-0.948,0.967-2.396,1.451-4.34,1.451c-1.939,0-3.383-0.484-4.331-1.451c-0.949-0.967-1.423-2.439-1.423-4.418V170.053"/>
|
||||
<path id="path6262" d="M68.606,170.053l3.509,0.051c1.381,2.793,2.899,5.531,4.078,8.41l0.197,0.504l-0.049-1.01l-0.18-7.951h2.948 v14.246h-3.182l-0.363-0.758c-1.15-2.398-3.13-6.629-3.602-7.697c-0.302-0.684-0.524-1.137-0.495-1.012 c0.029,0.127,0.083,2.309,0.119,4.848l0.065,4.619h-3.047V170.053z"/>
|
||||
<path id="text8203" d="M92.398,182.191l0.931,0.002c0.986,0,1.843-1.076,2.366-1.844c0.522-0.766,0.977-1.873,0.977-3.322 c0-1.424-0.431-2.496-0.905-3.213c-0.469-0.725-1.17-1.424-2.104-1.424l-1.265-0.002v9.805 M88.878,184.303v-14.25h4.209 c1.173,0,2.12,0.104,2.84,0.311c0.72,0.201,1.357,0.529,1.912,0.986c0.8,0.65,1.393,1.455,1.776,2.41 c0.39,0.949,0.584,2.084,0.584,3.404c0,1.223-0.2,2.328-0.6,3.314c-0.4,0.986-0.981,1.813-1.744,2.482 c-0.544,0.475-1.155,0.818-1.832,1.031c-0.678,0.207-1.558,0.311-2.641,0.311H88.878"/>
|
||||
<path id="path9182" d="M106.307,184.277l5.092-14.225h3.457c1.617,4.746,3.281,9.477,4.885,14.225l-3.457,0.023l-1.084-3.391 l-4.433,0.002l-1.083,3.389C108.559,184.289,107.431,184.324,106.307,184.277z M113.051,173.643l-1.51,4.887h2.873 C113.82,176.934,113.57,175.254,113.051,173.643z"/>
|
||||
<path id="path10154" d="M127.924,172.535h-3.867v-2.48c3.799,0,7.6-0.004,11.4,0c-0.002,0-0.529,2.479-0.529,2.479l-3.748,0.002 v11.768h-3.256V172.535z"/>
|
||||
<path id="path11126" d="M142.326,170.053h3.471v14.242h-3.471V170.053z"/>
|
||||
<path id="path14040" d="M159.949,184.227c-2.24-0.338-3.854-1.586-4.748-3.68c-0.512-1.191-0.656-2.105-0.598-3.748 c0.061-1.664,0.254-2.451,0.885-3.605c0.816-1.488,1.883-2.344,3.557-2.846c0.877-0.266,1.064-0.291,2.275-0.293 c1.094-0.004,1.445,0.029,2.076,0.201c2.305,0.627,3.803,2.172,4.426,4.559c0.289,1.105,0.266,3.764-0.041,4.846 c-0.697,2.463-2.387,4.098-4.676,4.527C162.395,184.322,160.713,184.342,159.949,184.227z M162.293,181.963 c0.926-0.281,1.578-1.137,1.875-2.463c0.205-0.91,0.24-3.596,0.059-4.547c-0.336-1.762-1.328-2.654-2.957-2.656 c-1.59-0.002-2.457,0.793-2.912,2.67c-0.215,0.883-0.238,3.438-0.041,4.396c0.279,1.357,0.877,2.211,1.762,2.512 C160.682,182.082,161.756,182.123,162.293,181.963z"/>
|
||||
<path id="path2224" d="M176.656,170.053l3.563,0.051c1.402,2.793,2.945,5.531,4.143,8.41l0.199,0.504l-0.049-1.01l-0.182-7.951 h2.992v14.246h-3.23l-0.369-0.758c-1.168-2.398-3.178-6.629-3.658-7.697c-0.305-0.684-0.531-1.137-0.502-1.012 c0.031,0.127,0.086,2.309,0.121,4.848l0.066,4.619h-3.094V170.053z"/>
|
||||
<g id="g3173" transform="translate(-0.4495808,1251.722)">
|
||||
<path id="path2320" d="M185.373-1089.163c-1.527-0.85-2.496-1.666-3.189-2.689l-0.402-0.596l-0.586,0.516 c-0.816,0.725-1.141,0.965-1.887,1.406c-1.727,1.02-4.043,1.439-6.92,1.252c-5.955-0.385-9.082-3.238-9.49-8.66 c-0.182-2.412,0.223-4.607,1.158-6.289c1.281-2.305,3.789-3.914,7.313-4.695c1.803-0.398,3.266-0.545,5.977-0.594l2.516-0.049 l-0.049-1.311c-0.027-0.723-0.09-1.543-0.137-1.824c-0.25-1.477-0.871-2.223-2.035-2.445c-0.902-0.17-1.32-0.18-2.357-0.053 c-2.006,0.246-4.098,1.066-6.678,2.623c-0.691,0.416-1.297,0.744-1.346,0.725c-0.096-0.037-3.463-5.682-3.461-5.799 c0.008-0.166,2.094-1.316,3.582-1.973c4.023-1.775,6.543-2.377,9.953-2.377c3.17,0,5.359,0.533,7.127,1.736 c0.693,0.473,1.736,1.551,2.115,2.186c0.35,0.586,0.777,1.646,1.012,2.51l0.189,0.691l-0.041,4.766 c-0.021,2.619-0.041,6.43-0.041,8.467c0,4.178,0,4.18,0.633,5.414c0.273,0.537,0.498,0.814,1.266,1.57 c0.855,0.842,0.922,0.93,0.809,1.059c-0.068,0.076-1.029,1.184-2.137,2.461c-1.109,1.275-2.057,2.328-2.107,2.338 C186.108-1088.786,185.756-1088.953,185.373-1089.163z M175.903-1095.453c1.229-0.154,2.246-0.641,3.182-1.518l0.508-0.477 l0.055-3.168l0.053-3.168l-1.316,0.057c-4.154,0.178-5.707,0.842-6.377,2.729c-0.213,0.596-0.295,2.16-0.146,2.787 c0.334,1.418,1.457,2.533,2.768,2.748c0.246,0.039,0.486,0.078,0.531,0.082C175.203-1095.374,175.539-1095.408,175.903-1095.453z M137.623-1089.718c-1.318-0.205-2.459-0.68-3.502-1.459c-1.109-0.826-1.762-1.803-2.238-3.348 c-0.445-1.441-0.436-1.025-0.49-18.475c-0.051-15.838-0.057-16.158-0.236-18.186c-0.102-1.135-0.162-2.086-0.137-2.111 c0.074-0.074,8.055-1.891,8.154-1.857c0.098,0.037,0.248,1.135,0.361,2.654c0.162,10.801,0.012,21.605,0.156,32.406 c0.063,2.619,0.086,2.936,0.24,3.373c0.379,1.078,0.902,1.469,1.963,1.465c0.336,0,0.662-0.018,0.727-0.039 c0.105-0.033,0.479,1.133,1.371,4.289l0.162,0.578l-1.271,0.334C141.034-1089.607,139.186-1089.476,137.623-1089.718z M154.555-1089.644c-1.066-0.125-1.982-0.391-2.85-0.822c-2.164-1.08-3.219-2.633-3.713-5.475 c-0.066-0.375-0.109-5.193-0.146-16.57c-0.053-15.98-0.055-16.049-0.25-18.383c-0.117-1.43-0.164-2.367-0.119-2.408 c0.102-0.088,8.1-1.91,8.152-1.857c0.088,0.088,0.268,1.711,0.348,3.148c0.213,11.125-0.035,22.258,0.162,33.383 c0.055,1.18,0.107,1.611,0.238,1.984c0.221,0.625,0.525,1.014,0.945,1.199c0.457,0.203,1.182,0.281,1.516,0.166 c0.174-0.063,0.283-0.066,0.316-0.014c0.078,0.129,1.402,4.816,1.369,4.85c-0.016,0.018-0.504,0.164-1.084,0.328 C158.041-1089.722,155.75-1089.501,154.555-1089.644z M67.676-1089.716c-0.134-0.018-0.61-0.076-1.059-0.127 c-0.447-0.053-1.237-0.215-1.756-0.363c-5.568-1.582-9.046-6.182-9.853-13.021c-0.124-1.047-0.123-3.988,0.001-5.09 c0.362-3.213,1.4-6.119,2.997-8.387c1.083-1.537,2.861-3.086,4.462-3.889c2.021-1.016,3.78-1.402,6.368-1.402 c2.15,0,3.536,0.219,5.156,0.816c3.931,1.449,7.106,5.004,8.254,9.238c0.922,3.398,0.905,8.645-0.037,12.051 c-0.744,2.691-2.024,4.861-3.966,6.725s-4.086,2.918-6.7,3.297C70.775-1089.757,68.143-1089.654,67.676-1089.716z M70.77-1096.027 c1.815-0.824,2.693-2.672,3.095-6.512c0.153-1.465,0.177-5.111,0.041-6.512c-0.375-3.879-1.335-5.748-3.356-6.531 c-1.055-0.41-2.505-0.303-3.577,0.262c-1.823,0.959-2.647,2.99-2.926,7.207c-0.158,2.404-0.013,5.633,0.343,7.572 c0.475,2.602,1.225,3.77,2.859,4.457c0.768,0.322,1.166,0.387,2.144,0.344C70.09-1095.769,70.293-1095.812,70.77-1096.027z M10.314-1116.745c-0.13-1.063-0.376-2.029-0.667-2.621c-0.147-0.301-0.224-0.547-0.182-0.584c0.09-0.078,7.021-1.965,7.22-1.965 c0.204,0,0.671,0.98,0.915,1.92c0.112,0.432,0.204,0.855,0.204,0.939c0,0.086,0.027,0.152,0.061,0.152 c0.034-0.002,0.391-0.277,0.794-0.615c1.52-1.27,3.532-2.127,5.465-2.324c2.115-0.217,4.02,0.1,5.551,0.92 c0.98,0.527,2.146,1.512,2.768,2.336l0.488,0.646l0.314-0.326c0.76-0.789,2.256-1.92,3.307-2.496 c0.898-0.494,2.17-0.893,3.413-1.074c1.114-0.16,3.312-0.063,4.384,0.197c1.185,0.287,2.204,0.719,2.971,1.26 c1.574,1.109,2.172,2.082,2.584,4.207c0.172,0.885,0.174,1.025,0.203,13.373l0.029,12.479H42.15v-10.934 c0-7.029-0.03-11.18-0.084-11.623c-0.198-1.623-0.574-2.096-1.798-2.268c-1.429-0.199-3.438,0.574-5.267,2.025l-0.667,0.529 l0,22.27h-7.729c-0.473-7.383,0.652-15.438-0.186-22.727c-0.296-1.432-0.807-1.955-2.059-2.107 c-1.462-0.178-3.452,0.498-5.153,1.75l-0.664,0.488l-0.005,22.596h-7.979C10.282-1099.062,11.051-1108.048,10.314-1116.745z M86.536-1095.492l14.459-19.949l-13.248-0.043v-5.779h23.043v5.578c-4.503,6.535-9.129,12.986-13.598,19.545l14.179,0.037 l-0.059,0.182l-1.888,5.559l-22.899,0.041L86.536-1095.492z M116.735-1120.853l0.184-0.041c2.588-0.43,5.186-0.809,7.775-1.227 l0.266-0.045v31.844h-8.225V-1120.853z M120.276-1124.939c-1.963-0.195-3.682-1.678-4.188-3.611 c-0.703-2.688,0.707-5.361,3.273-6.201c0.484-0.158,0.754-0.191,1.592-0.191c1.578,0,2.482,0.357,3.508,1.381 c0.986,0.986,1.412,2.068,1.418,3.586c0.004,1.563-0.406,2.584-1.457,3.637C123.332-1125.245,121.934-1124.773,120.276-1124.939z"/>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
После Ширина: | Высота: | Размер: 16 KiB |
Двоичный файл не отображается.
После Ширина: | Высота: | Размер: 1.4 KiB |
|
@ -0,0 +1 @@
|
|||
div { border: 10px solid black; }
|
|
@ -0,0 +1,5 @@
|
|||
/* 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/. */
|
||||
|
||||
// test-content-symbiont
|
|
@ -0,0 +1,5 @@
|
|||
/* 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/. */
|
||||
|
||||
self.on("context", function () true);
|
|
@ -0,0 +1,20 @@
|
|||
<!DOCTYPE HTML>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<script>
|
||||
window.addEventListener("message", function(msg) {
|
||||
parent.postMessage(msg.data, "*");
|
||||
});
|
||||
|
||||
parent.postMessage({
|
||||
first: "a string",
|
||||
second: ["an", "array"],
|
||||
third: {an: 'object'}
|
||||
}, '*');
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
<h1>Inner iframe</h1>
|
||||
</body>
|
||||
</html>
|
|
@ -0,0 +1,9 @@
|
|||
<!DOCTYPE HTML>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
</head>
|
||||
<body>
|
||||
<iframe id="inner" src="about:blank"></iframe>
|
||||
</body>
|
||||
</html>
|
|
@ -0,0 +1,11 @@
|
|||
|
||||
var count = 0
|
||||
|
||||
setTimeout(function() {
|
||||
window.addEventListener("message", function(msg) {
|
||||
if (++count > 1) self.postMessage(msg.data);
|
||||
else msg.source.postMessage(msg.data, '*');
|
||||
});
|
||||
|
||||
document.getElementById('inner').src = iframePath;
|
||||
}, 0);
|
|
@ -0,0 +1,6 @@
|
|||
/* 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/. */
|
||||
|
||||
const TEST_VALUE = 11;
|
||||
|
|
@ -0,0 +1 @@
|
|||
Hello, ゼロ!
|
|
@ -0,0 +1,12 @@
|
|||
<!-- 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/. -->
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Page Mod test</title>
|
||||
</head>
|
||||
<body>
|
||||
<p id="paragraph">Lorem ipsum dolor sit amet.</p>
|
||||
</body>
|
||||
</html>
|
|
@ -0,0 +1,13 @@
|
|||
<!-- 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/. -->
|
||||
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Page Worker test</title>
|
||||
</head>
|
||||
<body>
|
||||
<p id="paragraph">Lorem ipsum dolor sit amet.</p>
|
||||
</body>
|
||||
</html>
|
|
@ -0,0 +1,29 @@
|
|||
/* 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/. */
|
||||
|
||||
|
||||
// get title directly
|
||||
self.postMessage(["equal", document.title, "Page Worker test",
|
||||
"Correct page title accessed directly"]);
|
||||
|
||||
// get <p> directly
|
||||
let p = document.getElementById("paragraph");
|
||||
self.postMessage(["ok", !!p, "<p> can be accessed directly"]);
|
||||
self.postMessage(["equal", p.firstChild.nodeValue,
|
||||
"Lorem ipsum dolor sit amet.",
|
||||
"Correct text node expected"]);
|
||||
|
||||
// Modify page
|
||||
let div = document.createElement("div");
|
||||
div.setAttribute("id", "block");
|
||||
div.appendChild(document.createTextNode("Test text created"));
|
||||
document.body.appendChild(div);
|
||||
|
||||
// Check back the modification
|
||||
div = document.getElementById("block");
|
||||
self.postMessage(["ok", !!div, "<div> can be accessed directly"]);
|
||||
self.postMessage(["equal", div.firstChild.nodeValue,
|
||||
"Test text created", "Correct text node expected"]);
|
||||
self.postMessage(["done"]);
|
||||
|
|
@ -0,0 +1,18 @@
|
|||
<!-- 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/. -->
|
||||
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Worker test</title>
|
||||
</head>
|
||||
<body>
|
||||
<p id="paragraph">Lorem ipsum dolor sit amet.</p>
|
||||
<script>
|
||||
addon.port.on('addon-to-document', function (arg) {
|
||||
addon.port.emit('document-to-addon', arg);
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
|
@ -0,0 +1,13 @@
|
|||
<!-- 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/. -->
|
||||
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>foo</title>
|
||||
</head>
|
||||
<body>
|
||||
<p>bar</p>
|
||||
</body>
|
||||
</html>
|
|
@ -0,0 +1,908 @@
|
|||
<!-- 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 only
|
||||
applicable to a subset of the commands.
|
||||
|
||||
## Supported Commands ##
|
||||
|
||||
### cfx docs ###
|
||||
|
||||
This command displays the documentation for the SDK. The documentation is
|
||||
shipped with the SDK in [Markdown](http://daringfireball.net/projects/markdown/)
|
||||
format. The first time this command is executed, and any time after the
|
||||
Markdown files on disk have changed, `cfx docs` will generate a set of HTML
|
||||
pages from them and launch a web browser to display them. If the Markdown files
|
||||
haven't changed, `cfx docs` just launches a browser initialized to the set of
|
||||
generated pages.
|
||||
|
||||
To regenerate the documentation associated with a single file, you can
|
||||
specify the file as an argument. For example:
|
||||
|
||||
<pre>
|
||||
cfx docs doc/dev-guide-source/addon-development/cfx-tool.md
|
||||
</pre>
|
||||
|
||||
This command will regenerate only the HTML page you're reading.
|
||||
This is useful if you're iteratively editing a single file, and don't want to wait for cfx to
|
||||
regenerate the complete documentation tree.
|
||||
|
||||
### cfx init ####
|
||||
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>
|
||||
|
||||
### cfx run ###
|
||||
|
||||
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>--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>
|
||||
|
||||
### cfx test ###
|
||||
|
||||
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>--use-server</code>
|
||||
</td>
|
||||
<td>
|
||||
Run tests using a server previously started with <code>cfx develop</code>.
|
||||
</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>
|
||||
|
||||
### cfx xpi ###
|
||||
|
||||
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
|
||||
--update-url https://example.com/addon/update_rdf
|
||||
</pre>
|
||||
|
||||
`cfx` will create two files:
|
||||
|
||||
* an XPI file which embeds
|
||||
`https://example.com/addon/update_rdf` as the value of `updateURL`
|
||||
* an RDF file which embeds `https://example.com/addon/latest` 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>
|
||||
|
||||
## Experimental Commands ##
|
||||
|
||||
### cfx develop ###
|
||||
|
||||
This initiates an instance of a host application in development mode,
|
||||
and allows you to pipe commands into it from another shell without
|
||||
having to constantly restart it. Aside from convenience, for SDK
|
||||
Platform developers this has the added benefit of making it easier to
|
||||
detect leaks.
|
||||
|
||||
For example, in shell A, type:
|
||||
|
||||
<pre>
|
||||
cfx develop
|
||||
</pre>
|
||||
|
||||
In shell B, type:
|
||||
|
||||
<pre>
|
||||
cfx test --use-server
|
||||
</pre>
|
||||
|
||||
This will send `cfx test --use-server` output to shell A. If you repeat the
|
||||
command in shell B, `cfx test --use-server` output will appear again in shell A
|
||||
without restarting the host application.
|
||||
|
||||
`cfx develop` doesn't take any options.
|
||||
|
||||
## Internal Commands ##
|
||||
|
||||
### cfx sdocs ###
|
||||
|
||||
Executing this command builds a static HTML version of the SDK documentation
|
||||
that can be hosted on a web server without the special application support
|
||||
required by `cfx docs`.
|
||||
|
||||
#### 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.
|
|
@ -0,0 +1,46 @@
|
|||
<!-- 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
|
||||
the host application for your add-on from the command line (for example, by
|
||||
executing `cfx run` or `cfx test`) then these messages appear in the command
|
||||
shell you used. If the add-on has been installed in the host application, then
|
||||
the messages appear in the host application's
|
||||
[Error Console](https://developer.mozilla.org/en/Error_Console).
|
||||
|
||||
The `console` object has the following methods:
|
||||
|
||||
<code>console.**log**(*object*[, *object*, ...])</code>
|
||||
|
||||
Logs an informational message to the shell.
|
||||
Depending on the console's underlying implementation and user interface,
|
||||
you may be able to introspect into the properties of non-primitive objects
|
||||
that are logged.
|
||||
|
||||
<code>console.**info**(*object*[, *object*, ...])</code>
|
||||
|
||||
A synonym for `console.log()`.
|
||||
|
||||
<code>console.**warn**(*object*[, *object*, ...])</code>
|
||||
|
||||
Logs a warning message.
|
||||
|
||||
<code>console.**error**(*object*[, *object*, ...])</code>
|
||||
|
||||
Logs an error message.
|
||||
|
||||
<code>console.**debug**(*object*[, *object*, ...])</code>
|
||||
|
||||
Logs a debug message.
|
||||
|
||||
<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.
|
||||
|
||||
<code>console.**trace**()</code>
|
||||
|
||||
Logs a stack trace at the point this function is called.
|
|
@ -0,0 +1,68 @@
|
|||
<!-- 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:
|
||||
|
||||
* Adamantium
|
||||
* Ehsan Akhgari
|
||||
* arky
|
||||
* [Heather Arthur](https://github.com/harthur)
|
||||
* Dietrich Ayala
|
||||
* [Romain B](https://github.com/Niamor)
|
||||
* Will Bamberg
|
||||
* Zbigniew Braniecki
|
||||
* Daniel Buchner
|
||||
* James Burke
|
||||
* [Shane Caraveo](https://github.com/mixedpuppy)
|
||||
* [Matěj Cepl](https://github.com/mcepl)
|
||||
* Hernán Rodriguez Colmeiro
|
||||
* [David Creswick](https://github.com/dcrewi)
|
||||
* dexter
|
||||
* [Matteo Ferretti (ZER0)](https://github.com/ZER0)
|
||||
* fuzzykiller
|
||||
* [Marcio Galli](https://github.com/taboca)
|
||||
* [Ben Gillbanks](http://www.iconfinder.com/browse/iconset/circular_icons/)
|
||||
* Felipe Gomes
|
||||
* Irakli Gozalishvili
|
||||
* Luca Greco
|
||||
* Mark Hammond
|
||||
* Lloyd Hilaiel
|
||||
* Bobby Holley
|
||||
* Eric H. Jung
|
||||
* Hrishikesh Kale
|
||||
* Wes Kocher
|
||||
* Edward Lee
|
||||
* Myk Melez
|
||||
* Zandr Milewski
|
||||
* Noelle Murata
|
||||
* Joe R. Nassimian ([placidrage](https://github.com/placidrage))
|
||||
* Nick Nguyen
|
||||
* [ongaeshi](https://github.com/ongaeshi)
|
||||
* Paul O’Shannessy
|
||||
* l.m.orchard
|
||||
* Alexandre Poirot
|
||||
* Nickolay Ponomarev
|
||||
* Aza Raskin
|
||||
* Till Schneidereit
|
||||
* Justin Scott
|
||||
* Ayan Shah
|
||||
* [skratchdot](https://github.com/skratchdot)
|
||||
* [Mihai Sucan](https://github.com/mihaisucan)
|
||||
* Clint Talbert
|
||||
* Thomas
|
||||
* Dave Townsend
|
||||
* Peter Van der Beken
|
||||
* Atul Varma
|
||||
* [Erik Vold](https://github.com/erikvold)
|
||||
* Vladimir Vukicevic
|
||||
* Brian Warner
|
||||
* [Henri Wiechers](https://github.com/hwiechers)
|
||||
* Drew Willcoxon
|
||||
* Piotr Zalewa
|
||||
* [David Guo](https://github.com/dglol)
|
||||
* [Nils Maier](https://github.com/nmaier)
|
||||
* [Louis-Rémi Babé](https://github.com/louisremi)
|
||||
* [Matthias Tylkowski](https://github.com/tylkomat)
|
|
@ -0,0 +1,73 @@
|
|||
<!-- 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
|
|
@ -0,0 +1,164 @@
|
|||
<!-- 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.
|
|
@ -0,0 +1,246 @@
|
|||
<!-- 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);
|
||||
|
|
@ -0,0 +1,93 @@
|
|||
<!-- 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:
|
||||
|
||||
**[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.
|
||||
|
||||
**[widget](modules/sdk/widget.html)**<br>
|
||||
Host an add-on's user interface, including web content.
|
||||
|
||||
**[context-menu](modules/sdk/context-menu.html)**<br>
|
||||
Add items to the browser's context menu.
|
||||
|
||||
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
|
||||
* [Example](dev-guide/guides/content-scripts/reddit-example.html):
|
||||
a simple example add-on using content scripts
|
|
@ -0,0 +1,79 @@
|
|||
<!-- 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.
|
|
@ -0,0 +1,71 @@
|
|||
<!-- 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).
|
|
@ -0,0 +1,183 @@
|
|||
<!-- 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 which defines two
|
||||
functions:
|
||||
|
||||
**`emit()`** is used to emit an event. It may be called with any number of
|
||||
parameters, but is most likely to be called with a name for the event 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>
|
||||
|
||||
port.emit("myEvent", myEventPayload);
|
||||
|
||||
**`on()`** takes two parameters: the name of the event and a function to handle it:
|
||||
|
||||
port.on("myEvent", function handleMyEvent(myEventPayload) {
|
||||
// Handle the event
|
||||
});
|
||||
|
||||
Here's 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");
|
||||
|
||||
We could depict the interface between add-on code and content script code like
|
||||
this:
|
||||
|
||||
<img class="image-center" src="static-files/media/content-scripting-events.png"
|
||||
alt="Content script events">
|
||||
|
||||
Events are asynchronous: that is, the sender does not wait for a reply from
|
||||
the recipient but just emits the event and continues processing.
|
||||
|
||||
## 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 an event from a content script:
|
||||
|
||||
self.port.emit("myContentScriptEvent", myContentScriptEventPayload);
|
||||
|
||||
To receive an event from the add-on code:
|
||||
|
||||
self.port.on("myAddonEvent", function(myAddonEventPayload) {
|
||||
// Handle the event
|
||||
});
|
||||
|
||||
Compare this to the technique used to receive _built-in_ events in the
|
||||
content script. For example, to receive the `context` event 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 event
|
||||
});
|
||||
|
||||
So the `port` property is essentially used here as a namespace for
|
||||
user-defined events.
|
||||
|
||||
## 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` 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 events 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 events 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 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
|
||||
events from the content script, and take a reference to the worker so as to
|
||||
emit events to it.
|
||||
|
||||
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 events:
|
||||
|
||||
* `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
|
||||
|
||||
## <a name="json_serializable">JSON-Serializable Values</a> ##
|
||||
|
||||
The payload for an event can be any JSON-serializable value. When events are
|
||||
sent their payloads are automatically serialized, and when events 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]);
|
||||
});
|
||||
}
|
||||
});
|
|
@ -0,0 +1,184 @@
|
|||
<!-- 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 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
|
||||
}
|
||||
});
|
||||
|
||||
## Timing Issues Using postMessage ##
|
||||
|
||||
Content scripts are loaded according to the value of the
|
||||
[`contentScriptWhen`](dev-guide/guides/content-scripts/loading.html)
|
||||
option: until that point is reached, any attempt to send a message to
|
||||
the script using `postMessage()` will trigger an exception, probably
|
||||
this:
|
||||
|
||||
<span class="aside">
|
||||
This is a generic message which is emitted whenever we try to
|
||||
send a message to a content script, but can't find the worker
|
||||
which is supposed to receive it.
|
||||
</span>
|
||||
|
||||
<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>
|
||||
|
||||
So code like this, where we create a panel and then
|
||||
synchronously send it a message using `postMessage()`, will not work:
|
||||
|
||||
var data = require("sdk/self").data;
|
||||
|
||||
var panel = require("sdk/panel").Panel({
|
||||
contentURL: "http://www.bbc.co.uk/mobile/index.html",
|
||||
contentScriptFile: data.url("panel.js")
|
||||
});
|
||||
|
||||
panel.postMessage("hi from main.js");
|
||||
|
||||
[`port.emit()`](dev-guide/guides/content-scripts/using-port.html)
|
||||
queues messages until the content script is ready to receive them,
|
||||
so the equivalent code using `port.emit()` will work:
|
||||
|
||||
var data = require("sdk/self").data;
|
||||
|
||||
var panel = require("sdk/panel").Panel({
|
||||
contentURL: "http://www.bbc.co.uk/mobile/index.html",
|
||||
contentScriptFile: data.url("panel.js")
|
||||
});
|
||||
|
||||
panel.port.emit("hi from main.js");
|
||||
|
||||
|
||||
## 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);
|
||||
});
|
||||
}
|
||||
});
|
|
@ -0,0 +1,153 @@
|
|||
<!-- 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 two listeners with the
|
||||
[`private-browsing`](modules/sdk/private-browsing.html) module to
|
||||
listen for the `start` and `stop` events, and logs a string to the console
|
||||
reporting the change:
|
||||
|
||||
var pb = require("sdk/private-browsing");
|
||||
|
||||
pb.on("start", function() {
|
||||
console.log("Private browsing is on");
|
||||
});
|
||||
|
||||
pb.on("stop", function() {
|
||||
console.log("Private browsing is off");
|
||||
});
|
||||
|
||||
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
|
||||
`private-browsing` events, 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 private-browsing's `start`
|
||||
event, enter and exit private browsing, then remove the first listener and
|
||||
enter private browsing again.
|
||||
|
||||
var pb = require("sdk/private-browsing");
|
||||
|
||||
function listener1() {
|
||||
console.log("Listener 1");
|
||||
pb.removeListener("start", listener1);
|
||||
}
|
||||
|
||||
function listener2() {
|
||||
console.log("Listener 2");
|
||||
}
|
||||
|
||||
pb.on("start", listener1);
|
||||
pb.on("start", listener2);
|
||||
|
||||
pb.activate();
|
||||
pb.deactivate();
|
||||
pb.activate();
|
||||
|
||||
Removing listeners is optional since they will be removed in any case
|
||||
when the application or add-on is unloaded.
|
||||
|
|
@ -0,0 +1,103 @@
|
|||
<!-- 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.
|
|
@ -0,0 +1,190 @@
|
|||
<!-- 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="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/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>
|
|
@ -0,0 +1,218 @@
|
|||
<!-- 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" />
|
|
@ -0,0 +1,231 @@
|
|||
<!-- 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 ##
|
||||
|
||||
All the modules supplied with the SDK can be found in the "lib"
|
||||
directory under the SDK root. The following diagram shows a reduced view
|
||||
of the SDK tree, with the "lib" directory highlighted.
|
||||
|
||||
<ul class="tree">
|
||||
<li>addon-sdk
|
||||
<ul>
|
||||
<li>app-extension</li>
|
||||
<li>bin</li>
|
||||
<li>data</li>
|
||||
<li>doc</li>
|
||||
<li>examples</li>
|
||||
<li class="highlight-tree-node">lib
|
||||
<ul>
|
||||
<li>sdk
|
||||
<ul>
|
||||
<li>core
|
||||
<ul>
|
||||
<li>heritage.js</li>
|
||||
<li>namespace.js</li>
|
||||
</ul>
|
||||
</li>
|
||||
<li>panel.js</li>
|
||||
<li>page-mod.js</li>
|
||||
</ul>
|
||||
</li>
|
||||
<li>toolkit
|
||||
<ul>
|
||||
<li>loader.js</li>
|
||||
</ul>
|
||||
</li>
|
||||
</ul>
|
||||
</li>
|
||||
<li>python-lib</li>
|
||||
<li>test</li>
|
||||
</ul>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
All modules that are specifically intended for users of the
|
||||
SDK are stored under "lib" in the "sdk" directory.
|
||||
|
||||
[High-level modules](dev-guide/high-level-apis.html) like
|
||||
[`panel`](modules/sdk/panel.html) and
|
||||
[`page-mod`](modules/sdk/page-mod.html) are directly underneath
|
||||
the "sdk" directory.
|
||||
|
||||
[Low-level modules](dev-guide/low-level-apis.html) like
|
||||
[`heritage`](modules/sdk/core/heritage.html) and
|
||||
[`namespace`](modules/sdk/core/heritage.html) are grouped in subdirectories
|
||||
of "sdk" such as "core".
|
||||
|
||||
Very generic, platform-agnostic modules that are shared with other
|
||||
projects, such as [`loader`](modules/toolkit/loader.html), are stored
|
||||
in "toolkit".
|
||||
|
||||
<div style="clear:both"></div>
|
||||
|
||||
To use SDK modules, you can pass `require` a complete path from
|
||||
(but not including) the "lib" directory 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');
|
||||
|
||||
For high-level modules only, you can also pass just the name of the module:
|
||||
|
||||
var tabs = require("tabs");
|
||||
|
||||
However, this is ambiguous, as it could also refer to a local module in your
|
||||
add-on named `tabs`. For this reason it is better to use the full path from
|
||||
"lib".
|
||||
|
||||
## 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";
|
|
@ -0,0 +1,33 @@
|
|||
<!-- 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.
|
|
@ -0,0 +1,107 @@
|
|||
<!-- 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.
|
|
@ -0,0 +1,104 @@
|
|||
<!-- 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 three 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>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.
|
|
@ -0,0 +1,119 @@
|
|||
<!-- 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>
|
|
@ -0,0 +1,325 @@
|
|||
<!-- 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="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.
|
||||
|
||||
Suppose an add-on wants to make a cross-domain XMLHttpRequest based on some
|
||||
data extracted from a web page. In a XUL-based extension you would implement
|
||||
all this in a single script. An SDK-based equivalent would need to be
|
||||
structured like this:
|
||||
|
||||
* the main add-on code (1) attaches a content script to the page, and (2)
|
||||
registers a listener function for messages from the content script
|
||||
* the content script (3) extracts the data from the page and (4) sends
|
||||
it to the main add-on code in a message
|
||||
* the main add-on code (5) receives the message and (6) sends the request,
|
||||
using the SDK's [`request`](modules/sdk/request.html) API
|
||||
|
||||
<img class="image-center" src="static-files/media/xul-migration-cs.png"
|
||||
alt="Content script organization">
|
||||
|
||||
There are two related reasons for this design. The first is security: it
|
||||
reduces the risk that a malicious web page will be able to access privileged
|
||||
APIs. The second is the need to be compatible with the multi-process architecture
|
||||
planned for Firefox: after this is implemented in Firefox, all add-ons will
|
||||
need to use a similar pattern, so it's likely that a XUL-based add-on will
|
||||
need to be rewritten anyway.
|
||||
|
||||
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.
|
|
@ -0,0 +1,16 @@
|
|||
<!-- 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.
|
|
@ -0,0 +1,155 @@
|
|||
<!-- 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#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#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#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>
|
||||
<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>
|
||||
|
||||
</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>
|
|
@ -0,0 +1,34 @@
|
|||
<!-- 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.
|
|
@ -0,0 +1,121 @@
|
|||
<!-- 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 Specification #
|
||||
|
||||
A *package* is a directory that, at minimum, contains a JSON file
|
||||
called `package.json`. This file is also referred to as the
|
||||
*package manifest*.
|
||||
|
||||
## The Package Manifest ##
|
||||
|
||||
`package.json` may contain the following keys:
|
||||
|
||||
* `name` - the name of the package. The package system will only load
|
||||
one package with a given name. This name cannot contain spaces or periods.
|
||||
The name defaults to the name of the parent directory. If the package is
|
||||
ever built as an XPI and the `fullName` key is not present, this is
|
||||
used as the add-on's `em:name` element in its `install.rdf`.
|
||||
|
||||
* `fullName` - the full name of the package. It can contain spaces. If
|
||||
the package is ever built as an XPI, this is used as the add-on's
|
||||
`em:name` element in its `install.rdf`.
|
||||
|
||||
* `description` - a String describing the package. If the package is
|
||||
ever built as an XPI, this is used as the add-on's
|
||||
`em:description` element in its `install.rdf`.
|
||||
|
||||
* `author` - the original author of the package. The author may be a
|
||||
String including an optional URL in parentheses and optional email
|
||||
address in angle brackets. If the package is ever built as an XPI,
|
||||
this is used as the add-on's `em:creator` element in its
|
||||
`install.rdf`.
|
||||
|
||||
* `translators` - an Array of Strings consisted of translators of the package.
|
||||
|
||||
* `contributors` - may be an Array of additional author Strings.
|
||||
|
||||
* `homepage` - the URL of the package's website.
|
||||
|
||||
* `icon` - the relative path from the root of the package to a
|
||||
PNG file containing the icon for the package. By default, this
|
||||
is `icon.png`. If the package is built as an XPI, this is used
|
||||
as the add-on's icon to display in the Add-on Manager's add-ons list.
|
||||
This key maps on to the
|
||||
[`iconURL` entry in the Install Manifest](https://developer.mozilla.org/en/install_manifests#iconURL),
|
||||
so the icon may be up to 48x48 pixels in size.
|
||||
|
||||
* `icon64` - the relative path from the root of the package to a
|
||||
PNG file containing the icon64 for the package. By default, this
|
||||
is `icon64.png`. If the package is built as an XPI, this is used
|
||||
as the add-on's icon to display in the Addon Manager's add-on details view.
|
||||
This key maps on to the
|
||||
[`icon64URL` entry in the Install Manifest](https://developer.mozilla.org/en/install_manifests#icon64URL),
|
||||
so the icon should be 64x64 pixels in size.
|
||||
|
||||
* `preferences` - *experimental*
|
||||
An array of JSON objects that use the following keys `name`, `type`, `value`,
|
||||
`title`, and `description`. These JSON objects will be used to automatically
|
||||
create a preferences interface for the addon in the Add-ons Manager.
|
||||
For more information see the documentation of [simple-prefs](modules/sdk/simple-prefs.html).
|
||||
|
||||
* `license` - the name of the license as a String, with an optional
|
||||
URL in parentheses.
|
||||
|
||||
* `id` - a globally unique identifier for the package. When the package is
|
||||
built as an XPI, this is used as the add-on's `em:id` element in its
|
||||
`install.rdf`. See the
|
||||
[Program ID page](dev-guide/guides/program-id.html).
|
||||
|
||||
* `version` - a String representing the version of the package. If the
|
||||
package is ever built as an XPI, this is used as the add-on's
|
||||
`em:version` element in its `install.rdf`.
|
||||
|
||||
* `dependencies` - a String or Array of Strings representing package
|
||||
names that this package requires in order to function properly.
|
||||
|
||||
* `lib` - a String representing the top-level module directory provided in
|
||||
this package. Defaults to `"lib"`.
|
||||
|
||||
* `tests` - a String representing the top-level module directory containing
|
||||
test suites for this package. Defaults to `"tests"`.
|
||||
|
||||
* `packages` - a String or Array of Strings representing paths to
|
||||
directories containing additional packages, defaults to
|
||||
`"packages"`.
|
||||
|
||||
* `main` - a String representing the name of a program module that is
|
||||
located in one of the top-level module directories specified by
|
||||
`lib`. Defaults to `"main"`.
|
||||
|
||||
* `harnessClassID` - a String in the GUID format:
|
||||
`xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx`, where `x` represents a single
|
||||
hexadecimal digit. It is used as a `classID` (CID) of the "harness service"
|
||||
XPCOM component. Defaults to a random GUID generated by `cfx`.
|
||||
|
||||
|
||||
## Documentation ##
|
||||
|
||||
A package may optionally contain a
|
||||
[Markdown](http://daringfireball.net/projects/markdown/)-formatted file
|
||||
called `README.md` in its root directory. Package-browsing tools may display
|
||||
this file to developers.
|
||||
|
||||
Additionally, Markdown files can be placed in an optional `docs`
|
||||
directory. When package-browsing tools are asked to show the
|
||||
documentation for a module, they will look in this directory for a
|
||||
`.md` file with the module's name. Thus, for instance, if a user
|
||||
browses to a module at `lib/foo/bar.js`, the package-browsing tool
|
||||
will look for a file at `docs/foo/bar.js` to represent the module's
|
||||
API documentation.
|
||||
|
||||
## Data Resources ##
|
||||
|
||||
Packages may optionally contain a directory called `data` into which
|
||||
arbitrary files may be placed, such as images or text files. The
|
||||
URL for these resources may be reached using the
|
||||
[self](modules/sdk/self.html) module.
|
||||
|
||||
[Markdown]: http://daringfireball.net/projects/markdown/
|
||||
[non-bootstrapped XUL extension]: #guide/xul-extensions
|
|
@ -0,0 +1,190 @@
|
|||
<!-- 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="http://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="http://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>
|
|
@ -0,0 +1,7 @@
|
|||
<!-- 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.
|
|
@ -0,0 +1,89 @@
|
|||
<!-- 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).
|
|
@ -0,0 +1,142 @@
|
|||
<!-- 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.
|
||||
|
||||
## Installing `menuitems` ##
|
||||
|
||||
First we'll download the `menuitems` package from
|
||||
[https://github.com/erikvold/menuitems-jplib](https://github.com/erikvold/menuitems-jplib/zipball/51080383cbb0fe2a05f8992a8aae890f4c014176).
|
||||
|
||||
Third-party packages like `menuitems` can be installed in three
|
||||
different places:
|
||||
|
||||
* in the `packages` directory under the SDK root. If you do this the package
|
||||
is available to any other add-ons you're developing using that SDK instance,
|
||||
and the package's documentation is visible through `cfx docs`.
|
||||
* in a `packages` directory you create under your add-on's root: if you
|
||||
do this, the package is only available to that add-on.
|
||||
* in a directory indicated using the `packages` key in
|
||||
your add-on's [package.json](dev-guide/package-spec.html). If you
|
||||
do this, you may not keep any packages in your add-on's `packages`
|
||||
directory, or they will not be found.
|
||||
|
||||
In this example we will install the package under the SDK root. From
|
||||
the SDK root directory, execute something like the following commands:
|
||||
|
||||
<pre>
|
||||
cd packages
|
||||
tar -xf ../erikvold-menuitems-jplib-d80630c.zip
|
||||
</pre>
|
||||
|
||||
Now if you run `cfx docs` you'll see a new section appear in the sidebar
|
||||
labeled "Third-Party APIs", which lists the modules in the `menuitems`
|
||||
package: this package contains a single module, also
|
||||
called `menuitems`.
|
||||
|
||||
Click on the module name and you'll see API documentation for the module.
|
||||
|
||||
## 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` ##
|
||||
|
||||
We can use the `menuitems` module in exactly the same way we use built-in
|
||||
modules.
|
||||
|
||||
The documentation for the `menuitems` module tells us to we 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
|
||||
|
||||
Next, create a new add-on. Make a directory called 'clickme' wherever you
|
||||
like, navigate to it and run `cfx init`. Open `lib/main.js` and add the
|
||||
following code:
|
||||
|
||||
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 ##
|
||||
|
||||
Eventually we expect the availability of a rich set of third party packages
|
||||
will be one of the most valuable aspects of the SDK. Right now they're a great
|
||||
way to use features not supported by the supported APIs without the
|
||||
complexity of using the low-level APIs, but there are some caveats you should
|
||||
be aware of:
|
||||
|
||||
* our support for third party packages is still fairly immature. One
|
||||
consequence of this is that it's not always obvious where to find third-party
|
||||
packages, although the
|
||||
[Community Developed Modules](https://github.com/mozilla/addon-sdk/wiki/Community-developed-modules)
|
||||
page in the SDK's GitHub Wiki lists a number of packages.
|
||||
|
||||
* because third party modules typically use low-level APIs, they may be broken
|
||||
by new releases of Firefox.
|
|
@ -0,0 +1,174 @@
|
|||
<!-- 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).
|
|
@ -0,0 +1,344 @@
|
|||
<!-- 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).
|
|
@ -0,0 +1,213 @@
|
|||
<!-- 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.
|
|
@ -0,0 +1,31 @@
|
|||
<!-- 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)
|
||||
|
|
@ -0,0 +1,63 @@
|
|||
<!-- 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, so we'll use the
|
||||
`private-browsing` module for that.
|
||||
|
||||
## 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).
|
|
@ -0,0 +1,369 @@
|
|||
<!-- 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 prevent the user
|
||||
from creating annotations while the browser is in
|
||||
[Private Browsing](http://support.mozilla.com/en-US/kb/Private%20Browsing) mode.
|
||||
|
||||
First let's import the `private-browsing` module into `main.js`:
|
||||
|
||||
var privateBrowsing = require('sdk/private-browsing');
|
||||
|
||||
We already have a variable `annotatorIsOn` that we use to indicate whether the
|
||||
user can enter annotations. But we don't want to use that here, because we want
|
||||
to remember the underlying state so that when they exit Private Browsing the
|
||||
annotator is back in whichever state it was in before.
|
||||
|
||||
So we'll implement a function defining that to enter annotations, the annotator
|
||||
must be active *and* Private Browsing must be off:
|
||||
|
||||
function canEnterAnnotations() {
|
||||
return (annotatorIsOn && !privateBrowsing.isActive);
|
||||
}
|
||||
|
||||
Next, everywhere we previously used `annotatorIsOn` directly, we'll call this
|
||||
function instead:
|
||||
|
||||
function activateSelectors() {
|
||||
selectors.forEach(
|
||||
function (selector) {
|
||||
selector.postMessage(canEnterAnnotations());
|
||||
});
|
||||
}
|
||||
<br>
|
||||
|
||||
function toggleActivation() {
|
||||
annotatorIsOn = !annotatorIsOn;
|
||||
activateSelectors();
|
||||
return canEnterAnnotations();
|
||||
}
|
||||
<br>
|
||||
|
||||
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(canEnterAnnotations());
|
||||
selectors.push(worker);
|
||||
worker.port.on('show', function(data) {
|
||||
annotationEditor.annotationAnchor = data;
|
||||
annotationEditor.show();
|
||||
});
|
||||
worker.on('detach', function () {
|
||||
detachWorker(this, selectors);
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
We want to stop the user changing the underlying activation state when in
|
||||
Private Browsing mode, so we'll edit `toggleActivation` again:
|
||||
|
||||
function toggleActivation() {
|
||||
if (privateBrowsing.isActive) {
|
||||
return false;
|
||||
}
|
||||
annotatorIsOn = !annotatorIsOn;
|
||||
activateSelectors();
|
||||
return canEnterAnnotations();
|
||||
}
|
||||
|
||||
Finally, inside the `main` function, we'll add the following code to handle
|
||||
changes in Private Browsing state by changing the icon and notifying the
|
||||
selectors:
|
||||
|
||||
privateBrowsing.on('start', function() {
|
||||
widget.contentURL = data.url('widget/pencil-off.png');
|
||||
activateSelectors();
|
||||
});
|
||||
|
||||
privateBrowsing.on('stop', function() {
|
||||
if (canEnterAnnotations()) {
|
||||
widget.contentURL = data.url('widget/pencil-on.png');
|
||||
activateSelectors();
|
||||
}
|
||||
});
|
||||
|
||||
Try it: execute `cfx run`, and experiment with switching the annotator on and
|
||||
off while in and out of Private Browsing mode.
|
||||
|
||||
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).
|
|
@ -0,0 +1,115 @@
|
|||
<!-- 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).
|
|
@ -0,0 +1,105 @@
|
|||
<!-- 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.
|
|
@ -0,0 +1,151 @@
|
|||
<!-- 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).
|
|
@ -0,0 +1,273 @@
|
|||
<!-- 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 [`private-browsing`'s `start` event](modules/sdk/private-browsing.html#start) 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([Components.interfaces.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);
|
||||
}
|
|
@ -0,0 +1,170 @@
|
|||
<!-- 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 Firefox
|
||||
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).
|
|
@ -0,0 +1,244 @@
|
|||
<!-- 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>
|
||||
|
|
@ -0,0 +1,160 @@
|
|||
<!-- 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.
|
||||
|
||||
## cfx docs ##
|
||||
|
||||
If you're reading these documents online, try running `cfx docs`. This will
|
||||
build the documentation for the SDK and display it in a browser.
|
||||
|
||||
## 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.
|
|
@ -0,0 +1,441 @@
|
|||
<!-- 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.
|
||||
|
|
@ -0,0 +1,73 @@
|
|||
<!-- 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).
|
|
@ -0,0 +1,55 @@
|
|||
<!-- 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).
|
|
@ -0,0 +1,103 @@
|
|||
<!-- 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.
|
|
@ -0,0 +1,67 @@
|
|||
# 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).
|
||||
|
||||
## Learning More ##
|
||||
|
||||
For the complete `console` API, see its
|
||||
[API reference](dev-guide/console.html).
|
|
@ -0,0 +1,350 @@
|
|||
<!-- 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">highlighted</span> in the tables below.
|
||||
|
||||
### High-Level APIs ###
|
||||
|
||||
<ul class="module-list">
|
||||
<li class="unsupported-on-mobile"><a href="modules/sdk/addon-page.html">addon-page</a></li>
|
||||
<li><a href="modules/sdk/base64.html">base64</a></li>
|
||||
<li class="unsupported-on-mobile"><a href="modules/sdk/clipboard.html">clipboard</a></li>
|
||||
<li class="unsupported-on-mobile"><a href="modules/sdk/context-menu.html">context-menu</a></li>
|
||||
<li><a href="modules/sdk/hotkeys.html">hotkeys</a></li>
|
||||
<!-- test-l10n-locale, test-l10n-plural-rules -->
|
||||
<li><a href="modules/sdk/l10n.html">l10n</a></li>
|
||||
<li><a href="modules/sdk/notifications.html">notifications</a></li>
|
||||
<!-- test-page-mod fails, but we know the module works -->
|
||||
<li><a href="modules/sdk/page-mod.html">page-mod</a></li>
|
||||
<li class="unsupported-on-mobile"><a href="modules/sdk/panel.html">panel</a></li>
|
||||
<!-- test-passwords, test-passwords-utils (with exceptions / warning from js console) -->
|
||||
<li><a href="modules/sdk/passwords.html">passwords</a></li>
|
||||
<li class="unsupported-on-mobile"><a href="modules/sdk/private-browsing.html">private-browsing</a></li>
|
||||
<li><a href="modules/sdk/querystring.html">querystring</a></li>
|
||||
<li><a href="modules/sdk/request.html">request</a></li>
|
||||
<li class="unsupported-on-mobile"><a href="modules/sdk/selection.html">selection</a></li>
|
||||
<li><a href="modules/sdk/self.html">self</a></li>
|
||||
<li><a href="modules/sdk/simple-prefs.html">simple-prefs</a></li>
|
||||
<li><a href="modules/sdk/simple-storage.html">simple-storage</a></li>
|
||||
<!-- test-tabs, test-tabs-common -->
|
||||
<li><a href="modules/sdk/tabs.html">tabs</a></li>
|
||||
<!-- test-timer -->
|
||||
<li><a href="modules/sdk/timers.html">timers</a></li>
|
||||
<li><a href="modules/sdk/url.html">url</a></li>
|
||||
<li><a href="modules/sdk/widget.html">widget</a></li>
|
||||
<!-- test-windows-common, test-windows -->
|
||||
<li><a href="modules/sdk/windows.html">windows</a></li>
|
||||
</ul>
|
||||
|
||||
### Low-Level APIs ###
|
||||
|
||||
<ul class="module-list">
|
||||
<li><a href="modules/toolkit/loader.html">/loader</a></li>
|
||||
<li><a href="dev-guide/tutorials/chrome.html">chrome</a></li>
|
||||
<li><a href="modules/sdk/console/plain-text.html">console/plain-text</a></li>
|
||||
<li><a href="modules/sdk/console/traceback.html">console/traceback</a></li>
|
||||
<li class="unsupported-on-mobile"><a href="modules/sdk/content/content.html">content/content</a></li>
|
||||
<li><a href="modules/sdk/content/loader.html">content/loader</a></li>
|
||||
<li class="unsupported-on-mobile"><a href="modules/sdk/content/symbiont.html">content/symbiont</a></li>
|
||||
<li class="unsupported-on-mobile"><a href="modules/sdk/content/worker.html">content/worker</a></li>
|
||||
<li>core/disposable</li>
|
||||
<li><a href="modules/sdk/core/heritage.html">core/heritage</a></li>
|
||||
<li><a href="modules/sdk/core/namespace.html">core/namespace</a></li>
|
||||
<li><a href="modules/sdk/core/promise.html">core/promise</a></li>
|
||||
<li><a href="modules/sdk/deprecated/api-utils.html">deprecated/api-utils</a></li>
|
||||
<li><a href="modules/sdk/deprecated/app-strings.html">deprecated/app-strings</a></li>
|
||||
<li><a href="modules/sdk/deprecated/cortex.html">deprecated/cortex</a></li>
|
||||
<li><a href="modules/sdk/deprecated/errors.html">deprecated/errors</a></li>
|
||||
<li><a href="modules/sdk/deprecated/events.html">deprecated/events</a></li>
|
||||
<li><a href="modules/sdk/deprecated/light-traits.html">deprecated/light-traits</a></li>
|
||||
<li>deprecated/list</li>
|
||||
<li><a href="modules/sdk/deprecated/observer-service.html">deprecated/observer-service</a></li>
|
||||
<li class="unsupported-on-mobile"><a href="modules/sdk/deprecated/tab-browser.html">deprecated/tab-browser</a></li>
|
||||
<!-- test-traits-core, test-traits -->
|
||||
<li><a href="modules/sdk/deprecated/traits.html">deprecated/traits</a></li>
|
||||
<li class="unsupported-on-mobile"><a href="modules/sdk/deprecated/window-utils.html">deprecated/window-utils</a></li>
|
||||
<!-- test-dom -->
|
||||
<li>dom/events</li>
|
||||
<li><a href="modules/sdk/event/core.html">event/core</a></li>
|
||||
<li><a href="modules/sdk/event/target.html">event/target</a></li>
|
||||
<li><a href="modules/sdk/frame/hidden-frame.html">frame/hidden-frame</a></li>
|
||||
<li><a href="modules/sdk/frame/utils.html">frame/utils</a></li>
|
||||
<li><a href="modules/sdk/io/byte-streams.html">io/byte-streams</a></li>
|
||||
<li><a href="modules/sdk/io/file.html">io/file</a></li>
|
||||
<li><a href="modules/sdk/io/text-streams.html">io/text-streams</a></li>
|
||||
<li>keyboard/observer</li>
|
||||
<li>keyboard/utils</li>
|
||||
<li>lang/functional</li>
|
||||
<li>lang/type</li>
|
||||
<li><a href="modules/sdk/loader/cuddlefish.html">loader/cuddlefish</a></li>
|
||||
<li><a href="modules/sdk/loader/sandbox.html">loader/sandbox</a></li>
|
||||
<li><a href="modules/sdk/net/url.html">net/url</a></li>
|
||||
<li><a href="modules/sdk/net/xhr.html">net/xhr</a></li>
|
||||
<li><a href="modules/sdk/page-mod/match-pattern.html">page-mod/match-pattern</a></li>
|
||||
<li><a href="modules/sdk/platform/xpcom.html">platform/xpcom</a></li>
|
||||
<!-- test-preferences-service, test-preferences-target -->
|
||||
<li><a href="modules/sdk/preferences/service.html">preferences/service</a></li>
|
||||
<li><a href="modules/sdk/system/environment.html">system/environment</a></li>
|
||||
<!-- No test for `system/events`, assuming it works because other compatible modules are using it -->
|
||||
<li><a href="modules/sdk/system/events.html">system/events</a></li>
|
||||
<li>system/globals</li>
|
||||
<!-- No test for `system/events`, assuming it works because other compatible modules are using it -->
|
||||
<li><a href="modules/sdk/system/runtime.html">system/runtime</a></li>
|
||||
<li><a href="modules/sdk/system/unload.html">system/unload</a></li>
|
||||
<li><a href="modules/sdk/system/xul-app.html">system/xul-app</a></li>
|
||||
<!-- No test for `assert`, assuming it works because the test are using it -->
|
||||
<li><a href="modules/sdk/test/assert.html">test/assert</a></li>
|
||||
<!-- No test for `harness`, assuming it works because the test are using it -->
|
||||
<li><a href="modules/sdk/test/harness.html">test/harness</a></li>
|
||||
<li><a href="modules/sdk/test/httpd.html">test/httpd</a></li>
|
||||
<!-- No test for `runner`, assuming it works because the test are using it -->
|
||||
<li><a href="modules/sdk/test/runner.html">test/runner</a></li>
|
||||
<li>test/tmp-file</li>
|
||||
<li>util/array</li>
|
||||
<li><a href="modules/sdk/util/collection.html">util/collection</a></li>
|
||||
<li><a href="modules/sdk/util/deprecate.html">util/deprecate</a></li>
|
||||
<li><a href="modules/sdk/util/list.html">util/list</a></li>
|
||||
<li>util/registry</li>
|
||||
<li><a href="modules/sdk/util/uuid.html">util/uuid</a></li>
|
||||
<!-- test-window-utils2 -->
|
||||
<li><a href="modules/sdk/window/utils.html">window/utils</a></li>
|
||||
</ul>
|
|
@ -0,0 +1,153 @@
|
|||
<!-- 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 an 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).
|
|
@ -0,0 +1,242 @@
|
|||
<!-- 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/page-mod/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 an 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).
|
|
@ -0,0 +1,57 @@
|
|||
<!-- 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).
|
|
@ -0,0 +1,408 @@
|
|||
<!-- 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/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/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/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.
|
||||
|
||||
### Documentation ###
|
||||
|
||||
If you document your modules, people who install your package and
|
||||
execute `cfx docs` will see the documentation
|
||||
integrated with the SDK's own documentation.
|
||||
|
||||
You can document the geolocation module by creating a file called
|
||||
"geolocation.md" in your package's "doc" directory. This file is also
|
||||
written in Markdown, although you can optionally use some
|
||||
[extended syntax](https://wiki.mozilla.org/Jetpack/SDK/Writing_Documentation#APIDoc_Syntax)
|
||||
to document APIs.
|
||||
|
||||
Try it:
|
||||
|
||||
* add a "geolocation.md" under "doc"
|
||||
* copy your geolocation package under the "packages" directory in the SDK root
|
||||
* execute `cfx docs`
|
||||
|
||||
Once `cfx docs` has finished, you should see a new entry appear in the
|
||||
sidebar called "Third-Party APIs", which lists the geolocation module.
|
||||
|
||||
### 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).
|
|
@ -0,0 +1,5 @@
|
|||
# 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.
|
|
@ -0,0 +1,208 @@
|
|||
<!-- 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.
|
|
@ -0,0 +1,163 @@
|
|||
<!-- 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.
|
|
@ -0,0 +1,18 @@
|
|||
<!-- 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>
|
|
@ -0,0 +1,36 @@
|
|||
<!-- 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>
|
|
@ -0,0 +1,32 @@
|
|||
<!-- 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.
|
||||
|
|
@ -0,0 +1,50 @@
|
|||
<!-- 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>
|
|
@ -0,0 +1,91 @@
|
|||
<!-- 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>
|
|
@ -0,0 +1,7 @@
|
|||
<!-- 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.
|
|
@ -0,0 +1,66 @@
|
|||
<!-- 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>
|
||||
|
|
@ -0,0 +1,20 @@
|
|||
<!-- 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
|
||||
|
|
@ -0,0 +1,122 @@
|
|||
<!-- 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>
|
||||
|
|
@ -0,0 +1,149 @@
|
|||
<!-- 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>
|
||||
|
||||
|
|
@ -0,0 +1,135 @@
|
|||
<!-- 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 [`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/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.
|
||||
</api>
|
||||
|
||||
</api>
|
||||
|
|
@ -0,0 +1,726 @@
|
|||
<!-- 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` module lets you add items to Firefox's page context menu.
|
||||
|
||||
|
||||
Introduction
|
||||
------------
|
||||
|
||||
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/page-mod/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 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. If they
|
||||
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);' +
|
||||
'});'
|
||||
});
|
||||
|
||||
Your item is shown only when all declarative contexts are current and your
|
||||
context listener returns 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`. `node`
|
||||
is the node that the user context-clicked to invoke the menu. You can use it
|
||||
when performing some action. `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" })
|
||||
]
|
||||
});
|
||||
|
||||
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.
|
||||
|
||||
|
||||
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/page-mod/match-pattern.html) string, regexp or an
|
||||
array of match pattern strings or regexps.
|
||||
</api>
|
||||
</api>
|
Некоторые файлы не были показаны из-за слишком большого количества измененных файлов Показать больше
Загрузка…
Ссылка в новой задаче