SS13: Web Mining, HCI, TK3

This commit is contained in:
Michael Scholz 2013-04-16 10:05:26 +02:00
parent 53ea467109
commit abedfa08aa
8284 changed files with 1354078 additions and 2 deletions

View File

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<classpath>
<classpathentry kind="src" path="src"/>
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.6"/>
<classpathentry kind="lib" path="lib/jcommon-1.0.17.jar"/>
<classpathentry kind="lib" path="lib/jfreechart-1.0.14.jar"/>
<classpathentry kind="output" path="bin"/>
</classpath>

View File

@ -0,0 +1,3 @@
# Enerjy Software baseline data (files=1, problems=1).
path: /Textanalyse/src/Textanalyse.java
problem: JAVA0024:JAVA0024 Empty class 'Textanalyse'

View File

@ -0,0 +1,17 @@
<?xml version="1.0" encoding="UTF-8"?>
<projectDescription>
<name>Textanalyse</name>
<comment></comment>
<projects>
</projects>
<buildSpec>
<buildCommand>
<name>org.eclipse.jdt.core.javabuilder</name>
<arguments>
</arguments>
</buildCommand>
</buildSpec>
<natures>
<nature>org.eclipse.jdt.core.javanature</nature>
</natures>
</projectDescription>

View File

@ -0,0 +1,12 @@
#Thu Apr 26 19:49:33 CEST 2012
eclipse.preferences.version=1
org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled
org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.6
org.eclipse.jdt.core.compiler.codegen.unusedLocal=preserve
org.eclipse.jdt.core.compiler.compliance=1.6
org.eclipse.jdt.core.compiler.debug.lineNumber=generate
org.eclipse.jdt.core.compiler.debug.localVariable=generate
org.eclipse.jdt.core.compiler.debug.sourceFile=generate
org.eclipse.jdt.core.compiler.problem.assertIdentifier=error
org.eclipse.jdt.core.compiler.problem.enumIdentifier=error
org.eclipse.jdt.core.compiler.source=1.6

View File

@ -0,0 +1,112 @@
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
/**
* Text-Analyser
*
* @author chris
*
*/
public class Analyser {
private static Integer[] specialChars = {228,246,252,232}; // ä, ö, ü, ß
private static List<Integer> specialCharacters;
static{
specialCharacters = Arrays.asList(specialChars);
}
/**
* Count words of an input text
* @param textarr
* @return
*/
public static HashMap<String, Integer> analyseWords(ArrayList<String> textarr){
HashMap<String, Integer> map = new HashMap<String, Integer>();
for(String word : textarr){
Boolean valid = true;
word = word.replaceAll("\'[a-z]*", ""); // remove plural stuff
for (char c : word.toCharArray()) {
int code = (int) c;
if(!checkLetter(code)){
valid = false;
break;
}
}
if(valid && word.length() > 1) count(map, word);
}
return map;
}
/**
* Count letters of an input text
* @param textarr
* @return
*/
public static HashMap<String, Integer> analyseLetters(ArrayList<String> textarr){
HashMap<String, Integer> map = new HashMap<String, Integer>();
for(String word : textarr){
for (char c : word.toCharArray()) {
int code = (int) c;
if(checkLetter(code)) count(map, String.valueOf(c));
}
}
return map;
}
/**
* Count pairs of letters of input text
* @param textarr
* @return
*/
public static HashMap<String, Integer> analysePairsOfLetters(ArrayList<String> textarr){
HashMap<String, Integer> map = new HashMap<String, Integer>();
for(String word : textarr){
char[] charArray = word.toCharArray();
for (int i=0; i<charArray.length-1; i++) {
char c1 = charArray[i];
char c2 =charArray[i+1];
if(checkLetter((int) c1) && checkLetter((int) c2))
count(map, (String.valueOf(c1)+String.valueOf(c2)));
}
}
return map;
}
/**
* Determine if char is a letter
* <br>a-z = 97-122 (ascii-code) + special characters (äöüß)
* @param asciiCode
* @return
*/
private static boolean checkLetter(int asciiCode){
return (asciiCode >= 97 && asciiCode <= 122 || specialCharacters.contains(asciiCode));
}
/**
* Count current key to map
* @param map
* @param key
*/
private static void count(HashMap<String, Integer> map, String key){
if(map.containsKey(key)){
int val = map.get(key);
map.put(key, ++val);
}else map.put(key,1);
}
}

View File

@ -0,0 +1,90 @@
import java.awt.Color;
import java.util.List;
import org.jfree.chart.ChartFactory;
import org.jfree.chart.JFreeChart;
import org.jfree.chart.axis.LogarithmicAxis;
import org.jfree.chart.axis.NumberAxis;
import org.jfree.chart.axis.ValueAxis;
import org.jfree.chart.plot.CategoryPlot;
import org.jfree.chart.plot.PlotOrientation;
import org.jfree.chart.plot.XYPlot;
import org.jfree.data.category.DefaultCategoryDataset;
import org.jfree.data.xy.XYDataItem;
import org.jfree.data.xy.XYSeriesCollection;
/**
* Customize chart factory
*
* @author chris
*
*/
public class MyChartFactory {
/**
* Get custom chart
* @param title
* @param xLabel
* @param yLabel
* @param dataset
* @param logarithm
* @return
*/
public static JFreeChart getXYLineChart(String title, String xLabel, String yLabel, XYSeriesCollection dataset, Boolean logarithm){
int itemCount = dataset.getSeries(0).getItemCount();
JFreeChart chart = ChartFactory.createXYLineChart(title, xLabel, yLabel, dataset, PlotOrientation.VERTICAL, false, true, false);
chart.setBackgroundPaint(Color.white);
chart.getTitle().setPaint(Color.blue);
XYPlot p2 = chart.getXYPlot();
p2.setRangeGridlinePaint(Color.red);
if(logarithm){
NumberAxis xaxis = new LogarithmicAxis(xLabel);
NumberAxis yaxis = new LogarithmicAxis(yLabel);
xaxis.setAutoRange(true);
xaxis.setRange(1, ((itemCount>1)?itemCount:2));
yaxis.setAutoRange(true);
p2.setDomainAxis(xaxis);
p2.setRangeAxis(yaxis);
}
return chart;
}
/**
* Get custom chart
* @param title
* @param xLabel
* @param yLabel
* @param dataset
* @param logarithm
* @return
*/
public static JFreeChart getXYBarChart(String title, String xLabel, String yLabel, XYSeriesCollection dataset, Boolean logarithm){
int itemCount = dataset.getSeries(0).getItemCount();
JFreeChart chart = ChartFactory.createXYBarChart(title, xLabel, false, yLabel, dataset, PlotOrientation.VERTICAL, true, true, false);
chart.setBackgroundPaint(Color.white);
chart.getTitle().setPaint(Color.blue);
XYPlot p2 = chart.getXYPlot();
p2.setRangeGridlinePaint(Color.red);
if(logarithm){
NumberAxis xaxis = new LogarithmicAxis(xLabel);
NumberAxis yaxis = new LogarithmicAxis(yLabel);
xaxis.setAutoRange(true);
xaxis.setRange(1, ((itemCount>1)?itemCount:2));
yaxis.setAutoRange(true);
p2.setDomainAxis(xaxis);
p2.setRangeAxis(yaxis);
}
return chart;
}
}

View File

