SS13: Web Mining, HCI, TK3
This commit is contained in:
parent
53ea467109
commit
abedfa08aa
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
ss2013/1_Web Mining/SS12/Sachen Chris/Exercise01/Textanalyse/Textanalyse.zip
Executable file
BIN
ss2013/1_Web Mining/SS12/Sachen Chris/Exercise01/Textanalyse/Textanalyse.zip
Executable file
Binary file not shown.
@ -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>
|
||||
@ -0,0 +1,3 @@
|
||||
# Enerjy Software baseline data (files=1, problems=1).
|
||||
path: /Textanalyse/src/Textanalyse.java
|
||||
problem: JAVA0024:JAVA0024 Empty class 'Textanalyse'
|
||||
@ -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>
|
||||
@ -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
|
||||
Binary file not shown.
Binary file not shown.
@ -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);
|
||||
}
|
||||
}
|
||||
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
@ -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;
|
||||
|
||||
}
|
||||
}
|
||||
@ -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/
|
||||
|
||||
@ -0,0 +1,94 @@
|
||||
og
|
||||
i
|
||||
jeg
|
||||
det
|
||||
at
|
||||
en
|
||||
den
|
||||
til
|
||||
er
|
||||
som
|
||||
på
|
||||
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
|
||||
@ -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
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
@ -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
|
||||
@ -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
|
||||
@ -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ó
|
||||
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
|
||||
rá
|
||||
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
|
||||
@ -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
|
||||
@ -0,0 +1,176 @@
|
||||
og
|
||||
i
|
||||
jeg
|
||||
det
|
||||
at
|
||||
en
|
||||
et
|
||||
den
|
||||
til
|
||||
er
|
||||
som
|
||||
på
|
||||
de
|
||||
med
|
||||
han
|
||||
av
|
||||
ikke
|
||||
ikkje
|
||||
der
|
||||
så
|
||||
var
|
||||
meg
|
||||
seg
|
||||
men
|
||||
ett
|
||||
har
|
||||
om
|
||||
vi
|
||||
min
|
||||
mitt
|
||||
ha
|
||||
hadde
|
||||
hun
|
||||
nå
|
||||
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
|
||||
då
|
||||
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
|
||||
@ -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
|
||||
já
|
||||
eu
|
||||
também
|
||||
só
|
||||
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
|
||||
há
|
||||
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
|
||||
@ -0,0 +1,151 @@
|
||||
и
|
||||
в
|
||||
во
|
||||
не
|
||||
что
|
||||
он
|
||||
на
|
||||
я
|
||||
с
|
||||
со
|
||||
как
|
||||
а
|
||||
то
|
||||
все
|
||||
она
|
||||
так
|
||||
его
|
||||
но
|
||||
да
|
||||
ты
|
||||
к
|
||||
у
|
||||
же
|
||||
вы
|
||||
за
|
||||
бы
|
||||
по
|
||||
только
|
||||
ее
|
||||
мне
|
||||
было
|
||||
вот
|
||||
от
|
||||
меня
|
||||
еще
|
||||
нет
|
||||
о
|
||||
из
|
||||
ему
|
||||
теперь
|
||||
когда
|
||||
даже
|
||||
ну
|
||||
вдруг
|
||||
ли
|
||||
если
|
||||
уже
|
||||
или
|
||||
ни
|
||||
быть
|
||||
был
|
||||
него
|
||||
до
|
||||
вас
|
||||
нибудь
|
||||
опять
|
||||
уж
|
||||
вам
|
||||
ведь
|
||||
там
|
||||
потом
|
||||
себя
|
||||
ничего
|
||||
ей
|
||||
может
|
||||
они
|
||||
тут
|
||||
где
|
||||
есть
|
||||
надо
|
||||
ней
|
||||
для
|
||||
мы
|
||||
тебя
|
||||
их
|
||||
чем
|
||||
была
|
||||
сам
|
||||
чтоб
|
||||
без
|
||||
будто
|
||||
чего
|
||||
раз
|
||||
тоже
|
||||
себе
|
||||
под
|
||||
будет
|
||||
ж
|
||||
тогда
|
||||
кто
|
||||
этот
|
||||
того
|
||||
потому
|
||||
этого
|
||||
какой
|
||||
совсем
|
||||
ним
|
||||
здесь
|
||||
этом
|
||||
один
|
||||
почти
|
||||
мой
|
||||
тем
|
||||
чтобы
|
||||
нее
|
||||
сейчас
|
||||
были
|
||||
куда
|
||||
зачем
|
||||
всех
|
||||
никогда
|
||||
можно
|
||||
при
|
||||
наконец
|
||||
два
|
||||
об
|
||||
другой
|
||||
хоть
|
||||
после
|
||||
над
|
||||
больше
|
||||
тот
|
||||
через
|
||||
эти
|
||||
нас
|
||||
про
|
||||
всего
|
||||
них
|
||||
какая
|
||||
много
|
||||
разве
|
||||
три
|
||||
эту
|
||||
моя
|
||||
впрочем
|
||||
хорошо
|
||||
свою
|
||||
этой
|
||||
перед
|
||||
иногда
|
||||
лучше
|
||||
чуть
|
||||
том
|
||||
нельзя
|
||||
такой
|
||||
им
|
||||
более
|
||||
всегда
|
||||
конечно
|
||||
всю
|
||||
между
|
||||
@ -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
|
||||
sí
|
||||
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
|
||||
mí
|
||||
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
|
||||
tú
|
||||
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
|
||||
@ -0,0 +1,114 @@
|
||||
och
|
||||
det
|
||||
att
|
||||
i
|
||||
en
|
||||
jag
|
||||
hon
|
||||
som
|
||||
han
|
||||
på
|
||||
den
|
||||
med
|
||||
var
|
||||
sig
|
||||
för
|
||||
så
|
||||
till
|
||||
är
|
||||
men
|
||||
ett
|
||||
om
|
||||
hade
|
||||
de
|
||||
av
|
||||
icke
|
||||
mig
|
||||
du
|
||||
henne
|
||||
då
|
||||
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
|
||||
@ -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
|
||||
mü
|
||||
nasıl
|
||||
ne
|
||||
neden
|
||||
nerde
|
||||
nerede
|
||||
nereye
|
||||
niçin
|
||||
niye
|
||||
o
|
||||
sanki
|
||||
şey
|
||||
siz
|
||||
şu
|
||||
tüm
|
||||
ve
|
||||
veya
|
||||
ya
|
||||
yani
|
||||
Binary file not shown.
Binary file not shown.
@ -0,0 +1,30 @@
|
||||
The files which make up the SDK are developed by Mozilla and licensed
|
||||
under the MPL 2.0 (http://mozilla.org/MPL/2.0/), with the exception of the
|
||||
components listed below, which are made available by their authors under
|
||||
the licenses listed alongside.
|
||||
|
||||
syntaxhighlighter
|
||||
------------------
|
||||
doc/static-files/syntaxhighlighter
|
||||
Made available under the MIT license.
|
||||
|
||||
jQuery
|
||||
------
|
||||
examples/reddit-panel/data/jquery-1.4.4.min.js
|
||||
examples/annotator/data/jquery-1.4.2.min.js
|
||||
Made available under the MIT license.
|
||||
|
||||
simplejson
|
||||
----------
|
||||
python-lib/simplejson
|
||||
Made available under the MIT license.
|
||||
|
||||
Python Markdown
|
||||
---------------
|
||||
python-lib/markdown
|
||||
Made available under the BSD license.
|
||||
|
||||
LibraryDetector
|
||||
---------------
|
||||
examples/library-detector/data/library-detector.js
|
||||
Made available under the MIT license.
|
||||
@ -0,0 +1,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.
|
||||
@ -0,0 +1,86 @@
|
||||
# This Source Code Form is subject to the terms of the Mozilla Public
|
||||
# License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
# This file must be used with "source bin/activate" *from bash*
|
||||
# you cannot run it directly
|
||||
|
||||
deactivate () {
|
||||
if [ -n "$_OLD_VIRTUAL_PATH" ] ; then
|
||||
PATH="$_OLD_VIRTUAL_PATH"
|
||||
export PATH
|
||||
unset _OLD_VIRTUAL_PATH
|
||||
fi
|
||||
|
||||
# This should detect bash and zsh, which have a hash command that must
|
||||
# be called to get it to forget past commands. Without forgetting
|
||||
# past commands the $PATH changes we made may not be respected
|
||||
if [ -n "$BASH" -o -n "$ZSH_VERSION" ] ; then
|
||||
hash -r
|
||||
fi
|
||||
|
||||
if [ -n "$_OLD_VIRTUAL_PS1" ] ; then
|
||||
PS1="$_OLD_VIRTUAL_PS1"
|
||||
export PS1
|
||||
unset _OLD_VIRTUAL_PS1
|
||||
fi
|
||||
|
||||
if [ -n "$_OLD_PYTHONPATH" ] ; then
|
||||
PYTHONPATH="$_OLD_PYTHONPATH"
|
||||
export PYTHONPATH
|
||||
unset _OLD_PYTHONPATH
|
||||
fi
|
||||
|
||||
unset CUDDLEFISH_ROOT
|
||||
|
||||
unset VIRTUAL_ENV
|
||||
if [ ! "$1" = "nondestructive" ] ; then
|
||||
# Self destruct!
|
||||
unset deactivate
|
||||
fi
|
||||
}
|
||||
|
||||
# unset irrelavent variables
|
||||
deactivate nondestructive
|
||||
|
||||
_OLD_PYTHONPATH="$PYTHONPATH"
|
||||
_OLD_VIRTUAL_PATH="$PATH"
|
||||
|
||||
VIRTUAL_ENV="`pwd`"
|
||||
|
||||
if [ "x$OSTYPE" = "xmsys" ] ; then
|
||||
CUDDLEFISH_ROOT="`pwd -W | sed s,/,\\\\\\\\,g`"
|
||||
PATH="`pwd`/bin:$PATH"
|
||||
# msys will convert any env vars with PATH in it to use msys
|
||||
# form and will unconvert before launching
|
||||
PYTHONPATH="`pwd -W`/python-lib;$PYTHONPATH"
|
||||
else
|
||||
CUDDLEFISH_ROOT="$VIRTUAL_ENV"
|
||||
PYTHONPATH="$VIRTUAL_ENV/python-lib:$PYTHONPATH"
|
||||
PATH="$VIRTUAL_ENV/bin:$PATH"
|
||||
fi
|
||||
|
||||
VIRTUAL_ENV="`pwd`"
|
||||
|
||||
export CUDDLEFISH_ROOT
|
||||
export PYTHONPATH
|
||||
export PATH
|
||||
|
||||
_OLD_VIRTUAL_PS1="$PS1"
|
||||
if [ "`basename \"$VIRTUAL_ENV\"`" = "__" ] ; then
|
||||
# special case for Aspen magic directories
|
||||
# see http://www.zetadev.com/software/aspen/
|
||||
PS1="[`basename \`dirname \"$VIRTUAL_ENV\"\``] $PS1"
|
||||
else
|
||||
PS1="(`basename \"$VIRTUAL_ENV\"`)$PS1"
|
||||
fi
|
||||
export PS1
|
||||
|
||||
# This should detect bash and zsh, which have a hash command that must
|
||||
# be called to get it to forget past commands. Without forgetting
|
||||
# past commands the $PATH changes we made may not be respected
|
||||
if [ -n "$BASH" -o -n "$ZSH_VERSION" ] ; then
|
||||
hash -r
|
||||
fi
|
||||
|
||||
python -c "from jetpack_sdk_env import welcome; welcome()"
|
||||
@ -0,0 +1,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
|
||||
@ -0,0 +1,99 @@
|
||||
# This Source Code Form is subject to the terms of the Mozilla Public
|
||||
# License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
$Env:VIRTUAL_ENV = (gl);
|
||||
$Env:CUDDLEFISH_ROOT = $Env:VIRTUAL_ENV;
|
||||
|
||||
# http://stackoverflow.com/questions/5648931/powershell-test-if-registry-value-exists/5652674#5652674
|
||||
Function Test-RegistryValue {
|
||||
param(
|
||||
[Alias("PSPath")]
|
||||
[Parameter(Position = 0, Mandatory = $true, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true)]
|
||||
[String]$Path
|
||||
,
|
||||
[Parameter(Position = 1, Mandatory = $true)]
|
||||
[String]$Name
|
||||
,
|
||||
[Switch]$PassThru
|
||||
)
|
||||
|
||||
process {
|
||||
if (Test-Path $Path) {
|
||||
$Key = Get-Item -LiteralPath $Path
|
||||
if ($Key.GetValue($Name, $null) -ne $null) {
|
||||
if ($PassThru) {
|
||||
Get-ItemProperty $Path $Name
|
||||
} else {
|
||||
$true
|
||||
}
|
||||
} else {
|
||||
$false
|
||||
}
|
||||
} else {
|
||||
$false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$WINCURVERKEY = 'HKLM:SOFTWARE\Microsoft\Windows\CurrentVersion';
|
||||
$WIN64 = (Test-RegistryValue $WINCURVERKEY 'ProgramFilesDir (x86)');
|
||||
|
||||
if($WIN64) {
|
||||
$PYTHONKEY='HKLM:SOFTWARE\Wow6432Node\Python\PythonCore';
|
||||
}
|
||||
else {
|
||||
$PYTHONKEY='HKLM:SOFTWARE\Python\PythonCore';
|
||||
}
|
||||
|
||||
$Env:PYTHONVERSION = '';
|
||||
$Env:PYTHONINSTALL = '';
|
||||
|
||||
foreach ($version in @('2.6', '2.5', '2.4')) {
|
||||
if (Test-RegistryValue "$PYTHONKEY\$version\InstallPath" '(default)') {
|
||||
$Env:PYTHONVERSION = $version;
|
||||
}
|
||||
}
|
||||
|
||||
if ($Env:PYTHONVERSION) {
|
||||
$Env:PYTHONINSTALL = (Get-Item "$PYTHONKEY\$version\InstallPath)").'(default)';
|
||||
}
|
||||
|
||||
if ($Env:PYTHONINSTALL) {
|
||||
$Env:Path += ";$Env:PYTHONINSTALL";
|
||||
}
|
||||
|
||||
if (Test-Path Env:_OLD_PYTHONPATH) {
|
||||
$Env:PYTHONPATH = $Env:_OLD_PYTHONPATH;
|
||||
}
|
||||
else {
|
||||
$Env:PYTHONPATH = '';
|
||||
}
|
||||
|
||||
$Env:_OLD_PYTHONPATH=$Env:PYTHONPATH;
|
||||
$Env:PYTHONPATH= "$Env:VIRTUAL_ENV\python-lib;$Env:PYTHONPATH";
|
||||
|
||||
if (Test-Path Function:_OLD_VIRTUAL_PROMPT) {
|
||||
Set-Content Function:Prompt (Get-Content Function:_OLD_VIRTUAL_PROMPT);
|
||||
}
|
||||
else {
|
||||
function global:_OLD_VIRTUAL_PROMPT {}
|
||||
}
|
||||
|
||||
Set-Content Function:_OLD_VIRTUAL_PROMPT (Get-Content Function:Prompt);
|
||||
|
||||
function global:prompt { "($Env:VIRTUAL_ENV) $(_OLD_VIRTUAL_PROMPT)"; };
|
||||
|
||||
if (Test-Path Env:_OLD_VIRTUAL_PATH) {
|
||||
$Env:PATH = $Env:_OLD_VIRTUAL_PATH;
|
||||
}
|
||||
else {
|
||||
$Env:_OLD_VIRTUAL_PATH = $Env:PATH;
|
||||
}
|
||||
|
||||
$Env:Path="$Env:VIRTUAL_ENV\bin;$Env:Path"
|
||||
|
||||
[System.Console]::WriteLine("Note: this PowerShell SDK activation script is experimental.")
|
||||
|
||||
python -c "from jetpack_sdk_env import welcome; welcome()"
|
||||
|
||||
@ -0,0 +1,33 @@
|
||||
#! /usr/bin/env python
|
||||
# This Source Code Form is subject to the terms of the Mozilla Public
|
||||
# License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
|
||||
import os
|
||||
import sys
|
||||
|
||||
# set the cuddlefish "root directory" for this process if it's not already
|
||||
# set in the environment
|
||||
cuddlefish_root = os.path.dirname(os.path.dirname(os.path.realpath(sys.argv[0])))
|
||||
|
||||
if 'CUDDLEFISH_ROOT' not in os.environ:
|
||||
os.environ['CUDDLEFISH_ROOT'] = cuddlefish_root
|
||||
|
||||
# add our own python-lib path to the python module search path.
|
||||
python_lib_dir = os.path.join(cuddlefish_root, "python-lib")
|
||||
if python_lib_dir not in sys.path:
|
||||
sys.path.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()
|
||||
@ -0,0 +1,6 @@
|
||||
@echo off
|
||||
rem This Source Code Form is subject to the terms of the Mozilla Public
|
||||
rem License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
rem file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
python "%VIRTUAL_ENV%\bin\cfx" %*
|
||||
@ -0,0 +1,23 @@
|
||||
@echo off
|
||||
rem This Source Code Form is subject to the terms of the Mozilla Public
|
||||
rem License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
rem file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
if defined _OLD_VIRTUAL_PROMPT (
|
||||
set "PROMPT=%_OLD_VIRTUAL_PROMPT%"
|
||||
)
|
||||
set _OLD_VIRTUAL_PROMPT=
|
||||
|
||||
if defined _OLD_VIRTUAL_PATH (
|
||||
set "PATH=%_OLD_VIRTUAL_PATH%"
|
||||
)
|
||||
set _OLD_VIRTUAL_PATH=
|
||||
|
||||
if defined _OLD_PYTHONPATH (
|
||||
set "PYTHONPATH=%_OLD_PYTHONPATH%"
|
||||
)
|
||||
set _OLD_PYTHONPATH=
|
||||
|
||||
set CUDDLEFISH_ROOT=
|
||||
|
||||
:END
|
||||
@ -0,0 +1,14 @@
|
||||
#!/bin/bash
|
||||
# This Source Code Form is subject to the terms of the Mozilla Public
|
||||
# License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
|
||||
source ./bin/activate
|
||||
if [ type -P xvfb-run ]
|
||||
then
|
||||
xvfb-run cfx $*
|
||||
else
|
||||
cfx $*
|
||||
fi
|
||||
deactivate
|
||||
@ -0,0 +1,364 @@
|
||||
#!/usr/bin/env python
|
||||
# This Source Code Form is subject to the terms of the Mozilla Public
|
||||
# License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
import os
|
||||
import signal
|
||||
import threading
|
||||
import urllib2, urllib
|
||||
import zipfile
|
||||
import tarfile
|
||||
import subprocess
|
||||
import optparse
|
||||
import sys, re
|
||||
#import win32api
|
||||
|
||||
|
||||
class SDK:
|
||||
def __init__(self):
|
||||
try:
|
||||
# Take the current working directory
|
||||
self.default_path = os.getcwd()
|
||||
if sys.platform == "win32":
|
||||
self.mswindows = True
|
||||
else:
|
||||
self.mswindows = False
|
||||
# Take the default home path of the user.
|
||||
home = os.path.expanduser('~')
|
||||
|
||||
# The following are the parameters that can be used to pass a dynamic URL, a specific path or a binry. The binary is not used yet. It will be used in version 2.0
|
||||
# If a dynamic path is to be mentioned, it should start with a '/'. For eg. "/Desktop"
|
||||
parser = optparse.OptionParser()
|
||||
parser.add_option('-u', '--url', dest = 'url', default = 'https://ftp.mozilla.org/pub/mozilla.org/labs/jetpack/addon-sdk-latest.zip')
|
||||
parser.add_option('-p', '--path', dest = 'path', default = self.default_path)
|
||||
parser.add_option('-b', '--binary', dest = 'binary')#, default='/Applications/Firefox.app')
|
||||
(options, args) = parser.parse_args()
|
||||
|
||||
# Get the URL from the parameter
|
||||
self.link = options.url
|
||||
# Set the base path for the user. If the user supplies the path, use the home variable as well. Else, take the default path of this script as the installation directory.
|
||||
if options.path!=self.default_path:
|
||||
if self.mswindows:
|
||||
self.base_path = home + str(options.path).strip() + '\\'
|
||||
else:
|
||||
self.base_path = home + str(options.path).strip() + '/'
|
||||
else:
|
||||
if self.mswindows:
|
||||
self.base_path = str(options.path).strip() + '\\'
|
||||
else:
|
||||
self.base_path = str(options.path).strip() + '/'
|
||||
assert ' ' not in self.base_path, "You cannot have a space in your home path. Please remove the space before you continue."
|
||||
print('Your Base path is =' + self.base_path)
|
||||
|
||||
# This assignment is not used in this program. It will be used in version 2 of this script.
|
||||
self.bin = options.binary
|
||||
# if app or bin is empty, dont pass anything
|
||||
|
||||
# Search for the .zip file or tarball file in the URL.
|
||||
i = self.link.rfind('/')
|
||||
|
||||
self.fname = self.link[i+1:]
|
||||
z = re.search('zip',self.fname,re.I)
|
||||
g = re.search('gz',self.fname,re.I)
|
||||
if z:
|
||||
print 'zip file present in the URL.'
|
||||
self.zip = True
|
||||
self.gz = False
|
||||
elif g:
|
||||
print 'gz file present in the URL'
|
||||
self.gz = True
|
||||
self.zip = False
|
||||
else:
|
||||
print 'zip/gz file not present. Check the URL.'
|
||||
return
|
||||
print("File name is =" + self.fname)
|
||||
|
||||
# Join the base path and the zip/tar file name to crate a complete Local file path.
|
||||
self.fpath = self.base_path + self.fname
|
||||
print('Your local file path will be=' + self.fpath)
|
||||
except AssertionError, e:
|
||||
print e.args[0]
|
||||
sys.exit(1)
|
||||
|
||||
# Download function - to download the SDK from the URL to the local machine.
|
||||
def download(self,url,fpath,fname):
|
||||
try:
|
||||
# Start the download
|
||||
print("Downloading...Please be patient!")
|
||||
urllib.urlretrieve(url,filename = fname)
|
||||
print('Download was successful.')
|
||||
except ValueError: # Handles broken URL errors.
|
||||
print 'The URL is ether broken or the file does not exist. Please enter the correct URL.'
|
||||
raise
|
||||
except urllib2.URLError: # Handles URL errors
|
||||
print '\nURL not correct. Check again!'
|
||||
raise
|
||||
|
||||
# Function to extract the downloaded zipfile.
|
||||
def extract(self, zipfilepath, extfile):
|
||||
try:
|
||||
# Timeout is set to 30 seconds.
|
||||
timeout = 30
|
||||
# Change the directory to the location of the zip file.
|
||||
try:
|
||||
os.chdir(zipfilepath)
|
||||
except OSError:
|
||||
# Will reach here if zip file doesnt exist
|
||||
print 'O/S Error:' + zipfilepath + 'does not exist'
|
||||
raise
|
||||
|
||||
# Get the folder name of Jetpack to get the exact version number.
|
||||
if self.zip:
|
||||
try:
|
||||
f = zipfile.ZipFile(extfile, "r")
|
||||
except IOError as (errno, strerror): # Handles file errors
|
||||
print "I/O error - Cannot perform extract operation: {1}".format(errno, strerror)
|
||||
raise
|
||||
list = f.namelist()[0]
|
||||
temp_name = list.split('/')
|
||||
print('Folder Name= ' +temp_name[0])
|
||||
self.folder_name = temp_name[0]
|
||||
elif self.gz:
|
||||
try:
|
||||
f = tarfile.open(extfile,'r')
|
||||
except IOError as (errno, strerror): # Handles file errors
|
||||
print "I/O error - Cannot perform extract operation: {1}".format(errno, strerror)
|
||||
raise
|
||||
list = f.getnames()[0]
|
||||
temp_name = list.split('/')
|
||||
print('Folder Name= ' +temp_name[0])
|
||||
self.folder_name = temp_name[0]
|
||||
|
||||
print ('Starting to Extract...')
|
||||
|
||||
# Timeout code. The subprocess.popen exeutes the command and the thread waits for a timeout. If the process does not finish within the mentioned-
|
||||
# timeout, the process is killed.
|
||||
kill_check = threading.Event()
|
||||
|
||||
if self.zip:
|
||||
# Call the command to unzip the file.
|
||||
if self.mswindows:
|
||||
zipfile.ZipFile.extractall(f)
|
||||
else:
|
||||
p = subprocess.Popen('unzip '+extfile, stdout=subprocess.PIPE, shell=True)
|
||||
pid = p.pid
|
||||
elif self.gz:
|
||||
# Call the command to untar the file.
|
||||
if self.mswindows:
|
||||
tarfile.TarFile.extractall(f)
|
||||
else:
|
||||
p = subprocess.Popen('tar -xf '+extfile, stdout=subprocess.PIPE, shell=True)
|
||||
pid = p.pid
|
||||
|
||||
#No need to handle for windows because windows automatically replaces old files with new files. It does not ask the user(as it does in Mac/Unix)
|
||||
if self.mswindows==False:
|
||||
watch = threading.Timer(timeout, kill_process, args=(pid, kill_check, self.mswindows ))
|
||||
watch.start()
|
||||
(stdout, stderr) = p.communicate()
|
||||
watch.cancel() # if it's still waiting to run
|
||||
success = not kill_check.isSet()
|
||||
|
||||
# Abort process if process fails.
|
||||
if not success:
|
||||
raise RuntimeError
|
||||
kill_check.clear()
|
||||
print('Extraction Successful.')
|
||||
except RuntimeError:
|
||||
print "Ending the program"
|
||||
sys.exit(1)
|
||||
except:
|
||||
print "Error during file extraction: ", sys.exc_info()[0]
|
||||
raise
|
||||
|
||||
# Function to run the cfx testall comands and to make sure the SDK is not broken.
|
||||
def run_testall(self, home_path, folder_name):
|
||||
try:
|
||||
timeout = 500
|
||||
|
||||
self.new_dir = home_path + folder_name
|
||||
try:
|
||||
os.chdir(self.new_dir)
|
||||
except OSError:
|
||||
# Will reach here if the jetpack 0.X directory doesnt exist
|
||||
print 'O/S Error: Jetpack directory does not exist at ' + self.new_dir
|
||||
raise
|
||||
print '\nStarting tests...'
|
||||
# Timeout code. The subprocess.popen exeutes the command and the thread waits for a timeout. If the process does not finish within the mentioned-
|
||||
# timeout, the process is killed.
|
||||
kill_check = threading.Event()
|
||||
|
||||
# Set the path for the logs. They will be in the parent directory of the Jetpack SDK.
|
||||
log_path = home_path + 'tests.log'
|
||||
|
||||
# Subprocess call to set up the jetpack environment and to start the tests. Also sends the output to a log file.
|
||||
if self.bin != None:
|
||||
if self.mswindows:
|
||||
p = subprocess.Popen("bin\\activate && cfx testall -a firefox -b \"" + self.bin + "\"" , stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=True)
|
||||
proc_handle = p._handle
|
||||
(stdout,stderr) = p.communicate()
|
||||
else:
|
||||
p = subprocess.Popen('. bin/activate; cfx testall -a firefox -b ' + self.bin , stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=True)
|
||||
pid = p.pid
|
||||
(stdout,stderr) = p.communicate()
|
||||
elif self.bin == None:
|
||||
if self.mswindows:
|
||||
p=subprocess.Popen('bin\\activate && cfx testall -a firefox > '+log_path, stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=True)
|
||||
proc_handle = p._handle
|
||||
(stdout,stderr) = p.communicate()
|
||||
else:
|
||||
p = subprocess.Popen('. bin/activate; cfx testall -a firefox > '+log_path, stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=True)
|
||||
pid = p.pid
|
||||
(stdout,stderr) = p.communicate()
|
||||
|
||||
#Write the output to log file
|
||||
f=open(log_path,"w")
|
||||
f.write(stdout+stderr)
|
||||
f.close()
|
||||
|
||||
#Watchdog for timeout process
|
||||
if self.mswindows:
|
||||
watch = threading.Timer(timeout, kill_process, args=(proc_handle, kill_check, self.mswindows))
|
||||
else:
|
||||
watch = threading.Timer(timeout, kill_process, args=(pid, kill_check, self.mswindows))
|
||||
watch.start()
|
||||
watch.cancel() # if it's still waiting to run
|
||||
success = not kill_check.isSet()
|
||||
if not success:
|
||||
raise RuntimeError
|
||||
kill_check.clear()
|
||||
|
||||
if p.returncode!=0:
|
||||
print('\nAll tests were not successful. Check the test-logs in the jetpack directory.')
|
||||
result_sdk(home_path)
|
||||
#sys.exit(1)
|
||||
raise RuntimeError
|
||||
else:
|
||||
ret_code=result_sdk(home_path)
|
||||
if ret_code==0:
|
||||
print('\nAll tests were successful. Yay \o/ . Running a sample package test now...')
|
||||
else:
|
||||
print ('\nThere were errors during the tests.Take a look at logs')
|
||||
raise RuntimeError
|
||||
except RuntimeError:
|
||||
print "Ending the program"
|
||||
sys.exit(1)
|
||||
except:
|
||||
print "Error during the testall command execution:", sys.exc_info()[0]
|
||||
raise
|
||||
|
||||
def package(self, example_dir):
|
||||
try:
|
||||
timeout = 30
|
||||
|
||||
print '\nNow Running packaging tests...'
|
||||
|
||||
kill_check = threading.Event()
|
||||
|
||||
# Set the path for the example logs. They will be in the parent directory of the Jetpack SDK.
|
||||
exlog_path = example_dir + 'test-example.log'
|
||||
# Subprocess call to test the sample example for packaging.
|
||||
if self.bin!=None:
|
||||
if self.mswindows:
|
||||
p = subprocess.Popen('bin\\activate && cfx run --pkgdir examples\\reading-data --static-args="{\"quitWhenDone\":true}" -b \"" + self.bin + "\"' , stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=True)
|
||||
proc_handle = p._handle
|
||||
(stdout, stderr) = p.communicate()
|
||||
else:
|
||||
p = subprocess.Popen('. bin/activate; cfx run --pkgdir examples/reading-data --static-args=\'{\"quitWhenDone\":true}\' -b ' + self.bin , stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=True)
|
||||
pid = p.pid
|
||||
(stdout, stderr) = p.communicate()
|
||||
elif self.bin==None:
|
||||
if self.mswindows:
|
||||
p = subprocess.Popen('bin\\activate && cfx run --pkgdir examples\\reading-data --static-args="{\"quitWhenDone\":true}"', stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=True)
|
||||
proc_handle = p._handle
|
||||
(stdout, stderr) = p.communicate()
|
||||
else:
|
||||
p = subprocess.Popen('. bin/activate; cfx run --pkgdir examples/reading-data --static-args=\'{\"quitWhenDone\":true}\' ', stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=True)
|
||||
pid = p.pid
|
||||
(stdout, stderr) = p.communicate()
|
||||
|
||||
#Write the output to log file
|
||||
f=open(exlog_path,"w")
|
||||
f.write(stdout+stderr)
|
||||
f.close()
|
||||
|
||||
#Watch dog for timeout process
|
||||
if self.mswindows:
|
||||
watch = threading.Timer(timeout, kill_process, args=(proc_handle, kill_check, self.mswindows))
|
||||
else:
|
||||
watch = threading.Timer(timeout, kill_process, args=(pid, kill_check, self.mswindows))
|
||||
watch.start()
|
||||
watch.cancel() # if it's still waiting to run
|
||||
success = not kill_check.isSet()
|
||||
if not success:
|
||||
raise RuntimeError
|
||||
kill_check.clear()
|
||||
|
||||
if p.returncode != 0:
|
||||
print('\nSample tests were not executed correctly. Check the test-example log in jetpack diretory.')
|
||||
result_example(example_dir)
|
||||
raise RuntimeError
|
||||
else:
|
||||
ret_code=result_example(example_dir)
|
||||
if ret_code==0:
|
||||
print('\nAll tests pass. The SDK is working! Yay \o/')
|
||||
else:
|
||||
print ('\nTests passed with warning.Take a look at logs')
|
||||
sys.exit(1)
|
||||
|
||||
except RuntimeError:
|
||||
print "Ending program"
|
||||
sys.exit(1)
|
||||
except:
|
||||
print "Error during running sample tests:", sys.exc_info()[0]
|
||||
raise
|
||||
|
||||
def result_sdk(sdk_dir):
|
||||
log_path = sdk_dir + 'tests.log'
|
||||
print 'Results are logged at:' + log_path
|
||||
try:
|
||||
f = open(log_path,'r')
|
||||
# Handles file errors
|
||||
except IOError :
|
||||
print 'I/O error - Cannot open test log at ' + log_path
|
||||
raise
|
||||
|
||||
for line in reversed(open(log_path).readlines()):
|
||||
if line.strip()=='FAIL':
|
||||
print ('\nOverall result - FAIL. Look at the test log at '+log_path)
|
||||
return 1
|
||||
return 0
|
||||
|
||||
|
||||
def result_example(sdk_dir):
|
||||
exlog_path = sdk_dir + 'test-example.log'
|
||||
print 'Sample test results are logged at:' + exlog_path
|
||||
try:
|
||||
f = open(exlog_path,'r')
|
||||
# Handles file errors
|
||||
except IOError :
|
||||
print 'I/O error - Cannot open sample test log at ' + exlog_path
|
||||
raise
|
||||
|
||||
#Read the file in reverse and check for the keyword 'FAIL'.
|
||||
for line in reversed(open(exlog_path).readlines()):
|
||||
if line.strip()=='FAIL':
|
||||
print ('\nOverall result for Sample tests - FAIL. Look at the test log at '+exlog_path)
|
||||
return 1
|
||||
return 0
|
||||
|
||||
def kill_process(process, kill_check, mswindows):
|
||||
print '\nProcess Timedout. Killing the process. Please Rerun this script.'
|
||||
if mswindows:
|
||||
win32api.TerminateProcess(process, -1)
|
||||
else:
|
||||
os.kill(process, signal.SIGKILL)
|
||||
kill_check.set()# tell the main routine to kill. Used SIGKILL to hard kill the process.
|
||||
return
|
||||
|
||||
if __name__ == "__main__":
|
||||
obj = SDK()
|
||||
obj.download(obj.link,obj.fpath,obj.fname)
|
||||
obj.extract(obj.base_path,obj.fname)
|
||||
obj.run_testall(obj.base_path,obj.folder_name)
|
||||
obj.package(obj.base_path)
|
||||
@ -0,0 +1,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.
|
||||
@ -0,0 +1,46 @@
|
||||
<!-- This Source Code Form is subject to the terms of the Mozilla Public
|
||||
- License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
- file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
|
||||
|
||||
# console #
|
||||
|
||||
The `console` object enables your add-on to log messages. If you have started
|
||||
the host application for your add-on from the command line (for example, by
|
||||
executing `cfx run` or `cfx test`) then these messages appear in the command
|
||||
shell you used. If the add-on has been installed in the host application, then
|
||||
the messages appear in the host application's
|
||||
[Error Console](https://developer.mozilla.org/en/Error_Console).
|
||||
|
||||
The `console` object has the following methods:
|
||||
|
||||
<code>console.**log**(*object*[, *object*, ...])</code>
|
||||
|
||||
Logs an informational message to the shell.
|
||||
Depending on the console's underlying implementation and user interface,
|
||||
you may be able to introspect into the properties of non-primitive objects
|
||||
that are logged.
|
||||
|
||||
<code>console.**info**(*object*[, *object*, ...])</code>
|
||||
|
||||
A synonym for `console.log()`.
|
||||
|
||||
<code>console.**warn**(*object*[, *object*, ...])</code>
|
||||
|
||||
Logs a warning message.
|
||||
|
||||
<code>console.**error**(*object*[, *object*, ...])</code>
|
||||
|
||||
Logs an error message.
|
||||
|
||||
<code>console.**debug**(*object*[, *object*, ...])</code>
|
||||
|
||||
Logs a debug message.
|
||||
|
||||
<code>console.**exception**(*exception*)</code>
|
||||
|
||||
Logs the given exception instance as an error, outputting information
|
||||
about the exception's stack traceback if one is available.
|
||||
|
||||
<code>console.**trace**()</code>
|
||||
|
||||
Logs a stack trace at the point this function is called.
|
||||
@ -0,0 +1,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 O’Shannessy
|
||||
* 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)
|
||||
@ -0,0 +1,73 @@
|
||||
<!-- This Source Code Form is subject to the terms of the Mozilla Public
|
||||
- License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
- file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
|
||||
|
||||
# Glossary #
|
||||
|
||||
This glossary contains a list of terms used in the Add-on SDK.
|
||||
|
||||
__Add-on__: A software package that adds functionality to a Mozilla application,
|
||||
which can be built with either Mozilla's traditional add-on platform or the SDK.
|
||||
|
||||
__Add-on SDK__: A toolchain and associated applications for developing add-ons.
|
||||
|
||||
__API Utils__: A small, self-contained set of low-level modules that forms
|
||||
the base functionality for the SDK. The library can be "bootstrapped" into
|
||||
any Mozilla application or add-on.
|
||||
|
||||
__CFX__: A command-line build, testing, and packaging tool for SDK-based code.
|
||||
|
||||
__CommonJS__: A specification for a cross-platform JavaScript module
|
||||
system and standard library. [Web site](http://commonjs.org/).
|
||||
|
||||
__Extension__: Synonym for Add-on.
|
||||
|
||||
__Globals__: The set of global variables and objects provided
|
||||
to all modules, such as `console` and `memory`. Includes
|
||||
CommonJS globals like `require` and standard JavaScript globals such
|
||||
as `Array` and `Math`.
|
||||
|
||||
<span><a name="host-application">__Host Application__:</a> Add-ons are executed in
|
||||
the context of a host application, which is the application they are extending.
|
||||
Firefox and Thunderbird are the most obvious hosts for Mozilla add-ons, but
|
||||
at present only Firefox is supported as a host for add-ons developed using the
|
||||
Add-on SDK.</span>
|
||||
|
||||
__Jetpack Prototype__: A Mozilla Labs experiment that predated and inspired
|
||||
the SDK. The SDK incorporates many ideas and some code from the prototype.
|
||||
|
||||
__Loader__: An object capable of finding, evaluating, and
|
||||
exposing CommonJS modules to each other in a given security context,
|
||||
while providing each module with necessary globals and
|
||||
enforcing security boundaries between the modules as necessary. It's
|
||||
entirely possible for Loaders to create new Loaders.
|
||||
|
||||
__Low-Level Module__: A module with the following properties:
|
||||
|
||||
* Has "chrome" access to the Mozilla platform (e.g. `Components.classes`)
|
||||
and all globals.
|
||||
* Is reloadable without leaking memory.
|
||||
* Logs full exception tracebacks originating from client-provided
|
||||
callbacks (i.e., does not allow the exceptions to propagate into
|
||||
Mozilla platform code).
|
||||
* Can exist side-by-side with multiple instances and versions of
|
||||
itself.
|
||||
* Contains documentation on security concerns and threat modeling.
|
||||
|
||||
__Module__: A CommonJS module that is either a Low-Level Module
|
||||
or an Unprivileged Module.
|
||||
|
||||
__Package__: A directory structure containing modules,
|
||||
documentation, tests, and related metadata. If a package contains
|
||||
a program and includes proper metadata, it can be built into
|
||||
a Mozilla application or add-on.
|
||||
|
||||
__Program__: A module named `main` that optionally exports
|
||||
a `main()` function. This module is intended either to start an application for
|
||||
an end-user or add features to an existing application.
|
||||
|
||||
__Unprivileged Module__: A CommonJS module that may be run
|
||||
without unrestricted access to the Mozilla platform, and which may use
|
||||
all applicable globals that don't require chrome privileges.
|
||||
|
||||
[Low-Level Module Best Practices]: dev-guide/module-development/best-practices.html
|
||||
@ -0,0 +1,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.
|
||||
|
||||

|
||||
|
||||
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.
|
||||
@ -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"><, 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"><**<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
|
||||
@ -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".
|
||||
@ -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).
|
||||
@ -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]);
|
||||
});
|
||||
}
|
||||
});
|
||||
@ -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);
|
||||
});
|
||||
}
|
||||
});
|
||||
@ -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.
|
||||
|
||||
@ -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.
|
||||
@ -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>
|
||||
@ -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[
|
||||
<statusbar id="status-bar"> <box orient="horizontal" id="librarydetector"> </box> </statusbar>
|
||||
]]>
|
||||
</script>
|
||||
|
||||
The script does everything else.
|
||||
|
||||
The bulk of the script is an array of test objects, one for each library.
|
||||
Each test object contains a function called `test()`: if the
|
||||
function finds the library, it defines various additional properties for
|
||||
the test object, such as a `version` property containing the library version.
|
||||
Each test also contains a `chrome://` URL pointing to the icon associated with
|
||||
its library.
|
||||
|
||||
The script listens to [gBrowser](https://developer.mozilla.org/en/Code_snippets/Tabbed_browser)'s
|
||||
`DOMContentLoaded` event. When this is triggered, the `testLibraries()`
|
||||
function builds an array of libraries by iterating through the tests and
|
||||
adding an entry for each library which passes.
|
||||
|
||||
Once the list is built, the `switchLibraries()` function constructs a XUL
|
||||
`statusbarpanel` element for each library it found, populates it with the
|
||||
icon at the corresponding `chrome://` URL, and adds it to the box.
|
||||
|
||||
Finally, it listens to gBrowser's `TabSelect` event, to update the contents
|
||||
of the box for that window.
|
||||
|
||||
### Content Script Separation ###
|
||||
|
||||
The test objects in the original script need access to the DOM window object,
|
||||
so in the SDK port, they need to run in a content script. In fact, they need
|
||||
access to the un-proxied DOM window, so they can see the objects added by
|
||||
libraries, so we’ll need to use the experimental
|
||||
[unsafeWindow](dev-guide/guides/content-scripts/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 < elements.length; i++) {
|
||||
elements[i].addEventListener('mouseover', setLibraryInfo, false);
|
||||
}
|
||||
</code></pre>
|
||||
|
||||
* one in the panel, which updates the panel's content with the library
|
||||
information:
|
||||
|
||||
<pre><code>
|
||||
self.on("message", function(libraryInfo) {
|
||||
window.document.body.innerHTML = libraryInfo;
|
||||
});
|
||||
</code></pre>
|
||||
|
||||
Finally `main.js` relays the library information from the widget to the panel:
|
||||
|
||||
<pre><code>
|
||||
widget.port.on('setLibraryInfo', function(libraryInfo) {
|
||||
widget.panel.postMessage(libraryInfo);
|
||||
});
|
||||
</code></pre>
|
||||
|
||||
<img class="image-center" src="static-files/media/librarydetector/panel-content.png" alt="Updating panel content" />
|
||||
@ -0,0 +1,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.
|
||||
@ -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.
|
||||
@ -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.
|
||||
@ -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>
|
||||
@ -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.
|
||||
@ -0,0 +1,16 @@
|
||||
<!-- This Source Code Form is subject to the terms of the Mozilla Public
|
||||
- License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
- file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
|
||||
|
||||
# High-Level APIs #
|
||||
|
||||
Modules in this section implement high-level APIs for
|
||||
building add-ons:
|
||||
|
||||
* creating user interfaces
|
||||
* interacting with the web
|
||||
* interacting with the browser
|
||||
|
||||
These modules are "supported": meaning that they are relatively
|
||||
stable, and that we'll avoid making incompatible changes to them
|
||||
unless absolutely necessary.
|
||||
@ -0,0 +1,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>
|
||||
@ -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.
|
||||
@ -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
|
||||
@ -0,0 +1,7 @@
|
||||
<!-- This Source Code Form is subject to the terms of the Mozilla Public
|
||||
- License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
- file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
|
||||
|
||||
# Third-Party APIs #
|
||||
|
||||
This section lists modules which you've downloaded and added to your SDK installation.
|
||||
@ -0,0 +1,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).
|
||||
@ -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.
|
||||
@ -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).
|
||||
@ -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).
|
||||
@ -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.
|
||||
@ -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)
|
||||
|
||||
@ -0,0 +1,63 @@
|
||||
<!-- This Source Code Form is subject to the terms of the Mozilla Public
|
||||
- License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
- file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
|
||||
|
||||
# Annotator Design Overview #
|
||||
|
||||
The annotator uses content scripts to build user interfaces, get user input,
|
||||
and examine the DOM of pages loaded by the user.
|
||||
|
||||
Meanwhile the `main` module contains the application logic and mediates
|
||||
interactions between the different SDK objects.
|
||||
|
||||
We could represent the basic interactions between the `main` module and the
|
||||
various content scripts like this:
|
||||
|
||||
<img class="image-center"
|
||||
src="static-files/media/annotator/annotator-design.png" alt="Annotator Design">
|
||||
|
||||
## User Interface ##
|
||||
|
||||
The annotator's main user interface consists of a widget and three panels.
|
||||
|
||||
* The widget is used to switch the annotator on and off, and to display a list
|
||||
of all the stored annotations.
|
||||
* The **annotation-editor** panel enables the user to enter a new annotation.
|
||||
* The **annotation-list** panel shows a list of all stored annotations.
|
||||
* The **annotation** panel displays a single annotation.
|
||||
|
||||
Additionally, we use the `notifications` module to notify the user when the
|
||||
add-on's storage quota is full.
|
||||
|
||||
## Working with the DOM ##
|
||||
|
||||
We'll use two page-mods to interact with the DOMs of pages that the user has
|
||||
opened.
|
||||
|
||||
* The **selector** enables the user to choose an element to annotate.
|
||||
It identifies page elements which are eligible for annotation, highlights them
|
||||
on mouseover, and tells the main add-on code when the user clicks a highlighted
|
||||
element.
|
||||
|
||||
* The **matcher** is responsible for finding annotated elements: it is
|
||||
initialized with the list of annotations and searches web pages for the
|
||||
elements they are associated with. It highlights any annotated elements that
|
||||
are found. When the user moves the mouse over an annotated element
|
||||
the matcher tells the main add-on code, which displays the annotation panel.
|
||||
|
||||
## Working with Data ##
|
||||
|
||||
We'll use the `simple-storage` module to store annotations.
|
||||
|
||||
Because we are recording potentially sensitive information, we want to prevent
|
||||
the user creating annotations when in private browsing mode, so we'll use the
|
||||
`private-browsing` module for that.
|
||||
|
||||
## Getting Started ##
|
||||
|
||||
|
||||
Let's get started by creating a directory called "annotator". Navigate to it
|
||||
and type `cfx init`.
|
||||
|
||||
Next, we will implement the
|
||||
[widget](dev-guide/tutorials/annotator/widget.html).
|
||||
@ -0,0 +1,369 @@
|
||||
<!-- This Source Code Form is subject to the terms of the Mozilla Public
|
||||
- License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
- file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
|
||||
|
||||
# Storing Annotations #
|
||||
|
||||
Now we are able to create annotations, let's store them using the
|
||||
[`simple-storage`](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">
|
||||
<html>
|
||||
<head>
|
||||
<meta http-equiv="Content-type" content="text/html; charset=utf-8" />
|
||||
<title>Saved annotations</title>
|
||||
<link rel="stylesheet" type="text/css" href="annotation-list.css" />
|
||||
</head>
|
||||
|
||||
<body>
|
||||
|
||||
<div id="annotation-list">
|
||||
</div>
|
||||
|
||||
<div id="template">
|
||||
<div class="annotation-details">
|
||||
<a class="url"></a>
|
||||
<div class="selection-text"></div>
|
||||
<div class="annotation-text"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</body>
|
||||
|
||||
</html>
|
||||
|
||||
</pre>
|
||||
|
||||
Here's the corresponding CSS:
|
||||
|
||||
<script type="syntaxhighlighter" class="brush: css"><![CDATA[
|
||||
#annotation-list .annotation-details
|
||||
{
|
||||
padding: 10px;
|
||||
margin: 10px;
|
||||
border: solid 3px #EEE;
|
||||
background-color: white;
|
||||
}
|
||||
|
||||
#annotation-list .url, .selection-text, .annotation-text
|
||||
{
|
||||
padding: 5px;
|
||||
margin: 5px;
|
||||
}
|
||||
|
||||
#annotation-list .selection-text,#annotation-list .annotation-text
|
||||
{
|
||||
border: solid 1px #EEE;
|
||||
}
|
||||
|
||||
#annotation-list .annotation-text
|
||||
{
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
body
|
||||
{
|
||||
background-color: #F5F5F5;
|
||||
font: 100% arial, helvetica, sans-serif;
|
||||
}
|
||||
|
||||
h1
|
||||
{
|
||||
font-family: georgia,serif;
|
||||
font-size: 1.5em;
|
||||
text-align:center;
|
||||
}
|
||||
]]>
|
||||
</script>
|
||||
|
||||
Save these in `data/list` as `annotation-list.html` and `annotation-list.css`
|
||||
respectively.
|
||||
|
||||
### Updating main.js ###
|
||||
|
||||
Here's the code to create the panel, which can go in the `main` function.
|
||||
|
||||
var annotationList = panels.Panel({
|
||||
width: 420,
|
||||
height: 200,
|
||||
contentURL: data.url('list/annotation-list.html'),
|
||||
contentScriptFile: [data.url('jquery-1.4.2.min.js'),
|
||||
data.url('list/annotation-list.js')],
|
||||
contentScriptWhen: 'ready',
|
||||
onShow: function() {
|
||||
this.postMessage(simpleStorage.storage.annotations);
|
||||
},
|
||||
onMessage: function(message) {
|
||||
require('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).
|
||||
@ -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).
|
||||
@ -0,0 +1,105 @@
|
||||
<!-- This Source Code Form is subject to the terms of the Mozilla Public
|
||||
- License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
- file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
|
||||
|
||||
<div class="warning">The API used to gain Chrome access is currently an
|
||||
experimental feature of the SDK, and may change in future releases.</div>
|
||||
|
||||
# Chrome Authority #
|
||||
|
||||
## Using Chrome Authority ##
|
||||
|
||||
The most powerful low-level modules are run with "chrome privileges",
|
||||
which gives them access to the infamous <code>Components</code> object, which
|
||||
grants unfettered access to the host system. From this, the module can do
|
||||
pretty much anything the browser is capable of. To obtain these privileges,
|
||||
the module must declare its intent with a statement like the following:
|
||||
|
||||
var {Cc, Ci} = require("chrome");
|
||||
|
||||
The object returned by <code>require("chrome")</code>, when unpacked with the
|
||||
"destructuring assignment" feature available in the Mozilla JS environment,
|
||||
will provide the usual <code>Components.*</code> aliases:
|
||||
|
||||
<code>**Cc**</code>
|
||||
|
||||
An alias for `Components.classes`.
|
||||
|
||||
<code>**Ci**</code>
|
||||
|
||||
An alias for `Components.interfaces`.
|
||||
|
||||
<code>**Cu**</code>
|
||||
|
||||
An alias for `Components.utils`.
|
||||
|
||||
<code>**Cr**</code>
|
||||
|
||||
An alias for `Components.results`.
|
||||
|
||||
<code>**Cm**</code>
|
||||
|
||||
An alias for `Components.manager`.
|
||||
|
||||
<code>**components**</code>
|
||||
|
||||
An alias for `Components` itself (note the lower-case). From this you can
|
||||
access less-frequently-used properties like `Components.stack` and
|
||||
`Components.isSuccessCode`.
|
||||
|
||||
Note: the `require("chrome")` statement is the **only** way to access chrome
|
||||
functionality and the `Components` API. The `Components` object should
|
||||
**not** be accessed from modules. The SDK tools will emit a warning
|
||||
if it sees module code which references `Components` directly.
|
||||
|
||||
Your modules should refrain from using chrome privileges unless they are
|
||||
absolutely necessary. Chrome-authority-using modules must receive extra
|
||||
security review, and most bugs in these modules are security-critical.
|
||||
|
||||
## Manifest Generation ##
|
||||
|
||||
The **manifest** is a list, included in the generated XPI, which
|
||||
specifies which modules have requested `require()` access to which other
|
||||
modules. It also records which modules have requested chrome access. This
|
||||
list is generated by scanning all included modules for `require(XYZ)`
|
||||
statements and recording the particular "XYZ" strings that they reference.
|
||||
|
||||
When the manifest implementation is complete the runtime loader will actually
|
||||
prevent modules from `require()`ing modules that are not listed in the
|
||||
manifest. Likewise, it will prevent modules from getting chrome authority
|
||||
unless the manifest indicates that they have asked for it. This will ensure
|
||||
that reviewers see the same authority restrictions that are enforced upon the
|
||||
running code, increasing the effectiveness of the time spent reviewing the
|
||||
add-on. (until this work is complete, modules may be able to sneak around these
|
||||
restrictions).
|
||||
|
||||
The manifest is built with a simple regexp-based scanner, not a full
|
||||
Javascript parser. Developers should stick to simple `require` statements,
|
||||
with a single static string, one per line of code. If the scanner fails to
|
||||
see a `require` entry, the manifest will not include that entry, and (once
|
||||
the implementation is complete) the runtime code will get an exception.
|
||||
|
||||
For example, none of the following code will be matched by the manifest
|
||||
scanner, leading to exceptions at runtime, when the `require()` call is
|
||||
prohibited from importing the named modules:
|
||||
|
||||
// all of these will fail!
|
||||
var xhr = require("x"+"hr");
|
||||
var modname = "xpcom";
|
||||
var xpcom = require(modname);
|
||||
var one = require("one"); var two = require("two");
|
||||
|
||||
The intention is that developers use `require()` statements for two purposes:
|
||||
to declare (to security reviewers) what sorts of powers the module wants to
|
||||
use, and to control how those powers are mapped into the module's local
|
||||
namespace. Their statements must therefore be clear and easy to parse. A
|
||||
future manifest format may move the declaration portion out to a separate
|
||||
file, to allow for more fine-grained expression of authority.
|
||||
|
||||
Commands that build a manifest, like "`cfx xpi`" or "`cfx run`", will scan
|
||||
all included modules for use of `Cc`/`Ci` aliases (or the expanded
|
||||
`Components.classes` forms). It will emit a warning if it sees the expanded
|
||||
forms, or if it sees a use of e.g. "`Cc`" without a corresponding entry in
|
||||
the `require("chrome")` statement. These warnings will serve to guide
|
||||
developers to use the correct pattern. All module developers should heed the
|
||||
warnings and correct their code until the warnings go away.
|
||||
@ -0,0 +1,151 @@
|
||||
<!-- This Source Code Form is subject to the terms of the Mozilla Public
|
||||
- License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
- file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
|
||||
|
||||
# Display a Popup #
|
||||
|
||||
<span class="aside">
|
||||
To follow this tutorial you'll need to have
|
||||
[installed the SDK](dev-guide/tutorials/installation.html)
|
||||
and learned the
|
||||
[basics of `cfx`](dev-guide/tutorials/getting-started-with-cfx.html).
|
||||
</span>
|
||||
|
||||
To display a popup dialog, use the
|
||||
[`panel`](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">
|
||||
|
||||
<html>
|
||||
|
||||
<head>
|
||||
<style type="text/css" media="all">
|
||||
textarea {
|
||||
margin: 10px;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<textarea rows="10" cols="20" id="edit-box"></textarea>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
|
||||
</pre>
|
||||
|
||||
Try it out: "main.js" is saved in your add-on's `lib` directory,
|
||||
and the other two files go in your add-on's `data` directory:
|
||||
|
||||
<pre>
|
||||
my-addon/
|
||||
data/
|
||||
get-text.js
|
||||
text-entry.html
|
||||
lib/
|
||||
main.js
|
||||
</pre>
|
||||
|
||||
Run the add-on, click the widget, and you should see the panel.
|
||||
Type some text and press "return" and you should see the output
|
||||
in the console.
|
||||
|
||||
## Learning More ##
|
||||
|
||||
To learn more about the `panel` module, see the
|
||||
[`panel` API reference](packages/addon-kit/panel.html).
|
||||
|
||||
To learn more about attaching panels to widgets, see the
|
||||
[`widget` API reference](packages/addon-kit/widget.html).
|
||||
@ -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).
|
||||
@ -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>
|
||||
|
||||
@ -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.
|
||||
@ -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>
|
||||
"<town_name> is <person_name>'s home town."
|
||||
</pre>
|
||||
|
||||
<pre>
|
||||
"<person_name>'s home town is <town_name>"
|
||||
</pre>
|
||||
|
||||
To choose the first option, the `.properties` file can order the
|
||||
placeholders as follows:
|
||||
|
||||
<pre>
|
||||
home_town_id= %2s is %1s's home town.
|
||||
</pre>
|
||||
|
||||
This gives us the following output:
|
||||
|
||||
<pre>
|
||||
info: London is Bob's home town.
|
||||
</pre>
|
||||
|
||||
## <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).
|
||||
@ -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).
|
||||
@ -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).
|
||||
@ -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.
|
||||
@ -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).
|
||||
@ -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>
|
||||
@ -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).
|
||||
@ -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).
|
||||
@ -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).
|
||||
@ -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).
|
||||
@ -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
Loading…
x
Reference in New Issue
Block a user