├── .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 | [![Coverage Status](https://coveralls.io/repos/github/mozilla/example-addon-repo/badge.svg)](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 | --------------------------------------------------------------------------------