@ -0,0 +1,415 @@
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.GridLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.FocusEvent;
import java.awt.event.FocusListener;
import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import javax.swing.ButtonGroup;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JRadioButton;
import javax.swing.JScrollPane;
import javax.swing.JTextArea;
import org.jfree.chart.ChartFactory;
import org.jfree.chart.ChartFrame;
import org.jfree.chart.ChartPanel;
import org.jfree.chart.JFreeChart;
import org.jfree.chart.axis.LogarithmicAxis;
import org.jfree.chart.axis.ValueAxis;
import org.jfree.chart.plot.CategoryPlot;
import org.jfree.chart.plot.PlotOrientation;
import org.jfree.chart.plot.XYPlot;
import org.jfree.data.category.DefaultCategoryDataset;
import org.jfree.data.xy.XYSeries;
import org.jfree.data.xy.XYSeriesCollection;
public class Textanalyse {
// Visuelle Komponenten
private JFrame frame;
private JPanel centerFrame;
private JTextArea textarea;
private JTextArea textarea2;
private JButton evaluate;
private JScrollPane scrollPane;
private JScrollPane scrollPane2;
private ButtonGroup stopWordsLanguageSelectButtonGroup;
private ButtonGroup textAnalyzeSelectButtonGroup;
private JRadioButton english, german, noStopwords;
private JRadioButton analyzeWords, analyzePairOfLetters, analyzeLetters;
private JPanel stopWordsRadioButtonPanel;
private final String ADVICE = "Paste your text here";
private XYSeriesCollection dataset;
private XYSeriesCollection dataset2;
private XYSeries series;
private XYSeries series2;
private JPanel chartFrame;
private JFreeChart chart;
private JPanel chartFrame2;
private JFreeChart chart2;
private JPanel chartFrame3;
private JFreeChart chart3;
private JPanel chartFrame4;
private JFreeChart chart4;
// Logische Kompenenten
private ArrayList<String> stopwords = new ArrayList<String>();
private int selectedStopWordsLanguage = 0;
private int selectedTextAnalyzeMode = 0;
// GUI
public Textanalyse() {
// Frame
frame = new JFrame("Text Analysis");
frame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
frame.setSize(1000, 800);
frame.setLocation(350, 100);
frame.setVisible(true);
// Textfield
textarea = new JTextArea(ADVICE);
textarea.setSize(800, 300);
textarea.setVisible(true);
textarea.addFocusListener(new FocusListener() {
@Override
public void focusLost(FocusEvent e) {}
@Override
public void focusGained(FocusEvent e) {
JTextArea textarea = (JTextArea) e.getSource();
if(textarea.getText().equals(ADVICE.toString())) textarea.setText("");
}
});
/** result textarea */
textarea2 = new JTextArea("");
textarea2.setSize(800, 300);
textarea2.setVisible(true);
textarea2.setEditable(false);
// Button
evaluate = new JButton("Evaluate");
evaluate.setVisible(true);
// Scrollpane
scrollPane = new JScrollPane(textarea);
scrollPane2 = new JScrollPane(textarea2);
/** center panel with gridlayout */
centerFrame = new JPanel();
centerFrame.setLayout (new GridLayout(3,2,5,5));
centerFrame.add(scrollPane);
centerFrame.add(scrollPane2);
// RadioButtonGroups
// Auswahl der Sprache der Stopwoerter
english = new JRadioButton("english");
german = new JRadioButton("german");
noStopwords = new JRadioButton("no stopwords");
stopWordsLanguageSelectButtonGroup = new ButtonGroup();
stopWordsLanguageSelectButtonGroup.add(english);
stopWordsLanguageSelectButtonGroup.add(german);
stopWordsLanguageSelectButtonGroup.add(noStopwords);
noStopwords.setSelected(true);
stopWordsRadioButtonPanel = new JPanel();
stopWordsRadioButtonPanel.add(english);
stopWordsRadioButtonPanel.add(german);
stopWordsRadioButtonPanel.add(noStopwords);
// Auswahl der zu analysierenden Wortgruppen
analyzeWords = new JRadioButton("words");
analyzePairOfLetters = new JRadioButton("pairs of letters");
analyzeLetters = new JRadioButton("single letters");
textAnalyzeSelectButtonGroup = new ButtonGroup();
textAnalyzeSelectButtonGroup.add(analyzeWords);
textAnalyzeSelectButtonGroup.add(analyzePairOfLetters);
textAnalyzeSelectButtonGroup.add(analyzeLetters);
analyzeWords.setSelected(true);
stopWordsRadioButtonPanel.add(analyzeWords);
stopWordsRadioButtonPanel.add(analyzePairOfLetters);
stopWordsRadioButtonPanel.add(analyzeLetters);
dataset = new XYSeriesCollection();
dataset2 = new XYSeriesCollection();
series = new XYSeries("data");
series2 = new XYSeries("data");
dataset.addSeries(series);
dataset2.addSeries(series2);
showCharts();
centerFrame.setVisible(true);
// Layout
frame.getContentPane().add(stopWordsRadioButtonPanel,
BorderLayout.PAGE_START);
frame.getContentPane().add(centerFrame, BorderLayout.CENTER);
frame.getContentPane().add(evaluate, BorderLayout.PAGE_END);
// Evaluate-Button
evaluate.addActionListener(new ActionListener() {
String file = "src/noStopwords";
@Override
public void actionPerformed(ActionEvent arg0) {
if (english.isSelected()) {
file = "stopwords/english";
} else if (german.isSelected()) {
file = "stopwords/german";
} else {
file = "stopwords/noStopwords";
}
// Stopwords aus Datei laden
stopwords = new ArrayList<String>();
try {
BufferedReader buf = new BufferedReader(
new FileReader(file));
String s;
while ((s = buf.readLine()) != null) {
stopwords.add(s);
}
buf.close();
} catch (IOException e) {
System.err.println("Fehler beim Lesen der Datei.");
}
// get token-data
HashMap<String, Integer> data = sortMapByValue(eval(textarea.getText()));
/** update result textfield */
System.out.println("start set content");
textarea2.setText(formatOutput(data));
System.out.println("end set content");
System.out.println("updating charts");
/** update chart-data */
dataset.getSeries(0).clear();
dataset2.getSeries(0).clear();
// calculate
HashMap<Integer, Integer> counts = new HashMap<Integer, Integer>();
int i = 1;
for (String key : data.keySet()) {
int value = data.get(key);
dataset.getSeries(0).add(i, value);
if (counts.containsKey(value)){
int val = counts.get(value);
counts.put(value, ++val);
}else {
counts.put(value,1);
}
i++;
/** stop here or java will crash on too large texts */
//if (i > 5000) break;
}
// build second dataset
for (int key : counts.keySet()) {
dataset2.getSeries(0).add(Integer.valueOf(counts.get(key)).doubleValue(), key);
}
textarea2.append(formatOutputInteger(counts));
showCharts();
System.out.println("ready");
}
});
frame.setVisible(true);
}
/**
* Show charts
*/
private void showCharts(){
String title = null, xLabel = null, yLabel = null;
if (analyzeWords.isSelected()){
title = "Wortverteilung"; xLabel = "Wort"; yLabel = "#";
}else if (analyzeLetters.isSelected()){
title = "Buchstabenverteilung"; xLabel = "Buchstabe"; yLabel = "#";
}else if (analyzePairOfLetters.isSelected()){
title = "Buchstabenpaareverteilung"; xLabel = "Buchstabenpaar"; yLabel = "#";
}
chart = MyChartFactory.getXYLineChart(title, xLabel, yLabel+" (linear)", dataset, false);
chart2 = MyChartFactory.getXYLineChart(title, xLabel + " (HŠufigkeitsrang)", yLabel+" (log)", dataset, true);
chart3 = MyChartFactory.getXYBarChart("WorthŠufigkeit", xLabel, yLabel+" (linear)", dataset2, false);
chart4 = MyChartFactory.getXYBarChart("WorthŠufigkeit", xLabel + " (abs HŠufigkeit)", yLabel+" (log)", dataset2, true);
if(chartFrame != null) centerFrame.remove(chartFrame);
if(chartFrame2 != null) centerFrame.remove(chartFrame2);
if(chartFrame3 != null) centerFrame.remove(chartFrame3);
if(chartFrame4 != null) centerFrame.remove(chartFrame4);
chartFrame = new ChartPanel(chart);
chartFrame2 = new ChartPanel(chart2);
chartFrame3 = new ChartPanel(chart3);
chartFrame4 = new ChartPanel(chart4);
centerFrame.add(chartFrame);
centerFrame.add(chartFrame2);
centerFrame.add(chartFrame3);
centerFrame.add(chartFrame4);
chartFrame.setVisible(true);
chartFrame2.setVisible(true);
chartFrame3.setVisible(true);
chartFrame4.setVisible(true);
chartFrame.validate();
chartFrame2.validate();
chartFrame3.validate();
chartFrame4.validate();
centerFrame.validate();
}
/**
* @param args
*/
public static void main(String[] args) {
new Textanalyse();
}
/**
* Count words, letters oder pairs of letters and store the result into a map
*
* @param s
* @return Hashmap<String,Integer>
*/
private HashMap<String, Integer> eval(String s) {
// convert string to lower case
s = s.toLowerCase();
// split string on whitespaces and ignore punctuation
String[] temp = s.split("([.,!?:;\"-]|\\s)+");
// create difference between text array and stopwords array
ArrayList<String> arrText = new ArrayList<String>(Arrays.asList(temp));
arrText.removeAll(stopwords);
HashMap<String, Integer> h = null;
// analyse text
if (analyzeWords.isSelected()) {
h = Analyser.analyseWords(arrText);
}
else if(analyzeLetters.isSelected()){
h = Analyser.analyseLetters(arrText);
}
else if(analyzePairOfLetters.isSelected()){
h = Analyser.analysePairsOfLetters(arrText);
}
return h;
}
private String formatOutput(HashMap<String, Integer> h) {
int totalCount = 0;
for (int count : h.values()) totalCount += count;
int i = 1;
String result = "";
for (String s : h.keySet()) {
result = result + i++ + "\t" + s + "\t\t" + h.get(s) + ((totalCount>0)?"\t" + Math.round(10000*h.get(s)/totalCount)/100.0 +" %" :"")+ "\n";
}
return result;
}
private String formatOutputInteger(HashMap<Integer, Integer> h) {
String result = "";
for (int s : h.keySet()) {
result = result + s + "\t" + h.get(s) + "\n";
}
return result;
}
/**
* Quelle
* http://gaurav4556.blogspot.com/2009/01/sort-hashmap-by-value-in-java.html
*
* @param map
* @return Sortiert Hashmap aufteigend nach Hashwerten
*/
public HashMap<String, Integer> sortMapByValue(Map map) {
System.out.println("start sorting");
System.out.println("generate list from data");
List listForSort = new LinkedList(map.entrySet());
System.out.println("end generate list from data");
Map sortedList = new LinkedHashMap();
System.out.println("start sorting");
Collections.sort(listForSort, new Comparator() {
public int compare(Object value1, Object value2) {
return ((Comparable) ((Map.Entry) (value2)).getValue())
.compareTo(((Map.Entry) (value1)).getValue());
}
});
System.out.println("end sorting");
System.out.println("start copy values to linkedhashmap");
Iterator itret = listForSort.iterator();
while (itret.hasNext()) {
Map.Entry entry = (Map.Entry) itret.next();
sortedList.put(entry.getKey(), entry.getValue());
}
System.out.println("end copy values to linkedhashmap");
return (HashMap<String, Integer>)sortedList;
}
}

View File

@ -0,0 +1,9 @@
Stopwords Corpus
This corpus contains lists of stop words for several languages. These
are high-frequency grammatical words which are usually ignored in text
retrieval applications.
They were obtained from:
http://anoncvs.postgresql.org/cvsweb.cgi/pgsql/src/backend/snowball/stopwords/

View File

@ -0,0 +1,94 @@
og
i
jeg
det
at
en
den
til
er
som
de
med
han
af
for
ikke
der
var
mig
sig
men
et
har
om
vi
min
havde
ham
hun
nu
over
da
fra
du
ud
sin
dem
os
op
man
hans
hvor
eller
hvad
skal
selv
her
alle
vil
blev
kunne
ind
når
være
dog
noget
ville
jo
deres
efter
ned
skulle
denne
end
dette
mit
også
under
have
dig
anden
hende
mine
alt
meget
sit
sine
vor
mod
disse
hvis
din
nogle
hos
blive
mange
ad
bliver
hendes
været
thi
jer
sådan

View File

