├── .eslintignore
├── .eslintrc
├── .gitignore
├── .taskcluster.yml
├── LICENSE
├── README.md
├── add-on
├── .eslintrc
├── bootstrap.js
├── install.rdf
└── webextension
│ ├── _locales
│ ├── en-US
│ │ └── messages.json
│ └── fr
│ │ └── messages.json
│ ├── img
│ └── firefox-logo.svg
│ ├── main.js
│ └── manifest.json
├── docker-functional
├── Dockerfile
├── README.md
├── setup.sh
└── system-setup.sh
├── docker-lint
├── Dockerfile
└── README.md
├── docs
├── Developing.md
├── Functional.md
├── Linting.md
├── ModulesUpdating.md
├── Taskcluster.md
└── UnitTests.md
├── karma.conf.js
├── package-lock.json
├── package.json
├── requirements.txt
├── scripts
├── .eslintrc
├── get_ff.js
├── get_secret.py
├── install_ff.py
├── run-utils.js
├── runfx.js
└── uninstall_ff.js
└── test
├── .eslintrc
├── functional
├── button_test.js
└── utils.js
└── unit
└── main.test.js
/.eslintignore:
--------------------------------------------------------------------------------
1 | .node-virtualenv
2 | build
3 | dist
4 | node_modules
5 |
--------------------------------------------------------------------------------
/.eslintrc:
--------------------------------------------------------------------------------
1 | {
2 | // When adding items to this file please check for effects on sub-directories.
3 | "parserOptions": {
4 | "ecmaFeatures": {
5 | "jsx": true
6 | }
7 | },
8 | "env": {
9 | "browser": true,
10 | "es6": true,
11 | "mocha": true,
12 | "node": true,
13 | },
14 | "globals": {
15 | "chrome": true
16 | },
17 | "plugins": [
18 | "json",
19 | "mocha",
20 | "promise",
21 | ],
22 | "extends": [
23 | "eslint:recommended"
24 | ],
25 | "rules": {
26 | "mocha/handle-done-callback": 2,
27 | "mocha/max-top-level-suites": 2,
28 | "mocha/no-exclusive-tests": 2,
29 | "mocha/no-global-tests": 2,
30 | "mocha/no-identical-title": 2,
31 | "mocha/no-mocha-arrows": 2,
32 | "mocha/no-return-and-callback": 2,
33 | "mocha/no-skipped-tests": 0,
34 | "mocha/no-sibling-hooks": 2,
35 | "mocha/no-top-level-hooks": 2,
36 | "mocha/valid-suite-description": 2,
37 | "mocha/valid-test-description": 2,
38 |
39 | "promise/always-return": 2,
40 | "promise/catch-or-return": 2,
41 | "promise/param-names": 2,
42 |
43 | "accessor-pairs": [2, {"setWithoutGet": true, "getWithoutSet": false}],
44 | "array-bracket-spacing": [2, "never"],
45 | "array-callback-return": 2,
46 | "arrow-body-style": [2, "as-needed"],
47 | "arrow-parens": [2, "as-needed"],
48 | "arrow-spacing": 2,
49 | "block-scoped-var": 2,
50 | "block-spacing": [2, "never"],
51 | "brace-style": 0,
52 | "callback-return": 0,
53 | "camelcase": 0,
54 | "comma-dangle": [2, "never"],
55 | "comma-spacing": 2,
56 | "comma-style": 2,
57 | "complexity": [2, {"max": 20}],
58 | "computed-property-spacing": [2, "never"],
59 | "consistent-return": 2,
60 | "consistent-this": [2, "use-bind"],
61 | "constructor-super": 2,
62 | "curly": [2, "all"],
63 | "default-case": 0,
64 | "dot-location": [2, "property"],
65 | "dot-notation": 2,
66 | "eol-last": 2,
67 | "eqeqeq": 2,
68 | "func-names": 0,
69 | "func-style": 0,
70 | "generator-star-spacing": [2, {"before": false, "after": false}],
71 | "global-require": 0,
72 | "guard-for-in": 2,
73 | "handle-callback-err": 2,
74 | "id-blacklist": 0,
75 | "id-length": 0,
76 | "id-match": 0,
77 | "indent": [2, 2, {"SwitchCase": 1}],
78 | "init-declarations": 0,
79 | "jsx-quotes": [2, "prefer-double"],
80 | "key-spacing": [2, {"beforeColon": false, "afterColon": true}],
81 | "keyword-spacing": 2,
82 | "linebreak-style": [2, "unix"],
83 | "lines-around-comment": [2, {"beforeBlockComment": true, "allowObjectStart": true}],
84 | "max-depth": [2, 4],
85 | "max-len": 0,
86 | "max-lines": 0,
87 | "max-nested-callbacks": [2, 4],
88 | "max-params": [2, 6],
89 | "max-statements": [2, 50],
90 | "max-statements-per-line": [2, {"max": 2}],
91 | "multiline-ternary": 0,
92 | "new-cap": [2, {"newIsCap": true, "capIsNew": false}],
93 | "new-parens": 2,
94 | "newline-after-var": 0,
95 | "newline-before-return": 0,
96 | "newline-per-chained-call": [2, {"ignoreChainWithDepth": 3}],
97 | "no-alert": 2,
98 | "no-array-constructor": 2,
99 | "no-bitwise": 0,
100 | "no-caller": 2,
101 | "no-case-declarations": 2,
102 | "no-catch-shadow": 2,
103 | "no-class-assign": 2,
104 | "no-cond-assign": 2,
105 | "no-confusing-arrow": 2,
106 | "no-console": 2,
107 | "no-const-assign": 2,
108 | "no-constant-condition": 2,
109 | "no-continue": 0,
110 | "no-control-regex": 2,
111 | "no-debugger": 2,
112 | "no-delete-var": 2,
113 | "no-div-regex": 2,
114 | "no-dupe-args": 2,
115 | "no-dupe-class-members": 2,
116 | "no-dupe-keys": 2,
117 | "no-duplicate-case": 2,
118 | "no-duplicate-imports": 2,
119 | "no-else-return": 2,
120 | "no-empty": 2,
121 | "no-empty-character-class": 2,
122 | "no-empty-function": 0,
123 | "no-empty-pattern": 2,
124 | "no-eq-null": 2,
125 | "no-eval": 2,
126 | "no-ex-assign": 2,
127 | "no-extend-native": 2,
128 | "no-extra-bind": 2,
129 | "no-extra-boolean-cast": 2,
130 | "no-extra-label": 2,
131 | "no-extra-parens": 0,
132 | "no-extra-semi": 2,
133 | "no-fallthrough": 2,
134 | "no-floating-decimal": 2,
135 | "no-func-assign": 2,
136 | "no-implicit-coercion": [2, {"allow": ["!!"]}],
137 | "no-implicit-globals": 2,
138 | "no-implied-eval": 2,
139 | "no-inline-comments": 0,
140 | "no-inner-declarations": 2,
141 | "no-invalid-regexp": 2,
142 | "no-invalid-this": 0,
143 | "no-irregular-whitespace": 2,
144 | "no-iterator": 2,
145 | "no-label-var": 2,
146 | "no-labels": 2,
147 | "no-lone-blocks": 2,
148 | "no-lonely-if": 2,
149 | "no-loop-func": 2,
150 | "no-magic-numbers": 0,
151 | "no-mixed-operators": [2, {"allowSamePrecedence": true, "groups": [["&", "|", "^", "~", "<<", ">>", ">>>"], ["==", "!=", "===", "!==", ">", ">=", "<", "<="], ["&&", "||"], ["in", "instanceof"]]}],
152 | "no-mixed-requires": 2,
153 | "no-mixed-spaces-and-tabs": 2,
154 | "no-multi-spaces": 2,
155 | "no-multi-str": 2,
156 | "no-multiple-empty-lines": [2, {"max": 1, "maxBOF": 0, "maxEOF": 0}],
157 | "no-native-reassign": 2,
158 | "no-negated-condition": 0,
159 | "no-negated-in-lhs": 2,
160 | "no-nested-ternary": 2,
161 | "no-new": 2,
162 | "no-new-func": 2,
163 | "no-new-object": 2,
164 | "no-new-require": 2,
165 | "no-new-symbol": 2,
166 | "no-new-wrappers": 2,
167 | "no-obj-calls": 2,
168 | "no-octal": 2,
169 | "no-octal-escape": 2,
170 | "no-param-reassign": 2,
171 | "no-path-concat": 2,
172 | "no-plusplus": 0,
173 | "no-process-env": 0,
174 | "no-process-exit": 2,
175 | "no-proto": 2,
176 | "no-prototype-builtins": 2,
177 | "no-redeclare": 2,
178 | "no-regex-spaces": 2,
179 | "no-restricted-globals": 2,
180 | "no-restricted-imports": 2,
181 | "no-restricted-modules": 2,
182 | "no-restricted-syntax": 2,
183 | "no-return-assign": [2, "except-parens"],
184 | "no-script-url": 2,
185 | "no-self-assign": 2,
186 | "no-self-compare": 2,
187 | "no-sequences": 2,
188 | "no-shadow": 0, // TODO: Change to `1`?
189 | "no-shadow-restricted-names": 2,
190 | "no-spaced-func": 2,
191 | "no-sparse-arrays": 2,
192 | "no-sync": 2,
193 | "no-tabs": 2,
194 | "no-ternary": 0,
195 | "no-this-before-super": 2,
196 | "no-throw-literal": 2,
197 | "no-trailing-spaces": [2, {"skipBlankLines": false}],
198 | "no-undef": 2,
199 | "no-undef-init": 2,
200 | "no-undefined": 0,
201 | "no-underscore-dangle": 0,
202 | "no-unexpected-multiline": 2,
203 | "no-unmodified-loop-condition": 2,
204 | "no-unneeded-ternary": 2,
205 | "no-unreachable": 2,
206 | "no-unsafe-finally": 2,
207 | "no-unused-expressions": 2,
208 | "no-unused-labels": 2,
209 | "no-unused-vars": [2, {"vars": "all", "args": "none"}],
210 | "no-use-before-define": 2,
211 | "no-useless-call": 2,
212 | "no-useless-computed-key": 2,
213 | "no-useless-concat": 2,
214 | "no-useless-constructor": 2,
215 | "no-useless-escape": 2,
216 | "no-useless-rename": 2,
217 | "no-var": 2,
218 | "no-void": 2,
219 | "no-warning-comments": 0, // TODO: Change to `1`?
220 | "no-whitespace-before-property": 2,
221 | "no-with": 2,
222 | "object-curly-newline": [2, {"multiline": true}],
223 | "object-curly-spacing": [2, "never"],
224 | "object-property-newline": [2, {"allowMultiplePropertiesPerLine": true}],
225 | "object-shorthand": [2, "always"],
226 | "one-var": [2, "never"],
227 | "one-var-declaration-per-line": [2, "initializations"],
228 | "operator-assignment": [2, "always"],
229 | "operator-linebreak": [2, "after"],
230 | "padded-blocks": [2, "never"],
231 | "prefer-arrow-callback": ["error", {"allowNamedFunctions": true}],
232 | "prefer-const": 0, // TODO: Change to `1`?
233 | "prefer-reflect": 0,
234 | "prefer-rest-params": 2,
235 | "prefer-spread": 2,
236 | "prefer-template": 2,
237 | "quote-props": [2, "consistent"],
238 | "quotes": [2, "double", "avoid-escape"],
239 | "radix": [2, "always"],
240 | "require-jsdoc": 0,
241 | "require-yield": 2,
242 | "rest-spread-spacing": [2, "never"],
243 | "semi": [2, "always"],
244 | "semi-spacing": [2, {"before": false, "after": true}],
245 | "sort-imports": 2,
246 | "sort-vars": 2,
247 | "space-before-blocks": [2, "always"],
248 | "space-before-function-paren": [2, {"anonymous": "never", "named": "never"}],
249 | "space-in-parens": [2, "never"],
250 | "space-infix-ops": 2,
251 | "space-unary-ops": 2,
252 | "spaced-comment": [2, "always"],
253 | "strict": 0,
254 | "template-curly-spacing": [2, "never"],
255 | "unicode-bom": [2, "never"],
256 | "use-isnan": 2,
257 | "valid-jsdoc": [0, {"requireReturn": false, "requireParamDescription": false, "requireReturnDescription": false}],
258 | "valid-typeof": 2,
259 | "vars-on-top": 2,
260 | "wrap-iife": [2, "inside"],
261 | "wrap-regex": 0,
262 | "yield-star-spacing": [2, "after"],
263 | "yoda": [2, "never"]
264 | }
265 | }
266 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .node-virtualenv
2 | .DS_Store
3 | build
4 | dist
5 | node_modules
6 | npm-debug.log
7 |
--------------------------------------------------------------------------------
/.taskcluster.yml:
--------------------------------------------------------------------------------
1 | version: 0
2 | allowPullRequests: public
3 | metadata:
4 | name: Example Add-on
5 | description: Example Add-on CI Tasks
6 | owner: "{{ event.head.user.email }}"
7 | source: "{{ event.head.repo.url }}"
8 | tasks:
9 | - provisionerId: "{{ taskcluster.docker.provisionerId }}"
10 | metadata:
11 | name: Example Add-on Lint Tests
12 | description: Example Add-on Lint Tests
13 | owner: "{{ event.head.user.email }}"
14 | source: "{{ event.head.repo.url }}"
15 | workerType: "{{ taskcluster.docker.workerType }}"
16 | payload:
17 | maxRunTime: 1200
18 | image: "standard8/example-addon-repo-lint:2017080201.52285ea5e54c"
19 | command:
20 | - "/bin/bash"
21 | - "-lc"
22 | - "git clone {{event.head.repo.url}} repo && cd repo && git checkout {{event.head.repo.branch}} && npm install && npm run lint"
23 | extra:
24 | github:
25 | env: true
26 | events:
27 | - pull_request.opened
28 | - pull_request.synchronize
29 | - pull_request.reopened
30 | - push
31 | treeherder:
32 | jobKind: "test"
33 | machine:
34 | platform: "lint"
35 | symbol: "ES"
36 | - provisionerId: "{{ taskcluster.docker.provisionerId }}"
37 | metadata:
38 | name: Example Add-on Unit Tests
39 | description: Example Add-on Unit Tests
40 | owner: "{{ event.head.user.email }}"
41 | source: "{{ event.head.repo.url }}"
42 | workerType: "{{ taskcluster.docker.workerType }}"
43 | # Note: If you get an error such as "You didn't give the task-graph scopes
44 | # allowing it define tasks on the queue", then you need to get permissions
45 | # added for this scope. See [the taskcluster doc](docs/Taskcluster.md)
46 | # for more detail.
47 | scopes:
48 | - secrets:get:repo:github.com/mozilla/example-addon-repo:pull-request
49 | payload:
50 | env:
51 | NEED_WINDOW_MANAGER: true
52 | features:
53 | taskclusterProxy: true
54 | maxRunTime: 1200
55 | image: "standard8/example-addon-repo-functional:2017050301.82c2d17e74ef"
56 | command:
57 | # Note: post install for virtualenv is done as a separate step as currently
58 | # npm install is being run as root (bug 1093833). When that is fixed, we
59 | # should be able to merge them.
60 | - "/bin/bash"
61 | - "-lc"
62 | - "./bin/setup.sh && git clone {{event.head.repo.url}} repo && cd repo && git checkout {{event.head.repo.branch}} && npm install && npm run virtualenv-postinstall && npm run download && export FIREFOX_BINARY=`cat build/fflocation.txt` && COVERALLS_REPO_TOKEN=`python scripts/get_secret.py` FIREFOX_BIN=${FIREFOX_BINARY} npm run test:karma"
63 | extra:
64 | github:
65 | env: true
66 | events:
67 | - pull_request.opened
68 | - pull_request.synchronize
69 | - pull_request.reopened
70 | - push
71 | treeherder:
72 | jobKind: "test"
73 | machine:
74 | platform: "lint"
75 | symbol: "ES"
76 | - provisionerId: "{{ taskcluster.docker.provisionerId }}"
77 | metadata:
78 | name: Example Add-on Functional Tests Linux
79 | description: Example Add-on Functional Tests Linux
80 | owner: "{{ event.head.user.email }}"
81 | source: "{{ event.head.repo.url }}"
82 | workerType: "{{ taskcluster.docker.workerType }}"
83 | payload:
84 | env:
85 | NEED_WINDOW_MANAGER: true
86 | maxRunTime: 1200
87 | image: "standard8/example-addon-repo-functional:2017050301.82c2d17e74ef"
88 | command:
89 | - "/bin/bash"
90 | - "-lc"
91 | - "./bin/setup.sh && git clone {{event.head.repo.url}} repo && cd repo && git checkout {{event.head.repo.branch}} && npm install && npm run virtualenv-postinstall && npm run download && export FIREFOX_BINARY=`cat build/fflocation.txt` && npm run test:func"
92 | extra:
93 | github:
94 | env: true
95 | events:
96 | - pull_request.opened
97 | - pull_request.synchronize
98 | - pull_request.reopened
99 | - push
100 | treeherder:
101 | jobKind: "test"
102 | machine:
103 | platform: "lint"
104 | symbol: "ES"
105 | # - provisionerId: "{{ taskcluster.docker.provisionerId }}"
106 | # metadata:
107 | # name: Example Add-on Functional Tests Windows
108 | # description: Example Add-on Functional Tests Windows
109 | # owner: "{{ event.head.user.email }}"
110 | # source: "{{ event.head.repo.url }}"
111 | # workerType: "win2012r2"
112 | # payload:
113 | # env:
114 | # XPI_NAME: "dist/example_add-on-0.0.1.zip"
115 | # maxRunTime: 1200
116 | # command:
117 | # - "git clone https://github.com/mozilla/example-addon-repo.git repo"
118 | # - "cd repo"
119 | # - "git checkout {{event.head.repo.branch}}"
120 | # - "set PYTHON=C:\\mozilla-build\\python\\python.exe"
121 | # - "echo %PATH%"
122 | # - "npm install"
123 | # # XXX https://github.com/mozilla/example-addon-repo/issues/18
124 | # # virtualenv postinstall currently doesn't work properly on Windows,
125 | # # so we have to do this with the bash lines below.
126 | # # - "npm run virtualenv-postinstall"
127 | # # - "npm run download"
128 | # - "C:\\mozilla-build\\msys\\bin\\bash.exe -c 'virtualenv .venv'"
129 | # - "C:\\mozilla-build\\msys\\bin\\bash.exe -c '.venv/Scripts/pip install -r requirements.txt'"
130 | # - "C:\\mozilla-build\\msys\\bin\\bash.exe -c '.venv/Scripts/mozdownload --version=latest -d firefox-setup.exe'"
131 | # - "C:\\mozilla-build\\msys\\bin\\bash.exe -c '.venv/Scripts/python scripts/install_ff.py firefox-setup.exe'"
132 | # - "build\\fflocation.bat"
133 | # - "npm run funcnonbash"
134 | # - "npm run uninstall"
135 | # extra:
136 | # github:
137 | # env: true
138 | # events:
139 | # - pull_request.opened
140 | # - pull_request.synchronize
141 | # - pull_request.reopened
142 | # - push
143 | # treeherder:
144 | # jobKind: "test"
145 | # machine:
146 | # platform: "lint"
147 | # symbol: "ES"
148 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Mozilla Public License Version 2.0
2 | ==================================
3 |
4 | 1. Definitions
5 | --------------
6 |
7 | 1.1. "Contributor"
8 | means each individual or legal entity that creates, contributes to
9 | the creation of, or owns Covered Software.
10 |
11 | 1.2. "Contributor Version"
12 | means the combination of the Contributions of others (if any) used
13 | by a Contributor and that particular Contributor's Contribution.
14 |
15 | 1.3. "Contribution"
16 | means Covered Software of a particular Contributor.
17 |
18 | 1.4. "Covered Software"
19 | means Source Code Form to which the initial Contributor has attached
20 | the notice in Exhibit A, the Executable Form of such Source Code
21 | Form, and Modifications of such Source Code Form, in each case
22 | including portions thereof.
23 |
24 | 1.5. "Incompatible With Secondary Licenses"
25 | means
26 |
27 | (a) that the initial Contributor has attached the notice described
28 | in Exhibit B to the Covered Software; or
29 |
30 | (b) that the Covered Software was made available under the terms of
31 | version 1.1 or earlier of the License, but not also under the
32 | terms of a Secondary License.
33 |
34 | 1.6. "Executable Form"
35 | means any form of the work other than Source Code Form.
36 |
37 | 1.7. "Larger Work"
38 | means a work that combines Covered Software with other material, in
39 | a separate file or files, that is not Covered Software.
40 |
41 | 1.8. "License"
42 | means this document.
43 |
44 | 1.9. "Licensable"
45 | means having the right to grant, to the maximum extent possible,
46 | whether at the time of the initial grant or subsequently, any and
47 | all of the rights conveyed by this License.
48 |
49 | 1.10. "Modifications"
50 | means any of the following:
51 |
52 | (a) any file in Source Code Form that results from an addition to,
53 | deletion from, or modification of the contents of Covered
54 | Software; or
55 |
56 | (b) any new file in Source Code Form that contains any Covered
57 | Software.
58 |
59 | 1.11. "Patent Claims" of a Contributor
60 | means any patent claim(s), including without limitation, method,
61 | process, and apparatus claims, in any patent Licensable by such
62 | Contributor that would be infringed, but for the grant of the
63 | License, by the making, using, selling, offering for sale, having
64 | made, import, or transfer of either its Contributions or its
65 | Contributor Version.
66 |
67 | 1.12. "Secondary License"
68 | means either the GNU General Public License, Version 2.0, the GNU
69 | Lesser General Public License, Version 2.1, the GNU Affero General
70 | Public License, Version 3.0, or any later versions of those
71 | licenses.
72 |
73 | 1.13. "Source Code Form"
74 | means the form of the work preferred for making modifications.
75 |
76 | 1.14. "You" (or "Your")
77 | means an individual or a legal entity exercising rights under this
78 | License. For legal entities, "You" includes any entity that
79 | controls, is controlled by, or is under common control with You. For
80 | purposes of this definition, "control" means (a) the power, direct
81 | or indirect, to cause the direction or management of such entity,
82 | whether by contract or otherwise, or (b) ownership of more than
83 | fifty percent (50%) of the outstanding shares or beneficial
84 | ownership of such entity.
85 |
86 | 2. License Grants and Conditions
87 | --------------------------------
88 |
89 | 2.1. Grants
90 |
91 | Each Contributor hereby grants You a world-wide, royalty-free,
92 | non-exclusive license:
93 |
94 | (a) under intellectual property rights (other than patent or trademark)
95 | Licensable by such Contributor to use, reproduce, make available,
96 | modify, display, perform, distribute, and otherwise exploit its
97 | Contributions, either on an unmodified basis, with Modifications, or
98 | as part of a Larger Work; and
99 |
100 | (b) under Patent Claims of such Contributor to make, use, sell, offer
101 | for sale, have made, import, and otherwise transfer either its
102 | Contributions or its Contributor Version.
103 |
104 | 2.2. Effective Date
105 |
106 | The licenses granted in Section 2.1 with respect to any Contribution
107 | become effective for each Contribution on the date the Contributor first
108 | distributes such Contribution.
109 |
110 | 2.3. Limitations on Grant Scope
111 |
112 | The licenses granted in this Section 2 are the only rights granted under
113 | this License. No additional rights or licenses will be implied from the
114 | distribution or licensing of Covered Software under this License.
115 | Notwithstanding Section 2.1(b) above, no patent license is granted by a
116 | Contributor:
117 |
118 | (a) for any code that a Contributor has removed from Covered Software;
119 | or
120 |
121 | (b) for infringements caused by: (i) Your and any other third party's
122 | modifications of Covered Software, or (ii) the combination of its
123 | Contributions with other software (except as part of its Contributor
124 | Version); or
125 |
126 | (c) under Patent Claims infringed by Covered Software in the absence of
127 | its Contributions.
128 |
129 | This License does not grant any rights in the trademarks, service marks,
130 | or logos of any Contributor (except as may be necessary to comply with
131 | the notice requirements in Section 3.4).
132 |
133 | 2.4. Subsequent Licenses
134 |
135 | No Contributor makes additional grants as a result of Your choice to
136 | distribute the Covered Software under a subsequent version of this
137 | License (see Section 10.2) or under the terms of a Secondary License (if
138 | permitted under the terms of Section 3.3).
139 |
140 | 2.5. Representation
141 |
142 | Each Contributor represents that the Contributor believes its
143 | Contributions are its original creation(s) or it has sufficient rights
144 | to grant the rights to its Contributions conveyed by this License.
145 |
146 | 2.6. Fair Use
147 |
148 | This License is not intended to limit any rights You have under
149 | applicable copyright doctrines of fair use, fair dealing, or other
150 | equivalents.
151 |
152 | 2.7. Conditions
153 |
154 | Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted
155 | in Section 2.1.
156 |
157 | 3. Responsibilities
158 | -------------------
159 |
160 | 3.1. Distribution of Source Form
161 |
162 | All distribution of Covered Software in Source Code Form, including any
163 | Modifications that You create or to which You contribute, must be under
164 | the terms of this License. You must inform recipients that the Source
165 | Code Form of the Covered Software is governed by the terms of this
166 | License, and how they can obtain a copy of this License. You may not
167 | attempt to alter or restrict the recipients' rights in the Source Code
168 | Form.
169 |
170 | 3.2. Distribution of Executable Form
171 |
172 | If You distribute Covered Software in Executable Form then:
173 |
174 | (a) such Covered Software must also be made available in Source Code
175 | Form, as described in Section 3.1, and You must inform recipients of
176 | the Executable Form how they can obtain a copy of such Source Code
177 | Form by reasonable means in a timely manner, at a charge no more
178 | than the cost of distribution to the recipient; and
179 |
180 | (b) You may distribute such Executable Form under the terms of this
181 | License, or sublicense it under different terms, provided that the
182 | license for the Executable Form does not attempt to limit or alter
183 | the recipients' rights in the Source Code Form under this License.
184 |
185 | 3.3. Distribution of a Larger Work
186 |
187 | You may create and distribute a Larger Work under terms of Your choice,
188 | provided that You also comply with the requirements of this License for
189 | the Covered Software. If the Larger Work is a combination of Covered
190 | Software with a work governed by one or more Secondary Licenses, and the
191 | Covered Software is not Incompatible With Secondary Licenses, this
192 | License permits You to additionally distribute such Covered Software
193 | under the terms of such Secondary License(s), so that the recipient of
194 | the Larger Work may, at their option, further distribute the Covered
195 | Software under the terms of either this License or such Secondary
196 | License(s).
197 |
198 | 3.4. Notices
199 |
200 | You may not remove or alter the substance of any license notices
201 | (including copyright notices, patent notices, disclaimers of warranty,
202 | or limitations of liability) contained within the Source Code Form of
203 | the Covered Software, except that You may alter any license notices to
204 | the extent required to remedy known factual inaccuracies.
205 |
206 | 3.5. Application of Additional Terms
207 |
208 | You may choose to offer, and to charge a fee for, warranty, support,
209 | indemnity or liability obligations to one or more recipients of Covered
210 | Software. However, You may do so only on Your own behalf, and not on
211 | behalf of any Contributor. You must make it absolutely clear that any
212 | such warranty, support, indemnity, or liability obligation is offered by
213 | You alone, and You hereby agree to indemnify every Contributor for any
214 | liability incurred by such Contributor as a result of warranty, support,
215 | indemnity or liability terms You offer. You may include additional
216 | disclaimers of warranty and limitations of liability specific to any
217 | jurisdiction.
218 |
219 | 4. Inability to Comply Due to Statute or Regulation
220 | ---------------------------------------------------
221 |
222 | If it is impossible for You to comply with any of the terms of this
223 | License with respect to some or all of the Covered Software due to
224 | statute, judicial order, or regulation then You must: (a) comply with
225 | the terms of this License to the maximum extent possible; and (b)
226 | describe the limitations and the code they affect. Such description must
227 | be placed in a text file included with all distributions of the Covered
228 | Software under this License. Except to the extent prohibited by statute
229 | or regulation, such description must be sufficiently detailed for a
230 | recipient of ordinary skill to be able to understand it.
231 |
232 | 5. Termination
233 | --------------
234 |
235 | 5.1. The rights granted under this License will terminate automatically
236 | if You fail to comply with any of its terms. However, if You become
237 | compliant, then the rights granted under this License from a particular
238 | Contributor are reinstated (a) provisionally, unless and until such
239 | Contributor explicitly and finally terminates Your grants, and (b) on an
240 | ongoing basis, if such Contributor fails to notify You of the
241 | non-compliance by some reasonable means prior to 60 days after You have
242 | come back into compliance. Moreover, Your grants from a particular
243 | Contributor are reinstated on an ongoing basis if such Contributor
244 | notifies You of the non-compliance by some reasonable means, this is the
245 | first time You have received notice of non-compliance with this License
246 | from such Contributor, and You become compliant prior to 30 days after
247 | Your receipt of the notice.
248 |
249 | 5.2. If You initiate litigation against any entity by asserting a patent
250 | infringement claim (excluding declaratory judgment actions,
251 | counter-claims, and cross-claims) alleging that a Contributor Version
252 | directly or indirectly infringes any patent, then the rights granted to
253 | You by any and all Contributors for the Covered Software under Section
254 | 2.1 of this License shall terminate.
255 |
256 | 5.3. In the event of termination under Sections 5.1 or 5.2 above, all
257 | end user license agreements (excluding distributors and resellers) which
258 | have been validly granted by You or Your distributors under this License
259 | prior to termination shall survive termination.
260 |
261 | ************************************************************************
262 | * *
263 | * 6. Disclaimer of Warranty *
264 | * ------------------------- *
265 | * *
266 | * Covered Software is provided under this License on an "as is" *
267 | * basis, without warranty of any kind, either expressed, implied, or *
268 | * statutory, including, without limitation, warranties that the *
269 | * Covered Software is free of defects, merchantable, fit for a *
270 | * particular purpose or non-infringing. The entire risk as to the *
271 | * quality and performance of the Covered Software is with You. *
272 | * Should any Covered Software prove defective in any respect, You *
273 | * (not any Contributor) assume the cost of any necessary servicing, *
274 | * repair, or correction. This disclaimer of warranty constitutes an *
275 | * essential part of this License. No use of any Covered Software is *
276 | * authorized under this License except under this disclaimer. *
277 | * *
278 | ************************************************************************
279 |
280 | ************************************************************************
281 | * *
282 | * 7. Limitation of Liability *
283 | * -------------------------- *
284 | * *
285 | * Under no circumstances and under no legal theory, whether tort *
286 | * (including negligence), contract, or otherwise, shall any *
287 | * Contributor, or anyone who distributes Covered Software as *
288 | * permitted above, be liable to You for any direct, indirect, *
289 | * special, incidental, or consequential damages of any character *
290 | * including, without limitation, damages for lost profits, loss of *
291 | * goodwill, work stoppage, computer failure or malfunction, or any *
292 | * and all other commercial damages or losses, even if such party *
293 | * shall have been informed of the possibility of such damages. This *
294 | * limitation of liability shall not apply to liability for death or *
295 | * personal injury resulting from such party's negligence to the *
296 | * extent applicable law prohibits such limitation. Some *
297 | * jurisdictions do not allow the exclusion or limitation of *
298 | * incidental or consequential damages, so this exclusion and *
299 | * limitation may not apply to You. *
300 | * *
301 | ************************************************************************
302 |
303 | 8. Litigation
304 | -------------
305 |
306 | Any litigation relating to this License may be brought only in the
307 | courts of a jurisdiction where the defendant maintains its principal
308 | place of business and such litigation shall be governed by laws of that
309 | jurisdiction, without reference to its conflict-of-law provisions.
310 | Nothing in this Section shall prevent a party's ability to bring
311 | cross-claims or counter-claims.
312 |
313 | 9. Miscellaneous
314 | ----------------
315 |
316 | This License represents the complete agreement concerning the subject
317 | matter hereof. If any provision of this License is held to be
318 | unenforceable, such provision shall be reformed only to the extent
319 | necessary to make it enforceable. Any law or regulation which provides
320 | that the language of a contract shall be construed against the drafter
321 | shall not be used to construe this License against a Contributor.
322 |
323 | 10. Versions of the License
324 | ---------------------------
325 |
326 | 10.1. New Versions
327 |
328 | Mozilla Foundation is the license steward. Except as provided in Section
329 | 10.3, no one other than the license steward has the right to modify or
330 | publish new versions of this License. Each version will be given a
331 | distinguishing version number.
332 |
333 | 10.2. Effect of New Versions
334 |
335 | You may distribute the Covered Software under the terms of the version
336 | of the License under which You originally received the Covered Software,
337 | or under the terms of any subsequent version published by the license
338 | steward.
339 |
340 | 10.3. Modified Versions
341 |
342 | If you create software not governed by this License, and you want to
343 | create a new license for such software, you may create and use a
344 | modified version of this License if you rename the license and remove
345 | any references to the name of the license steward (except to note that
346 | such modified license differs from this License).
347 |
348 | 10.4. Distributing Source Code Form that is Incompatible With Secondary
349 | Licenses
350 |
351 | If You choose to distribute Source Code Form that is Incompatible With
352 | Secondary Licenses under the terms of this version of the License, the
353 | notice described in Exhibit B of this License must be attached.
354 |
355 | Exhibit A - Source Code Form License Notice
356 | -------------------------------------------
357 |
358 | This Source Code Form is subject to the terms of the Mozilla Public
359 | License, v. 2.0. If a copy of the MPL was not distributed with this
360 | file, You can obtain one at http://mozilla.org/MPL/2.0/.
361 |
362 | If it is not possible or desirable to put the notice in a particular
363 | file, then You may include the notice in a location (such as a LICENSE
364 | file in a relevant directory) where a recipient would be likely to look
365 | for such a notice.
366 |
367 | You may add additional accurate notices of copyright ownership.
368 |
369 | Exhibit B - "Incompatible With Secondary Licenses" Notice
370 | ---------------------------------------------------------
371 |
372 | This Source Code Form is "Incompatible With Secondary Licenses", as
373 | defined by the Mozilla Public License, v. 2.0.
374 |
375 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | [](https://coveralls.io/github/mozilla/example-addon-repo)
2 |
3 | This repository is intended as an example repository containing templates and good
4 | practices for creating an add-on for Firefox. This could be a general add-on, or
5 | a system add-on built in a separate repository to mozilla-central.
6 |
7 | This repository is based on WebExtensions, which are the way forward for extensions
8 | in Firefox. There are a few issues with WebExtensions and SystemAddons, please see
9 | the Issues for more detail.
10 |
11 | # Aims
12 |
13 | The aim is to bring together tools and services we've used on other system add-ons
14 | (e.g. Hello), into a template/example repository, so that new projects can come
15 | along and get a infrastructure together quickly, and be up and running with code,
16 | test suites, coverage etc quickly.
17 |
18 | # Bits currently missing
19 |
20 | * Integration with m-c.
21 |
22 | # Issues
23 |
24 | Please see the [issues list](https://github.com/mozilla/example-addon-repo/issues)
25 |
26 | # Documentation
27 |
28 | It is intended that all parts of this repository have at least outline
29 | documentation. If you find any parts that are missing, please file an issue or
30 | create a PR.
31 |
32 | * [Building, running code and tests](docs/Developing.md)
33 | * [Keeping modules up to date via automated services](docs/ModulesUpdating.md)
34 | * Testing
35 | * [Linting](docs/Linting.md)
36 | * [Unit Tests](docs/UnitTests.md)
37 | * [Functional Tests](docs/Functional.md)
38 | * [Continous Integration via Taskcluster](docs/Taskcluster.md)
39 |
--------------------------------------------------------------------------------
/add-on/.eslintrc:
--------------------------------------------------------------------------------
1 | {
2 | "env": {
3 | "browser": true,
4 | "node": false
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/add-on/bootstrap.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 | /* eslint no-implicit-globals:off, no-console:off */
3 | /* exported startup, shutdown, install, uninstall */
4 |
5 | function startup({webExtension}) {
6 | webExtension.startup().then(() => {
7 | console.log("Example WebExtension started!");
8 | return Promise.resolved();
9 | }).catch(() => {
10 | console.error("Example WebExtension startup failed!");
11 | });
12 | }
13 |
14 | function shutdown() {
15 | }
16 |
17 | function install() {
18 |
19 | }
20 |
21 | function uninstall() {
22 | }
23 |
--------------------------------------------------------------------------------
/add-on/install.rdf:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 |
8 |
9 |
10 | exampleaddonrepo@mozilla.org
11 | 0.0.1
12 | 2
13 | true
14 | true
15 | true
16 |
17 |
19 |
20 |
21 | {ec8030f7-c20a-464f-9b0e-13a3a9e97384}
22 | 48.0
23 | 52.*
24 |
25 |
26 |
27 |
28 | Example Add-on
29 | Example Add-on built from the example repository.
30 |
31 |
32 |
--------------------------------------------------------------------------------
/add-on/webextension/_locales/en-US/messages.json:
--------------------------------------------------------------------------------
1 | {
2 | "extensionName": {
3 | "message": "Example Add-on",
4 | "description": "Name of the extension"
5 | },
6 | "extensionDescription": {
7 | "message": "Example Add-on built from the example repository",
8 | "description": "Description of the extension."
9 | },
10 | "buttonTitle": {
11 | "message": "Visit Mozilla",
12 | "description": "Description shown on the toolbar when hovered. DO NOT localise 'Mozilla'."
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/add-on/webextension/_locales/fr/messages.json:
--------------------------------------------------------------------------------
1 | {
2 | "extensionName": {
3 | "message": "Exemple d'extension",
4 | "description": "Name of the extension"
5 | },
6 | "extensionDescription": {
7 | "message": "Exemple d'extension générée à partir du dépôt de test",
8 | "description": "Description of the extension."
9 | },
10 | "buttonTitle": {
11 | "message": "Visitez Mozilla",
12 | "description": "Description shown on the toolbar when hovered. DO NOT localise 'Mozilla'."
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/add-on/webextension/main.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 |
3 | /* global chrome */
4 |
5 | chrome.browserAction.onClicked.addListener(() => {
6 | chrome.tabs.create({
7 | active: true,
8 | url: "https://www.mozilla.org"
9 | });
10 | });
11 |
--------------------------------------------------------------------------------
/add-on/webextension/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "applications": {
3 | "gecko": {
4 | "id": "exampleaddonrepo@mozilla.org",
5 | "strict_min_version": "48.0",
6 | "strict_max_version": "100.*"
7 | }
8 | },
9 | "background": {
10 | "scripts": ["main.js"]
11 | },
12 | "browser_action": {
13 | "browser_style": true,
14 | "default_icon": "img/firefox-logo.svg",
15 | "default_title": "__MSG_buttonTitle__"
16 | },
17 |
18 | "default_locale": "en-US",
19 |
20 | "description": "__MSG_extensionDescription__",
21 |
22 | "manifest_version": 2,
23 |
24 | "name": "Example Add-on",
25 |
26 | "permissions": [],
27 |
28 | "version": "0.0.1"
29 | }
30 |
--------------------------------------------------------------------------------
/docker-functional/Dockerfile:
--------------------------------------------------------------------------------
1 | # Based on desktop1604-test to give us a builder similar to the mozilla-central
2 | # ones, and which has a window manager capability.
3 | FROM quay.io/mozilla/desktop1604-test
4 |
5 | # Add any extra things we need, i.e. node 4.
6 | ADD system-setup.sh /tmp/system-setup.sh
7 | RUN bash /tmp/system-setup.sh
8 |
9 | # Custom startup script to avoid mozharness.
10 | ADD setup.sh /home/worker/bin/setup.sh
11 | RUN chmod 755 /home/worker/bin/*
12 |
--------------------------------------------------------------------------------
/docker-functional/README.md:
--------------------------------------------------------------------------------
1 | # Generating docker-functional
2 |
3 | We assume you have experience of Docker, if not, take a look at their
4 | [getting started guide](https://docs.docker.com/engine/getstarted/).
5 |
6 | From a mozilla-central checkout (does not need to be built), run:
7 |
8 | ```sh
9 | ./mach taskcluster-build-image desktop1604-test
10 | ```
11 |
12 | This creates a local copy of the image, currently tagged "quay.io/mozilla/desktop1604-test".
13 |
14 | Now build the docker image:
15 |
16 | ```sh
17 | docker build -t /example-addon-repo-functional .
18 | ```
19 |
20 | Ideally at this stage you should test it out, but if you're working on an
21 | experimental branch, it is sometimes easier to push it, and see what taskcluster
22 | does with it.
23 |
24 | Tag the image, and push it. For the tag, the recommended setting is
25 | `YYYYMMDDNN.`, where `YYYYMMDD` is the date, `NN` is the number of the
26 | image created on that day, and `` is the mozilla-central changeset the
27 | original build image is based on.
28 |
29 | ```
30 | docker tag /example-addon-repo-functional:latest /example-addon-repo-functional:2016082401.ca24710db69a
31 | docker push /example-addon-repo-functional:2016082401.ca24710db69a
32 | ```
33 |
--------------------------------------------------------------------------------
/docker-functional/setup.sh:
--------------------------------------------------------------------------------
1 | #! /bin/bash -vex
2 |
3 | # This file is based on
4 | # https://dxr.mozilla.org/mozilla-central/source/taskcluster/scripts/tester/test-linux.sh
5 | # We have our own version of it, as we don't need the mozharness setup.
6 |
7 | set -x -e
8 |
9 | # Inputs, with defaults
10 |
11 | : NEED_XVFB ${NEED_XVFB:=true}
12 | : NEED_WINDOW_MANAGER ${NEED_WINDOW_MANAGER:=false}
13 | : START_VNC ${START_VNC:=false}
14 |
15 | # TODO: when bug 1093833 is solved and tasks can run as non-root, reduce this
16 | # to a simple fail-if-root check
17 | if [ $(id -u) = 0 ]; then
18 | chown -R worker:worker /home/worker
19 | # drop privileges by re-running this script
20 | exec sudo -E -u worker bash /home/worker/bin/setup.sh "${@}"
21 | fi
22 |
23 | fail() {
24 | echo # make sure error message is on a new line
25 | echo "[setup.sh:error]" "${@}"
26 | exit 1
27 | }
28 |
29 | ####
30 | # Now get the test-linux.sh script from the given Gecko tree and run it with
31 | # the same arguments.
32 | ####
33 |
34 | mkdir -p ~/artifacts/public
35 |
36 | # run XVfb in the background, if necessary
37 | if $NEED_XVFB; then
38 | Xvfb :0 -nolisten tcp -screen 0 1600x1200x24 \
39 | > ~/artifacts/public/xvfb.log 2>&1 &
40 | export DISPLAY=:0
41 | xvfb_pid=$!
42 | # Only error code 255 matters, because it signifies that no
43 | # display could be opened. As long as we can open the display
44 | # tests should work. We'll retry a few times with a sleep before
45 | # failing.
46 | retry_count=0
47 | max_retries=2
48 | xvfb_test=0
49 | until [ $retry_count -gt $max_retries ]; do
50 | xvinfo || xvfb_test=$?
51 | if [ $xvfb_test != 255 ]; then
52 | retry_count=$(($max_retries + 1))
53 | else
54 | retry_count=$(($retry_count + 1))
55 | echo "Failed to start Xvfb, retry: $retry_count"
56 | sleep 2
57 | fi
58 | done
59 | if [ $xvfb_test == 255 ]; then fail "xvfb did not start properly"; fi
60 | fi
61 |
62 | if $START_VNC; then
63 | x11vnc --usepw -forever > ~/artifacts/public/x11vnc.log 2>&1 &
64 | fi
65 |
66 | if $NEED_WINDOW_MANAGER; then
67 | # This is read by xsession to select the window manager
68 | echo DESKTOP_SESSION=ubuntu > /home/worker/.xsessionrc
69 |
70 | # note that doing anything with this display before running Xsession will cause sadness (like,
71 | # crashes in compiz). Make sure that X has enough time to start
72 | sleep 15
73 | # DISPLAY has already been set above
74 | # XXX: it would be ideal to add a semaphore logic to make sure that the
75 | # window manager is ready
76 | /etc/X11/Xsession 2>&1 &
77 |
78 | # Turn off the screen saver and screen locking
79 | gsettings set org.gnome.desktop.screensaver idle-activation-enabled false
80 | gsettings set org.gnome.desktop.screensaver lock-enabled false
81 | gsettings set org.gnome.desktop.screensaver lock-delay 3600
82 | # Disable the screen saver
83 | xset s off s reset
84 | fi
85 |
86 | [ -d $WORKSPACE ] || mkdir -p $WORKSPACE
87 | cd $WORKSPACE
88 |
--------------------------------------------------------------------------------
/docker-functional/system-setup.sh:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 | # This allows ubuntu-desktop to be installed without human interaction
3 | export DEBIAN_FRONTEND=noninteractive
4 |
5 | set -ve
6 |
7 | test `whoami` == 'root'
8 |
9 | mkdir -p /setup
10 | cd /setup
11 |
12 | # Without this we get spurious "LC_ALL: cannot change locale (en_US.UTF-8)" errors,
13 | # and python scripts raise UnicodeEncodeError when trying to print unicode characters.
14 | locale-gen en_US.UTF-8
15 | dpkg-reconfigure locales
16 |
17 | # The desktop1604-test version is currently good enough for us.
18 | # install node
19 |
20 | #curl -O https://nodejs.org/dist/v4.5.0/node-v4.5.0-linux-x64.tar.xz
21 | #tar -C /usr/local --strip-components 1 -xJ < node-*.tar.xz
22 | #node -v # verify
23 | #npm -v
24 |
25 | cd /
26 | rm -rf /setup
27 |
--------------------------------------------------------------------------------
/docker-lint/Dockerfile:
--------------------------------------------------------------------------------
1 | # Based on the mozilla-central lint image, as it is nice and small.
2 | FROM quay.io/mozilla/lint
3 |
4 | # Add git to the image.
5 | RUN apt-get update -y && apt-get install -y git
6 |
--------------------------------------------------------------------------------
/docker-lint/README.md:
--------------------------------------------------------------------------------
1 | # Generating docker-lint
2 |
3 | We assume you have experience of Docker, if not, take a look at their
4 | [getting started guide](https://docs.docker.com/engine/getstarted/).
5 |
6 | From a mozilla-central checkout (does not need to be built), run:
7 |
8 | ```sh
9 | ./mach taskcluster-build-image lint
10 | ```
11 |
12 | This creates a local copy of the image, currently tagged "quay.io/mozilla/lint".
13 |
14 | Now build the docker image:
15 |
16 | ```sh
17 | docker build -t /example-addon-repo-lint .
18 | ```
19 |
20 | Ideally at this stage you should test it out, but if you're working on an
21 | experiemental branch, it is sometimes easier to push it, and see what taskcluster
22 | does with it.
23 |
24 | Tag the image, and push it. For the tag, the recommended setting is
25 | `YYYYMMDDNN.`, where `YYYYMMDD` is the date, `NN` is the number of the
26 | image created on that day, and `` is the mozilla-central changeset the
27 | original build image is based on.
28 |
29 | ```
30 | docker tag /example-addon-repo-lint:latest /example-addon-repo-lint:2016082401.ca24710db69a
31 | docker push /example-addon-repo-lint:2016082401.ca24710db69a
32 | ```
33 |
34 |
--------------------------------------------------------------------------------
/docs/Developing.md:
--------------------------------------------------------------------------------
1 | # Installation
2 |
3 | You'll need normal unix command-line utilities. Currently the build process has
4 | only been tested/maintained on Mac/Linux systems.
5 |
6 | If you want to try it on Windows, then installing
7 | [MozillaBuild](https://wiki.mozilla.org/MozillaBuild) will provide some tools
8 | for the command line.
9 |
10 | Assuming you have the basic utilities, then you also need:
11 |
12 | * [node.js](https://nodejs.org/) with npm.
13 | * Version 4.x of node is currently the minium required.
14 | * Firefox Nightly installed on your system.
15 | * This is the default for the repository,
16 | [but can be changed](#Changing-the-Firefox-binary-location)
17 |
18 | Note: The split of different Firefox versions will be fixed by
19 | https://github.com/mozilla/example-addon-repo/issues/5.
20 |
21 | To install all the support packages for the repository run:
22 |
23 | ```shell
24 | $ npm install
25 | ```
26 |
27 | # Running the add-on in Firefox
28 |
29 | ```shell
30 | $ npm run firefox
31 | ```
32 |
33 | Note: If the source to the add-on is changed, this will cause the add-on to be
34 | rebuilt and reloaded.
35 |
36 | # Running tests
37 |
38 | This command runs all tests:
39 |
40 | ```shell
41 | $ npm test
42 | ```
43 |
44 | You can run individual tests, e.g.
45 |
46 | ```shell
47 | $ npm run lint
48 | $ npm run test:karma
49 | $ npm run test:func
50 | ```
51 |
52 | More information on the tests in this repository
53 | [can be found here](https://github.com/mozilla/example-addon-repo/#documentation).
54 |
55 | # Changing the Firefox binary location
56 |
57 | You can use a different Firefox binary other than nightly by specifying
58 | a value for `FIREFOX_BINARY` on the command line, or in environment variables.
59 |
60 | ```shell
61 | $ # Runs Firefox Release
62 | $ FIREFOX_BINARY=firefox npm test
63 | $ # Runs a specific copy of Firefox
64 | $ FIREFOX_BINARY=/path/to/firefox npm run firefox
65 | ```
66 |
67 | The various shorthands are:
68 |
69 | * `firefox` - Firefox Release
70 | * `beta` - Firefox Beta
71 | * `firefoxdeveloperedition` - Firefox developer edition (on osx)
72 | * `aurora` - Firefox Aurora (developer edition on non-osx)
73 | * `nightly` - Firefox Nightly
74 |
75 | # Bundling a Zip File for Upload to AMO.
76 |
77 | ```shell
78 | $ npm run bundle
79 | ```
80 |
81 |
--------------------------------------------------------------------------------
/docs/Functional.md:
--------------------------------------------------------------------------------
1 | # Functional Tests
2 |
3 | The functional tests in this repository are run via
4 | [Selenium](http://www.seleniumhq.org/) and
5 | [Geckodriver](https://github.com/mozilla/geckodriver).
6 |
7 | Selenium is being driven via the node/javascript language, although python may
8 | also work well (the
9 | [Loop](https://github.com/mozilla/loop/blob/master/docs/Developing.md#functional-tests)
10 | project used Python).
11 |
12 | [Mocha](https://mochajs.org/) is used as the test framework.
13 |
14 | # Running the tests.
15 |
16 | The functional tests can be run on their own by:
17 |
18 | ```
19 | $ npm run test:func
20 | ```
21 |
22 | ## Running against different browser versions.
23 |
24 | TBD.
25 |
26 | # Useful Documents
27 |
28 | * [Javascript API for webdriver](https://seleniumhq.github.io/selenium/docs/api/javascript/module/selenium-webdriver/firefox/index.html)
29 |
--------------------------------------------------------------------------------
/docs/Linting.md:
--------------------------------------------------------------------------------
1 | # Linting
2 |
3 | [Linting](http://en.wikipedia.org/wiki/Lint_(software)) is important part of
4 | code development that provides static analysis and helps to find bugs in code. It
5 | also helps to developers to adhere to varios style guidelines during the coding
6 | stage, rather than only finding out at review time.
7 |
8 | It is recommended for any new project to have linting set up from the start.
9 |
10 | # ESLint - Javascript Linting
11 |
12 | This respository is has [ESLint](http://eslint.org) for providing javascript
13 | analysis. It is a highly flexible tool especially as it is pluggable, so more
14 | rules can be added easily.
15 |
16 | The rules turned on here, are a combination of the rules used for the
17 | [Hello](https://github.com/mozilla/loop) and
18 | [Activity Stream](https://github.com/mozilla/activity-stream/) projects.
19 |
20 | ## Editor Integration
21 |
22 | Editor integration is where developers using linting tools can really gain time.
23 | They can get feedback on their code as they are writing it, flagging up potential
24 | issues before they even try to run it.
25 |
26 | ### How to integrate
27 |
28 | * [ESLint's page on available integrations](http://eslint.org/docs/user-guide/integrations)
29 | * [Details written by the DevTools teams](https://wiki.mozilla.org/DevTools/CodingStandards#Running_ESLint_in_SublimeText)
30 |
31 | ## Useful ESLint plugins.
32 |
33 | Additionally, there are some plugins that are recommended. Included in this example
34 | repository:
35 |
36 | * [eslint-plugin-json](https://www.npmjs.com/package/eslint-plugin-json)
37 | * Provides linting of json files.
38 | * [eslint-plugin-mocha](https://www.npmjs.com/package/eslint-plugin-mocha)
39 | * Provides rules for various good practices for [Mocha](https://mochajs.org/)
40 | tests.
41 | * [eslint-plugin-promise](https://www.npmjs.com/package/eslint-plugin-promise)
42 | * Provides rules for enforcing best practices for Javascript promises.
43 |
44 | Not included, but useful depending on the project:
45 |
46 | * [eslint-plugin-mozilla](https://www.npmjs.com/package/eslint-plugin-mozilla)
47 | * A collection of rules that help enforce various coding practice from Mozilla.
48 | * Mainly based on chrome style code, can help with module imports for example.
49 | * [eslint-plugin-react](https://www.npmjs.com/package/eslint-plugin-react)
50 | * Provides [React](https://facebook.github.io/react/) specific linting rules.
51 |
52 | # Web-ext lint
53 |
54 | The [web-ext tool provides](https://developer.mozilla.org/en-US/Add-ons/WebExtensions/web-ext_command_reference#web-ext_lint)
55 | its own linting mechanism for checking WebExtension based add-ons conform to
56 | [specific rules](http://mozilla.github.io/addons-linter/).
57 |
58 | # flake8 - Python Linting
59 |
60 | Although not currently used in this repository,
61 | [flake8](http://flake8.pycqa.org/en/latest/) is a good tool for linting Python
62 | scripts.
63 |
64 | The [Loop](https://github.com/mozilla/loop) project used this tool.
65 |
66 | There are also editor integrations available for Flake8.
67 |
--------------------------------------------------------------------------------
/docs/ModulesUpdating.md:
--------------------------------------------------------------------------------
1 | # Keeping Modules Up to Date
2 |
3 | Whilst npm has an `npm outdated` command, keeping modules up to date can be a big
4 | manual task.
5 |
6 | However, there's various services that make this easier, and can even create pull
7 | requests (PRs) automatically.
8 |
9 | ## Node/Npm
10 |
11 | There's a couple of services known for npm that are worth investigating:
12 |
13 | * [Greenkeeper.io](https://greenkeeper.io/)
14 | * [Loop](https://github.com/mozilla/loop) and
15 | [Activity Stream](https://github.com/mozilla/activity-stream/) (possibly
16 | others in Mozilla as well) have used this.
17 | * It is enabled on this repository - see
18 | [the list of PRs Greenkeeper has previously created](https://github.com/mozilla/example-addon-repo/pulls?utf8=%E2%9C%93&q=is%3Apr%20author%3Agreenkeeperio-bot%20)
19 | * The service creates a PR when first enabled to bring your package.json up to date.
20 | * The service then creates individual PRs for each package update.
21 | * If it can find them, it will give you the changelog information for each package
22 | within the PR.
23 | * As it creates PRs, your tests get run automatically.
24 | * Although the service operates with branches within your main repository, it
25 | also means that you can push additional changes to the branch to fix issues
26 | (e.g. test failures) due to package upgrades.
27 | * [VersionEye](https://www.versioneye.com/)
28 | * VersionEye has good monitoring, and will send you email updates about out
29 | of date packages.
30 | * It can also now comment on PRs about out of date modules when a PR adds
31 | new dependencies.
32 | * Unfortunately it doesn't create PRs for updating your modules.
33 |
34 | ## Python/Pip
35 |
36 | * [requires.io](https://requires.io/)
37 | * This is basically the Greenkeeper equivalent, but for python modules.
38 | * The PRs aren't quite as nice as the Greenkeeper ones as they don't separate
39 | out the packages / include the release notes. However, it still makes the
40 | process a lot simpler and more automated.
41 |
--------------------------------------------------------------------------------
/docs/Taskcluster.md:
--------------------------------------------------------------------------------
1 | # What is Taskcluster?
2 |
3 | [TaskCluster](https://docs.taskcluster.net/) is the task execution
4 | framework that supports Mozilla's continuous integration and release
5 | processes.
6 |
7 | In this respository, it is used with
8 | [taskcluster-github](https://docs.taskcluster.net/manual/vcs/github)
9 | integration to provide testing for the example add-on.
10 |
11 | Note that Taskcluster will only work with repositories in the "mozilla"
12 | organisation on github. Mozilla repositories outside of that will need
13 | to be specially whitelisted. Ask the #taskcluster team for details.
14 |
15 | # Setting up Taskcluster on a new Repository
16 |
17 | Taskcluster tests will only run if there is a .taskcluster.yml on the
18 | branch being pushed to. Therefore, integration will need to be developed and
19 | tested on a branch within a repository in the Mozilla organisation.
20 |
21 | ## Docker images
22 |
23 | The docker images are based on mozilla-central images. They may need to be
24 | regenerated from time to time to add features, or to keep updated.
25 |
26 | See the instructions in the `docker-*` directories for more information on how
27 | to regenerate them.
28 |
29 | ## .taskcluster.yml
30 |
31 | The .taskcluster.yml file lists the tests to run, which Docker images to run them
32 | on and when to run them. This should be configured as necessary for the repository,
33 | see the [taskcluster-github](https://docs.taskcluster.net/manual/vcs/github) docs
34 | for more information.
35 |
36 | ## TaskClusterRobot
37 |
38 | The TaskClusterRobot needs to be added to the list of contributors for
39 | the repository for it to be able to report PR status properly. You'll need
40 | to add it with read/write permissions.
41 |
42 | ## Unit Tests and Coveralls.
43 |
44 | When run on Taskcluster, the unit tests will generate code coverage information
45 | to be uploaded to coveralls.
46 |
47 | To enable this for a new repository:
48 |
49 | * Currently, you may need additional credentials. If you find issues in the steps
50 | below, you'll need to [file a bug](https://bugzilla.mozilla.org/enter_bug.cgi?component=General&product=Taskcluster) for:
51 | * Yourself/main developers to get:
52 | * secrets:set:repo:github.com/mozilla/example-addon-repo:*
53 | * secrets:get:repo:github.com/mozilla/example-addon-repo:*
54 | * Your repository to get:
55 | * secrets:get:repo:github.com/mozilla/example-addon-repo:*
56 | * For access to secrets, e.g. coverals.
57 | * queue:define-task:aws-provisioner-v1/win2012r2
58 | * To allow it to use the windows builders.
59 | * Replace the url to your repository in the secrets above.
60 | * Log into Coveralls.
61 | * Go to the "Add repositories section".
62 | * Search for the repository and select "Turn On".
63 | * Go to the repository page (select "Details").
64 | * There you will see the repo_token. Make a copy of it.
65 | * In Taskcluster, go to the [secrets tool](https://tools.taskcluster.net/secrets/).
66 | * Enter the secret name as `repo:github.com/mozilla/example-addon-repo:pull-request`
67 | * (replace the repository url as appropriate)
68 | * Set the expiry date to something in the future.
69 | * Set the JSON secret to (using the appropriate token):
70 |
71 | ```json
72 | {
73 | "COVERALLS_REPO_TOKEN": "abcdefghijklmnopq"
74 | }
75 | ```
76 |
77 | * Hit "Create Secret".
78 |
79 | # Integrating with TreeHerder
80 |
81 | Integration with TreeHerder not possible at this time. In theory it should be
82 | as simple as cloning [bug 1297671](https://bugzilla.mozilla.org/show_bug.cgi?id=1297671)
83 | and its patch, however the work to hook github and treeherder together is not
84 | complete (there are examples, but they are hacks).
85 |
--------------------------------------------------------------------------------
/docs/UnitTests.md:
--------------------------------------------------------------------------------
1 | # Unit Tests
2 |
3 | The unit tests are based around [Mocha](http://mochajs.org/) and
4 | [Sinon](http://sinonjs.org/). This gives a very simple mocking and test interface.
5 |
6 | They are run using [Karma](https://karma-runner.github.io), which also provides
7 | for code coverage.
8 |
9 | # WebExtension Stubbing
10 |
11 | WebExtensions are automatically stubbed by the
12 | [sinon-chrome](https://github.com/acvetkov/sinon-chrome) package.
13 |
14 | Note: Currently the package only stubs chrome.* APIs. There is an
15 | [issue on file](https://github.com/acvetkov/sinon-chrome/issues/40) to consider
16 | supporting the Firefox ones - browser.*
17 |
18 | # Test Files
19 |
20 | The test files live in the `test/unit` directory. `karma.conf.js` controls the
21 | loading and running of tests.
22 |
23 | # Running the Tests
24 |
25 | ```shell
26 | $ npm run test:karma
27 | ```
28 |
29 | # Viewing coverage output
30 |
31 | You can view the code output in the `build/coverage/` directory.
32 |
--------------------------------------------------------------------------------
/karma.conf.js:
--------------------------------------------------------------------------------
1 | const reporters = ["mocha", "coverage"];
2 | if (process.env.COVERALLS_REPO_TOKEN) {
3 | reporters.push("coveralls");
4 | }
5 |
6 | module.exports = function(config) {
7 | config.set({
8 | singleRun: true,
9 | browsers: ["Firefox"],
10 | frameworks: ["mocha"],
11 | reporters,
12 | coverageReporter: {
13 | dir: "build/coverage",
14 | reporters: [
15 | {
16 | type: "lcov",
17 | subdir: "lcov"
18 | },
19 | {
20 | type: "html",
21 | subdir(browser) {
22 | // normalization process to keep a consistent browser name
23 | // across different OS
24 | return browser.toLowerCase().split(/[ /-]/)[0];
25 | }
26 | }, {type: "text-summary"}
27 | ]
28 | },
29 | files: [
30 | "node_modules/sinon/pkg/sinon.js",
31 | "node_modules/sinon-chrome/bundle/sinon-chrome.min.js",
32 | "add-on/webextension/main.js",
33 | "test/unit/*.test.js"
34 | ],
35 | preprocessors: {"add-on/*.js": ["coverage"]},
36 | plugins: [
37 | "karma-coveralls",
38 | "karma-coverage",
39 | "karma-firefox-launcher",
40 | "karma-mocha",
41 | "karma-mocha-reporter"
42 | ]
43 | });
44 | };
45 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "exampleaddonrepo",
3 | "version": "0.0.1",
4 | "description": "Example add-on repository",
5 | "repository": {
6 | "type": "git",
7 | "url": "git@github.com:mozilla/example-addon-repo.git"
8 | },
9 | "bugs": {
10 | "url": "https://github.com/mozilla/example-addon-repo/issues"
11 | },
12 | "engines": {
13 | "firefox": ">=48.0a1"
14 | },
15 | "dependencies": {},
16 | "devDependencies": {
17 | "commander": "2.11.0",
18 | "eslint": "4.8.0",
19 | "eslint-plugin-json": "1.2.0",
20 | "eslint-plugin-mocha": "4.11.0",
21 | "eslint-plugin-promise": "3.5.0",
22 | "fx-runner": "1.0.7",
23 | "geckodriver": "1.9.0",
24 | "jpm": "1.3.1",
25 | "karma": "1.7.1",
26 | "karma-coverage": "1.1.1",
27 | "karma-coveralls": "1.1.2",
28 | "karma-firefox-launcher": "1.0.1",
29 | "karma-mocha": "1.3.0",
30 | "karma-mocha-reporter": "2.2.4",
31 | "mocha": "3.5.3",
32 | "mz": "2.7.0",
33 | "sinon": "4.0.0",
34 | "sinon-chrome": "2.2.1",
35 | "npm-run-all": "4.1.1",
36 | "selenium-webdriver": "3.4.0",
37 | "virtualenv": "0.3.1",
38 | "web-ext": "2.0.0"
39 | },
40 | "greenkeeper": {
41 | "ignore": [
42 | "selenium-webdriver"
43 | ]
44 | },
45 | "permissions": {
46 | "multiprocess": true
47 | },
48 | "preferences": [
49 | {
50 | "name": "buttonUrl",
51 | "title": "Web page url",
52 | "description": "The web page url to open when button is clicked",
53 | "type": "string",
54 | "value": "https://www.mozilla.org/"
55 | }
56 | ],
57 | "scripts": {
58 | "bundle": "mkdir -p dist && cd add-on && zip -r -D -q ../dist/example-add-on.xpi . && cd ..",
59 | "download": "node scripts/get_ff.js",
60 | "firefox": "scripts/runfx.js --binary ${FIREFOX_BINARY:-nightly} --profile dev",
61 | "lint": "npm-run-all test:eslint test:webextlint",
62 | "virtualenv-postinstall": "virtualenv-postinstall",
63 | "test": "npm-run-all test:*",
64 | "test:eslint": "eslint --ext=.js,.json .",
65 | "test:webextlint": "web-ext -s add-on/webextension lint",
66 | "test:karma": "NODE_ENV=test karma start",
67 | "test:func": "export FIREFOX_BINARY=${FIREFOX_BINARY:-nightly} && npm run bundle && XPI_NAME=dist/example-add-on.xpi mocha test/functional/",
68 | "uninstall": "node scripts/uninstall_ff.js",
69 | "funcnonbash": "npm run bundle && mocha test/functional/"
70 | },
71 | "license": "MPL-2.0",
72 | "keywords": [
73 | "jetpack"
74 | ]
75 | }
76 |
--------------------------------------------------------------------------------
/requirements.txt:
--------------------------------------------------------------------------------
1 | mozdownload==1.21
2 | mozinstall==1.12
3 |
--------------------------------------------------------------------------------
/scripts/.eslintrc:
--------------------------------------------------------------------------------
1 | {
2 | "rules": {
3 | "no-console": 0
4 | }
5 | }
6 |
--------------------------------------------------------------------------------
/scripts/get_ff.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 |
3 | const virtualenv = require("virtualenv");
4 | const fs = require("fs");
5 |
6 | const packagePath = require.resolve("../package.json");
7 | let env = virtualenv(packagePath);
8 |
9 | function getDownloadFileName() {
10 | switch (process.platform) {
11 | case "darwin":
12 | return "build/firefox-setup.dmg";
13 | case "linux":
14 | return "build/firefox-setup.tar.bz2";
15 | case "win32":
16 | return "build/firefox-setup.exe";
17 | default:
18 | throw new Error(`Unsupported platform ${process.platform}`);
19 | }
20 | }
21 |
22 | function installFirefox() {
23 | console.info("Installing...");
24 |
25 | let install = env.spawnPython(["scripts/install_ff.py", `${getDownloadFileName()}`]);
26 |
27 | install.on("close", code => {
28 | console.info("Complete");
29 | });
30 | }
31 |
32 | // Remove existing files
33 | fs.unlink("build/templocation.txt", () => {
34 | fs.unlink("build/fflocation.txt", () => {
35 | fs.unlink("build/fflocation.bat", () => {
36 | fs.unlink(getDownloadFileName(), () => {
37 | // Then do the main download & install.
38 | console.info("Downloading...");
39 |
40 | let download = env.spawn("mozdownload", ["--version=latest", "-d", `${getDownloadFileName()}`]);
41 |
42 | download.on("close", installFirefox);
43 | });
44 | });
45 | });
46 | });
47 |
--------------------------------------------------------------------------------
/scripts/get_secret.py:
--------------------------------------------------------------------------------
1 | import urllib2
2 | import json
3 | import sys
4 |
5 | url = "http://taskcluster/secrets/v1/secret/repo:github.com/mozilla/example-addon-repo:pull-request"
6 |
7 | try:
8 | res = urllib2.urlopen(url)
9 | except:
10 | sys.exit(0)
11 |
12 | if res.getcode() == 200:
13 | print json.load(res)['secret']['COVERALLS_REPO_TOKEN']
14 |
--------------------------------------------------------------------------------
/scripts/install_ff.py:
--------------------------------------------------------------------------------
1 | import mozinstall
2 | import os
3 | import sys
4 | import tempfile
5 |
6 | tempdir = tempfile.mkdtemp()
7 | firefox_exe = sys.argv[1]
8 |
9 | install_folder = mozinstall.install(src=firefox_exe, dest=tempdir)
10 | binary = mozinstall.get_binary(install_folder, 'Firefox')
11 |
12 | if not os.path.exists("build"):
13 | os.makedirs("build")
14 |
15 | f = open("build/fflocation.bat", "w")
16 | f.write("set FIREFOX_BINARY=%s" % binary)
17 | f.close()
18 | f = open("build/fflocation.txt", "w")
19 | f.write(binary)
20 | f.close()
21 | f = open("build/templocation.txt", "w")
22 | f.write(tempdir)
23 | f.close()
24 |
--------------------------------------------------------------------------------
/scripts/run-utils.js:
--------------------------------------------------------------------------------
1 | /* This Source Code Form is subject to the terms of the Mozilla Public
2 | * License, v. 2.0. If a copy of the MPL was not distributed with this
3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
4 | "use strict";
5 |
6 | /* eslint-env node */
7 | /* eslint promise/always-return:off */
8 |
9 | const Extend = require("lodash").extend;
10 | const Fs = require("mz/fs");
11 | const FxProfileFinder = require("firefox-profile/lib/profile_finder");
12 | const FxRunner = require("fx-runner/lib/run");
13 | const Path = require("path");
14 | const When = require("when");
15 |
16 | /**
17 | * Profiles that do not include "/" are treated as profile names to be used by
18 | * the Fx Profile Manager.
19 | *
20 | * @param {String} profile Name of the profile to test
21 | * @return {Boolean}
22 | */
23 | function isProfileName(profile) {
24 | if (!profile) {
25 | return false;
26 | }
27 | return !/[\\/]/.test(profile);
28 | }
29 |
30 | /**
31 | * Retrieve the valid path of a profile.
32 | *
33 | * @param {String} profile Name or path of the profile
34 | * @return {Promise} Will be resolved with a valid absolute path pointing to the
35 | * profile or rejected when the path does not exist or when the
36 | * profile name can not be found.
37 | */
38 | function getProfilePath(profile) {
39 | if (isProfileName(profile)) {
40 | let finder = new FxProfileFinder();
41 | return When.promise((resolve, reject) => {
42 | finder.getPath(profile, (err, path) => {
43 | if (err) {
44 | reject(err);
45 | return;
46 | }
47 |
48 | resolve(path);
49 | });
50 | });
51 | }
52 |
53 | return When.promise((resolve, reject) => {
54 | let newProfile = Path.normalize(profile);
55 | if (!Path.isAbsolute(newProfile)) {
56 | newProfile = Path.join(process.cwd(), profile);
57 | }
58 | Fs.stat(newProfile).then(stat => {
59 | if (!stat.isDirectory()) {
60 | reject(`Profile path '${newProfile}' is not a directory`);
61 | return;
62 | }
63 | resolve(newProfile);
64 | }).catch(reject);
65 | });
66 | }
67 | exports.getProfilePath = getProfilePath;
68 |
69 | // Environment variables that make sure that all messages and errors end up in
70 | // the process stdout or stderr streams.
71 | const FX_ENV = {
72 | "XPCOM_DEBUG_BREAK": "stack",
73 | "NS_TRACE_MALLOC_DISABLE_STACKS": "1"
74 | };
75 |
76 | /**
77 | * Check a chunk of process output to check if it contains an error message.
78 | *
79 | * @param {String} line
80 | * @return {Boolean}
81 | */
82 | function isErrorString(line) {
83 | return /^\*{25}/.test(line) || /^\s*Message: [\D]*Error/.test(line);
84 | }
85 |
86 | const GARBAGE = [
87 | /\[JavaScript Warning: "TypeError: "[\w\d]+" is read-only"\]/,
88 | /JavaScript strict warning: /,
89 | /###!!! \[Child\]\[DispatchAsyncMessage\]/
90 | ];
91 |
92 | /**
93 | * Checks of the chunk of process output can be discarded. The patterns that are
94 | * tested against can be found in the `GARBAGE` array above.
95 | *
96 | * @param {String} data
97 | * @return {Boolean}
98 | */
99 | function isGarbage(data) {
100 | if (!data || !data.replace(/[\s\t\r\n]+/, "")) {
101 | return true;
102 | }
103 | for (let i = 0, l = GARBAGE.length; i < l; ++i) {
104 | if (GARBAGE[i].test(data)) {
105 | return true;
106 | }
107 | }
108 | return false;
109 | }
110 |
111 | /**
112 | * Output a chunk of process output to the console (Terminal).
113 | *
114 | * @param {String} data
115 | * @param {String} type Type of output; can be 'log' (default), 'warn' or 'error'
116 | */
117 | function writeLog(data, type) {
118 | if (isGarbage(data)) {
119 | return;
120 | }
121 | process[(type === "error") ? "stderr" : "stdout"].write(data);
122 | }
123 |
124 | /**
125 | * Takes options, and runs Firefox. Inspiration of this code can be found at
126 | * https://github.com/mozilla-jetpack/jpm.
127 | *
128 | * @param {Object} options
129 | * - `binary` path to Firefox binary to use
130 | * - `profile` path to profile or profile name to use
131 | * - `binaryArgs` binary arguments to pass into Firefox, split up by spaces
132 | * @return {Promise}
133 | */
134 | function runFirefox(options) {
135 | return When.promise((resolve, reject) => {
136 | let newOptions = options || {};
137 |
138 | FxRunner({
139 | "binary": newOptions.binary,
140 | "foreground": true,
141 | "profile": newOptions.profile,
142 | "env": Extend({}, process.env, FX_ENV),
143 | "verbose": true,
144 | "binary-args": newOptions.binaryArgs
145 | }).then(results => {
146 | let firefox = results.process;
147 |
148 | console.log(`Executing Firefox binary: ${results.binary}`);
149 | console.log(`Executing Firefox with args: ${results.args}`);
150 |
151 | firefox.on("error", err => {
152 | if (/No such file/.test(err) || err.code === "ENOENT") {
153 | console.error(`No Firefox binary found at ${results.binary}`);
154 | if (!newOptions.binary) {
155 | console.error("Specify a Firefox binary to use with the `-b` flag.");
156 | }
157 | } else {
158 | console.error(err);
159 | }
160 | reject(err);
161 | });
162 |
163 | firefox.on("close", resolve);
164 |
165 | firefox.stderr.setEncoding("utf8");
166 | firefox.stderr.on("data", data => {
167 | if (/^\s*System JS : WARNING/.test(data)) {
168 | writeLog(data, "warn");
169 | } else {
170 | writeLog(data, "error");
171 | }
172 | });
173 |
174 | // Many errors in addons are printed to stdout instead of stderr;
175 | // we should check for errors here and print them out.
176 | firefox.stdout.setEncoding("utf8");
177 | firefox.stdout.on("data", data => {
178 | if (isErrorString(data)) {
179 | writeLog(data, "error");
180 | } else {
181 | writeLog(data);
182 | }
183 | });
184 | }).catch(ex => {
185 | reject(ex);
186 | });
187 | });
188 | }
189 | exports.runFirefox = runFirefox;
190 |
--------------------------------------------------------------------------------
/scripts/runfx.js:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env node
2 | /* This Source Code Form is subject to the terms of the Mozilla Public
3 | * License, v. 2.0. If a copy of the MPL was not distributed with this
4 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
5 | "use strict";
6 |
7 | /* eslint-env node */
8 | /* eslint promise/always-return:off */
9 |
10 | const Commander = require("commander");
11 | const Fs = require("mz/fs");
12 | const Path = require("path");
13 | const Rimraf = require("rimraf");
14 | const Util = require("./run-utils");
15 | const Version = require("../package.json").version;
16 | const When = require("when");
17 | const WhenNode = require("when/node");
18 | // Transform mkdirp() to use promises.
19 | const Mkdirp = WhenNode.lift(require("mkdirp"));
20 |
21 | const DEFAULT_PROFILE = "dev";
22 |
23 | Commander
24 | .version(Version)
25 | .option("-b, --binary ", "Path of Firefox binary to use.")
26 | .option("-p, --profile ", "Path or name of Firefox profile to use.")
27 | .option("--binary-args ", "Pass additional arguments into Firefox.")
28 | .parse(process.argv);
29 |
30 | function ensureRemoved(path) {
31 | return When.promise((resolve, reject) => {
32 | Fs.lstat(path).then(targetStat => {
33 | if (targetStat.isDirectory()) {
34 | Rimraf(path, err => {
35 | if (err) {
36 | reject(`Removing add-on directory '${path}' failed ${err}`);
37 | return;
38 | }
39 |
40 | resolve();
41 | });
42 | } else {
43 | // Removing old symlink or file.
44 | Fs.unlink(path).then(resolve).catch(reject);
45 | }
46 | }).catch(resolve);
47 | });
48 | }
49 |
50 | function onExit(...args) {
51 | let i = 0;
52 | let errLen = args.length;
53 | for (; i < errLen; ++i) {
54 | console.error(args[i].message || args[i]);
55 | }
56 |
57 | if (errLen) {
58 | process.exit(1); // eslint-disable-line no-process-exit
59 | }
60 | }
61 |
62 | let addonSourceDir = Path.normalize(Path.join(__dirname, "..", "add-on"));
63 | let profile = Commander.profile || DEFAULT_PROFILE;
64 | let extensionsDir;
65 | let addonTargetDir;
66 | Fs.stat(addonSourceDir).then(sourceStat => {
67 | if (!sourceStat.isDirectory()) {
68 | throw new Error("Not a directory");
69 | }
70 | }).catch(err => onExit(err, "ERROR! Please run `make build` first!"))
71 | .then(() => Util.getProfilePath(profile))
72 | .catch(() => {
73 | // No valid profile path found, so bail out.
74 | onExit(`ERROR! Could not find a suitable profile.
75 | Please create a new profile '${profile}' and re-run this script.`); })
76 | .then(profilePath => {
77 | // Since we've got the profile path now, we can create the symlink in the
78 | // profile directory IF it doesn't exist yet.
79 | extensionsDir = Path.join(profilePath, "extensions");
80 | addonTargetDir = Path.join(extensionsDir, "exampleaddonrepo@mozilla.org");
81 | return Mkdirp(extensionsDir);
82 | })
83 | .then(() => ensureRemoved(addonTargetDir))
84 | // This should fail at a certain point, because we don't want the add-on
85 | // directory to exist.
86 | .then(() => Fs.symlink(addonSourceDir, addonTargetDir))
87 | .then(() => {
88 | console.log(`Symlinked add-on at '${addonTargetDir}'`);
89 |
90 | // Add-on should be in the correct place, so now we can run Firefox.
91 | return Util.runFirefox(Commander).catch(() => {
92 | // Ignore run Firefox issues and continue to cleanup
93 | }).then(() => {
94 | console.log("Removing symlink");
95 | return Fs.unlink(addonTargetDir);
96 | });
97 | })
98 | .catch(onExit);
99 |
--------------------------------------------------------------------------------
/scripts/uninstall_ff.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 |
3 | const fs = require("fs");
4 | const async = require("async");
5 |
6 | function removeFolder(location, next) {
7 | fs.readdir(location, (err, files) => { // eslint-disable-line handle-callback-err
8 | async.each(files, (file, cb) => {
9 | let newFile = `${location}/${file}`;
10 | fs.stat(newFile, (err, stat) => {
11 | if (err) {
12 | cb(err);
13 | }
14 | if (stat.isDirectory()) {
15 | removeFolder(newFile, cb);
16 | } else {
17 | fs.unlink(newFile, err => {
18 | if (err) {
19 | return cb(err);
20 | }
21 | return cb();
22 | });
23 | }
24 | });
25 | }, err => {
26 | if (err) {
27 | next(err);
28 | return;
29 | }
30 | fs.rmdir(location, err => next(err));
31 | });
32 | });
33 | }
34 |
35 | fs.readFile("build/templocation.txt", (err, data) => {
36 | if (err) {
37 | throw err;
38 | }
39 |
40 | removeFolder(data, err => {
41 | if (err) {
42 | throw err;
43 | }
44 |
45 | console.info("Done");
46 | });
47 | });
48 |
--------------------------------------------------------------------------------
/test/.eslintrc:
--------------------------------------------------------------------------------
1 | {
2 | "globals": {
3 | "sinon": false
4 | },
5 | "rules": {
6 | "prefer-arrow-callback": 0
7 | }
8 | }
9 |
--------------------------------------------------------------------------------
/test/functional/button_test.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 |
3 | let assert = require("assert");
4 | let utils = require("./utils");
5 | let firefox = require("selenium-webdriver/firefox");
6 | let Context = firefox.Context;
7 |
8 | // Mocha can't use arrow functions as sometimes we need to call `this` and
9 | // using an arrow function alters the binding of `this`.
10 | // Hence we disable prefer-arrow-callback here so that mocha/no-mocha-arrows can
11 | // be applied nicely.
12 |
13 | describe("Example Add-on Functional Tests", function() {
14 | // This gives Firefox time to start, and us a bit longer during some of the tests.
15 | this.timeout(10000);
16 |
17 | let driver;
18 |
19 | before(function() {
20 | let promise = utils.promiseSetupDriver();
21 |
22 | return promise.then(newDriver => {
23 | driver = newDriver;
24 | return Promise.resolve();
25 | });
26 | });
27 |
28 | after(function() {
29 | return driver.quit();
30 | });
31 |
32 | it("should have a toolbar button", function() {
33 | return utils.promiseAddonButton(driver)
34 | .then(button => button.getAttribute("tooltiptext"))
35 | .then(text => assert.equal(text, "Visit Mozilla"));
36 | });
37 |
38 | // XXX Currently failing, see
39 | // https://github.com/mozilla/example-addon-repo/issues/1
40 | it("should open a webpage when the button is clicked", function() {
41 | return driver.getAllWindowHandles()
42 | .then(handles => assert.equal(1, 1))
43 | // Find the button, click it and check it opens a new tab.
44 | .then(function*() {
45 | let button = yield utils.promiseAddonButton(driver);
46 |
47 | button.click();
48 |
49 | return driver.wait(function*() {
50 | let handles = yield driver.getAllWindowHandles();
51 | return handles.length === 2;
52 | }, 9000, "Should have opened a new tab.");
53 | })
54 | // Switch selenium to the new tab.
55 | .then(function*() {
56 | let handles = yield driver.getAllWindowHandles();
57 |
58 | let currentHandle = yield driver.getWindowHandle();
59 |
60 | driver.setContext(Context.CONTENT);
61 | // Find the new window handle.
62 | let newWindowHandle = null;
63 | for (const handle of handles) {
64 | if (handle !== currentHandle) {
65 | newWindowHandle = handle;
66 | }
67 | }
68 |
69 | return driver.switchTo().window(newWindowHandle);
70 | })
71 | // Check the tab has loaded the right page.
72 | // We use driver.wait to wait for the page to be loaded, as due to the click()
73 | // we're not able to easily use the load listeners built into selenium.
74 | .then(() => driver.wait(function*() {
75 | let currentUrl = yield driver.getCurrentUrl();
76 |
77 | return currentUrl === "https://www.mozilla.org/en-US/";
78 | }, 5000, "Should have loaded mozilla.org"));
79 | });
80 | });
81 |
--------------------------------------------------------------------------------
/test/functional/utils.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 |
3 | // The geckodriver package downloads and installs geckodriver for us.
4 | // We use it by requiring it.
5 | require("geckodriver");
6 |
7 | let firefox = require("selenium-webdriver/firefox");
8 | let webdriver = require("selenium-webdriver");
9 | let FxRunnerUtils = require("fx-runner/lib/utils");
10 | let Fs = require("fs-promise");
11 | let By = webdriver.By;
12 | let Context = firefox.Context;
13 | let until = webdriver.until;
14 | let path = require("path");
15 |
16 | // Note: Geckodriver already has quite a good set of default preferences
17 | // for disabling various items.
18 | // https://github.com/mozilla/geckodriver/blob/master/src/marionette.rs
19 | const FIREFOX_PREFERENCES = {
20 | // Ensure e10s is turned on.
21 | "browser.tabs.remote.autostart": true,
22 | "browser.tabs.remote.autostart.1": true,
23 | "browser.tabs.remote.autostart.2": true,
24 | // These are good to have set up if you're debugging tests with the browser
25 | // toolbox.
26 | "devtools.chrome.enabled": true,
27 | "devtools.debugger.remote-enabled": true
28 | };
29 |
30 | function promiseActualBinary(binary) {
31 | return FxRunnerUtils.normalizeBinary(binary)
32 | .then(binary => Fs.stat(binary).then(() => binary))
33 | .catch(ex => {
34 | if (ex.code === "ENOENT") {
35 | throw new Error("Could not find ${binary}");
36 | }
37 | throw ex;
38 | });
39 | }
40 |
41 | module.exports.promiseSetupDriver = () => {
42 | let profile = new firefox.Profile();
43 |
44 | Object.keys(FIREFOX_PREFERENCES).forEach(key => {
45 | profile.setPreference(key, FIREFOX_PREFERENCES[key]);
46 | });
47 |
48 | let options = new firefox.Options();
49 | options.setProfile(profile);
50 |
51 | let builder = new webdriver.Builder()
52 | .forBrowser("firefox")
53 | .setFirefoxOptions(options);
54 |
55 | return promiseActualBinary(process.env.FIREFOX_BINARY || "firefox")
56 | .then(binaryLocation => options.setBinary(new firefox.Binary(binaryLocation)))
57 | .then(() => builder.build())
58 | .then(driver => {
59 | driver.setContext(Context.CHROME);
60 |
61 | let fileLocation = path.join(process.cwd(), process.env.XPI_NAME);
62 |
63 | // This manually installs the add-on as a temporary add-on.
64 | // Hopefully selenium/geckodriver will get a way to do this soon:
65 | // https://bugzilla.mozilla.org/show_bug.cgi?id=1298025
66 | return driver.executeAsyncScript(
67 | "let fileUtils = Components.utils.import('resource://gre/modules/FileUtils.jsm');" +
68 | "let FileUtils = fileUtils.FileUtils;" +
69 | "let callback = arguments[arguments.length - 1];" +
70 | "Components.utils.import('resource://gre/modules/AddonManager.jsm');" +
71 |
72 | "let listener = {" +
73 | " onInstallEnded: function(install, addon) {" +
74 | " callback([addon.id, 0]);" +
75 | " }," +
76 |
77 | " onInstallFailed: function(install) {" +
78 | " callback([null, install.error]);" +
79 | " }," +
80 |
81 | " onInstalled: function(addon) {" +
82 | " AddonManager.removeAddonListener(listener);" +
83 | " callback([addon.id, 0]);" +
84 | " }" +
85 | "};" +
86 |
87 | "let file = new FileUtils.File(arguments[0]);" +
88 |
89 | "AddonManager.addAddonListener(listener);" +
90 | "AddonManager.installTemporaryAddon(file).catch(error => {" +
91 | " Components.utils.reportError(error); callback([null, error])" +
92 | "});",
93 | fileLocation)
94 | .then(result => {
95 | if (!result[0] && result[1]) {
96 | return driver.quit().then(() => {
97 | throw new Error(`Failed to install add-on: ${result[1]}`);
98 | });
99 | }
100 |
101 | return driver;
102 | });
103 | });
104 | };
105 |
106 | module.exports.promiseAddonButton = driver => {
107 | driver.setContext(Context.CHROME);
108 | return driver.wait(until.elementLocated(
109 | By.id("exampleaddonrepo_mozilla_org-browser-action")), 1000);
110 | };
111 |
--------------------------------------------------------------------------------
/test/unit/main.test.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 |
3 | describe("main.js", function() {
4 | describe("browserAction", function() {
5 | it("should register a listener for onClicked", function() {
6 | sinon.assert.calledOnce(chrome.browserAction.onClicked.addListener);
7 | });
8 |
9 | it("should open a tab when the button is clicked", function() {
10 | chrome.browserAction.onClicked.trigger();
11 |
12 | sinon.assert.calledOnce(chrome.tabs.create);
13 | sinon.assert.calledWithExactly(chrome.tabs.create, {
14 | active: true,
15 | url: "https://www.mozilla.org"
16 | });
17 | });
18 | });
19 | });
20 |
--------------------------------------------------------------------------------