@ -0,0 +1,101 @@
de
en
van
ik
te
dat
die
in
een
hij
het
niet
zijn
is
was
op
aan
met
als
voor
had
er
maar
om
hem
dan
zou
of
wat
mijn
men
dit
zo
door
over
ze
zich
bij
ook
tot
je
mij
uit
der
daar
haar
naar
heb
hoe
heeft
hebben
deze
u
want
nog
zal
me
zij
nu
ge
geen
omdat
iets
worden
toch
al
waren
veel
meer
doen
toen
moet
ben
zonder
kan
hun
dus
alles
onder
ja
eens
hier
wie
werd
altijd
doch
wordt
wezen
kunnen
ons
zelf
tegen
na
reeds
wil
kon
niets
uw
iemand
geweest
andere

View File

@ -0,0 +1,128 @@
i
me
my
myself
we
our
ours
ourselves
you
your
yours
yourself
yourselves
he
him
his
himself
she
her
hers
herself
it
its
itself
they
them
their
theirs
themselves
what
which
who
whom
this
that
these
those
am
is
are
was
were
be
been
being
have
has
had
having
do
does
did
doing
a
an
the
and
but
if
or
because
as
until
while
of
at
by
for
with
about
against
between
into
through
during
before
after
above
below
to
from
up
down
in
out
on
off
over
under
again
further
then
once
here
there
when
where
why
how
all
any
both
each
few
more
most
other
some
such
no
nor
not
only
own
same
so
than
too
very
s
t
can
will
just
don
should
now

View File

@ -0,0 +1,235 @@
olla
olen
olet
on
olemme
olette
ovat
ole
oli
olisi
olisit
olisin
olisimme
olisitte
olisivat
olit
olin
olimme
olitte
olivat
ollut
olleet
en
et
ei
emme
ette
eivät
minä
minun
minut
minua
minussa
minusta
minuun
minulla
minulta
minulle
sinä
sinun
sinut
sinua
sinussa
sinusta
sinuun
sinulla
sinulta
sinulle
hän
hänen
hänet
häntä
hänessä
hänestä
häneen
hänellä
häneltä
hänelle
me
meidän
meidät
meitä
meissä
meistä
meihin
meillä
meiltä
meille
te
teidän
teidät
teitä
teissä
teistä
teihin
teillä
teiltä
teille
he
heidän
heidät
heitä
heissä
heistä
heihin
heillä
heiltä
heille
tämä
tämän
tätä
tässä
tästä
tähän
tallä
tältä
tälle
tänä
täksi
tuo
tuon
tuotä
tuossa
tuosta
tuohon
tuolla
tuolta
tuolle
tuona
tuoksi
se
sen
sitä
siinä
siitä
siihen
sillä
siltä
sille
sinä
siksi
nämä
näiden
näitä
näissä
näistä
näihin
näillä
näiltä
näille
näinä
näiksi
nuo
noiden
noita
noissa
noista
noihin
noilla
noilta
noille
noina
noiksi
ne
niiden
niitä
niissä
niistä
niihin
niillä
niiltä
niille
niinä
niiksi
kuka
kenen
kenet
ketä
kenessä
kenestä
keneen
kenellä
keneltä
kenelle
kenenä
keneksi
ketkä
keiden
ketkä
keitä
keissä
keistä
keihin
keillä
keiltä
keille
keinä
keiksi
mikä
minkä
minkä
mitä
missä
mistä
mihin
millä
miltä
mille
minä
miksi
mitkä
joka
jonka
jota
jossa
josta
johon
jolla
jolta
jolle
jona
joksi
jotka
joiden
joita
joissa
joista
joihin
joilla
joilta
joille
joina
joiksi
että
ja
jos
koska
kuin
mutta
niin
sekä
sillä
tai
vaan
vai
vaikka
kanssa
mukaan
noin
poikki
yli
kun
niin
nyt
itse

View File

@ -0,0 +1,155 @@
au
aux
avec
ce
ces
dans
de
des
du
elle
en
et
eux
il
je
la
le
leur
lui
ma
mais
me
même
mes
moi
mon
ne
nos
notre
nous
on
ou
par
pas
pour
qu
que
qui
sa
se
ses
son
sur
ta
te
tes
toi
ton
tu
un
une
vos
votre
vous
c
d
j
l
à
m
n
s
t
y
été
étée
étées
étés
étant
étante
étants
étantes
suis
es
est
sommes
êtes
sont
serai
seras
sera
serons
serez
seront
serais
serait
serions
seriez
seraient
étais
était
étions
étiez
étaient
fus
fut
fûmes
fûtes
furent
sois
soit
soyons
soyez
soient
fusse
fusses
fût
fussions
fussiez
fussent
ayant
ayante
ayantes
ayants
eu
eue
eues
eus
ai
as
avons
avez
ont
aurai
auras
aura
aurons
aurez
auront
aurais
aurait
aurions
auriez
auraient
avais
avait
avions
aviez
avaient
eut
eûmes
eûtes
eurent
aie
aies
ait
ayons
ayez
aient
eusse
eusses
eût
eussions
eussiez
eussent

View File

@ -0,0 +1,231 @@
aber
alle
allem
allen
aller
alles
als
also
am
an
ander
andere
anderem
anderen
anderer
anderes
anderm
andern
anderr
anders
auch
auf
aus
bei
bin
bis
bist
da
damit
dann
der
den
des
dem
die
das
daß
derselbe
derselben
denselben
desselben
demselben
dieselbe
dieselben
dasselbe
dazu
dein
deine
deinem
deinen
deiner
deines
denn
derer
dessen
dich
dir
du
dies
diese
diesem
diesen
dieser
dieses
doch
dort
durch
ein
eine
einem
einen
einer
eines
einig
einige
einigem
einigen
einiger
einiges
einmal
er
ihn
ihm
es
etwas
euer
eure
eurem
euren
eurer
eures
für
gegen
gewesen
hab
habe
haben
hat
hatte
hatten
hier
hin
hinter
ich
mich
mir
ihr
ihre
ihrem
ihren
ihrer
ihres
euch
im
in
indem
ins
ist
jede
jedem
jeden
jeder
jedes
jene
jenem
jenen
jener
jenes
jetzt
kann
kein
keine
keinem
keinen
keiner
keines
können
könnte
machen
man
manche
manchem
manchen
mancher
manches
mein
meine
meinem
meinen
meiner
meines
mit
muss
musste
nach
nicht
nichts
noch
nun
nur
ob
oder
ohne
sehr
sein
seine
seinem
seinen
seiner
seines
selbst
sich
sie
ihnen
sind
so
solche
solchem
solchen
solcher
solches
soll
sollte
sondern
sonst
über
um
und
uns
unse
unsem
unsen
unser
unses
unter
viel
vom
von
vor
während
war
waren
warst
was
weg
weil
weiter
welche
welchem
welchen
welcher
welches
wenn
werde
werden
wie
wieder
will
wir
wird
wirst
wo
wollen
wollte
würde
würden
zu
zum
zur
zwar
zwischen

View File

@ -0,0 +1,199 @@
a
ahogy
ahol
aki
akik
akkor
alatt
által
általában
amely
amelyek
amelyekben
amelyeket
amelyet
amelynek
ami
amit
amolyan
amíg
amikor
át
abban
ahhoz
annak
arra
arról
az
azok
azon
azt
azzal
azért
aztán
azután
azonban
bár
be
belül
benne
cikk
cikkek
cikkeket
csak
de
e
eddig
egész
egy
egyes
egyetlen
egyéb
egyik
egyre
ekkor
el
elég
ellen
elõ
elõször
elõtt
elsõ
én
éppen
ebben
ehhez
emilyen
ennek
erre
ez
ezt
ezek
ezen
ezzel
ezért
és
fel
felé
hanem
hiszen
hogy
hogyan
igen
így
illetve
ill.
ill
ilyen
ilyenkor
ison
ismét
itt
jól
jobban
kell
kellett
keresztül
keressünk
ki
kívül
között
közül
legalább
lehet
lehetett
legyen
lenne
lenni
lesz
lett
maga
magát
majd
majd
már
más
másik
meg
még
mellett
mert
mely
melyek
mi
mit
míg
miért
milyen
mikor
minden
mindent
mindenki
mindig
mint
mintha
mivel
most
nagy
nagyobb
nagyon
ne
néha
nekem
neki
nem
néhány
nélkül
nincs
olyan
ott
össze
õ
õk
õket
pedig
persze
s
saját
sem
semmi
sok
sokat
sokkal
számára
szemben
szerint
szinte
talán
tehát
teljes
tovább
továbbá
több
úgy
ugyanis
új
újabb
újra
után
utána
utolsó
vagy
vagyis
valaki
valami
valamint
való
vagyok
van
vannak
volt
voltam
voltak
voltunk
vissza
vele
viszont
volna

View File

@ -0,0 +1,279 @@
ad
al
allo
ai
agli
all
agl
alla
alle
con
col
coi
da
dal
dallo
dai
dagli
dall
dagl
dalla
dalle
di
del
dello
dei
degli
dell
degl
della
delle
in
nel
nello
nei
negli
nell
negl
nella
nelle
su
sul
sullo
sui
sugli
sull
sugl
sulla
sulle
per
tra
contro
io
tu
lui
lei
noi
voi
loro
mio
mia
miei
mie
tuo
tua
tuoi
tue
suo
sua
suoi
sue
nostro
nostra
nostri
nostre
vostro
vostra
vostri
vostre
mi
ti
ci
vi
lo
la
li
le
gli
ne
il
un
uno
una
ma
ed
se
perché
anche
come
dov
dove
che
chi
cui
non
più
quale
quanto
quanti
quanta
quante
quello
quelli
quella
quelle
questo
questi
questa
queste
si
tutto
tutti
a
c
e
i
l
o
ho
hai
ha
abbiamo
avete
hanno
abbia
abbiate
abbiano
avrò
avrai
avrà
avremo
avrete
avranno
avrei
avresti
avrebbe
avremmo
avreste
avrebbero
avevo
avevi
aveva
avevamo
avevate
avevano
ebbi
avesti
ebbe
avemmo
aveste
ebbero
avessi
avesse
avessimo
avessero
avendo
avuto
avuta
avuti
avute
sono
sei
è
siamo
siete
sia
siate
siano
sarò
sarai
sarà
saremo
sarete
saranno
sarei
saresti
sarebbe
saremmo
sareste
sarebbero
ero
eri
era
eravamo
eravate
erano
fui
fosti
fu
fummo
foste
furono
fossi
fosse
fossimo
fossero
essendo
faccio
fai
facciamo
fanno
faccia
facciate
facciano
farò
farai
farà
faremo
farete
faranno
farei
faresti
farebbe
faremmo
fareste
farebbero
facevo
facevi
faceva
facevamo
facevate
facevano
feci
facesti
fece
facemmo
faceste
fecero
facessi
facesse
facessimo
facessero
facendo
sto
stai
sta
stiamo
stanno
stia
stiate
stiano
starò
starai
starà
staremo
starete
staranno
starei
staresti
starebbe
staremmo
stareste
starebbero
stavo
stavi
stava
stavamo
stavate
stavano
stetti
stesti
stette
stemmo
steste
stettero
stessi
stesse
stessimo
stessero
stando

View File

@ -0,0 +1,176 @@
og
i
jeg
det
at
en
et
den
til
er
som
de
med
han
av
ikke
ikkje
der
var
meg
seg
men
ett
har
om
vi
min
mitt
ha
hadde
hun
over
da
ved
fra
du
ut
sin
dem
oss
opp
man
kan
hans
hvor
eller
hva
skal
selv
sjøl
her
alle
vil
bli
ble
blei
blitt
kunne
inn
når
være
kom
noen
noe
ville
dere
som
deres
kun
ja
etter
ned
skulle
denne
for
deg
si
sine
sitt
mot
å
meget
hvorfor
dette
disse
uten
hvordan
ingen
din
ditt
blir
samme
hvilken
hvilke
sånn
inni
mellom
vår
hver
hvem
vors
hvis
både
bare
enn
fordi
før
mange
også
slik
vært
være
båe
begge
siden
dykk
dykkar
dei
deira
deires
deim
di
eg
ein
eit
eitt
elles
honom
hjå
ho
hoe
henne
hennar
hennes
hoss
hossen
ikkje
ingi
inkje
korleis
korso
kva
kvar
kvarhelst
kven
kvi
kvifor
me
medan
mi
mine
mykje
no
nokon
noka
nokor
noko
nokre
si
sia
sidan
so
somt
somme
um
upp
vere
vore
verte
vort
varte
vart

View File

@ -0,0 +1,203 @@
de
a
o
que
e
do
da
em
um
para
com
não
uma
os
no
se
na
por
mais
as
dos
como
mas
ao
ele
das
à
seu
sua
ou
quando
muito
nos
eu
também
pelo
pela
até
isso
ela
entre
depois
sem
mesmo
aos
seus
quem
nas
me
esse
eles
você
essa
num
nem
suas
meu
às
minha
numa
pelos
elas
qual
nós
lhe
deles
essas
esses
pelas
este
dele
tu
te
vocês
vos
lhes
meus
minhas
teu
tua
teus
tuas
nosso
nossa
nossos
nossas
dela
delas
esta
estes
estas
aquele
aquela
aqueles
aquelas
isto
aquilo
estou
está
estamos
estão
estive
esteve
estivemos
estiveram
estava
estávamos
estavam
estivera
estivéramos
esteja
estejamos
estejam
estivesse
estivéssemos
estivessem
estiver
estivermos
estiverem
hei
havemos
hão
houve
houvemos
houveram
houvera
houvéramos
haja
hajamos
hajam
houvesse
houvéssemos
houvessem
houver
houvermos
houverem
houverei
houverá
houveremos
houverão
houveria
houveríamos
houveriam
sou
somos
são
era
éramos
eram
fui
foi
fomos
foram
fora
fôramos
seja
sejamos
sejam
fosse
fôssemos
fossem
for
formos
forem
serei
será
seremos
serão
seria
seríamos
seriam
tenho
tem
temos
tém
tinha
tínhamos
tinham
tive
teve
tivemos
tiveram
tivera
tivéramos
tenha
tenhamos
tenham
tivesse
tivéssemos
tivessem
tiver
tivermos
tiverem
terei
terá
teremos
terão
teria
teríamos
teriam

View File

@ -0,0 +1,151 @@
и
в
во
не
что
он
на
я
с
со
как
а
то
все
она
так
его
но
да
ты
к
у
же
вы
за
бы
по
только
ее
мне
было
вот
от
меня
еще
нет
о
из
ему
теперь
когда
даже
ну
вдруг
ли
если
уже
или
ни
быть
был
него
до
вас
нибудь
опять
уж
вам
ведь
там
потом
себя
ничего
ей
может
они
тут
где
есть
надо
ней
для
мы
тебя
их
чем
была
сам
чтоб
без
будто
чего
раз
тоже
себе
под
будет
ж
тогда
кто
этот
того
потому
этого
какой
совсем
ним
здесь
этом
один
почти
мой
тем
чтобы
нее
сейчас
были
куда
зачем
всех
никогда
можно
при
наконец
два
об
другой
хоть
после
над
больше
тот
через
эти
нас
про
всего
них
какая
много
разве
три
эту
моя
впрочем
хорошо
свою
этой
перед
иногда
лучше
чуть
том
нельзя
такой
им
более
всегда
конечно
всю
между

View File

@ -0,0 +1,313 @@
de
la
que
el
en
y
a
los
del
se
las
por
un
para
con
no
una
su
al
lo
como
más
pero
sus
le
ya
o
este
porque
esta
entre
cuando
muy
sin
sobre
también
me
hasta
hay
donde
quien
desde
todo
nos
durante
todos
uno
les
ni
contra
otros
ese
eso
ante
ellos
e
esto
antes
algunos
qué
unos
yo
otro
otras
otra
él
tanto
esa
estos
mucho
quienes
nada
muchos
cual
poco
ella
estar
estas
algunas
algo
nosotros
mi
mis
te
ti
tu
tus
ellas
nosotras
vosostros
vosostras
os
mío
mía
míos
mías
tuyo
tuya
tuyos
tuyas
suyo
suya
suyos
suyas
nuestro
nuestra
nuestros
nuestras
vuestro
vuestra
vuestros
vuestras
esos
esas
estoy
estás
está
estamos
estáis
están
esté
estés
estemos
estéis
estén
estaré
estarás
estará
estaremos
estaréis
estarán
estaría
estarías
estaríamos
estaríais
estarían
estaba
estabas
estábamos
estabais
estaban
estuve
estuviste
estuvo
estuvimos
estuvisteis
estuvieron
estuviera
estuvieras
estuviéramos
estuvierais
estuvieran
estuviese
estuvieses
estuviésemos
estuvieseis
estuviesen
estando
estado
estada
estados
estadas
estad
he
has
ha
hemos
habéis
han
haya
hayas
hayamos
hayáis
hayan
habré
habrás
habrá
habremos
habréis
habrán
habría
habrías
habríamos
habríais
habrían
había
habías
habíamos
habíais
habían
hube
hubiste
hubo
hubimos
hubisteis
hubieron
hubiera
hubieras
hubiéramos
hubierais
hubieran
hubiese
hubieses
hubiésemos
hubieseis
hubiesen
habiendo
habido
habida
habidos
habidas
soy
eres
es
somos
sois
son
sea
seas
seamos
seáis
sean
seré
serás
será
seremos
seréis
serán
sería
serías
seríamos
seríais
serían
era
eras
éramos
erais
eran
fui
fuiste
fue
fuimos
fuisteis
fueron
fuera
fueras
fuéramos
fuerais
fueran
fuese
fueses
fuésemos
fueseis
fuesen
sintiendo
sentido
sentida
sentidos
sentidas
siente
sentid
tengo
tienes
tiene
tenemos
tenéis
tienen
tenga
tengas
tengamos
tengáis
tengan
tendré
tendrás
tendrá
tendremos
tendréis
tendrán
tendría
tendrías
tendríamos
tendríais
tendrían
tenía
tenías
teníamos
teníais
tenían
tuve
tuviste
tuvo
tuvimos
tuvisteis
tuvieron
tuviera
tuvieras
tuviéramos
tuvierais
tuvieran
tuviese
tuvieses
tuviésemos
tuvieseis
tuviesen
teniendo
tenido
tenida
tenidos
tenidas
tened

View File

@ -0,0 +1,114 @@
och
det
att
i
en
jag
hon
som
han
den
med
var
sig
för
till
är
men
ett
om
hade
de
av
icke
mig
du
henne
sin
nu
har
inte
hans
honom
skulle
hennes
där
min
man
ej
vid
kunde
något
från
ut
när
efter
upp
vi
dem
vara
vad
över
än
dig
kan
sina
här
ha
mot
alla
under
någon
eller
allt
mycket
sedan
ju
denna
själv
detta
åt
utan
varit
hur
ingen
mitt
ni
bli
blev
oss
din
dessa
några
deras
blir
mina
samma
vilken
er
sådan
vår
blivit
dess
inom
mellan
sådant
varför
varje
vilka
ditt
vem
vilket
sitta
sådana
vart
dina
vars
vårt
våra
ert
era
vilkas

View File

@ -0,0 +1,53 @@
acaba
ama
aslında
az
bazı
belki
biri
birkaç
birşey
biz
bu
çok
çünkü
da
daha
de
defa
diye
eğer
en
gibi
hem
hep
hepsi
her
hiç
için
ile
ise
kez
ki
kim
mı
mu
nasıl
ne
neden
nerde
nerede
nereye
niçin
niye
o
sanki
şey
siz
şu
tüm
ve
veya
ya
yani

View File

@ -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.

View File

@ -0,0 +1,30 @@
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.

View File

@ -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()"

View File

@ -0,0 +1,135 @@
@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=
cd "%VIRTUAL_ENV%"
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

View File

@ -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()"

View File

@ -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.append(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()

View File

@ -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" %*

View File

@ -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

View File

@ -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

View File

@ -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)

View File

@ -0,0 +1,888 @@
<!-- This Source Code Form is subject to the terms of the Mozilla Public
- License, v. 2.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 packages.
`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, 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:
<pre>
README.md
package.json
data/
lib/
main.js
tests/
test-main.js
docs/
main.md
</pre>
### 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. Defaults to <code>addon-kit</code>.
</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>python-lib/cuddlefish/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 and run its tests by loading from the `tests` directory
any modules that start with the word `test-` and calling each of their exported
functions, passing them a [test runner](packages/api-utils/unit-test.html)
object as an argument.
#### 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 packages that your package
depends on.
<br>
For example: if your add-on depends on <code>addon-kit</code> and you
supply this option, then <code>cfx</code> will run the unit tests for
<code>addon-kit</code> as well as those for your add-on.
</td>
</tr>
<tr>
<td>
<code>-f FILTER, --filter=FILTER</code>
</td>
<td>
Run only those test modules whose names match the regexp supplied in
FILTER.
<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. Defaults to <code>addon-kit</code>.
</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>python-lib/cuddlefish/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 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](packages/addon-kit/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 --profile-dir="~/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` 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 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.

View File

@ -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.

View File

@ -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/. -->
# 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
* 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 OShannessy
* l.m.orchard
* Alexandre Poirot
* Nickolay Ponomarev
* Aza Raskin
* 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)

View File

@ -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

View File

@ -0,0 +1,144 @@
<!-- This Source Code Form is subject to the terms of the Mozilla Public
- License, v. 2.0. If a copy of the MPL was not distributed with this
- file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
# CommonJS, Packages, and the SDK #
CommonJS is the underlying infrastructure for both the SDK and the add-ons
you build using the SDK.
The [CommonJS group](http://wiki.commonjs.org/wiki/CommonJS) defines
specifications for **modules** and **packages**.
## CommonJS Modules ##
A CommonJS **module** is a piece of reusable JavaScript: it exports certain
objects which are thus made available to dependent code. To facilitate this
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)
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("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";
## CommonJS Packages ##
A CommonJS **package** is a structure which can wrap a collection of related
modules: this makes it easier to distribute, install and manage modules.
Minimally, a package must include a package descriptor file named
`package.json`: this file contains information about the package such as a short
description, the authors, and the other packages it depends on.
Packages must also follow a particular directory structure, which is the
structure `cfx init` created for your add-on.
## CommonJS and the Add-on SDK ##
<img class="image-right" src="static-files/media/commonjs-wikipanel.png"
alt="CommonJS wikipanel">
* The JavaScript modules which the SDK provides are CommonJS modules, and they
are collected into CommonJS packages.
* The JavaScript components of an add-on constitute one or more
CommonJS modules, and a complete add-on is a CommonJS package.
According to the CommonJS specification, if a module called `main` exists in a
CommonJS package, that module will be evaluated as soon as your program is
loaded. For an add-on, that means that the `main` module will be evaluated as
soon as Firefox has enabled the add-on.
Because an add-on is a CommonJS package it's possible to include more than one
module in an add-on, and to make your modules available to any code that want
to use them.
## Packages in the SDK ##
Navigate to the root of your SDK installation and list the contents of
the "packages" directory:
<pre>
ls packages
</pre>
You will see something like this:
<pre>
addon-kit api-utils test-harness
</pre>
So the modules which implement the SDK's APIs are
collected into three packages, `addon-kit`, `api-utils` and `test-harness`.
### <a name="addon-kit">addon-kit</a> ###
Modules in the `addon-kit` package 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 stable, and that
we'll avoid making incompatible changes to them unless absolutely
necessary.
They are documented in the "High-Level APIs" section
of the sidebar.
### <a name="api-utils">api-utils</a> ###
Modules in the `api-utils` package implement low-level APIs. These
modules fall roughly into three categories:
* fundamental utilities such as
[collection](packages/api-utils/collection.html) and
[url](packages/api-utils/url.html). Many add-ons are likely to
want to use modules from this category.
* building blocks for higher level modules, such as
[base](packages/api-utils/base.html) and
[namespace](packages/api-utils/namespace.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 [xhr](packages/api-utils/xhr.html) and
[xpcom](packages/api-utils/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 in the high-level addon-kit package (for
example, [tabs](packages/addon-kit/tabs.html) or
[request](packages/addon-kit/request.html)).
<div class="warning">
<p>These modules are still in active development,
and we expect to make incompatible changes to them in future releases.
</p>
If you use these modules in your add-on you may need to rewrite your
code when upgrading to a newer release of the SDK.
</div>
They are documented in the "Low-Level APIs" section of the sidebar.
### test-harness ###
Modules in this packages are used internally by the SDK's test code.

View File

@ -0,0 +1,206 @@
<!-- This Source Code Form is subject to the terms of the Mozilla Public
- License, v. 2.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 Script Access #
This page talks about the access content scripts have to:
* DOM objects in the pages they are attached to
* other content scripts
* other scripts loaded by the page they are attached to
## Access to the DOM ##
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 via a proxy.
Any changes they make are made to the proxy, and so are not visible to
page content.
The proxy is based on `XRayWrapper`, (also known as
[`XPCNativeWrapper`](https://developer.mozilla.org/en/XPCNativeWrapper)).
These wrappers give the user access to the native values of DOM functions
and properties, even if they have been redefined by a script.
For example: the page below redefines `window.confirm()` to return
`true` without showing a confirmation dialog:
<script type="syntaxhighlighter" class="brush: html"><![CDATA[
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html lang='en' xml:lang='en' xmlns="http://www.w3.org/1999/xhtml">
<head>
<script>
window.confirm = function(message) {
return true;
}
&lt;/script>
</head>
</html>
</script>
But thanks to the content proxy, a content script which calls
`window.confirm()` will get the native implementation:
var widgets = require("widget");
var tabs = require("tabs");
var data = require("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 proxy 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).
### 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.
## Access to Other 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.
## Access to Page Scripts ##
You can communicate between the content script and page scripts using
[`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.
The following page script uses
[`window.addEventListener`](https://developer.mozilla.org/en/DOM/element.addEventListener)
to listen for messages:
<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.addEventListener("message", function(event) {
window.alert(event.data);
}, false);
&lt;/script>
</head>
</html>
</script>
Content scripts can send it messages using `document.defaultView.postMessage()`:
var widgets = require("widget");
var tabs = require("tabs");
var data = require("self").data;
var widget = widgets.Widget({
id: "postMessage",
label: "demonstrate document.defaultView.postMessage",
contentURL: "http://www.mozilla.org/favicon.ico",
onClick: function() {
tabs.activeTab.attach({
contentScript: "document.defaultView.postMessage('hi there!', '*');"
});
}
});
tabs.open(data.url("listener.html"));

View File

@ -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/. -->
# 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](packages/addon-kit/panel.html)**<br>
Create a dialog that can host web content.
**[page-worker](packages/addon-kit/page-worker.html)**<br>
Retrieve a page and access its content, without displaying it to the user.
**[page-mod](packages/addon-kit/page-mod.html)**<br>
Execute scripts in the context of selected web pages.
**[widget](packages/addon-kit/widget.html)**<br>
Host an add-on's user interface, including web content.
**[context-menu](packages/addon-kit/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/access.html">
content script access</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](packages/addon-kit/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("page-mod");
pageMod.add(new 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
* [Content Script Access](dev-guide/guides/content-scripts/access.html):
detail about the access content scripts get to the DOM, to other content scripts,
and to scripts loaded by the 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

View File

@ -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/. -->
# 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
</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 package's
root directory, use a line like:
// "data" is supplied by the "self" module
var data = require("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("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".

View File

@ -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("self").data;
var reddit_panel = require("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("tabs").open(url);
});
require("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](packages/addon-kit/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).

View File

@ -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("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](packages/addon-kit/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](packages/addon-kit/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("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("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('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]);
});
}
});

View File

@ -0,0 +1,140 @@
<!-- This Source Code Form is subject to the terms of the Mozilla Public
- License, v. 2.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('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("panel").Panel({
onMessage: function(contentScriptMessage) {
// Handle message from the content script
}
});
## Message Events Versus User-Defined Events ##
You can use message events as an alternative to user-defined events:
var pageModScript = "window.addEventListener('mouseover', function(event) {" +
" self.postMessage(event.target.toString());" +
"}, false);";
var pageMod = require('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('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('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);
});
}
});

View File

@ -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`](packages/api-utils/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`](packages/addon-kit/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("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`](packages/addon-kit/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("widget");
var tabs = require("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("widget");
var tabs = require("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("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.

View File

@ -0,0 +1,100 @@
# Firefox Compatibility #
One of the promises the SDK makes is to maintain compatibility for its
["supported" or "high-level" APIs](packages/addon-kit/index.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>
python-lib/cuddlefish/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.

View File

@ -0,0 +1,171 @@
<!-- This Source Code Form is subject to the terms of the Mozilla Public
- License, v. 2.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/commonjs.html">CommonJS, packages, and the SDK</a></h4>
<a href="http://www.commonjs.org/">CommonJS</a> includes a specification
for JavaScript modules: reusable pieces of JavaScript code. This guide
provides an introduction to the CommonJS module specification and
explains its relationship to the SDK.
</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>
<h4><a href="dev-guide/guides/module-search.html">Module search</a></h4>
The algorithm used to find and load modules imported using the
<code>require()</code> statement.
</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>
</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/access.html">Content script access</a></h4>
Detailed information on the objects available to content scripts,
the differences between content scripts and normal page scripts,
and how to communicate between content scripts and page scripts.
</td>
<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>
</tr>
<tr>
<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>
<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>

View File

@ -0,0 +1,219 @@
<!-- This Source Code Form is subject to the terms of the Mozilla Public
- License, v. 2.0. If a copy of the MPL was not distributed with this
- file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
# Porting the Library Detector #
This example walks through the process of porting a XUL-based add-on to the
SDK. It's a very simple add-on and a good candidate for porting because
there are suitable SDK APIs for all its features.
<img class="image-right" src="static-files/media/librarydetector/library-detector.png" alt="Library Detector Screenshot" />
The add-on is Paul Bakaus's
[Library Detector](https://addons.mozilla.org/en-US/firefox/addon/library-detector/).
The Library Detector tells you which JavaScript frameworks the current
web page is using. It does this by checking whether particular objects
that those libraries add to the global window object are defined.
For example, if `window.jQuery` is defined, then the page has loaded
[jQuery](http://jquery.com/).
For each library that it finds, the library detector adds an icon
representing that library to the status bar. It adds a tooltip to each
icon, which contains the library name and version.
You can browse and run the ported version in the SDK's `examples` directory.
### How the Library Detector Works ###
All the work is done inside a single file,
[`librarydetector.xul`](http://code.google.com/p/librarydetector/source/browse/trunk/chrome/content/librarydetector.xul)
This contains:
<ul>
<li>a XUL overlay</li>
<li>a script</li>
</ul>
The XUL overlay adds a `box` element to the browser's status bar:
<script type="syntaxhighlighter" class="brush: html"><![CDATA[
&lt;statusbar id="status-bar"&gt; &lt;box orient="horizontal" id="librarydetector"&gt; &lt;/box&gt; &lt;/statusbar&gt;
]]>
</script>
The script does everything else.
The bulk of the script is an array of test objects, one for each library.
Each test object contains a function called `test()`: if the
function finds the library, it defines various additional properties for
the test object, such as a `version` property containing the library version.
Each test also contains a `chrome://` URL pointing to the icon associated with
its library.
The script listens to [gBrowser](https://developer.mozilla.org/en/Code_snippets/Tabbed_browser)'s
`DOMContentLoaded` event. When this is triggered, the `testLibraries()`
function builds an array of libraries by iterating through the tests and
adding an entry for each library which passes.
Once the list is built, the `switchLibraries()` function constructs a XUL
`statusbarpanel` element for each library it found, populates it with the
icon at the corresponding `chrome://` URL, and adds it to the box.
Finally, it listens to gBrowser's `TabSelect` event, to update the contents
of the box for that window.
### Content Script Separation ###
The test objects in the original script need access to the DOM window object,
so in the SDK port, they need to run in a content script. In fact, they need
access to the un-proxied DOM window, so they can see the objects added by
libraries, so well need to use the experimental
[unsafeWindow](dev-guide/guides/content-scripts/access.html) object.
The main add-on script, `main.js`, will use a
[`page-mod`](packages/addon-kit/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`](packages/api-utils/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`](packages/addon-kit/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`](packages/addon-kit/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`](packages/addon-kit/tabs.html) module to update the
widget's content when necessary (for example, when the user switches between
tabs):
tabs.on('activate', function(tab) {
updateWidgetView(tab);
});
tabs.on('ready', function(tab) {
tab.libraries = [];
});
#### Showing the Library Detail ####
The XUL library detector displayed the detailed information about each
library on mouseover in a tooltip: we can't do this using a widget, so
instead will use a panel. This means we'll need two additional content
scripts:
* one in the widget's context, which listens for icon mouseover events
and sends a message to `main.js` containing the name of the corresponding
library:
<pre><code>
function setLibraryInfo(element) {
self.port.emit('setLibraryInfo', element.target.title);
}
var elements = document.getElementsByTagName('img');
for (var i = 0; i &lt; elements.length; i++) {
elements[i].addEventListener('mouseover', setLibraryInfo, false);
}
</code></pre>
* one in the panel, which updates the panel's content with the library
information:
<pre><code>
self.on("message", function(libraryInfo) {
window.document.body.innerHTML = libraryInfo;
});
</code></pre>
Finally `main.js` relays the library information from the widget to the panel:
<pre><code>
widget.port.on('setLibraryInfo', function(libraryInfo) {
widget.panel.postMessage(libraryInfo);
});
</code></pre>
<img class="image-center" src="static-files/media/librarydetector/panel-content.png" alt="Updating panel content" />

View File

@ -0,0 +1,117 @@
<!-- This Source Code Form is subject to the terms of the Mozilla Public
- License, v. 2.0. If a copy of the MPL was not distributed with this
- file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
# Module Search #
## require(what?) ##
The Add-on SDK uses [CommonJS](dev-guide/guides/commonjs.html)
modules, in which all functionality is acquired by using the `require()`
function. The first argument to `require()` is a "module name" which roughly
indicates what kind of functionality is desired.
CommonJS does not specify how implementations are supposed to map these
module names to actual code. As with any programming environment, a set of
conventions have developed, and are encouraged/enforced by each runtime
system.
The module-search logic needs to provide features like:
* support for "packages": groups of related modules that are bundled together
for easy distribution
* easy and concise use of "stdlib" modules like `panel` and `page-mod` in
`packages/addon-kit/lib`, perhaps searching multiple packages for a module
with the right name
* "absolute" imports: minimize searching (and ambiguity) by specifying
exactly which package contains the module of interest
* relative imports: when two related modules live in the same directory, they
should be able to import each other without concern about namespace
collisions with other, unrelated modules
## Packages ##
Modules are split up into separate "packages", such as the "addon-kit"
package shipped with the SDK. Each module lives in exactly one package. Each
packages is a directory with a `package.json` file that contains metadata
about the package.
As described in the
[Package Specification](dev-guide/package-spec.html), code
modules are usually placed in the `lib/` subdirectory of a package, but the
`directories` key can be used to override this (e.g. to put the modules in
the package's root directory instead of `lib/`). The `dependencies` key is
used to indicate other packages that should be searched for modules (when
searching is done at all, see below), and the SDK automatically adds
`addon-kit` to the `.dependencies` list for the top-level addon package.
Certain packages (such as those distributed via [NPM](http://npmjs.org/), the
Node Package Manager) hide their internal structure begin a single "entry
point". This is indicated by a `main` key in `package.json` that points to a
module (e.g. `"main": "lib/main.js"`).
When the SDK starts any operation (`cfx test`, `cfx run`, or `cfx xpi`), it
builds a list of all known packages. This list includes the top-level addon
itself (i.e. the current working directory when `cfx` was invoked), all the
subdirectories of the SDK's `packages/` directory (including `addon-kit`),
and all subdirectories of each entry in the `--package-path`. Each package
must have a unique name, otherwise `cfx` will raise an error.
## SDK Search Rules ##
The Add-on SDK's CommonJS loader uses a set of rules to get from the
`require()` module name to a file of code. There are two setup steps:
* First, determine the package that owns the module doing the `require()`.
This is called "FROM-PACKAGE" and is used for relative imports and
searches. Likewise, "FROM-MODULE" is the on-disk location of the module
doing the `require()`.
* Second, build a list of packages to be searched, in case a search is called
for. This list always starts with FROM-PACKAGE, then the list of
`.dependencies` from FROM-PACKAGE's `package.json` is appended. For
example, if package A has a `package.json` with a `.dependencies` key that
includes modules B and C, the search-path for A will contain [A, B, C]. If
the package does not have a `.dependencies`, then any search will first
check FROM-PACKAGE, then will check all known packages (in alphabetical
order).
Then the lookup logic works as follows:
1. If the module-name starts with `./` or `../` then this is a "relative
import". These imports always find modules from the same package as the
importer (i.e. from FROM-PACKAGE). `./bar` will always point to a module
in the same directory as FROM-MODULE, and `../up` goes up a directory.
Some examples:
* FROM-MODULE=`packages/pkg-one/lib/foo.js`: `require("./bar")` will
locate `packages/pkg-one/lib/bar.js`
* FROM-MODULE=`packages/pkg-one/lib/foo.js`: `require("./sub/baz")` will
locate `packages/pkg-one/lib/sub/baz.js`
* FROM-MODULE=`packages/pkg-one/lib/sub/abc.js`: `require("../def")` will
load `packages/pkg-one/lib/def.js`
* FROM-MODULE=`packages/pkg-one/lib/sub/abc.js`: `require("../misc/ghi")` w
will load `packages/pkg-one/lib/misc/ghi.js`
* If the module cannot be found by these rules, an error is raised.
2. If the module-name contains a slash "`/`" but does not start with a
period, such as `require("A/misc/foo")`, the loader interprets the first
component ("A") as a package name. If there is such a package, it
interprets the rest of the name ("misc/foo") as a module path relative to
the top of the package. This uses the `.directories` key, so for packages
that use `lib/`, this will look for e.g. `packages/A/lib/misc/foo.js`. If
the first component does not match a known package name, processing
continues with the package-search below.
3. If the module-name does not contain a slash "`/`", the loader
attempts to interpret it as a package name (intending to use that
package's "entry point"). If there is a package with that name, the `main`
property is consulted, interpreted as a filename relative to the
`package.json` file, and the resulting module is loaded. If there is no
package by that name, processing continues with the package-search below.
4. The module-name (either a single component, or multiple components
joined by slashes) is used as the subject of a package-search. Each package
in the search list is checked to see if the named module is present, and
the first matching module is loaded. For example, if
FROM-MODULE=`packages/A/lib/sub/foo.js`, and `packages/A/package.json` has
a `.dependencies` of `[B,C]`, the search-path will contain `[A,B,C]`. If
foo.js does `require("bar/baz")`, the loader will look first for
`packages/A/lib/bar/baz.js`, then `packages/B/lib/bar/baz.js`, then finally
`packages/C/lib/bar/baz.js`.
5. If no module is found by those steps, an error is raised.

View File

@ -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`](packages/addon-kit/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.

View File

@ -0,0 +1,113 @@
<!-- This Source Code Form is subject to the terms of the Mozilla Public
- License, v. 2.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>
<tr>
<td><strong><a name="localization">Localization Support</a></strong></td>
<td><p>The SDK currently only has fairly basic <a href="dev-guide/tutorials/l10n.html">localization support</a>.
</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.

View File

@ -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/commonjs.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>

View File

@ -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`](packages/addon-kit/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.
These are collected together in the
[`addon-kit`](packages/addon-kit/index.html)
package. 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 in the "Developer Guide" sidebar.
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`](packages/addon-kit/widget.html) and
[`panel`](packages/addon-kit/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 some of which, such
as [`tab-browser`](packages/api-utils/tab-browser.html), [`xhr`](packages/api-utils/xhr.html), and
[`window-utils`](packages/api-utils/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`](packages/api-utils/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`:
var windowUtils = require("window-utils");
windowUtils = new windowUtils.WindowTracker({
onTrack: function (window) {
if ("chrome://browser/content/browser.xul" != window.location) return;
var forward = window.document.getElementById('forward-button');
var parent = window.document.getElementById('unified-back-forward-button');
parent.removeChild(forward);
}
});
This example just removes the 'forward' button from the browser. It constructs
a `WindowTracker` object and assigns a function to the constructor's `onTrack`
option. This function will be called whenever a window is opened. The function
checks whether the window is the browser's chrome window, and if it is, uses
DOM manipulation functions to modify it.
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 [`tab-browser`](packages/api-utils/tab-browser.html) module gives
you direct access to the
[tabbrowser](https://developer.mozilla.org/en/XUL/tabbrowser) object. This
simple example modifies the selected tab's CSS to enable the user to highlight
the selected tab:
var widgets = require("widget");
var tabbrowser = require("tab-browser");
var self = require("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() {
highlightTab(tabbrowser.activeTab);
}
});
### 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`](packages/addon-kit/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`](packages/api-utils/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("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("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.

View File

@ -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.

View File

@ -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/. -->
<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/commonjs.html">CommonJS</a>, the
<a href="dev-guide/guides/program-id.html">Program ID</a>, the
<a href="dev-guide/guides/module-search.html">module search algorithm</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>API reference</h4>
Reference documentation for the high-level SDK APIs found in the
<a href="packages/addon-kit/index.html">addon-kit</a>
package, and the low-level APIs found in the
<a href="packages/api-utils/index.html">api-utils</a> package.
</td>
<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>
</tr>
</table>

View File

@ -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](packages/api-utils/collection.html) and
[url](packages/api-utils/url.html). Many add-ons are likely to
want to use modules from this category.
* building blocks for higher level modules, such as
[events](packages/api-utils/events.html),
[worker](packages/api-utils/content/worker.html), and
[api-utils](packages/api-utils/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](packages/api-utils/tab-browser.html),
[xhr](packages/api-utils/xhr.html), and
[xpcom](packages/api-utils/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](packages/addon-kit/tabs.html) or
[request](packages/addon-kit/request.html)).
These modules are still in active development, and we expect to
make incompatible changes to them in future releases.

View File

@ -0,0 +1,115 @@
# 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`.
* `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](packages/addon-kit/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](packages/addon-kit/self.html) module.
[Markdown]: http://daringfireball.net/projects/markdown/
[non-bootstrapped XUL extension]: #guide/xul-extensions

View File

@ -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.

View File

@ -0,0 +1,88 @@
<!-- This Source Code Form is subject to the terms of the Mozilla Public
- License, v. 2.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`](packages/addon-kit/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 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](packages/addon-kit/context-menu.html).

View File

@ -0,0 +1,117 @@
<!-- This Source Code Form is subject to the terms of the Mozilla Public
- License, v. 2.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) package
that enables us to add menu items.
## Installing `menuitems` ##
First we'll download `menuitems` from
[https://github.com/erikvold/menuitems-jplib](https://github.com/erikvold/menuitems-jplib/zipball/51080383cbb0fe2a05f8992a8aae890f4c014176).
Next, extract it under the SDK's `packages` directory:
<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 contains the `menuitems` package.
The modules it contains are listed below it: you'll
see that `menuitems` contains a single module, also
called `menuitems`.
Click on the module name and you'll see API documentation for the module. Click
on the package name and you'll see basic information about the package.
One important entry in the package page lists the package's dependencies:
<pre>
Dependencies api-utils, 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 replace its contents
with this:
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
built-in packages like [`addon-kit`](packages/addon-kit/index.html), then
you must also declare your dependency on that built-in package, like this:
<pre>
"dependencies": ["menuitems", "addon-kit"]
</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 some are collected in the
[Jetpack Wiki](https://wiki.mozilla.org/Jetpack/Modules)
* because third party modules typically use low-level APIs, they may be broken
by new releases of Firefox.

View File

@ -0,0 +1,175 @@
<!-- This Source Code Form is subject to the terms of the Mozilla Public
- License, v. 2.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`](packages/addon-kit/widget.html) module.
The default add-on created by `cfx init`
uses a widget, so we'll start with that as an example. If you haven't already
followed the tutorial introducing
[`cfx init`](dev-guide/tutorials/getting-started-with-cfx.html#cfx-init),
do that now, then come back here.
Create a new directory, navigate to it, and execute `cfx init`. Then open the file called
"main.js" in the "lib" directory:
const widgets = require("widget");
const tabs = require("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("widget");
var tabs = require("tabs");
var self = require("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.
## 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("widget");
var tabs = require("tabs");
var self = require("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("self").data
var clockPanel = require("panel").Panel({
width:215,
height:160,
contentURL: data.url("clock.html")
});
require("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](packages/addon-kit/widget.html).
To learn more about content scripts, see the
[content scripts guide](dev-guide/guides/content-scripts/index.html).

View File

@ -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](packages/api-utils/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](packages/addon-kit/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('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('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).

View File

@ -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 `addon-kit` package.

View File

@ -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 most of the modules in the
addon-kit package.
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)

View File

@ -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).

View File

@ -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`](packages/addon-kit/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('simple-storage');
In the module scope, initialize an array which will contain the stored annotations:
if (!simpleStorage.storage.annotations)
simpleStorage.storage.annotations = [];
Now we'll add a function to the module scope which deals with a new
annotation. The annotation is composed of the text the user entered and the
"annotation anchor", which consists of the URL, element ID and element content:
function handleNewAnnotation(annotationText, anchor) {
var newAnnotation = new Annotation(annotationText, anchor);
simpleStorage.storage.annotations.push(newAnnotation);
}
This function calls a constructor for an `Annotation` object, which we also
need to supply:
function Annotation(annotationText, anchor) {
this.annotationText = annotationText;
this.url = anchor[0];
this.ancestorId = anchor[1];
this.anchorText = anchor[2];
}
Now we need to link this code to the annotation editor, so that when the user
presses the return key in the editor, we create and store the new annotation:
var annotationEditor = panels.Panel({
width: 220,
height: 220,
contentURL: data.url('editor/annotation-editor.html'),
contentScriptFile: data.url('editor/annotation-editor.js'),
onMessage: function(annotationText) {
if (annotationText)
handleNewAnnotation(annotationText, this.annotationAnchor);
annotationEditor.hide();
},
onShow: function() {
this.postMessage('focus');
}
});
## Listing Stored Annotations ##
To prove that this works, let's implement the part of the add-on that displays
all the previously entered annotations. This is implemented as a panel that's
shown in response to the widget's `right-click` message.
The panel has three new files associated with it:
* a content-script which builds the panel content
* a simple HTML file used as a template for the panel's content
* a simple CSS file to provide some basic styling.
These three files can all go in a new subdirectory of `data` which we will call `list`.
### Annotation List Content Script ###
Here's the annotation list's content script:
self.on("message", function onMessage(storedAnnotations) {
var annotationList = $('#annotation-list');
annotationList.empty();
storedAnnotations.forEach(
function(storedAnnotation) {
var annotationHtml = $('#template .annotation-details').clone();
annotationHtml.find('.url').text(storedAnnotation.url)
.attr('href', storedAnnotation.url);
annotationHtml.find('.url').bind('click', function(event) {
event.stopPropagation();
event.preventDefault();
self.postMessage(storedAnnotation.url);
});
annotationHtml.find('.selection-text')
.text(storedAnnotation.anchorText);
annotationHtml.find('.annotation-text')
.text(storedAnnotation.annotationText);
annotationList.append(annotationHtml);
});
});
It builds the DOM for the panel from the array of annotations it is given.
The user will be able to click links in the panel, but we want to open them in
the main browser window rather than the panel. So the content script binds a
click handler to the links which will send the URL to the add-on.
Save this file in `data/list` as `annotation-list.js`.
### Annotation List HTML and CSS ###
Here's the HTML for the annotation list:
<pre class="brush: html">
&lt;html&gt;
&lt;head&gt;
&lt;meta http-equiv="Content-type" content="text/html; charset=utf-8" /&gt;
&lt;title&gt;Saved annotations&lt;/title&gt;
&lt;link rel="stylesheet" type="text/css" href="annotation-list.css" /&gt;
&lt;/head&gt;
&lt;body&gt;
&lt;div id="annotation-list"&gt;
&lt;/div&gt;
&lt;div id="template"&gt;
&lt;div class="annotation-details"&gt;
&lt;a class="url"&gt;&lt;/a&gt;
&lt;div class="selection-text"&gt;&lt;/div&gt;
&lt;div class="annotation-text"&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/body&gt;
&lt;/html&gt;
</pre>
Here's the corresponding CSS:
<script type="syntaxhighlighter" class="brush: css"><![CDATA[
#annotation-list .annotation-details
{
padding: 10px;
margin: 10px;
border: solid 3px #EEE;
background-color: white;
}
#annotation-list .url, .selection-text, .annotation-text
{
padding: 5px;
margin: 5px;
}
#annotation-list .selection-text,#annotation-list .annotation-text
{
border: solid 1px #EEE;
}
#annotation-list .annotation-text
{
font-style: italic;
}
body
{
background-color: #F5F5F5;
font: 100% arial, helvetica, sans-serif;
}
h1
{
font-family: georgia,serif;
font-size: 1.5em;
text-align:center;
}
]]>
</script>
Save these in `data/list` as `annotation-list.html` and `annotation-list.css`
respectively.
### Updating main.js ###
Here's the code to create the panel, which can go in the `main` function.
var annotationList = panels.Panel({
width: 420,
height: 200,
contentURL: data.url('list/annotation-list.html'),
contentScriptFile: [data.url('jquery-1.4.2.min.js'),
data.url('list/annotation-list.js')],
contentScriptWhen: 'ready',
onShow: function() {
this.postMessage(simpleStorage.storage.annotations);
},
onMessage: function(message) {
require('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("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('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).

View File

@ -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 replace its contents with this:
var widgets = require('widget');
var data = require('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).

View File

@ -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.

View File

@ -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`](packages/addon-kit/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("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("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("widget").Widget({
label: "Text entry",
id: "text-entry",
contentURL: "http://www.mozilla.org/favicon.ico",
panel: text_entry
});
// When the panel is displayed it generated an event called
// "show": we will listen for that event and when it happens,
// send our own "show" event to the panel's script, so the
// script can prepare the panel for display.
text_entry.on("show", function() {
text_entry.port.emit("show");
});
// Listen for messages called "text-entered" coming from
// the content script. The message payload is the text the user
// entered.
// In this implementation we'll just log the text to the console.
text_entry.port.on("text-entered", function (text) {
console.log(text);
text_entry.hide();
});
The content script "get-text.js" looks like this:
// When the user hits return, send the "text-entered"
// message to main.js.
// The message payload is the contents of the edit box.
var textArea = document.getElementById("edit-box");
textArea.addEventListener('keyup', function onkeyup(event) {
if (event.keyCode == 13) {
// Remove the newline.
text = textArea.value.replace(/(\r\n|\n|\r)/gm,"");
self.port.emit("text-entered", text);
textArea.value = '';
}
}, false);
// Listen for the "show" event being sent from the
// main add-on code. It means that the panel's about
// to be shown.
//
// Set the focus to the text area so the user can
// just start typing.
self.port.on("show", function onShow() {
textArea.focus();
});
Finally, the "text-entry.html" file defines the `<textarea>` element:
<pre class="brush: html">
&lt;html&gt;
&lt;head&gt;
&lt;style type="text/css" media="all"&gt;
textarea {
margin: 10px;
}
&lt;/style&gt;
&lt;/head&gt;
&lt;body&gt;
&lt;textarea rows="10" cols="20" id="edit-box">&lt;/textarea&gt;
&lt;/body&gt;
&lt;/html&gt;
</pre>
Try it out: "main.js" is saved in your add-on's `lib` directory,
and the other two files go in your add-on's `data` directory:
<pre>
my-addon/
data/
get-text.js
text-entry.html
lib/
main.js
</pre>
Run the add-on, click the widget, and you should see the panel.
Type some text and press "return" and you should see the output
in the console.
## Learning More ##
To learn more about the `panel` module, see the
[`panel` API reference](packages/addon-kit/panel.html).
To learn more about attaching panels to widgets, see the
[`widget` API reference](packages/addon-kit/widget.html).

View File

@ -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.
`cfx init` actually creates a very basic add-on, so to see `cfx run` in action
we don't need to write any code. Just 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.
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:
const widgets = require("widget");
const tabs = require("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/");
}
});
This add-on uses two SDK modules: the
[`widget`](packages/addon-kit/widget.html) module, which enables you
to add buttons to the browser, and the
[`tabs`](packages/addon-kit/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:
const widgets = require("widget");
const tabs = require("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).

View File

@ -0,0 +1,233 @@
<!-- This Source Code Form is subject to the terms of the Mozilla Public
- License, v. 2.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="packages/addon-kit/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="packages/addon-kit/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="packages/addon-kit/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>
</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>

View File

@ -0,0 +1,154 @@
<!-- This Source Code Form is subject to the terms of the Mozilla Public
- License, v. 2.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:
<pre>
source 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.

View File

@ -0,0 +1,230 @@
<!-- This Source Code Form is subject to the terms of the Mozilla Public
- License, v. 2.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 basic localization of strings appearing in your
main add-on's JavaScript code. It doesn't, yet, support localization
of HTML, CSS, or content scripts.
## Writing Localizable Code ##
To write localizable code, you do this:
var _ = require("l10n").get;
console.log(_("Hello!"));
<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.
That's a fully functional add-on as it stands. If you run it
you'll see the expected output:
<pre>
info: Hello!
</pre>
## Localizing Your Code ##
To localize the code, create a directory called "locale" under
your main add-on directory. In it, create one file for each locale
you need to support. 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.
For example, if you want to add a French localization for the
add-on above, you'd add a file called "fr-FR.properties" in the
"locale" directory, and add the following to it:
<pre>
Hello!= Bonjour!
</pre>
Now if you run the add-on with Firefox switched to the French
locale, you'll see:
<pre>
info: Bonjour!
</pre>
## Using Identifiers ##
If `l10n.get()` can't find a localization of a string using the current
locale, then it just returns the string you passed in. This is why the
example above displayed "Hello!" when there were no locale files present.
This has the nice property that you can write localizable, fully
functional code without having to write any locale files. You can just
use the default language in your code, and subsequently supply
`.properties` files for all the additional locales you want to support.
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.
To avoid this you can use identifiers in your code, and supply
localizations for all the languages you intend to support. For example,
in your main.js:
var _ = require("l10n").get;
console.log(_("hello_string"));
Then you can create `.properties` files for both `en-US` and `fr-FR`:
<pre>
# en-US translations
hello_string= Hello!
</pre>
<pre>
# fr-FR translations
hello_string= Bonjour!
</pre>
## Plurals ##
The `l10n` module has basic support for plural forms. The following
`.properties` file includes separate localizations for the singular
and plural form of "child":
<pre>
child_id[one]= one child
child_id= %d children
</pre>
To use it, list the count of the item after its identifier:
var _ = require("l10n").get;
console.log(_("child_id", 1));
console.log(_("child_id", 2));
This will give the following output:
<pre>
info: one child
info: 2 children
</pre>
At the moment `l10n` only distinguishes between two plural forms:
"one", and "not one". So it doesn't support
[languages which have different plural rules](https://developer.mozilla.org/en/Localization_and_Plurals).
## 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("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("l10n").get;
console.log(_("home_town_id", "Bob", "London"));
An English translator might want to choose between the following:
<pre>
"&lt;town_name> is &lt;person_name>'s home town."
</pre>
<pre>
"&lt;person_name>'s home town is &lt;town_name>"
</pre>
To choose the first option, the `.properties` file can order the
placeholders as follows:
<pre>
home_town_id= %2s is %1s's home town.
</pre>
This gives us the following output:
<pre>
info: London is Bob's home town.
</pre>
## <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, HTML files, or CSS files: at
the moment, you can only localize strings appearing in JavaScript files
that can `require()` SDK modules.
* 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_string" 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.
* The algorithm used to find a matching locale is based on the
[Firefox implementation](http://mxr.mozilla.org/mozilla-central/source/chrome/src/nsChromeRegistryChrome.cpp#93)
which is known to be sub-optimal for some locales. We're working on
improving this in
[bug 711041](https://bugzilla.mozilla.org/show_bug.cgi?id=711041).

View File

@ -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`](packages/addon-kit/tabs.html) object itself.
The following add-on adds a
[`widget`](packages/addon-kit/widget.html) that logs
the URLs of open tabs when the user clicks it:
var widget = require("widget").Widget({
id: "mozilla-link",
label: "Mozilla website",
contentURL: "http://www.mozilla.org/favicon.ico",
onClick: listTabs
});
function listTabs() {
var tabs = require("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("widget").Widget({
id: "mozilla-link",
label: "Mozilla website",
contentURL: "http://www.mozilla.org/favicon.ico",
onClick: listTabs
});
function listTabs() {
var tabs = require("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](packages/addon-kit/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).

View File

@ -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`](packages/addon-kit/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("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("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](packages/addon-kit/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).

View File

@ -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 main() will be invoked a moment 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) {};
<span class="aside">
Note that if your add-on is unloaded with reason `disable`, it will not be
notified about `uninstall` while it is disabled: see
bug [571049](https://bugzilla.mozilla.org/show_bug.cgi?id=571049).
</span>
`reason` is one of the following strings describing the reason your add-on was
unloaded:
<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.

View File

@ -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 replace its contents with 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("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).

View File

@ -0,0 +1,241 @@
<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 only the following modules are fully functional:
* [page-mod](packages/addon-kit/page-mod.html)
* [page-worker](packages/addon-kit/page-worker.html)
* [request](packages/addon-kit/request.html)
* [self](packages/addon-kit/self.html)
* [simple-storage](packages/addon-kit/simple-storage.html)
* [timers](packages/addon-kit/timers.html)
We're working on adding support for the other modules.
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>

View File

@ -0,0 +1,145 @@
<!-- This Source Code Form is subject to the terms of the Mozilla Public
- License, v. 2.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](packages/addon-kit/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("widget");
var tabs = require("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 replace its contents with 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("widget");
var tabs = require("tabs");
var self = require("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("widget");
var tabs = require("tabs");
var self = require("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.
## 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](packages/addon-kit/tabs.html).
To learn more about content scripts, see the
[content scripts guide](dev-guide/guides/content-scripts/index.html).

View File

@ -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/. -->
# 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`](packages/addon-kit/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("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 replace its contents with 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`](packages/api-utils/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("page-mod");
// Import the self API
var self = require("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("page-mod");
// Import the self API
var self = require("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("page-mod");
// Import the self API
var self = require("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("page-mod");
// Import the self API
var self = require("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.
## Learning More ##
To learn more about page-mod, see its
[API reference page](packages/addon-kit/page-mod.html).
To learn more about content scripts, see the
[content scripts guide](dev-guide/guides/content-scripts/index.html).

View File

@ -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`](packages/addon-kit/tabs.html) module:
var tabs = require("tabs");
tabs.open("http://www.example.com");
This function is asynchronous, so you don't immediately get back a
[`tab` object](packages/addon-kit/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("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 attaches a script to all open
tabs. The script adds a red border to the tab's document:
var tabs = require("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](packages/addon-kit/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).

View File

@ -0,0 +1,413 @@
<!-- This Source Code Form is subject to the terms of the Mozilla Public
- License, v. 2.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`](packages/addon-kit/widget.html) or
[`panel`](packages/addon-kit/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("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 replace its contents with 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("window-utils").activeBrowserWindow;
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("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("window-utils").activeBrowserWindow;
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("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("self").name;
var addonId = require("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 the package and the modules it contains, then people
who install your package and execute `cfx docs` will see the documentation
integrated with the SDK's own documentation.
You can document the package that contains the geolocation module by editing
the "README.md" file that `cfx init` created in the package root. It's in
[Markdown](http://daringfireball.net/projects/markdown/syntax) syntax.
You can document the geolocation module itself 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:
* edit "README.md", and 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 package
and the module it contains.
### 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).

View File

@ -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/. -->
# 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.
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.

Some files were not shown because too many files have changed in this diff Show More